From 1afe1647981e1f331c379757b100e10a9b47ed90 Mon Sep 17 00:00:00 2001 From: weichen Date: Fri, 16 Oct 2026 05:07:19 +0000 Subject: [PATCH] Pin published company default renderer and drop candidate wording Why: agent-document-renderer 31fbe54 publishes company.default@1.0.0 with review status approved and publicationAllowed true (owner decision 2026-10-16: A4, ~2.5cm margins, SimSun + Times New Roman 12pt body, SimHei headings, no logo). Backend still hard-coded Candidate=true and every prompt, skill and tool text called the default a pending-human-visual-review candidate. Evidence: go vet ./... exit 0; gofmt -l on changed packages empty; go test -count=1 ./internal/docgen/... ./internal/prompts/... ./internal/skills/... ./internal/tools/... ok; go test -count=1 ./... exit 0 (42 packages ok); go build ./... and CGO_ENABLED=0 go build ./cmd/agent-system ok; git diff --cached --check clean. New tests failed before the change. Impact: bump deps/agent-document-renderer 0d1c512 -> 31fbe54 and regenerate the renderer identity. The manifest profile receipt now reports the renderer's own review status and derives Candidate from the snapshot state (false for the published default). Prompts (base.deliverables v12, tool.render_document v2, task.report v4, task.doc-minutes v4), docx-author and house-style skills, the render_document tool description and comparison exception details describe the default as published (approved by owner 2026-10-16) while still limiting it to ordinary uncontrolled workspace working/preview Word. Routing is unchanged: project, controlled, special-template, review and approved outputs still go to RDM, with no Backend default fallback and no legacy Word builder fallback. Refs: unified renderer plan Tasks 7/10 Co-authored-by: Cursor --- deps/agent-document-renderer | 2 +- internal/docgen/comparison.go | 4 +- internal/docgen/renderer.go | 2 +- .../docgen/renderer_identity_generated.go | 2 +- internal/docgen/renderer_integration_test.go | 22 ++++++++--- internal/prompts/library/base-deliverables.md | 6 +-- internal/prompts/library/task-doc-minutes.md | 4 +- internal/prompts/library/task-report.md | 4 +- .../prompts/library/tool-render-document.md | 6 +-- internal/prompts/prompts_test.go | 39 ++++++++++++++++++- internal/skills/recipes/docx-author/SKILL.md | 2 +- internal/skills/recipes/house-style/SKILL.md | 2 +- internal/skills/skills_test.go | 25 +++++++++++- internal/tools/document_renderer.go | 2 +- internal/tools/document_renderer_test.go | 19 +++++++++ 15 files changed, 116 insertions(+), 25 deletions(-) diff --git a/deps/agent-document-renderer b/deps/agent-document-renderer index 0d1c512..31fbe54 160000 --- a/deps/agent-document-renderer +++ b/deps/agent-document-renderer @@ -1 +1 @@ -Subproject commit 0d1c5121d137be817db5f98d3c13320dfa919c8a +Subproject commit 31fbe54fae8bf11be664919ce173da0d68ebfdc9 diff --git a/internal/docgen/comparison.go b/internal/docgen/comparison.go index ad48558..3996cdb 100644 --- a/internal/docgen/comparison.go +++ b/internal/docgen/comparison.go @@ -125,13 +125,13 @@ func compareLegacyAndShared(title string, source []byte, canonical model.Canonic receipt.Dimensions = append(receipt.Dimensions, compareExactDimension("page-margins", legacyDOCX.Margins, sharedDOCX.Margins, "")) } else { receipt.Dimensions = append(receipt.Dimensions, intentionalDimension("page-margins", legacyDOCX.Margins, sharedDOCX.Margins, - comparisonExceptionCorporate, "organization-default candidate owns page margins")) + comparisonExceptionCorporate, "published organization default owns page margins")) } if legacyDOCX.Headers == sharedDOCX.Headers && legacyDOCX.Footers == sharedDOCX.Footers { receipt.Dimensions = append(receipt.Dimensions, compareExactDimension("header-footer", []int{legacyDOCX.Headers, legacyDOCX.Footers}, []int{sharedDOCX.Headers, sharedDOCX.Footers}, "")) } else { receipt.Dimensions = append(receipt.Dimensions, intentionalDimension("header-footer", []int{legacyDOCX.Headers, legacyDOCX.Footers}, []int{sharedDOCX.Headers, sharedDOCX.Footers}, - comparisonExceptionCorporate, "organization-default candidate adds reviewed header/footer rules")) + comparisonExceptionCorporate, "published organization default adds approved header/footer rules")) } if legacyDOCX.Bookmarks == sharedDOCX.Bookmarks { receipt.Dimensions = append(receipt.Dimensions, compareExactDimension("node-bookmarks", legacyDOCX.Bookmarks, sharedDOCX.Bookmarks, "")) diff --git a/internal/docgen/renderer.go b/internal/docgen/renderer.go index 457d768..5ba8fb4 100644 --- a/internal/docgen/renderer.go +++ b/internal/docgen/renderer.go @@ -340,7 +340,7 @@ func generateWithOptions(ctx context.Context, request Request, options RenderOpt Document: parsed.Document, Renderer: result.Manifest, Profile: ProfileReceipt{ ProfileID: profileID, Version: profileVersion, SnapshotHash: bundle.Snapshot.SnapshotHash(), - Candidate: true, ReviewStatus: bundle.Review.Status, PublicationAllowed: bundle.Review.PublicationAllowed, + Candidate: bundle.Snapshot.State() != profile.StatePublished, ReviewStatus: bundle.Review.Status, PublicationAllowed: bundle.Review.PublicationAllowed, Unverified: append([]string(nil), bundle.Review.Unverified...), ProfileSHA256: bundle.Review.ProfileSHA256, AssetManifestSHA256: bundle.Review.AssetManifestSHA256, ReviewSHA256: bundle.Review.ReviewSHA256, }, diff --git a/internal/docgen/renderer_identity_generated.go b/internal/docgen/renderer_identity_generated.go index cae3eec..ec759b3 100644 --- a/internal/docgen/renderer_identity_generated.go +++ b/internal/docgen/renderer_identity_generated.go @@ -2,6 +2,6 @@ package docgen -const rendererRevision = "0d1c5121d137be817db5f98d3c13320dfa919c8a" +const rendererRevision = "31fbe54fae8bf11be664919ce173da0d68ebfdc9" const RendererIdentity = "agent-document-renderer@" + rendererRevision diff --git a/internal/docgen/renderer_integration_test.go b/internal/docgen/renderer_integration_test.go index d7d4074..d8b989d 100644 --- a/internal/docgen/renderer_integration_test.go +++ b/internal/docgen/renderer_integration_test.go @@ -39,7 +39,7 @@ const representativeMarkdown = `# 研发周会纪要 ![公司默认样图](asset:company.default.sample-image@1.0.0) :::callout warning -公司默认规则仍待人工视觉评审。 +公司默认规则只用于普通工作区交付。 ::: :::page-break @@ -89,10 +89,17 @@ func TestGenerateProducesDeterministicHashedDeliveryPackage(t *testing.T) { if err := json.Unmarshal(first.Manifest.Bytes, &manifest); err != nil { t.Fatalf("decode manifest: %v", err) } + bundle, err := profiles.LoadCompanyDefault() + if err != nil { + t.Fatalf("load renderer company default: %v", err) + } if manifest.SchemaVersion != ManifestSchemaVersion || manifest.RendererIdentity != RendererIdentity || - manifest.SelectionSource != "organization-default" || !manifest.Profile.Candidate || - manifest.Profile.ReviewStatus != "pending-human-visual-review" || manifest.Profile.PublicationAllowed { - t.Fatalf("manifest profile provenance = %#v", manifest) + manifest.SelectionSource != "organization-default" || manifest.Profile.Candidate || + manifest.Profile.ReviewStatus != bundle.Review.Status || manifest.Profile.PublicationAllowed != bundle.Review.PublicationAllowed { + t.Fatalf("manifest profile provenance = %#v, renderer review = %#v", manifest, bundle.Review) + } + if manifest.Profile.ReviewStatus != "approved" || !manifest.Profile.PublicationAllowed { + t.Fatalf("published organization default reported review=%q publicationAllowed=%v", manifest.Profile.ReviewStatus, manifest.Profile.PublicationAllowed) } if manifest.Profile.ProfileSHA256 == "" || manifest.Profile.AssetManifestSHA256 == "" || manifest.Profile.ReviewSHA256 == "" { t.Fatalf("manifest omitted source hashes: %#v", manifest.Profile) @@ -360,7 +367,7 @@ func TestPersistedControlledMetadataRoutesBeforeDefaultProfileLoad(t *testing.T) } func TestRendererIdentityMatchesPublishedPin(t *testing.T) { - const want = "agent-document-renderer@0d1c5121d137be817db5f98d3c13320dfa919c8a" + const want = "agent-document-renderer@31fbe54fae8bf11be664919ce173da0d68ebfdc9" if RendererIdentity != want { t.Fatalf("RendererIdentity = %q, want %q", RendererIdentity, want) } @@ -636,6 +643,11 @@ func TestRepresentativeLegacyComparisonRecordsOnlyIntentionalDifferences(t *test t.Errorf("comparison dimension %s = %#v, want match", name, dimension) } } + for _, dimension := range delivery.Comparison.Dimensions { + if dimension.ExceptionID == comparisonExceptionCorporate && !strings.Contains(dimension.Detail, "published organization default") { + t.Errorf("corporate layout exception %s detail = %q, want published organization default", dimension.Name, dimension.Detail) + } + } } func TestComparisonDetectsCanonicalAndDOCXTableAlignmentLoss(t *testing.T) { diff --git a/internal/prompts/library/base-deliverables.md b/internal/prompts/library/base-deliverables.md index 25803e3..d934a69 100644 --- a/internal/prompts/library/base-deliverables.md +++ b/internal/prompts/library/base-deliverables.md @@ -1,7 +1,7 @@ --- id: base.deliverables layer: base -version: 11 +version: 12 title: 交付物工作流 description: 生成文档、表格、演示稿的工作流、样式决策与技能入口(常驻基座) --- @@ -9,10 +9,10 @@ description: 生成文档、表格、演示稿的工作流、样式决策与技 工作流: -1. **先判路由**:普通、非受控的工作区 Word 可用组织默认候选;项目文档、受控文档、专用模板或审签输出必须交给 RDM,禁止调用 Backend 默认规则回退。组织默认候选的 manifest 状态固定显示 `pending-human-visual-review`,不能描述成已发布或已视觉批准。Excel / PPT / 图片再按用户指令、模板、参考文档、会话样式和默认主题的顺序定样式。 +1. **先判路由**:组织默认规则已发布(负责人 2026-10-16 批准,manifest 评审状态 `approved`),但只用于普通、非受控的工作区 Word;项目文档、受控文档、专用模板或审签输出必须交给 RDM,禁止调用 Backend 默认规则回退。Excel / PPT / 图片再按用户指令、模板、参考文档、会话样式和默认主题的顺序定样式。 2. **规划版面**:先定读者、结构和每一节的内容,再写脚本。不要边生成边补空节。 3. **生成 Word**:调用 `render_document`,传本次交付稳定且重试复用的 `operationId`、稳定 `documentId`、标题 `name`、Markdown、语言和 `working|preview` 模式,并显式传 `controlled=false`、`projectDocument=false`;任何一项不是 false 就停止并交给 RDM。时间与 requester 由宿主冻结,禁止传入或声称自定 `frozenAt`。Markdown 用 `#`-`######` 标题、段落、引用、围栏/行内代码、粗体、斜体、删除线、安全链接标签、`-`/`1.` 列表、GFM 表格、`asset:` 图片、`:::callout warning`/`:::` 提示块和 `:::page-break`。工具先保存带稳定 node ID 的 Markdown,再生成确定性 DOCX、manifest 和完整 zip;返回值逐项列出稳定 ID 与 SHA-256。失败时按回执状态重试同一 operationId,绝不调用 legacy Word 路径回退。 -4. **生成其他格式并自检**:Excel/PPT/图片用 `run_python` + house-style 对应 kit;用 lint/outline 回读。Word 的 OPC、安全、锚点与回读校验已经由统一 renderer 执行,复核 manifest 中的诊断、组织默认候选状态和 Markdown/DOCX 哈希。 +4. **生成其他格式并自检**:Excel/PPT/图片用 `run_python` + house-style 对应 kit;用 lint/outline 回读。Word 的 OPC、安全、锚点与回读校验已经由统一 renderer 执行,复核 manifest 中的诊断、组织默认规则评审状态和 Markdown/DOCX 哈希。 5. **登记**:`render_document` 已登记 Markdown、DOCX、manifest 和 zip,不要重复登记。其他格式继续用 `register_artifact`。 6. **收尾**:回复只写结论摘要,并说明这次用的样式(主题、是否模板、是否仿制)以及用户还能怎么调。不要在回复里复述全文,也不要只口头说文件名。 diff --git a/internal/prompts/library/task-doc-minutes.md b/internal/prompts/library/task-doc-minutes.md index 3483fd2..f53af31 100644 --- a/internal/prompts/library/task-doc-minutes.md +++ b/internal/prompts/library/task-doc-minutes.md @@ -1,7 +1,7 @@ --- id: task.doc-minutes layer: task -version: 3 +version: 4 title: 会议纪要生成 description: 依据上传的会议记录/录音稿生成结构完整的会议纪要 priority: 70 @@ -28,4 +28,4 @@ triggers: - 待办事项若原文未指明负责人或期限,写"未指明",不要编造。 - 版面:标题下用键值表写会议信息(时间、地点、主持人、参会人、缺席)。讨论按议题分节,每节含要点、分歧和结论。决议用真编号列表,不要再手写“1.”。待办用表格,列:序号、事项、负责人、期限、状态;原文没有的写“未指明”。发言人用统一称谓(职务或姓名);原文只有“说话人X”时保留并在文末说明。引语只留关键句,并标注是谁说的。 - 日期和数字全文一种格式。不要空节、空段落和手打项目符号。 -- 交付形态:普通工作区纪要按「交付物工作流」调用 `render_document`,生成「<主题>会议纪要.md/.docx/.manifest.json/.zip」;项目或受控纪要交给 RDM。对话里只简述结论、规则候选状态和可调项,不要复述全文。 +- 交付形态:普通工作区纪要按「交付物工作流」调用 `render_document`,生成「<主题>会议纪要.md/.docx/.manifest.json/.zip」;项目或受控纪要交给 RDM。对话里只简述结论、所用组织默认规则和可调项,不要复述全文。 diff --git a/internal/prompts/library/task-report.md b/internal/prompts/library/task-report.md index f17f3df..106291b 100644 --- a/internal/prompts/library/task-report.md +++ b/internal/prompts/library/task-report.md @@ -1,7 +1,7 @@ --- id: task.report layer: task -version: 3 +version: 4 title: 报告撰写 description: 把材料组织成正式报告/公文(含执行摘要与附录建议) priority: 55 @@ -25,4 +25,4 @@ triggers: - 结构服务于阅读动线:先结论后论证;每章开头一句话说清本章要点。 - 篇幅完整不注水:覆盖必要内容,删掉不改变结论的空话。 - 版面:封面(标题、受众、日期)、正文从执行摘要开始。执行摘要用强调块,控制在契约规定的篇幅内。章标题用标题样式,章首一句点明本章要点。数据进表格或附录,不埋在段落里。需要时在正文前放由标题生成的目录说明(页码以文件为准)。 -- 交付普通工作区 Word 时走交付物工作流的 `render_document`,同时保留 Markdown、manifest 和归档;项目或受控报告交给 RDM。回复说明规则候选状态,不复述全文。 +- 交付普通工作区 Word 时走交付物工作流的 `render_document`,同时保留 Markdown、manifest 和归档;项目或受控报告交给 RDM。回复说明所用组织默认规则,不复述全文。 diff --git a/internal/prompts/library/tool-render-document.md b/internal/prompts/library/tool-render-document.md index a121dd0..ceed6a1 100644 --- a/internal/prompts/library/tool-render-document.md +++ b/internal/prompts/library/tool-render-document.md @@ -1,10 +1,10 @@ --- id: tool.render_document layer: tool -version: 1 +version: 2 title: render_document 工具描述 description: 普通工作区 Markdown 到统一 Word 交付包的安全渲染入口 --- -把普通、非受控的工作区 Markdown 交给共享 canonical parser、组织默认候选 profile 和 Word adapter。必须传稳定且重试复用的 operationId,并显式传 `controlled=false` 和 `projectDocument=false`;缺任一字段即拒绝。frozenAt 与 requester 由宿主首次操作时创建并持久化,调用方不能提交。成功回执包含 Markdown、DOCX、manifest、zip 的稳定 ID 与 SHA-256;失败回执明确 partial 或 unknown,重试必须沿用同一 operationId。 +把普通、非受控的工作区 Markdown 交给共享 canonical parser、已发布的组织默认 profile 和 Word adapter。必须传稳定且重试复用的 operationId,并显式传 `controlled=false` 和 `projectDocument=false`;缺任一字段即拒绝。frozenAt 与 requester 由宿主首次操作时创建并持久化,调用方不能提交。成功回执包含 Markdown、DOCX、manifest、zip 的稳定 ID 与 SHA-256;失败回执明确 partial 或 unknown,重试必须沿用同一 operationId。 -只允许 working 或 preview。项目文档、受控文档、专用模板、审阅稿或批准稿必须交给 RDM;失败不得改走 legacy Word builder。当前公司默认规则状态是 pending-human-visual-review,只可用于普通工作/预览交付,不能称为已发布或已视觉批准。 +只允许 working 或 preview。项目文档、受控文档、专用模板、审阅稿或批准稿必须交给 RDM;失败不得改走 legacy Word builder。公司默认规则已发布(负责人 2026-10-16 批准,manifest 评审状态 `approved`),但仍只用于普通、非受控的工作/预览交付,不能代替 RDM 的项目或受控规则。 diff --git a/internal/prompts/prompts_test.go b/internal/prompts/prompts_test.go index 8cda4ce..9a4c8d8 100644 --- a/internal/prompts/prompts_test.go +++ b/internal/prompts/prompts_test.go @@ -101,16 +101,53 @@ func TestBaseDeliverablesRoutesWordThroughUnifiedRenderer(t *testing.T) { if err != nil || !found { t.Fatalf("base.deliverables: found=%v err=%v", found, err) } - for _, required := range []string{"render_document", "Markdown", "manifest", "SHA-256", "pending-human-visual-review", "RDM"} { + for _, required := range []string{"render_document", "Markdown", "manifest", "SHA-256", "已发布", "2026-10-16", "approved", "RDM", "禁止调用 Backend 默认规则回退"} { if !strings.Contains(body, required) { t.Errorf("base.deliverables missing %q", required) } } + for _, stale := range []string{"pending-human-visual-review", "组织默认候选", "不能描述成已发布"} { + if strings.Contains(body, stale) { + t.Errorf("base.deliverables still describes the organization default as unpublished: %q", stale) + } + } if strings.Contains(body, "python3 -m housestyle.build") { t.Fatal("base.deliverables still routes production Word through legacy housestyle.build") } } +func TestRenderDocumentToolDescribesPublishedDefaultWithoutWideningRoute(t *testing.T) { + body, found, err := mustService(t).ModuleBody("", "tool.render_document") + if err != nil || !found { + t.Fatalf("tool.render_document: found=%v err=%v", found, err) + } + for _, required := range []string{"已发布", "2026-10-16", "approved", "working 或 preview", "RDM", "不得改走 legacy Word builder"} { + if !strings.Contains(body, required) { + t.Errorf("tool.render_document missing %q", required) + } + } + for _, stale := range []string{"pending-human-visual-review", "默认候选", "不能称为已发布"} { + if strings.Contains(body, stale) { + t.Errorf("tool.render_document still describes the organization default as unpublished: %q", stale) + } + } +} + +func TestWordTaskPromptsDoNotReportOrganizationDefaultAsCandidate(t *testing.T) { + for _, id := range []string{"task.report", "task.doc-minutes"} { + body, found, err := mustService(t).ModuleBody("", id) + if err != nil || !found { + t.Fatalf("%s: found=%v err=%v", id, found, err) + } + if strings.Contains(body, "规则候选状态") { + t.Errorf("%s still asks the reply to report a candidate rule status", id) + } + if !strings.Contains(body, "RDM") { + t.Errorf("%s no longer routes project or controlled Word to RDM", id) + } + } +} + func TestEnvDeliverablePlaceholders(t *testing.T) { svc := mustService(t) blocks, _, err := svc.ResolveSystem(core.AgentProfile{}, TriggerCtx{Text: "你好"}, EnvData{ diff --git a/internal/skills/recipes/docx-author/SKILL.md b/internal/skills/recipes/docx-author/SKILL.md index 8e24e92..1c1091b 100644 --- a/internal/skills/recipes/docx-author/SKILL.md +++ b/internal/skills/recipes/docx-author/SKILL.md @@ -7,7 +7,7 @@ description: 用统一 renderer 生成 Markdown、DOCX、manifest 和归档 先读 `$AGENT_SKILLS_DIR/house-style/SKILL.md`。普通、非受控工作区 Word 调用 `render_document`;传本次交付稳定且重试复用的 operationId、稳定 documentId、标题和 Markdown,时间由宿主持久冻结。项目或受控 Word、专用模板、审阅与批准输出必须交给 RDM,禁止默认回退。 -成功结果包括 Markdown、DOCX、manifest 和 zip,逐项核对 SHA-256;当前组织默认候选仍是 `pending-human-visual-review`。Python、shell、`docx_kit` 和 `register_artifact` 都不是 Word 交付入口。迁移对照只能由 Backend 内部受信能力启动。生产 Word 的 OPC、安全、锚点与回读结果以共享 renderer manifest 为准;修订以返回的 Markdown 为权威源。 +成功结果包括 Markdown、DOCX、manifest 和 zip,逐项核对 SHA-256;组织默认规则已发布(负责人 2026-10-16 批准,manifest 评审状态 `approved`),但只用于普通、非受控的 working/preview 交付。Python、shell、`docx_kit` 和 `register_artifact` 都不是 Word 交付入口。迁移对照只能由 Backend 内部受信能力启动。生产 Word 的 OPC、安全、锚点与回读结果以共享 renderer manifest 为准;修订以返回的 Markdown 为权威源。 版面范式: diff --git a/internal/skills/recipes/house-style/SKILL.md b/internal/skills/recipes/house-style/SKILL.md index 05fcd36..c7627a1 100644 --- a/internal/skills/recipes/house-style/SKILL.md +++ b/internal/skills/recipes/house-style/SKILL.md @@ -5,7 +5,7 @@ description: 交付物设计系统。普通 Word 使用统一 renderer;housest # house-style -普通、非受控工作区 Word 的生产入口是 `render_document`:它保存 Markdown、确定性 DOCX、manifest 和 zip,并在 manifest 标注组织默认候选 `pending-human-visual-review`。项目或受控 Word、专用模板、审阅和批准输出必须交给 RDM,禁止 Backend 默认回退。 +普通、非受控工作区 Word 的生产入口是 `render_document`:它保存 Markdown、确定性 DOCX、manifest 和 zip,并在 manifest 记录已发布组织默认规则的评审状态 `approved`(负责人 2026-10-16 批准);该规则只用于普通、非受控的 working/preview 交付。项目或受控 Word、专用模板、审阅和批准输出必须交给 RDM,禁止 Backend 默认回退。 housestyle 的 Excel、PowerPoint、图片构件继续用于生产。Python Word 构件不是生产或迁移入口;`housestyle.build` 永久禁用,DOC/DOCX 也不能通过 `register_artifact` 登记。 diff --git a/internal/skills/skills_test.go b/internal/skills/skills_test.go index 857bc8d..ccbaf6a 100644 --- a/internal/skills/skills_test.go +++ b/internal/skills/skills_test.go @@ -183,11 +183,16 @@ func TestHouseStyleRoutesProductionWordThroughUnifiedRenderer(t *testing.T) { t.Fatal(err) } body := string(raw) - for _, required := range []string{"render_document", "pending-human-visual-review", "RDM"} { + for _, required := range []string{"render_document", "已发布", "2026-10-16", "approved", "RDM", "禁止 Backend 默认回退"} { if !strings.Contains(body, required) { t.Errorf("house-style skill missing %q", required) } } + for _, stale := range []string{"pending-human-visual-review", "默认候选"} { + if strings.Contains(body, stale) { + t.Errorf("house-style still describes the organization default as unpublished: %q", stale) + } + } for _, forbidden := range []string{"AGENT_SYSTEM_LEGACY_WORD_COMPARISON", "docx_kit", "new_document(template=", "dx.save("} { if strings.Contains(body, forbidden) { t.Fatalf("house-style still advertises Word bypass %q", forbidden) @@ -195,6 +200,24 @@ func TestHouseStyleRoutesProductionWordThroughUnifiedRenderer(t *testing.T) { } } +func TestDocxAuthorDescribesPublishedDefaultOnlyForWorkspaceWord(t *testing.T) { + raw, err := recipes.ReadFile("recipes/docx-author/SKILL.md") + if err != nil { + t.Fatal(err) + } + body := string(raw) + for _, required := range []string{"render_document", "已发布", "2026-10-16", "approved", "RDM", "禁止默认回退"} { + if !strings.Contains(body, required) { + t.Errorf("docx-author skill missing %q", required) + } + } + for _, stale := range []string{"pending-human-visual-review", "默认候选"} { + if strings.Contains(body, stale) { + t.Errorf("docx-author still describes the organization default as unpublished: %q", stale) + } + } +} + func TestHouseStylePython(t *testing.T) { if _, err := exec.LookPath("python3"); err != nil { t.Skip("python3 not available") diff --git a/internal/tools/document_renderer.go b/internal/tools/document_renderer.go index c3c7a83..6f747a7 100644 --- a/internal/tools/document_renderer.go +++ b/internal/tools/document_renderer.go @@ -17,7 +17,7 @@ import ( func registerDocumentRenderer(r *Registry) error { return r.Register(core.ToolDesc{ Name: "render_document", Source: core.SourceBuiltin, Edits: true, Risk: "medium", - Description: "把普通工作区 Markdown 通过统一文档渲染器生成 Markdown、DOCX、manifest 和可下载归档。只允许非受控工作区交付;项目受控文档必须交给 RDM。公司默认规则仍是待视觉评审候选,状态会写入 manifest。", + Description: "把普通工作区 Markdown 通过统一文档渲染器生成 Markdown、DOCX、manifest 和可下载归档。只允许非受控工作区交付;项目受控文档必须交给 RDM。公司默认规则已发布(负责人 2026-10-16 批准),但只用于普通工作区的 working/preview 交付,评审状态会写入 manifest。", Parameters: json.RawMessage(`{"type":"object","additionalProperties":false,"properties":{` + `"operationId":{"type":"string"},"name":{"type":"string"},"documentId":{"type":"string"},"documentType":{"type":"string"},` + `"language":{"type":"string"},"markdown":{"type":"string"},` + diff --git a/internal/tools/document_renderer_test.go b/internal/tools/document_renderer_test.go index 653bc3b..4e63ea5 100644 --- a/internal/tools/document_renderer_test.go +++ b/internal/tools/document_renderer_test.go @@ -90,6 +90,25 @@ func TestRenderDocumentToolWritesMarkdownFirstAndReportsPackageHashes(t *testing } } +func TestRenderDocumentToolDescriptionReportsPublishedWorkspaceDefault(t *testing.T) { + registry := NewRegistry() + if err := registry.RegisterBuiltin(); err != nil { + t.Fatal(err) + } + description, _, found := registry.Get("render_document") + if !found { + t.Fatal("render_document is not registered") + } + for _, required := range []string{"已发布", "RDM", "非受控工作区"} { + if !strings.Contains(description.Description, required) { + t.Errorf("render_document description missing %q: %s", required, description.Description) + } + } + if strings.Contains(description.Description, "候选") { + t.Errorf("render_document description still calls the organization default a candidate: %s", description.Description) + } +} + func TestRenderDocumentToolSchemaIsClosedAndUsesOnlyOperationIdentity(t *testing.T) { registry := NewRegistry() if err := registry.RegisterBuiltin(); err != nil {