diff --git a/.doc/ops/long-term/2026-09-19-audit-remainder.md b/.doc/ops/long-term/2026-09-19-audit-remainder.md index edc7c368..362fffe1 100644 --- a/.doc/ops/long-term/2026-09-19-audit-remainder.md +++ b/.doc/ops/long-term/2026-09-19-audit-remainder.md @@ -49,7 +49,7 @@ updated: 2026-09-19 | FE App.tsx | 部分。登录壳不再静态引用导出引擎、文件预览、管理台、块编辑器和图表。入口包 2.83 MB → 1.00 MB(br 626 KB → 235 KB)。`loginShellImports.test.ts`。工作台主体仍在 `App.tsx`,未按 hook 拆文件 | | H1-del | 关闭理由:`allowLegacyTestPrincipal` 仍被 `AllowInMemory && ContextDbHost==""` 的 dev/test 内存测试使用。删除会把这批测试变成未登录 401。保留告警,删除另开、先改测试夹具 | -仍开放:rdev P3 三样本真 PI、P4 键名空间化、首次加载 U1/U6/U7、dev 容器重建与治理库 PG。P2 规划界面已折叠长阶段、可批量纳入人工审批,并在选中 rdev 时提示 CDO 前置。U4 登录扇出收口仍有效,浏览器请求次数证据还没有。2026-09-19 在 `fix/audit-remainder-closeout` 跑过 `cd web && npm test && npm run build`:node:test 414 pass / 0 fail,vitest 32 pass;入口 `index-BFf6n8IA.js` 1,005.01 kB,br 235,285 B。CURRENT 文首 schema 52 与三套容器一致,00053 要等重建后才升。H2 批注/审计的项目成员检查在 Postgres 上走 `RequireProjectAccess`;内存授权器在无库时把任意正用户 id 视为成员,这是 `TestAuthorizeDocRevision_ReviewerAndNonMemberCannotWrite` 的隔离前提,不能改成默认拒绝。H2 的 HTTP 负例因此要在 `TEST_DATABASE_URL` 上补,本机未设置该变量。M7 的投影意图已随账本提交(goose `00053`);容器 schema 仍是 52,要等占槽重建后才升到 53。 +仍开放:rdev P3 三样本真 PI、首次加载 U1/U6/U7、dev 容器重建与治理库 PG。P4 键名空间化已在本分支落地:新清单用带前缀的任务键,旧键与旧 doc_id 仍可读,`TestPublishedTaskKeysAreNamespacedAndDisjoint`。意图短语仍输出旧键,执行路径按别名接上。P2 规划界面已折叠长阶段、可批量纳入人工审批,并在选中 rdev 时提示 CDO 前置。U4 登录扇出收口仍有效,浏览器请求次数证据还没有。2026-09-19 在 `fix/audit-remainder-closeout` 跑过 `cd web && npm test && npm run build`:node:test 414 pass / 0 fail,vitest 32 pass;入口 `index-BFf6n8IA.js` 1,005.01 kB,br 235,285 B。CURRENT 文首 schema 52 与三套容器一致,00053 要等重建后才升。H2 批注/审计的项目成员检查在 Postgres 上走 `RequireProjectAccess`;内存授权器在无库时把任意正用户 id 视为成员,这是 `TestAuthorizeDocRevision_ReviewerAndNonMemberCannotWrite` 的隔离前提,不能改成默认拒绝。H2 的 HTTP 负例因此要在 `TEST_DATABASE_URL` 上补,本机未设置该变量。M7 的投影意图已随账本提交(goose `00053`);容器 schema 仍是 52,要等占槽重建后才升到 53。 红线关闭(不是实现): diff --git a/.doc/status/FEATURE-MATRIX.md b/.doc/status/FEATURE-MATRIX.md index e7ee8768..f9a9f6f0 100644 --- a/.doc/status/FEATURE-MATRIX.md +++ b/.doc/status/FEATURE-MATRIX.md @@ -57,7 +57,7 @@ | 对话数据治理(D7) | 留存/删除口径、库·备份·日志控制、敏感字段加密边界 | deferred | deferred_enhancement(违规则升 blocker) | [计划](../remediation/2026-09-16-feature-matrix-production-readiness-plan.md) Scope Boundaries;[收口 §7.1](../remediation/2026-09-16-partial-deferred-completion-plan.md);[D7 ADR 草案](../adr/2026-09-17-d7-chat-data-retention.md);[W7 盘点](../audit/2026-09-17-d7-data-inventory.md);[报告](../test-reports/2026-09-17-w7-d7-inventory-test-report.md) | **after W7**:只读盘点完成(sessions/messages/KB/日志/备份/event)。**未发现**已确认基线违规,未写 gap-matrix。口径仍 `pending-compliance-signoff`;禁止按 365/180/30 物理删除。本行不升 done | `chat` | | 智能体小队 | 角色-任务-工作区-黑板 | done | non_blocker | [agent-squad](../system-design/agent-squad/);[test 2026-09-07](../test-reports/2026-09-07-dynamic-squad-planning-test-report.md) | 非关键步骤降级(A5/A6)属 backlog | `squad` | | 工作流治理 | 三正式工作流、设置页/DutyMode、动作级鉴权、真实 PI 三工作流 E2E、full/none 混合长链 | done | non_blocker | [三工作流整改](../remediation/2026-09-12-three-workflows-strict-governance-remediation.md);[409/职责](../test-reports/2026-09-14-workflow-plan-409-frozen-and-optional-duties-test-report.md);[鉴权](../test-reports/2026-09-14-governance-action-level-authz-test-report.md);[PI E2E](../test-reports/2026-09-16-pi-three-workflow-e2e-test-report.md);[Wave2 full/none](../test-reports/2026-09-16-wave2-real-pi-evidence-test-report.md) | full 跨用户审批 + none 免审批不阻塞已签。纯软件自举仍独立 planned | `workflow` | -| 研发设计控制(rdev) | 医疗第 4 工作流:13×60 manifest/模板、`squad.rdev_doc.v1` kind 编排、main-completed 硬 precheck、StageKey 动态化;不含前端/真 PI/键名空间化 | partial | deferred_enhancement | [设计](../system-design/project-workflow/07-研发工作流rdev接入-设计与实施计划.md);[short-term](../archive/ops/2026-09-17-rdev-workflow-integration.md);[模板深度](../archive/ops/2026-09-17-rdev-template-depth.md);[S0+S1 首批报告](../test-reports/2026-09-17-rdev-template-depth-test-report.md);[S1 续批报告](../test-reports/2026-09-17-rdev-template-depth-s1-continued-test-report.md);[写作规范](../system-design/project-workflow/08-rdev模板写作规范.md);[backlog](../backlog/2026-09-17-rdev-phase2-backlog.md) | **P0+P1 done**。**模板深度 S0+S1 首批 + S1 续批 done**:60/60 模板已补强;真 PI 抽样 PASS(含 `rd_planning_review` + `rd_design_vv`,项目 6446)。剩余:S3 overlay 与 R2/R3 已接。P2 名称匹配、阶段折叠、批量审批已接。选中 rdev 时显示与服务端相同的 CDO 前置说明(`workflowPrerequisite.test.ts`)。P3 三样本真 PI、P4 键名空间化仍未做 | `workflow-def` / `rdev-templates` | +| 研发设计控制(rdev) | 医疗第 4 工作流:13×60 manifest/模板、`squad.rdev_doc.v1` kind 编排、main-completed 硬 precheck、StageKey 动态化;不含前端/真 PI/键名空间化 | partial | deferred_enhancement | [设计](../system-design/project-workflow/07-研发工作流rdev接入-设计与实施计划.md);[short-term](../archive/ops/2026-09-17-rdev-workflow-integration.md);[模板深度](../archive/ops/2026-09-17-rdev-template-depth.md);[S0+S1 首批报告](../test-reports/2026-09-17-rdev-template-depth-test-report.md);[S1 续批报告](../test-reports/2026-09-17-rdev-template-depth-s1-continued-test-report.md);[写作规范](../system-design/project-workflow/08-rdev模板写作规范.md);[backlog](../backlog/2026-09-17-rdev-phase2-backlog.md) | **P0+P1 done**。**模板深度 S0+S1 首批 + S1 续批 done**:60/60 模板已补强;真 PI 抽样 PASS(含 `rd_planning_review` + `rd_design_vv`,项目 6446)。剩余:S3 overlay 与 R2/R3 已接。P2 名称匹配、阶段折叠、批量审批已接。选中 rdev 时显示与服务端相同的 CDO 前置说明(`workflowPrerequisite.test.ts`)。P4 键名空间化已接:发布清单用 `bd_`/`pm_`/`tm_`/`sw_`,旧键仍走同一套步骤和模板,旧 doc_id 仍可查(`keyspace_test.go`)。P3 三样本真 PI 仍未做 | `workflow-def` / `rdev-templates` | | 纯软件项目自举 | 软件项目创建、Bootstrap、Delivery、受控工程交接 | partial | deferred_enhancement(明确不挡首发) | [设计](../system-design/2026-09-14-software-project-bootstrap-workflow.md);[ADR](../adr/2026-09-16-software-project-bootstrap-keys.md);[L1 报告](../test-reports/2026-09-16-software-project-bootstrap-test-report.md);[W5a](../test-reports/2026-09-17-w5a-software-bootstrap-ui-pi-test-report.md) | **before**:L1 + UI 仍克隆医疗模板。**after W5a**:catalog 驱动创建、软件不挂医疗 key、docs_only 真 PI 建议 delivery 且不自动激活。**SWBOOT-10 仍 stub**(W5b);本行不升 done | `workflow-design` | | IAM / 鉴权 | 项目级 API 鉴权 + RBAC;会话/阶段授权 | done | 加固 | [v3 full inventory](../audit/authorization-matrix-v3-full.md);[session 中断](../test-reports/2026-09-16-iam-route-inventory-test-report.md);[fail-closed](../test-reports/2026-09-16-wave1-iam-fail-closed-test-report.md);[410 策略](../audit/2026-09-16-bug11-410-retire-decision.md);[R13](../audit/2026-09-16-r13-data-safety-checklist.md) | 全路由 inventory + session 中断负例 + 410 冻结 + R13 基线已签 | `authz` | | 文档治理 | 法定文档生命周期 + 租约 + 受控正文读取 | done | non_blocker | [docgov](../test-reports/2026-09-08-docgov-lifecycle-and-claiming-framework-test-report.md);[正文 UI](../test-reports/2026-09-14-controlled-content-ui-acceptance.md);[canonical hash](../remediation/2026-09-14-controlled-content-canonical-hash-remediation.md) | 项目 143 只读;不终止 | `docgov` | diff --git a/internal/app/instance_duty_adapter.go b/internal/app/instance_duty_adapter.go index 301d4c51..d1d66a5f 100644 --- a/internal/app/instance_duty_adapter.go +++ b/internal/app/instance_duty_adapter.go @@ -5,6 +5,7 @@ import ( "strings" "chat-agent-system/internal/projectinstance" + "chat-agent-system/internal/workflowdef" ) // projectInstanceDutyAdapter 将活跃工作流实例的逐文档职责投影为 bizdev 窄接口, @@ -28,7 +29,7 @@ func (a *projectInstanceDutyAdapter) ResolveGovernedDuty(ctx context.Context, pr } for _, st := range detail.Instance.ConfigSnapshot.Stages { for _, dt := range st.DocTasks { - if dt.TaskKey != taskKey || !dt.Enabled { + if !workflowdef.SameTaskKey(dt.TaskKey, taskKey) || !dt.Enabled { continue } mode, effective, isAuthorOnly := projectinstance.ResolveDocTaskDuty(dt) diff --git a/internal/bizdev/doc_id.go b/internal/bizdev/doc_id.go index 64f10b32..eebe2ce1 100644 --- a/internal/bizdev/doc_id.go +++ b/internal/bizdev/doc_id.go @@ -5,11 +5,14 @@ import ( "fmt" "regexp" "strings" + + "chat-agent-system/internal/docgov" + "chat-agent-system/internal/workflowdef" ) var ( - legacyDocPPattern = regexp.MustCompile(`^doc-p(\d+)-(.+)$`) - legacyDocProjHyphenPattern = regexp.MustCompile(`^doc-proj(\d+)-(.+)$`) + legacyDocPPattern = regexp.MustCompile(`^doc-p(\d+)-(.+)$`) + legacyDocProjHyphenPattern = regexp.MustCompile(`^doc-proj(\d+)-(.+)$`) ) // normalizeDocID 将 legacy 本地 ID 映射为服务端权威 doc_id。 @@ -46,7 +49,7 @@ func (s *Service) ResolveAndValidateDocID(ctx context.Context, req TaskRunReques return docID, nil } - doc, err := s.docGov.GetDocument(ctx, docID) + doc, err := lookupDocument(ctx, s, docID) if err != nil { // 尚未建档:与空 doc_id 行为一致,交由 CreateOrGetDocument 创建 if fallback := canonicalDocID(req.ProjectID, req.TaskKey); fallback != "" { @@ -61,9 +64,23 @@ func (s *Service) ResolveAndValidateDocID(ctx context.Context, req TaskRunReques if req.ProjectID > 0 && doc.ProjectID != req.ProjectID { return "", fmt.Errorf("doc_id %s does not belong to project %d", docID, req.ProjectID) } - // 已注册文档的 task_key 以账本为准;客户端 doc_id 不得改写或覆盖请求 task_key。 - if req.TaskKey != "" && doc.TaskKey != "" && doc.TaskKey != req.TaskKey { + // 已注册文档的 task_key 以账本为准。旧键与新键是同一任务,不算改写。 + if req.TaskKey != "" && doc.TaskKey != "" && !workflowdef.SameTaskKey(doc.TaskKey, req.TaskKey) { return "", fmt.Errorf("doc_id %s task_key mismatch: expected %s got %s", docID, req.TaskKey, doc.TaskKey) } return doc.DocID, nil } + +func lookupDocument(ctx context.Context, s *Service, docID string) (*docgov.DocumentRecord, error) { + doc, err := s.docGov.GetDocument(ctx, docID) + if err == nil { + return doc, nil + } + for _, alt := range workflowdef.AlternateDocIDs(docID) { + altDoc, altErr := s.docGov.GetDocument(ctx, alt) + if altErr == nil { + return altDoc, nil + } + } + return nil, err +} diff --git a/internal/bizdev/full_link_e2e_test.go b/internal/bizdev/full_link_e2e_test.go index 717452a8..1f7b4022 100644 --- a/internal/bizdev/full_link_e2e_test.go +++ b/internal/bizdev/full_link_e2e_test.go @@ -11,6 +11,7 @@ import ( "chat-agent-system/internal/docgov" "chat-agent-system/internal/docrevision" "chat-agent-system/internal/stream" + "chat-agent-system/internal/workflowdef" ) // executeStageDrainingPending runs ExecuteStage, confirming high-risk agent candidates @@ -139,7 +140,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) { if !resV01.Success { t.Fatalf("Stage V0.1 should succeed, got error: %s", resV01.ErrorReason) } - if resV01.PrimaryArtifact == nil || resV01.PrimaryArtifact.TaskKey != "intake" { + if resV01.PrimaryArtifact == nil || !workflowdef.SameTaskKey(resV01.PrimaryArtifact.TaskKey, "intake") { t.Fatalf("Stage V0.1 primary artifact should be intake, got: %+v", resV01.PrimaryArtifact) } if resV01.AdvisorBrief == nil || resV01.AdvisorBrief.MaturityLevel != "V0.1" { @@ -195,7 +196,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) { keys, resV05.AdvisorBrief.MaturityScore, resV05.AdvisorBrief.MaturityLevel) // 验证 feasibility_reg 受控文档已建立 - docID := "doc_proj1001_feasibility_reg" + docID := "doc_proj1001_bd_feasibility_reg" docRec, err := bizdev.Snapshot(svc).Gov.GetDocument(ctx, docID) if err != nil || docRec == nil { t.Fatalf("get docgov record failed: %v", err) @@ -282,7 +283,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) { t.Fatalf("Stage V0.5 force re-run should succeed, error: %s", resV05Updated.ErrorReason) } for _, art := range resV05Updated.SubArtifacts { - if art.TaskKey == "feasibility_reg" { + if workflowdef.SameTaskKey(art.TaskKey, "feasibility_reg") { _ = bizdev.Snapshot(svc).Gov.SetDocumentWorkDir(ctx, docID, art.WorkDir) break } @@ -362,7 +363,7 @@ func TestBizDev_FullLink_V01_V05_V10_E2E(t *testing.T) { if !resV10.Success { t.Fatalf("Stage V1.0 should succeed, error: %s", resV10.ErrorReason) } - if resV10.PrimaryArtifact == nil || resV10.PrimaryArtifact.TaskKey != "cdo_quote" { + if resV10.PrimaryArtifact == nil || !workflowdef.SameTaskKey(resV10.PrimaryArtifact.TaskKey, "cdo_quote") { t.Fatalf("Stage V1.0 primary artifact should be cdo_quote") } diff --git a/internal/bizdev/service.go b/internal/bizdev/service.go index 138d43fa..6c65dbae 100644 --- a/internal/bizdev/service.go +++ b/internal/bizdev/service.go @@ -220,7 +220,7 @@ func NewService(cfg *config.Config) (*Service, error) { } } - if pwSvc != nil && doc.TaskKey == "system_reqs" { + if pwSvc != nil && workflowdef.SameTaskKey(doc.TaskKey, "system_reqs") { _, _ = pwSvc.CheckAndPropagateStale(ctx, doc.DocID, doc.ContentSHA256, fmt.Sprintf("上游系统需求规格书 V%d 已正式批准,已标记关联下游验证方案为待复核(stale)", doc.Version)) } }) @@ -285,22 +285,31 @@ var SupportedStages = []StageMeta{ Stage: StageV01_Triage, Title: "V0.1 闪电摸底与盲区探测", Description: "快速梳理客户诉求与约束,建立基准技术假设,生成首期澄清微信话术与初始需求表", - Tasks: []string{"intake"}, + Tasks: []string{"bd_intake"}, }, { Stage: StageV05_Feasibility, Title: "V0.5 方案成型与法规对齐", Description: "法规与注册路径初筛、竞品调研、系统初步架构设计与关键选型建议", - Tasks: []string{"research", "feasibility_dev", "feasibility_reg", "user_requirements", "prelim_design"}, + Tasks: []string{"bd_research", "bd_feasibility_dev", "bd_feasibility_reg", "bd_user_requirements", "bd_prelim_design"}, }, { Stage: StageV10_Quoting, Title: "V1.0 工程拆解与商业锁定", Description: "全维度 WBS 工时拆解、Excel 定价核算、正式 CDO 商务报价单与谈判防御条款建议", - Tasks: []string{"cdo_quote"}, + Tasks: []string{"bd_cdo_quote"}, }, } +func taskIs(key string, wants ...string) bool { + for _, want := range wants { + if workflowdef.SameTaskKey(key, want) { + return true + } + } + return false +} + func (s *Service) ListTasks() []TaskMeta { return s.squad.ListTasks() } @@ -311,7 +320,7 @@ func resolveDocStage(req TaskRunRequest) string { if sk := strings.TrimSpace(req.StageKey); sk != "" { return sk } - switch strings.TrimSpace(req.TaskKey) { + switch workflowdef.ImplementationTaskKey(strings.TrimSpace(req.TaskKey)) { case "intake": return string(StageV01_Triage) case "research", "feasibility_dev", "feasibility_reg", "user_requirements", "prelim_design": @@ -390,7 +399,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun } // 针对 system_reqs(智能产品经理):若未显式提供 materials,自动抽取项目事实黑板/集体上下文为需求输入 - if req.TaskKey == "system_reqs" && len(materials) == 0 && s.ctxMgr != nil && req.ProjectID > 0 { + if workflowdef.SameTaskKey(req.TaskKey, "system_reqs") && len(materials) == 0 && s.ctxMgr != nil && req.ProjectID > 0 { coll, err := s.getProjectContextForUser(ctx, req.ProjectID, req.UserID) if err == nil && coll != nil { var b strings.Builder @@ -437,7 +446,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun // 自动补充知识库检索 (如针对 research、feasibility_reg、cleanroom_spec、prelim_design、system_reqs、test_manager 等阶段) hits := req.KnowledgeHits - if len(hits) == 0 && (req.TaskKey == "research" || req.TaskKey == "feasibility_reg" || req.TaskKey == "cleanroom_spec" || req.TaskKey == "prelim_design" || req.TaskKey == "system_reqs" || req.TaskKey == "test_manager") { + if len(hits) == 0 && taskIs(req.TaskKey, "research", "feasibility_reg", "cleanroom_spec", "prelim_design", "system_reqs", "test_manager") { device := req.DeviceType if device == "" && req.Fields != nil { device = req.Fields["device_type"] @@ -490,7 +499,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun } // 智能测试工程师:统一项目上下文快照;SRS 仅为可选高权威来源 var upstreamSRDoc *docgov.DocumentRecord - if req.TaskKey == "test_manager" || req.TaskKey == "system_reqs" { + if taskIs(req.TaskKey, "test_manager", "system_reqs") { priorDocs := make([]squad.PriorDocItem, 0) facts := map[string]string{} if req.Fields != nil { @@ -554,7 +563,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req TaskRunRequest) (*TaskRun } } // 优先注入已批准 SR(PM 或 rd_system_reqs) - for _, key := range []string{"system_reqs", "rd_system_reqs"} { + for _, key := range []string{"system_reqs", "pm_system_reqs", "rd_system_reqs"} { docID := fmt.Sprintf("doc_proj%d_%s", req.ProjectID, key) if s.docGov == nil { continue @@ -1157,7 +1166,7 @@ func (s *Service) finalizeSquadDeliverable( map[string]any{"task_key": res.TaskKey, "work_dir": res.WorkDir}, true) } - if res.Success && req.TaskKey == "test_manager" && upstreamSRDoc != nil && s.projectWorkflow != nil && req.ProjectID > 0 { + if res.Success && workflowdef.SameTaskKey(req.TaskKey, "test_manager") && upstreamSRDoc != nil && s.projectWorkflow != nil && req.ProjectID > 0 { _ = s.projectWorkflow.RecordLineage(ctx, &projectworkflow.DocumentLineage{ ProjectID: req.ProjectID, DownstreamDocID: docID, @@ -1169,7 +1178,7 @@ func (s *Service) finalizeSquadDeliverable( }) } // Stale propagation only after fact source committed (not while pending confirmation). - if res.Success && req.TaskKey == "system_reqs" && s.projectWorkflow != nil && req.ProjectID > 0 && + if res.Success && workflowdef.SameTaskKey(req.TaskKey, "system_reqs") && s.projectWorkflow != nil && req.ProjectID > 0 && (s.docRev == nil || (docRec != nil && docRec.ContentSHA256 != "")) { sha := sha256.Sum256([]byte(res.MarkdownBody)) shaStr := hex.EncodeToString(sha[:]) diff --git a/internal/bizdev/stage_quality_stop_test.go b/internal/bizdev/stage_quality_stop_test.go index 2dfe59d8..f6baecb6 100644 --- a/internal/bizdev/stage_quality_stop_test.go +++ b/internal/bizdev/stage_quality_stop_test.go @@ -10,6 +10,7 @@ import ( "chat-agent-system/internal/config" "chat-agent-system/internal/squad" + "chat-agent-system/internal/workflowdef" ) func TestClassifyStageTaskHalt_QualityFailedStopsWithoutAbsorbing(t *testing.T) { @@ -77,12 +78,12 @@ func TestExecuteStage_QualityFailed_StopsChainWithoutAbsorbingFactsOrPriorDoc(t var seenKeys []string svc.stageTaskHook = func(_ context.Context, req TaskRunRequest) (*TaskRunResult, error) { seenKeys = append(seenKeys, req.TaskKey) - if req.TaskKey != "research" { + if !workflowdef.SameTaskKey(req.TaskKey, "research") { t.Fatalf("quality fail must stop before subsequent task %q; seen=%v priors=%d", req.TaskKey, seenKeys, len(req.PriorDocs)) } for _, p := range req.PriorDocs { - if p.DocKey == "research" || strings.Contains(p.Content, "未过门禁") { + if workflowdef.SameTaskKey(p.DocKey, "research") || strings.Contains(p.Content, "未过门禁") { t.Fatalf("must not feed failed markdown as PriorDoc: %+v", p) } } @@ -135,10 +136,10 @@ func TestExecuteStage_QualityFailed_StopsChainWithoutAbsorbingFactsOrPriorDoc(t if stageRes.PrimaryArtifact.TerminalStatus() != TaskStatusQualityFailed { t.Fatalf("primary status=%q", stageRes.PrimaryArtifact.TerminalStatus()) } - if len(stageRes.SubArtifacts) != 1 || seenKeys[0] != "research" || len(seenKeys) != 1 { + if len(stageRes.SubArtifacts) != 1 || !workflowdef.SameTaskKey(seenKeys[0], "research") || len(seenKeys) != 1 { t.Fatalf("must stop after first task; sub=%d seen=%v", len(stageRes.SubArtifacts), seenKeys) } - for _, next := range []string{"feasibility_dev", "feasibility_reg", "user_requirements", "prelim_design"} { + for _, next := range []string{"feasibility_dev", "bd_feasibility_dev", "feasibility_reg", "bd_feasibility_reg", "user_requirements", "bd_user_requirements", "prelim_design", "bd_prelim_design"} { if _, err := os.Stat(filepath.Join(stageWork, next, "output.md")); err == nil { t.Fatalf("subsequent task %s must not run", next) } diff --git a/internal/projectinstance/activation_official_pg_test.go b/internal/projectinstance/activation_official_pg_test.go index 55d848a1..8596f8c3 100644 --- a/internal/projectinstance/activation_official_pg_test.go +++ b/internal/projectinstance/activation_official_pg_test.go @@ -88,7 +88,7 @@ func TestPG_ActivateOfficialWorkflowRuntimeMetadata(t *testing.T) { SELECT dti.executor_key FROM project_doc_task_instances dti JOIN project_stage_instances si ON si.id = dti.stage_instance_id - WHERE si.workflow_instance_id = $1 AND dti.task_key = 'intake' + WHERE si.workflow_instance_id = $1 AND dti.task_key IN ('intake', 'bd_intake') `, activated.Instance.ID).Scan(&executorKey) if err != nil { t.Fatalf("query runtime metadata: %v", err) diff --git a/internal/projectinstance/pg_integration_test.go b/internal/projectinstance/pg_integration_test.go index e0e71db4..58ef79d6 100644 --- a/internal/projectinstance/pg_integration_test.go +++ b/internal/projectinstance/pg_integration_test.go @@ -96,7 +96,7 @@ func TestPG_ActivationAndStageAdvance(t *testing.T) { } for i := range plan.Stages { for j := range plan.Stages[i].DocTasks { - if plan.Stages[i].DocTasks[j].TaskKey == "intake" { + if plan.Stages[i].DocTasks[j].TaskKey == "intake" || plan.Stages[i].DocTasks[j].TaskKey == "bd_intake" { plan.Stages[i].DocTasks[j].Duties = projectinstance.DocDuty{ AuthorUserID: 2, ReviewerUserID: 3, ApproverUserID: 4, } diff --git a/internal/projectinstance/stage_access.go b/internal/projectinstance/stage_access.go index 47ca72e3..a2552526 100644 --- a/internal/projectinstance/stage_access.go +++ b/internal/projectinstance/stage_access.go @@ -2,15 +2,17 @@ package projectinstance import ( "context" + + "chat-agent-system/internal/workflowdef" ) // StageAccess 用户在某项目实例下可访问的阶段与文档任务范围。 type StageAccess struct { - Restricted bool `json:"restricted"` - ActiveStageKey string `json:"active_stage_key,omitempty"` - AllowedStageKeys map[string]bool `json:"allowed_stage_keys,omitempty"` - AllowedTaskKeys map[string]bool `json:"allowed_task_keys,omitempty"` - ViewableTaskKeys map[string]bool `json:"viewable_task_keys,omitempty"` + Restricted bool `json:"restricted"` + ActiveStageKey string `json:"active_stage_key,omitempty"` + AllowedStageKeys map[string]bool `json:"allowed_stage_keys,omitempty"` + AllowedTaskKeys map[string]bool `json:"allowed_task_keys,omitempty"` + ViewableTaskKeys map[string]bool `json:"viewable_task_keys,omitempty"` } // ResolveStageAccess 基于治理实例与三权指派计算阶段 ABAC 范围。 @@ -129,7 +131,14 @@ func (a *StageAccess) TaskKeyAllowed(taskKey string) bool { if a == nil || !a.Restricted { return true } - return a.AllowedTaskKeys[taskKey] + return mapHasTaskKey(a.AllowedTaskKeys, taskKey) +} + +func mapHasTaskKey(keys map[string]bool, taskKey string) bool { + if keys[taskKey] || keys[workflowdef.CanonicalTaskKey(taskKey)] || keys[workflowdef.ImplementationTaskKey(taskKey)] { + return true + } + return false } // TaskKeyVisible 判断文档是否对项目成员可见(view 豁免阶段收窄)。 @@ -138,9 +147,9 @@ func (a *StageAccess) TaskKeyVisible(taskKey string) bool { return true } if len(a.ViewableTaskKeys) == 0 { - return a.AllowedTaskKeys[taskKey] + return mapHasTaskKey(a.AllowedTaskKeys, taskKey) } - return a.ViewableTaskKeys[taskKey] + return mapHasTaskKey(a.ViewableTaskKeys, taskKey) } // StageKeyAllowed 判断 stage_key 是否在允许范围内。 diff --git a/internal/projectworkflow/catalog.go b/internal/projectworkflow/catalog.go index 36a0b58e..3ff2d8f3 100644 --- a/internal/projectworkflow/catalog.go +++ b/internal/projectworkflow/catalog.go @@ -85,8 +85,8 @@ func NewDefaultCatalog() Catalog { Name: "软件项目自举流程", Description: "S0–S4:章程、现状、需求、架构、风险、路线与就绪评审", StageKey: "S0_boundary", - TaskKey: "software_charter", - OutputDocType: "software_charter", + TaskKey: "sw_software_charter", + OutputDocType: "sw_software_charter", OutputDocTitle: "项目章程", Enabled: true, } @@ -96,8 +96,8 @@ func NewDefaultCatalog() Catalog { Name: "软件交付迭代流程", Description: "D0–D4:变更定义、实施证据、验证、发布与复盘", StageKey: "D0_change_definition", - TaskKey: "change_brief", - OutputDocType: "change_brief", + TaskKey: "sw_change_brief", + OutputDocType: "sw_change_brief", OutputDocTitle: "变更简报", Enabled: true, } diff --git a/internal/server/workflow_governance_full_none_piagent_pg_test.go b/internal/server/workflow_governance_full_none_piagent_pg_test.go index 008e6767..299a8350 100644 --- a/internal/server/workflow_governance_full_none_piagent_pg_test.go +++ b/internal/server/workflow_governance_full_none_piagent_pg_test.go @@ -92,12 +92,12 @@ func TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain(t *testing.T) { for j := range stage.DocTasks { dt := &stage.DocTasks[j] switch { - case stage.StageKey == "V0.1_triage" && dt.TaskKey == "intake": + case stage.StageKey == "V0.1_triage" && (dt.TaskKey == "intake" || dt.TaskKey == "bd_intake"): dt.Enabled = true dt.Duties = projectinstance.DocDuty{ AuthorUserID: creatorID, ReviewerUserID: reviewerID, ApproverUserID: approverID, } - case stage.StageKey == "V0.5_feasibility" && dt.TaskKey == "research": + case stage.StageKey == "V0.5_feasibility" && (dt.TaskKey == "research" || dt.TaskKey == "bd_research"): dt.Enabled = true dt.Duties = projectinstance.DocDuty{} // none default: @@ -265,7 +265,7 @@ func TestWorkflowGovernance_PiAgent_PG_FullNoneMixedChain(t *testing.T) { } var researchItem *projectinstance.DocTaskReadiness for i := range readiness.DocTasks { - if readiness.DocTasks[i].TaskKey == "research" { + if readiness.DocTasks[i].TaskKey == "research" || readiness.DocTasks[i].TaskKey == "bd_research" { researchItem = &readiness.DocTasks[i] break } diff --git a/internal/squad/extract.go b/internal/squad/extract.go index 74bbcd3f..0752cc9e 100644 --- a/internal/squad/extract.go +++ b/internal/squad/extract.go @@ -9,6 +9,8 @@ import ( "path/filepath" "strings" "unicode/utf8" + + "chat-agent-system/internal/workflowdef" ) // ExtractTextWithAnchors 从不同格式材料中提取纯文本,并自动注入物理页码锚点 @@ -230,6 +232,7 @@ func ExtractAdvisorBriefing(taskKey string, markdownBody string, fields map[stri } // 2. 根据任务类型与提取事实定制成熟度、风险与话术 + taskKey = workflowdef.ImplementationTaskKey(taskKey) switch taskKey { case "intake", "user_requirements": brief.MaturityScore = 65 @@ -343,4 +346,3 @@ func ExtractAdvisorBriefing(taskKey string, markdownBody string, fields map[stri return brief } - diff --git a/internal/squad/keyspace_alias_test.go b/internal/squad/keyspace_alias_test.go new file mode 100644 index 00000000..0fcbc09a --- /dev/null +++ b/internal/squad/keyspace_alias_test.go @@ -0,0 +1,20 @@ +package squad + +import "testing" + +func TestDefaultWorkflowSteps_CanonicalAlias(t *testing.T) { + legacy := DefaultWorkflowSteps("intake") + canonical := DefaultWorkflowSteps("bd_intake") + if len(legacy) == 0 || len(legacy) != len(canonical) { + t.Fatalf("intake steps %d, bd_intake steps %d", len(legacy), len(canonical)) + } + if legacy[0].Role != canonical[0].Role { + t.Fatalf("alias diverged: %s vs %s", legacy[0].Role, canonical[0].Role) + } + if TaskArtifactTitle("intake") != TaskArtifactTitle("bd_intake") { + t.Fatal("artifact title must match across the alias") + } + if !isStandardTask("tm_system_verification") || !isStandardTask("test_manager") { + t.Fatal("test manager alias must stay on the whitelist") + } +} diff --git a/internal/squad/planner.go b/internal/squad/planner.go index 5c3229a8..a7cc1534 100644 --- a/internal/squad/planner.go +++ b/internal/squad/planner.go @@ -55,12 +55,7 @@ func (p *AdaptivePlanner) Plan(req PlannerRequest) (*PlannerResult, error) { } func isStandardTask(taskKey string) bool { - for _, t := range SupportedTasks { - if t.Key == taskKey { - return true - } - } - return false + return taskKnown(taskKey) } // planFixedAdaptive 固定工作流动态微调 diff --git a/internal/squad/quality_gate.go b/internal/squad/quality_gate.go index d45ac294..2f3b8fa4 100644 --- a/internal/squad/quality_gate.go +++ b/internal/squad/quality_gate.go @@ -16,6 +16,7 @@ import ( "chat-agent-system/internal/agent" "chat-agent-system/internal/stream" + "chat-agent-system/internal/workflowdef" ) // ProhibitedPhrases 医疗器械文档小队严禁出现的敷衍套话与省略词 @@ -97,6 +98,9 @@ func (q *QualityGate) Validate(wsDir string, taskKey string, content string) (*Q report.Passed = false } + // 新键与旧键共用同一套门禁;rd_* 不在别名表里,不会被改写。 + taskKey = workflowdef.ImplementationTaskKey(taskKey) + // 4. CDO 报价任务检查 if taskKey == "cdo_quote" { xlsxPath := filepath.Join(wsDir, "CDO报价核算-V01.xlsx") diff --git a/internal/squad/service.go b/internal/squad/service.go index a90f42a6..3a99f37f 100644 --- a/internal/squad/service.go +++ b/internal/squad/service.go @@ -13,6 +13,7 @@ import ( "chat-agent-system/internal/config" "chat-agent-system/internal/quote" "chat-agent-system/internal/stream" + "chat-agent-system/internal/workflowdef" ) // TaskMeta 任务元信息 @@ -126,15 +127,15 @@ func NewService(cfg *config.Config) *Service { } var baseSupportedTasks = []TaskMeta{ - {Key: "intake", Title: "用户初始需求收集", ArtifactTitle: "用户初始需求收集表", Description: "梳理客户原始需求、提炼事实清单与缺口"}, - {Key: "research", Title: "调研", ArtifactTitle: "调研报告", Description: "市场竞品同维度对比、法规要点与技术方案初筛"}, - {Key: "feasibility_dev", Title: "设计开发可行性评估", ArtifactTitle: "设计开发可行性评估报告", Description: "工程化差距、技术路线与系统架构可行性推导"}, - {Key: "feasibility_reg", Title: "注册路径可行性评估", ArtifactTitle: "注册路径可行性评估报告", Description: "分类建议、临床评价策略与主要标准检测规划"}, - {Key: "user_requirements", Title: "用户需求表", ArtifactTitle: "用户需求表", Description: "用户角色画像与可追溯需求条目清单"}, - {Key: "system_reqs", Title: "系统需求规格书", ArtifactTitle: "系统需求规格书", Description: "智能产品经理深度提炼,输出系统需求规格书及工程追溯"}, - {Key: "test_manager", Title: "系统验证方案及计划", ArtifactTitle: "系统验证方案及计划", Description: "智能测试经理基于系统需求生成系统测试方案与测试用例"}, - {Key: "prelim_design", Title: "系统初步设计方案", ArtifactTitle: "系统初步设计方案", Description: "子系统架构边界、数据接口与Mermaid上下文流向"}, - {Key: "cdo_quote", Title: "CDO报价单", ArtifactTitle: "CDO报价单(含定价核算 Excel)", Description: "基于公司定价模型的工时拆解、成本核算与法定说明"}, + {Key: "bd_intake", Title: "用户初始需求收集", ArtifactTitle: "用户初始需求收集表", Description: "梳理客户原始需求、提炼事实清单与缺口"}, + {Key: "bd_research", Title: "调研", ArtifactTitle: "调研报告", Description: "市场竞品同维度对比、法规要点与技术方案初筛"}, + {Key: "bd_feasibility_dev", Title: "设计开发可行性评估", ArtifactTitle: "设计开发可行性评估报告", Description: "工程化差距、技术路线与系统架构可行性推导"}, + {Key: "bd_feasibility_reg", Title: "注册路径可行性评估", ArtifactTitle: "注册路径可行性评估报告", Description: "分类建议、临床评价策略与主要标准检测规划"}, + {Key: "bd_user_requirements", Title: "用户需求表", ArtifactTitle: "用户需求表", Description: "用户角色画像与可追溯需求条目清单"}, + {Key: "pm_system_reqs", Title: "系统需求规格书", ArtifactTitle: "系统需求规格书", Description: "智能产品经理深度提炼,输出系统需求规格书及工程追溯"}, + {Key: "tm_system_verification", Title: "系统验证方案及计划", ArtifactTitle: "系统验证方案及计划", Description: "智能测试经理基于系统需求生成系统测试方案与测试用例"}, + {Key: "bd_prelim_design", Title: "系统初步设计方案", ArtifactTitle: "系统初步设计方案", Description: "子系统架构边界、数据接口与Mermaid上下文流向"}, + {Key: "bd_cdo_quote", Title: "CDO报价单", ArtifactTitle: "CDO报价单(含定价核算 Excel)", Description: "基于公司定价模型的工时拆解、成本核算与法定说明"}, } // SupportedTasks 标准小队任务白名单(商务九键 + rdev 六十键)。 @@ -148,9 +149,20 @@ func (s *Service) ListTasks() []TaskMeta { return SupportedTasks } -func TaskArtifactTitle(taskKey string) string { +func taskKnown(taskKey string) bool { + canon := workflowdef.CanonicalTaskKey(taskKey) for _, t := range SupportedTasks { - if t.Key == taskKey { + if t.Key == canon { + return true + } + } + return false +} + +func TaskArtifactTitle(taskKey string) string { + canon := workflowdef.CanonicalTaskKey(taskKey) + for _, t := range SupportedTasks { + if t.Key == canon { return t.ArtifactTitle } } @@ -231,7 +243,7 @@ func (s *Service) ExecuteTask(ctx context.Context, req SquadRunRequest) (*SquadR // 2. 若为 CDO 报价任务,先执行 Excel 报价核算引擎并回填 var attachments []Attachment - if req.TaskKey == "cdo_quote" { + if workflowdef.SameTaskKey(req.TaskKey, "cdo_quote") { att, err := s.handleCDOQuotePreparation(workDir, req) if err == nil && att != nil { attachments = append(attachments, *att) diff --git a/internal/squad/workspace.go b/internal/squad/workspace.go index c27a9f07..b1599549 100644 --- a/internal/squad/workspace.go +++ b/internal/squad/workspace.go @@ -9,6 +9,7 @@ import ( "time" "chat-agent-system/internal/template" + "chat-agent-system/internal/workflowdef" ) // WorkspacePrepareRequest 工作区准备请求 @@ -608,6 +609,7 @@ func DefaultSquadAgents(taskKey string) []AgentSpec { if strings.HasPrefix(taskKey, "rd_") { return rdevSquadAgents(taskKey, "") } + taskKey = workflowdef.ImplementationTaskKey(taskKey) switch taskKey { case "intake": return []AgentSpec{ @@ -722,7 +724,7 @@ func DefaultSquadAgents(taskKey string) []AgentSpec { Model: "claude-3-7-sonnet", Tools: []string{"read", "write", "edit"}, BashPermission: "deny", - Responsibilities: []string{"严格按 system_reqs.md 模板成文", "正文结构完整", "填满追溯表"}, + Responsibilities: []string{"严格按 pm_system_reqs.md 模板成文", "正文结构完整", "填满追溯表"}, ForbiddenActions: []string{"严禁残留占位符", "严禁以省略写法跳过应写章节"}, }, { @@ -770,7 +772,7 @@ func DefaultSquadAgents(taskKey string) []AgentSpec { Model: "claude-3-7-sonnet", Tools: []string{"read", "write", "edit"}, BashPermission: "deny", - Responsibilities: []string{"严格按 test_manager.md 模板成文", "用例主表翔实完整"}, + Responsibilities: []string{"严格按 tm_system_verification.md 模板成文", "用例主表翔实完整"}, ForbiddenActions: []string{"严禁残留占位符", "严禁以省略写法跳过应写章节"}, }, { @@ -818,6 +820,7 @@ func DefaultWorkflowSteps(taskKey string) []StepSpec { if strings.HasPrefix(taskKey, "rd_") { return rdevWorkflowSteps(taskKey, "") } + taskKey = workflowdef.ImplementationTaskKey(taskKey) switch taskKey { case "intake": return []StepSpec{ @@ -1099,7 +1102,7 @@ func DefaultWorkflowSteps(taskKey string) []StepSpec { "tasks/03_regulatory/output.md", }, Accept: []string{ - "严格按 test_manager.md 模板生成正式方案文本", + "严格按 tm_system_verification.md 模板生成正式方案文本", "嵌入测试用例清单与覆盖率统计,严禁遗漏上游需求", }, }, diff --git a/internal/template/docker_context_test.go b/internal/template/docker_context_test.go index 5cdc0476..f7c1255d 100644 --- a/internal/template/docker_context_test.go +++ b/internal/template/docker_context_test.go @@ -71,12 +71,12 @@ func TestGetDocTemplate_AppRootLayout(t *testing.T) { if err := os.MkdirAll(dest, 0o755); err != nil { t.Fatal(err) } - src := filepath.Join(root, "templates", "docs", "prelim_design.md") + src := filepath.Join(root, "templates", "docs", "bd_prelim_design.md") body, err := os.ReadFile(src) if err != nil { t.Fatal(err) } - if err := os.WriteFile(filepath.Join(dest, "prelim_design.md"), body, 0o644); err != nil { + if err := os.WriteFile(filepath.Join(dest, "bd_prelim_design.md"), body, 0o644); err != nil { t.Fatal(err) } agentDir := filepath.Join(app, "configs", "agents") @@ -132,10 +132,10 @@ func TestDockerBuildContext_TemplatesDocsReadable(t *testing.T) { _ = exec.Command("docker", "rmi", "-f", tag).Run() }) - cat := exec.Command("docker", "run", "--rm", "--entrypoint", "cat", tag, "/app/templates/docs/prelim_design.md") + cat := exec.Command("docker", "run", "--rm", "--entrypoint", "cat", tag, "/app/templates/docs/bd_prelim_design.md") body, err := runTimed(cat, 30*time.Second) if err != nil { - t.Fatalf("image missing /app/templates/docs/prelim_design.md: %v\n%s", err, body) + t.Fatalf("image missing /app/templates/docs/bd_prelim_design.md: %v\n%s", err, body) } if !bytes.Contains(body, []byte("系统初步设计方案")) || !bytes.Contains(body, []byte("子系统划分")) { t.Fatalf("image template is not the official prelim_design (%d bytes)", len(body)) diff --git a/internal/template/loader.go b/internal/template/loader.go index f68f8292..c33ef283 100644 --- a/internal/template/loader.go +++ b/internal/template/loader.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strings" + "chat-agent-system/internal/workflowdef" "chat-agent-system/templates" ) @@ -42,16 +43,29 @@ func GetDocTemplate(name string) (string, error) { if name == "" || strings.Contains(name, "..") || strings.ContainsAny(name, `/\`) { return "", fmt.Errorf("read doc template: invalid name %q", name) } - if b, err := templates.DocTemplates.ReadFile("docs/" + name + ".md"); err == nil && len(strings.TrimSpace(string(b))) > 0 { - return string(b), nil + for _, candidate := range templateNames(name) { + if b, err := templates.DocTemplates.ReadFile("docs/" + candidate + ".md"); err == nil && len(strings.TrimSpace(string(b))) > 0 { + return string(b), nil + } + root := findProjectRoot() + path := filepath.Join(root, "templates", "docs", candidate+".md") + b, err := os.ReadFile(path) + if err == nil && len(strings.TrimSpace(string(b))) > 0 { + return string(b), nil + } } - root := findProjectRoot() - path := filepath.Join(root, "templates", "docs", name+".md") - b, err := os.ReadFile(path) - if err != nil { - return "", fmt.Errorf("read doc template %s: %w", name, err) + return "", fmt.Errorf("read doc template %s: file not found", name) +} + +func templateNames(name string) []string { + names := []string{name} + if canonical := workflowdef.CanonicalTaskKey(name); canonical != name { + names = append(names, canonical) } - return string(b), nil + if legacy := workflowdef.ImplementationTaskKey(name); legacy != name { + names = append(names, legacy) + } + return names } func GetAgentInstruction(role string) (string, error) { diff --git a/internal/workflowdef/keyspace.go b/internal/workflowdef/keyspace.go new file mode 100644 index 00000000..779a0965 --- /dev/null +++ b/internal/workflowdef/keyspace.go @@ -0,0 +1,98 @@ +package workflowdef + +import ( + "regexp" + "strings" +) + +// legacyToCanonical 是 D12 的旧任务键 → 新任务键。 +// 冻结实例继续持有旧键;新发布的清单使用新键。两边必须指向同一套步骤和模板。 +var legacyToCanonical = map[string]string{ + "intake": "bd_intake", + "research": "bd_research", + "feasibility_dev": "bd_feasibility_dev", + "feasibility_reg": "bd_feasibility_reg", + "user_requirements": "bd_user_requirements", + "prelim_design": "bd_prelim_design", + "cdo_quote": "bd_cdo_quote", + "system_reqs": "pm_system_reqs", + "test_manager": "tm_system_verification", + "software_charter": "sw_software_charter", + "source_inventory": "sw_source_inventory", + "current_state_audit": "sw_current_state_audit", + "architecture_baseline": "sw_architecture_baseline", + "software_risk_register": "sw_software_risk_register", + "product_requirements": "sw_product_requirements", + "scope_backlog": "sw_scope_backlog", + "target_architecture": "sw_target_architecture", + "decision_log": "sw_decision_log", + "bootstrap_roadmap": "sw_bootstrap_roadmap", + "verification_strategy": "sw_verification_strategy", + "operations_baseline": "sw_operations_baseline", + "bootstrap_readiness": "sw_bootstrap_readiness", + "change_brief": "sw_change_brief", + "implementation_plan": "sw_implementation_plan", + "change_manifest": "sw_change_manifest", + "adr_delta": "sw_adr_delta", + "test_report": "sw_test_report", + "security_review": "sw_security_review", + "release_plan": "sw_release_plan", + "release_notes": "sw_release_notes", + "retrospective": "sw_retrospective", + "backlog_delta": "sw_backlog_delta", +} + +var canonicalToLegacy map[string]string + +func init() { + canonicalToLegacy = make(map[string]string, len(legacyToCanonical)) + for legacy, canonical := range legacyToCanonical { + canonicalToLegacy[canonical] = legacy + } +} + +// CanonicalTaskKey 把旧键收成发布用的新键。未知键(含 rd_*)原样返回。 +func CanonicalTaskKey(key string) string { + key = strings.TrimSpace(key) + if canonical, ok := legacyToCanonical[key]; ok { + return canonical + } + return key +} + +// ImplementationTaskKey 把新键收成现有步骤开关仍使用的旧键。 +// 存量执行路径不改写;新键只是别名。 +func ImplementationTaskKey(key string) string { + key = strings.TrimSpace(key) + if legacy, ok := canonicalToLegacy[key]; ok { + return legacy + } + return key +} + +// SameTaskKey 判断两个键是否同一任务(旧键与新键视为相同)。 +func SameTaskKey(a, b string) bool { + ca := CanonicalTaskKey(a) + cb := CanonicalTaskKey(b) + return ca != "" && ca == cb +} + +var namespacedDocID = regexp.MustCompile(`^(doc_proj\d+_)(.+)$`) + +// AlternateDocIDs 给出同一文档的另一种键名形式。 +// 账本不批量迁移:旧 ID 仍可查,新 ID 也要能指回旧行。 +func AlternateDocIDs(docID string) []string { + m := namespacedDocID.FindStringSubmatch(strings.TrimSpace(docID)) + if len(m) != 3 { + return nil + } + prefix, suffix := m[1], m[2] + var out []string + if canonical := CanonicalTaskKey(suffix); canonical != suffix { + out = append(out, prefix+canonical) + } + if legacy := ImplementationTaskKey(suffix); legacy != suffix { + out = append(out, prefix+legacy) + } + return out +} diff --git a/internal/workflowdef/keyspace_test.go b/internal/workflowdef/keyspace_test.go new file mode 100644 index 00000000..3dab1d4f --- /dev/null +++ b/internal/workflowdef/keyspace_test.go @@ -0,0 +1,93 @@ +package workflowdef + +import ( + "strings" + "testing" +) + +func TestSameTaskKey_LegacyAndCanonical(t *testing.T) { + pairs := [][2]string{ + {"intake", "bd_intake"}, + {"system_reqs", "pm_system_reqs"}, + {"test_manager", "tm_system_verification"}, + {"software_charter", "sw_software_charter"}, + {"change_brief", "sw_change_brief"}, + } + for _, pair := range pairs { + if !SameTaskKey(pair[0], pair[1]) { + t.Fatalf("expected %s == %s", pair[0], pair[1]) + } + if CanonicalTaskKey(pair[0]) != pair[1] { + t.Fatalf("canonical %s = %s, want %s", pair[0], CanonicalTaskKey(pair[0]), pair[1]) + } + if ImplementationTaskKey(pair[1]) != pair[0] { + t.Fatalf("implementation %s = %s, want %s", pair[1], ImplementationTaskKey(pair[1]), pair[0]) + } + } + if SameTaskKey("intake", "research") { + t.Fatal("intake and research must stay distinct") + } + if CanonicalTaskKey("rd_proposal") != "rd_proposal" { + t.Fatal("rd_ keys are already namespaced") + } +} + +func TestAlternateDocIDs_RoundTrip(t *testing.T) { + alts := AlternateDocIDs("doc_proj56_intake") + if len(alts) != 1 || alts[0] != "doc_proj56_bd_intake" { + t.Fatalf("old id aliases = %v", alts) + } + back := AlternateDocIDs("doc_proj56_bd_intake") + if len(back) != 1 || back[0] != "doc_proj56_intake" { + t.Fatalf("new id aliases = %v", back) + } + if got := AlternateDocIDs("doc_proj56_rd_proposal"); len(got) != 0 { + t.Fatalf("rd_ doc id should not alias, got %v", got) + } +} + +func TestPublishedTaskKeysAreNamespacedAndDisjoint(t *testing.T) { + files := map[string]string{ + "main.v2.json": "bd_", + "product_manager.v2.json": "pm_", + "test_manager.v2.json": "tm_", + "rdev.v2.json": "rd_", + "software_bootstrap.v2.json": "sw_", + "software_delivery.v2.json": "sw_", + } + seen := map[string]string{} + for file, prefix := range files { + def, err := LoadManifestDefinition(file) + if err != nil { + t.Fatal(err) + } + for _, stage := range def.Stages { + for _, task := range stage.DocTasks { + key := task.Key + if !strings.HasPrefix(key, prefix) { + t.Fatalf("%s task %s missing prefix %s", file, key, prefix) + } + if prev, ok := seen[key]; ok { + t.Fatalf("task key %s intersects %s and %s", key, prev, file) + } + seen[key] = file + if task.OutputDocType != key { + t.Fatalf("%s output_doc_type %s != key %s", file, task.OutputDocType, key) + } + if prefix == "rd_" { + if ImplementationTaskKey(key) != key { + t.Fatalf("rd_ key %s must not alias", key) + } + continue + } + legacy := ImplementationTaskKey(key) + if legacy == key { + t.Fatalf("%s has no legacy alias", key) + } + if CanonicalTaskKey(legacy) != key { + t.Fatalf("legacy %s does not round-trip to %s", legacy, key) + } + } + } + } +} diff --git a/internal/workflowdef/manifests/main.v2.json b/internal/workflowdef/manifests/main.v2.json index d6b78a76..17d9c9c6 100644 --- a/internal/workflowdef/manifests/main.v2.json +++ b/internal/workflowdef/manifests/main.v2.json @@ -1,24 +1,29 @@ { "schema_version": 2, "workflow_key": "main", - "product_domains": ["medical_device", "non_medical_software"], + "product_domains": [ + "medical_device", + "non_medical_software" + ], "stages": [ { "key": "V0.1_triage", "name": "V0.1 需求摸底", "position": 10, "optional": false, - "doc_tasks": [{ - "key": "intake", - "title": "用户初始需求收集表", - "default_enabled": true, - "creator_configurable": true, - "blocking_when_enabled": true, - "executor_key": "bizdev.intake.v1", - "quality_gate_key": "quality.generic.v1", - "output_doc_type": "intake", - "output_doc_title": "用户初始需求收集表" - }] + "doc_tasks": [ + { + "key": "bd_intake", + "title": "用户初始需求收集表", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.intake.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_intake", + "output_doc_title": "用户初始需求收集表" + } + ] }, { "key": "V0.5_feasibility", @@ -26,11 +31,61 @@ "position": 20, "optional": false, "doc_tasks": [ - {"key": "research", "title": "调研报告", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "bizdev.research.v1", "quality_gate_key": "quality.generic.v1", "output_doc_type": "research", "output_doc_title": "调研报告"}, - {"key": "feasibility_dev", "title": "设计开发可行性评估报告", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "bizdev.feasibility_dev.v1", "quality_gate_key": "quality.generic.v1", "output_doc_type": "feasibility_dev", "output_doc_title": "设计开发可行性评估报告"}, - {"key": "feasibility_reg", "title": "注册路径可行性评估报告", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "bizdev.feasibility.v1", "quality_gate_key": "quality.generic.v1", "output_doc_type": "feasibility_reg", "output_doc_title": "注册路径可行性评估报告"}, - {"key": "user_requirements", "title": "用户需求表", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "bizdev.user_requirements.v1", "quality_gate_key": "quality.generic.v1", "output_doc_type": "user_requirements", "output_doc_title": "用户需求表"}, - {"key": "prelim_design", "title": "系统初步设计方案", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "bizdev.prelim_design.v1", "quality_gate_key": "quality.generic.v1", "output_doc_type": "prelim_design", "output_doc_title": "系统初步设计方案"} + { + "key": "bd_research", + "title": "调研报告", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.research.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_research", + "output_doc_title": "调研报告" + }, + { + "key": "bd_feasibility_dev", + "title": "设计开发可行性评估报告", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.feasibility_dev.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_feasibility_dev", + "output_doc_title": "设计开发可行性评估报告" + }, + { + "key": "bd_feasibility_reg", + "title": "注册路径可行性评估报告", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.feasibility.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_feasibility_reg", + "output_doc_title": "注册路径可行性评估报告" + }, + { + "key": "bd_user_requirements", + "title": "用户需求表", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.user_requirements.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_user_requirements", + "output_doc_title": "用户需求表" + }, + { + "key": "bd_prelim_design", + "title": "系统初步设计方案", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.prelim_design.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_prelim_design", + "output_doc_title": "系统初步设计方案" + } ] }, { @@ -38,17 +93,19 @@ "name": "V1.0 商务报价", "position": 30, "optional": false, - "doc_tasks": [{ - "key": "cdo_quote", - "title": "CDO 报价单", - "default_enabled": true, - "creator_configurable": true, - "blocking_when_enabled": true, - "executor_key": "bizdev.cdo_quote.v1", - "quality_gate_key": "quality.generic.v1", - "output_doc_type": "cdo_quote", - "output_doc_title": "CDO 报价单" - }] + "doc_tasks": [ + { + "key": "bd_cdo_quote", + "title": "CDO 报价单", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "bizdev.cdo_quote.v1", + "quality_gate_key": "quality.generic.v1", + "output_doc_type": "bd_cdo_quote", + "output_doc_title": "CDO 报价单" + } + ] } ] } diff --git a/internal/workflowdef/manifests/product_manager.v2.json b/internal/workflowdef/manifests/product_manager.v2.json index ad87d2d4..cf60ce91 100644 --- a/internal/workflowdef/manifests/product_manager.v2.json +++ b/internal/workflowdef/manifests/product_manager.v2.json @@ -1,22 +1,29 @@ { "schema_version": 2, "workflow_key": "product_manager", - "product_domains": ["medical_device", "non_medical_software"], - "stages": [{ - "key": "requirements_baseline", - "name": "系统需求基线", - "position": 10, - "optional": false, - "doc_tasks": [{ - "key": "system_reqs", - "title": "系统需求规格书", - "default_enabled": true, - "creator_configurable": true, - "blocking_when_enabled": true, - "executor_key": "squad.system_reqs.v1", - "quality_gate_key": "quality.system_reqs.v2", - "output_doc_type": "system_reqs", - "output_doc_title": "系统需求规格书" - }] - }] + "product_domains": [ + "medical_device", + "non_medical_software" + ], + "stages": [ + { + "key": "requirements_baseline", + "name": "系统需求基线", + "position": 10, + "optional": false, + "doc_tasks": [ + { + "key": "pm_system_reqs", + "title": "系统需求规格书", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "squad.system_reqs.v1", + "quality_gate_key": "quality.system_reqs.v2", + "output_doc_type": "pm_system_reqs", + "output_doc_title": "系统需求规格书" + } + ] + } + ] } diff --git a/internal/workflowdef/manifests/software_bootstrap.v2.json b/internal/workflowdef/manifests/software_bootstrap.v2.json index 3ccf584b..fb5196c0 100644 --- a/internal/workflowdef/manifests/software_bootstrap.v2.json +++ b/internal/workflowdef/manifests/software_bootstrap.v2.json @@ -1,7 +1,9 @@ { "schema_version": 2, "workflow_key": "software_bootstrap", - "product_domains": ["non_medical_software"], + "product_domains": [ + "non_medical_software" + ], "stages": [ { "key": "S0_boundary", @@ -11,25 +13,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "software_charter", + "key": "sw_software_charter", "title": "项目章程", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "software_charter", + "output_doc_type": "sw_software_charter", "output_doc_title": "项目章程" }, { - "key": "source_inventory", + "key": "sw_source_inventory", "title": "资料与仓库清单", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "source_inventory", + "output_doc_type": "sw_source_inventory", "output_doc_title": "资料与仓库清单" } ] @@ -42,36 +44,36 @@ "manual_gate": true, "doc_tasks": [ { - "key": "current_state_audit", + "key": "sw_current_state_audit", "title": "现状审计", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "current_state_audit", + "output_doc_type": "sw_current_state_audit", "output_doc_title": "现状审计" }, { - "key": "architecture_baseline", + "key": "sw_architecture_baseline", "title": "当前架构与依赖图", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "architecture_baseline", + "output_doc_type": "sw_architecture_baseline", "output_doc_title": "当前架构与依赖图" }, { - "key": "software_risk_register", + "key": "sw_software_risk_register", "title": "风险登记册", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "software_risk_register", + "output_doc_type": "sw_software_risk_register", "output_doc_title": "风险登记册" } ] @@ -84,25 +86,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "product_requirements", + "key": "sw_product_requirements", "title": "产品与系统需求基线", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "product_requirements", + "output_doc_type": "sw_product_requirements", "output_doc_title": "产品与系统需求基线" }, { - "key": "scope_backlog", + "key": "sw_scope_backlog", "title": "范围化 backlog", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "scope_backlog", + "output_doc_type": "sw_scope_backlog", "output_doc_title": "范围化 backlog" } ] @@ -115,25 +117,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "target_architecture", + "key": "sw_target_architecture", "title": "目标架构方案", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "target_architecture", + "output_doc_type": "sw_target_architecture", "output_doc_title": "目标架构方案" }, { - "key": "decision_log", + "key": "sw_decision_log", "title": "架构决策记录", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "decision_log", + "output_doc_type": "sw_decision_log", "output_doc_title": "架构决策记录" } ] @@ -146,47 +148,47 @@ "manual_gate": true, "doc_tasks": [ { - "key": "bootstrap_roadmap", + "key": "sw_bootstrap_roadmap", "title": "分阶段建设路线", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "bootstrap_roadmap", + "output_doc_type": "sw_bootstrap_roadmap", "output_doc_title": "分阶段建设路线" }, { - "key": "verification_strategy", + "key": "sw_verification_strategy", "title": "验证策略与测试矩阵", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "verification_strategy", + "output_doc_type": "sw_verification_strategy", "output_doc_title": "验证策略与测试矩阵" }, { - "key": "operations_baseline", + "key": "sw_operations_baseline", "title": "部署运维与恢复基线", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "operations_baseline", + "output_doc_type": "sw_operations_baseline", "output_doc_title": "部署运维与恢复基线" }, { - "key": "bootstrap_readiness", + "key": "sw_bootstrap_readiness", "title": "自举就绪评审", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "bootstrap_readiness", + "output_doc_type": "sw_bootstrap_readiness", "output_doc_title": "自举就绪评审" } ] diff --git a/internal/workflowdef/manifests/software_delivery.v2.json b/internal/workflowdef/manifests/software_delivery.v2.json index 947d8b7d..3812a356 100644 --- a/internal/workflowdef/manifests/software_delivery.v2.json +++ b/internal/workflowdef/manifests/software_delivery.v2.json @@ -1,7 +1,9 @@ { "schema_version": 2, "workflow_key": "software_delivery", - "product_domains": ["non_medical_software"], + "product_domains": [ + "non_medical_software" + ], "required_upstream_workflow": "", "stages": [ { @@ -12,25 +14,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "change_brief", + "key": "sw_change_brief", "title": "变更简报", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "change_brief", + "output_doc_type": "sw_change_brief", "output_doc_title": "变更简报" }, { - "key": "implementation_plan", + "key": "sw_implementation_plan", "title": "实施计划", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "implementation_plan", + "output_doc_type": "sw_implementation_plan", "output_doc_title": "实施计划" } ] @@ -43,25 +45,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "change_manifest", + "key": "sw_change_manifest", "title": "变更清单", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "change_manifest", + "output_doc_type": "sw_change_manifest", "output_doc_title": "变更清单" }, { - "key": "adr_delta", + "key": "sw_adr_delta", "title": "架构决策增量", "default_enabled": false, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "adr_delta", + "output_doc_type": "sw_adr_delta", "output_doc_title": "架构决策增量" } ] @@ -74,25 +76,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "test_report", + "key": "sw_test_report", "title": "测试报告", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "test_report", + "output_doc_type": "sw_test_report", "output_doc_title": "测试报告" }, { - "key": "security_review", + "key": "sw_security_review", "title": "安全审查", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "security_review", + "output_doc_type": "sw_security_review", "output_doc_title": "安全审查" } ] @@ -105,25 +107,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "release_plan", + "key": "sw_release_plan", "title": "发布计划", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "release_plan", + "output_doc_type": "sw_release_plan", "output_doc_title": "发布计划" }, { - "key": "release_notes", + "key": "sw_release_notes", "title": "发布说明", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "release_notes", + "output_doc_type": "sw_release_notes", "output_doc_title": "发布说明" } ] @@ -136,25 +138,25 @@ "manual_gate": true, "doc_tasks": [ { - "key": "retrospective", + "key": "sw_retrospective", "title": "复盘", "default_enabled": true, "creator_configurable": false, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "retrospective", + "output_doc_type": "sw_retrospective", "output_doc_title": "复盘" }, { - "key": "backlog_delta", + "key": "sw_backlog_delta", "title": "Backlog 增量", "default_enabled": true, "creator_configurable": true, "blocking_when_enabled": true, "executor_key": "software.doc.v1", "quality_gate_key": "quality.generic.v1", - "output_doc_type": "backlog_delta", + "output_doc_type": "sw_backlog_delta", "output_doc_title": "Backlog 增量" } ] diff --git a/internal/workflowdef/manifests/test_manager.v2.json b/internal/workflowdef/manifests/test_manager.v2.json index a55e49f1..96bfde23 100644 --- a/internal/workflowdef/manifests/test_manager.v2.json +++ b/internal/workflowdef/manifests/test_manager.v2.json @@ -1,22 +1,29 @@ { "schema_version": 2, "workflow_key": "test_manager", - "product_domains": ["medical_device", "non_medical_software"], - "stages": [{ - "key": "system_verification_planning", - "name": "系统验证策划", - "position": 10, - "optional": false, - "doc_tasks": [{ - "key": "test_manager", - "title": "系统验证方案及计划", - "default_enabled": true, - "creator_configurable": true, - "blocking_when_enabled": true, - "executor_key": "squad.test_manager.v1", - "quality_gate_key": "quality.test_manager.v2", - "output_doc_type": "test_manager", - "output_doc_title": "系统验证方案及计划" - }] - }] + "product_domains": [ + "medical_device", + "non_medical_software" + ], + "stages": [ + { + "key": "system_verification_planning", + "name": "系统验证策划", + "position": 10, + "optional": false, + "doc_tasks": [ + { + "key": "tm_system_verification", + "title": "系统验证方案及计划", + "default_enabled": true, + "creator_configurable": true, + "blocking_when_enabled": true, + "executor_key": "squad.test_manager.v1", + "quality_gate_key": "quality.test_manager.v2", + "output_doc_type": "tm_system_verification", + "output_doc_title": "系统验证方案及计划" + } + ] + } + ] } diff --git a/templates/docs/cdo_quote.md b/templates/docs/bd_cdo_quote.md similarity index 100% rename from templates/docs/cdo_quote.md rename to templates/docs/bd_cdo_quote.md diff --git a/templates/docs/feasibility_dev.md b/templates/docs/bd_feasibility_dev.md similarity index 100% rename from templates/docs/feasibility_dev.md rename to templates/docs/bd_feasibility_dev.md diff --git a/templates/docs/feasibility_reg.md b/templates/docs/bd_feasibility_reg.md similarity index 100% rename from templates/docs/feasibility_reg.md rename to templates/docs/bd_feasibility_reg.md diff --git a/templates/docs/intake.md b/templates/docs/bd_intake.md similarity index 100% rename from templates/docs/intake.md rename to templates/docs/bd_intake.md diff --git a/templates/docs/prelim_design.md b/templates/docs/bd_prelim_design.md similarity index 100% rename from templates/docs/prelim_design.md rename to templates/docs/bd_prelim_design.md diff --git a/templates/docs/research.md b/templates/docs/bd_research.md similarity index 100% rename from templates/docs/research.md rename to templates/docs/bd_research.md diff --git a/templates/docs/user_requirements.md b/templates/docs/bd_user_requirements.md similarity index 100% rename from templates/docs/user_requirements.md rename to templates/docs/bd_user_requirements.md diff --git a/templates/docs/system_reqs.md b/templates/docs/pm_system_reqs.md similarity index 100% rename from templates/docs/system_reqs.md rename to templates/docs/pm_system_reqs.md diff --git a/templates/docs/test_manager.md b/templates/docs/tm_system_verification.md similarity index 100% rename from templates/docs/test_manager.md rename to templates/docs/tm_system_verification.md