feat(workflow): namespace task keys without breaking old instances
Published manifests now use bd_/pm_/tm_/sw_ prefixes so workflows cannot share a doc id. Frozen instances keep the old keys; lookup, templates, and duty checks treat the two names as the same task. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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。
|
||||
|
||||
红线关闭(不是实现):
|
||||
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
|
||||
+20
-11
@@ -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[:])
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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 是否在允许范围内。
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"chat-agent-system/internal/workflowdef"
|
||||
)
|
||||
|
||||
// ExtractTextWithAnchors 从不同格式材料中提取纯文本,并自动注入物理页码锚点 <!-- page N -->
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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 固定工作流动态微调
|
||||
|
||||
@@ -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")
|
||||
|
||||
+24
-12
@@ -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)
|
||||
|
||||
@@ -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 模板生成正式方案文本",
|
||||
"嵌入测试用例清单与覆盖率统计,严禁遗漏上游需求",
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 报价单"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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": "系统需求规格书"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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": "自举就绪评审"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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 增量"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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": "系统验证方案及计划"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user