Files
weichenandCursor dba8139d56 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>
2026-09-19 10:50:25 +00:00

99 lines
3.4 KiB
Go

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
}