@xulthekl/team-flow 0.65.0 → 0.68.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +1 -1
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/CHANGELOG.md +64 -0
  9. package/GEMINI.md +1 -1
  10. package/INSTALL.md +1 -1
  11. package/README.md +1 -1
  12. package/agents/architecture-reviewer.md +4 -0
  13. package/agents/code-reviewer.md +11 -4
  14. package/agents/prd-completeness-reviewer.md +10 -0
  15. package/agents/prd-writer.md +1 -0
  16. package/agents/release-archivist.md +2 -0
  17. package/docs/README_en.md +1 -1
  18. package/docs/team-flow /344/275/277/347/224/250/350/257/264/346/230/216/357/274/210/347/240/224/345/217/221/345/233/242/351/230/237/347/211/210/357/274/211.md" +12 -8
  19. package/gemini-extension.json +1 -1
  20. package/hooks/session-start +2 -2
  21. package/llms.txt +1 -1
  22. package/package.json +1 -1
  23. package/plugin.json +1 -1
  24. package/prd/v1/prd.md +1 -1
  25. package/scripts/guard/checks/history-risk.mjs +132 -0
  26. package/scripts/guard/checks/prd-clarity-state.mjs +41 -0
  27. package/scripts/guard/checks/prd-clarity.mjs +176 -0
  28. package/scripts/guard/checks/security-baseline-light.mjs +9 -0
  29. package/scripts/guard/checks/security-baseline.mjs +141 -0
  30. package/scripts/guard/guard.mjs +37 -12
  31. package/scripts/infer-workflow.mjs +20 -0
  32. package/scripts/lib/cmd-doctor.mjs +2 -2
  33. package/scripts/lib/cmd-prd.mjs +84 -1
  34. package/scripts/lib/cmd-security.mjs +61 -0
  35. package/scripts/lib/cmd-solutions.mjs +3 -0
  36. package/scripts/lib/cmd-state.mjs +36 -1
  37. package/scripts/lib/security-scope.mjs +188 -0
  38. package/scripts/lib/solutions-capture.mjs +5 -0
  39. package/scripts/lib/solutions-index-gen.mjs +33 -3
  40. package/scripts/lib/solutions-inject.mjs +34 -9
  41. package/scripts/lib/state-loader.mjs +25 -0
  42. package/scripts/team-flow.mjs +7 -0
  43. package/skills/ce-brainstorm/SKILL.md +3 -3
  44. package/skills/ce-brainstorm/references/grounding.md +1 -1
  45. package/skills/ce-brainstorm/references/prd-84-authoring-spec.md +26 -3
  46. package/skills/ce-brainstorm/references/prototype-loop.md +8 -0
  47. package/skills/ce-brainstorm/references/security-baseline.json +50 -0
  48. package/skills/ce-brainstorm/references/security-baseline.md +80 -0
  49. package/skills/ce-compound/references/agents/security-sentinel.md +5 -0
  50. package/skills/ce-compound/references/promotion-rules.md +1 -1
  51. package/skills/ce-compound/references/three-tier-index.md +1 -1
  52. package/skills/ce-plan/references/agents/security-sentinel.md +5 -0
  53. package/skills/ce-plan/references/research-workflow.md +1 -1
  54. package/skills/code-reviewer/SKILL.md +5 -1
  55. package/skills/code-reviewer/code-reviewer-prompt.md +10 -0
  56. package/skills/jarvis/references/protocols.md +1 -0
  57. package/skills/release-archivist/SKILL.md +21 -2
  58. package/skills/spec-writer/SKILL.md +5 -0
  59. package/skills/workflow-orchestrator/SKILL.md +6 -2
  60. package/skills/workflow-orchestrator/references/s1-path-router.md +7 -0
  61. package/skills/workflow-orchestrator/references/s2-prd-prototype-loop.md +16 -2
  62. package/skills/workflow-orchestrator/references/s3-plan-pipeline.md +1 -1
  63. package/skills/workflow-start/SKILL.md +9 -4
  64. package/skills/workflow-start/references/routing-rules.md +1 -0
  65. package/templates/prd.md +9 -1
@@ -0,0 +1,176 @@
1
+ // scripts/guard/checks/prd-clarity.mjs — 产品级 PRD 清晰度机械门(agent-governance P0-1)
2
+ //
3
+ // 背景(2026-09-25):LT 拍板 Q5 推翻 DEC-8 中的弱词机械门决策(DEC-8a)——
4
+ // 「接活前 fail-closed」必须有机械层;LLM reviewer(prd-completeness-reviewer)保留,
5
+ // 与本 checker 并存:checker 管机械可判定项,reviewer 管语义完整性。
6
+ //
7
+ // 检查对象:**产品级 PRD**(requirement/vN/prd.md)。不查变更级 change spec
8
+ // (归 spec-writer/评审既有职责);tasks_skipped 类 change 不经产品级冻结路径,天然不适用。
9
+ //
10
+ // 接入 = 三处(SKILL 调用纪律级,非 hook 强制——如实声明):
11
+ // 1. `tf prd check-clarity <dir|file>`(cmd-prd.mjs,末行 STATUS: PASS | FAIL)
12
+ // 2. standalone:`references/prototype-loop.md` §3.5.5 前置段(ce-brainstorm SKILL QA-4 引用之)
13
+ // 3. orchestrated:`s2-prd-prototype-loop.md` 步骤 3.4(3.5 完整性评审之前)
14
+ //
15
+ // 判定语义 fail-closed:必需段缺失 / 判据无法执行 → FAIL,不得落 N/A 放行。
16
+ // 首版**不建豁免通道**(不过就改 PRD 或调 checker,走正常代码变更流程)。
17
+ // 产品级无 state 文件(.team-flow.yaml 属 change 级),故不使用 gates-probed 式
18
+ // skip-with-reason 豁免模式——那是首版不建豁免的根本原因,不得照抄。
19
+ //
20
+ // 规则源(单真相源裁定,memo v0.5 §4.1 / 反方 I7;**回写已闭合 2026-09-25**):
21
+ // 权威正文 = skills/ce-brainstorm/references/prd-84-authoring-spec.md §4「精确性」,
22
+ // DEC-8a 复议时已扩为 8 类全集(第 6–8 类:否定句/连接歧义/被动语态),
23
+ // 镜像弱词表(工作区 prd-business-readable-design)不再独立维护。
24
+
25
+ import fs from 'node:fs';
26
+
27
+ /**
28
+ * ISO/IEC/IEEE 29148 弱词 8 类规则表(数据可调——误报校准的唯一落点)。
29
+ * 词表来源:prd-84 §4(权威,8 类全集,DEC-8a 复议后收口于此)。
30
+ * 校准纪律:调词表 = 改本文件规则数据并同步权威正文,不得在调用侧打补丁。
31
+ * 格式:{ category, words: string[], note? };words 为子串匹配(中文无词边界)。
32
+ *
33
+ * **2026-09-25 校准排除决策(刻意不收,权威正文 §4 已留痕)**:
34
+ * - `处理`(§4 类 3 示例词):子串会命中模板段名「系统功能处理说明书」→ 冻结死循环;
35
+ * - 单字 `等`(§4 类 4 示例):子串命中「等待/等于/以及」等一切含字场景 → 噪声失控;
36
+ * 词表只收「等等」。两者判读由 LLM reviewer 精确性检查覆盖(并存层职责)。
37
+ */
38
+ export const WEAK_RULES = [
39
+ { category: '比较级与最高级', words: ['更好', '较好', '更快', '更佳', '更稳定', '最快', '最高级', '较快'] },
40
+ { category: '主观评价词', words: ['友好', '易用', '良好', '美观', '简洁', '直观'] },
41
+ { category: '歧义词', words: ['尽量', '总是', '最优', '最小化', '最大化', '适当', '必要时'] },
42
+ { category: '开放式表述', words: ['至少', '不限于', '尽可能', '视情况', '等等'] },
43
+ { category: '漏洞词', words: ['可能', '如适用', '一般', '通常', '支持'] },
44
+ { category: '否定句', words: ['不支持'], note: '改为「当 X 时,系统执行 Y」' },
45
+ { category: '连接歧义', words: ['和/或'], note: '拆分条目或用决策表' },
46
+ { category: '被动语态', words: ['应被'], note: '明确动作主体' },
47
+ ];
48
+
49
+ /** 段缺失判据(fail-closed 最小必需集):命中任一 pattern 即认为段存在。 */
50
+ export const REQUIRED_SECTIONS = [
51
+ // version-1.2:只认 1.2 修订行 / 1.1 版本信息子段(h2「版本修订记录」兜底过松——
52
+ // 子段全删时 h2 仍在,首版实测会漏判,故收紧到子段级)。
53
+ { id: 'version-1.2', desc: '### 1.1 版本信息 / 1.2 修订记录', pattern: /^#{2,4}\s*.*(?:1\.[12]\s*(?:版本信息|修订)|版本信息\s*$)/m },
54
+ { id: 'func-list', desc: '功能清单章节(## 七 / 功能清单)', pattern: /^#{2,3}\s*.*(?:七、|功能清单|系统功能)/m },
55
+ { id: 'sec-8.4', desc: '### 8.4 功能模块详细说明', pattern: /^#{2,4}\s*.*8\.4/m },
56
+ ];
57
+
58
+ /**
59
+ * §8.4 左列语义漂移判据(判据源 = prd-completeness-reviewer.md D6/G7 第 1 条):
60
+ * 左列须为画面/入口锚点;技术维度名 = 违规。
61
+ * 机械实现的边界:**整格匹配**(防「单据类型」这类业务词误杀),另加强技术词子串。
62
+ */
63
+ const DRIFT_EXACT = /^(维度|字段|类型|属性|参数|枚举|单位|数据项)(列表|清单|定义)?$/;
64
+ const DRIFT_SUBSTRING = /(字段名|维度名|数据类型|技术维度|参数名|表字段)/;
65
+
66
+ /** 去除围栏代码块与行内 code(弱词扫描的误报源),保留原文行结构用于定位。 */
67
+ export function stripCode(text) {
68
+ const lines = text.split(/\r?\n/);
69
+ let inFence = false;
70
+ return lines.map((line) => {
71
+ if (/^\s*```/.test(line)) { inFence = !inFence; return ''; }
72
+ if (inFence) return '';
73
+ return line.replace(/`[^`]*`/g, '');
74
+ }).join('\n');
75
+ }
76
+
77
+ /** 提取标题段正文(到下一个同级或更高级标题)。返回 null 表示段不存在。 */
78
+ export function extractSection(text, headingRe) {
79
+ const lines = text.split(/\r?\n/);
80
+ let start = -1;
81
+ let level = 0;
82
+ for (let i = 0; i < lines.length; i += 1) {
83
+ const m = lines[i].match(/^(#{1,6})\s/);
84
+ if (m && headingRe.test(lines[i])) { start = i; level = m[1].length; break; }
85
+ }
86
+ if (start === -1) return null;
87
+ const body = [];
88
+ for (let i = start + 1; i < lines.length; i += 1) {
89
+ const m = lines[i].match(/^(#{1,6})\s/);
90
+ if (m && m[1].length <= level) break;
91
+ body.push(lines[i]);
92
+ }
93
+ return body.join('\n');
94
+ }
95
+
96
+ /** 收集段内所有 markdown 表格的首列数据单元格(跳过表头与分隔行)。 */
97
+ export function firstColumnCells(sectionBody) {
98
+ const cells = [];
99
+ const lines = sectionBody.split(/\r?\n/);
100
+ let inTable = false;
101
+ let headerSeen = false;
102
+ for (const line of lines) {
103
+ if (!line.trim().startsWith('|')) { inTable = false; headerSeen = false; continue; }
104
+ const cols = line.split('|').map((c) => c.trim());
105
+ // cols[0] === ''(前导竖线),数据首列在 index 1
106
+ if (!inTable) { inTable = true; headerSeen = false; continue; } // 表头行
107
+ if (!headerSeen) { headerSeen = true; continue; } // 分隔行(|---|---|)
108
+ const first = cols[1] || '';
109
+ if (first) cells.push(first);
110
+ }
111
+ return cells;
112
+ }
113
+
114
+ /**
115
+ * 核心判定。
116
+ * @param {string} prdPath prd.md 文件绝对/相对路径
117
+ * @returns {{pass: boolean, failures: string[], stats: object}}
118
+ * 失败条目格式:`[类别] 行 N: <片段>` / `[section] ...` / `[drift] ...`
119
+ */
120
+ export function checkPrdClarity(prdPath) {
121
+ const failures = [];
122
+ const stats = { weakHits: 0, driftCells: 0, sectionsMissing: [] };
123
+
124
+ if (!prdPath || !fs.existsSync(prdPath)) {
125
+ return {
126
+ pass: false,
127
+ failures: [`[section] PRD file not found: ${prdPath ?? '(unset)'} — locate requirement/vN/prd.md first`],
128
+ stats,
129
+ };
130
+ }
131
+ const raw = fs.readFileSync(prdPath, 'utf-8');
132
+ if (!raw.trim()) {
133
+ return { pass: false, failures: ['[section] PRD file is empty — write the PRD before running clarity check'], stats };
134
+ }
135
+
136
+ // 1) 必需段存在性(fail-closed:缺失 = FAIL,不得 N/A 放行)。
137
+ for (const sec of REQUIRED_SECTIONS) {
138
+ if (!sec.pattern.test(raw)) {
139
+ stats.sectionsMissing.push(sec.id);
140
+ failures.push(`[section] required section missing: ${sec.desc} — template skeleton incomplete`);
141
+ }
142
+ }
143
+
144
+ // 2) 弱词 8 类扫描(排除代码块/行内 code;表格内容照扫——PRD 表格是业务正文)。
145
+ const clean = stripCode(raw);
146
+ const cleanLines = clean.split(/\r?\n/);
147
+ for (let i = 0; i < cleanLines.length; i += 1) {
148
+ const line = cleanLines[i];
149
+ if (!line.trim()) continue;
150
+ for (const rule of WEAK_RULES) {
151
+ for (const word of rule.words) {
152
+ const idx = line.indexOf(word);
153
+ if (idx >= 0) {
154
+ stats.weakHits += 1;
155
+ const snippet = line.slice(Math.max(0, idx - 12), idx + word.length + 12).trim();
156
+ failures.push(`[weak:${rule.category}] 行 ${i + 1}: …${snippet}… (${rule.note ?? '改为精确表述'})`);
157
+ }
158
+ }
159
+ }
160
+ }
161
+
162
+ // 3) §8.4 左列语义漂移(G7 第 1 条机械化)。
163
+ const sec84 = extractSection(clean, /8\.4/);
164
+ if (sec84 === null) {
165
+ // 必需段已在其处 FAIL(REQUIRED_SECTIONS sec-8.4),此处不重复计。
166
+ } else {
167
+ for (const cell of firstColumnCells(sec84)) {
168
+ if (DRIFT_EXACT.test(cell) || DRIFT_SUBSTRING.test(cell)) {
169
+ stats.driftCells += 1;
170
+ failures.push(`[drift] §8.4 左列技术维度名: "${cell}" — 左列须为画面/入口锚点(G7-1)`);
171
+ }
172
+ }
173
+ }
174
+
175
+ return { pass: failures.length === 0, failures, stats };
176
+ }
@@ -0,0 +1,9 @@
1
+ // scripts/guard/checks/security-baseline-light.mjs — 安全基线闸门(轻档:quick/lightweight)
2
+ // 设计:docs/plan/security-baseline-integration-design.md v1.3 §L2
3
+ // 与重档共享全部判定(存量分档 / 推导落盘 / skip 短路 / infra 交接),
4
+ // **省略逐条 verdict 核验**(direct 档无审查 receipt,verification 无生产者——诚实降级 §八.5)。
5
+ import { evaluateSecurityBaseline } from './security-baseline.mjs';
6
+
7
+ export function checkSecurityBaselineLight(changeDir) {
8
+ return evaluateSecurityBaseline(changeDir, 'light');
9
+ }
@@ -0,0 +1,141 @@
1
+ // scripts/guard/checks/security-baseline.mjs — 安全基线闸门(重档:full/hotfix/planned)
2
+ // 设计:docs/plan/security-baseline-integration-design.md v1.3 §L2
3
+ // 输入只读结构化制品,不解析 markdown 审查报告:
4
+ // ① .security-scope.json(现场推导落盘,security-scope.mjs 唯一实现)
5
+ // ② .security-verification.json(L3 code-reviewer 产出)
6
+ // ③ state 键(security_baseline_skipped / _skip_reason / security_scope_hash + schema_version)
7
+ // 判定序(v1.3 重排):存量分档 → 现场推导 → 无类别 PASS → skip 短路 →
8
+ // 重/轻档分流(完备性对账 + verdict 映射)→ infra 交接检查。
9
+ import { existsSync, readFileSync } from 'node:fs';
10
+ import { join } from 'node:path';
11
+ import { readState } from '../../lib/state-loader.mjs';
12
+ import { deriveSecurityScope, loadSecurityBaseline, readScopeFile } from '../../lib/security-scope.mjs';
13
+
14
+ const VERIFICATION_FILE = '.security-verification.json';
15
+ const HANDOFF_REL = join('security', 'handoff.md');
16
+
17
+ function readVerification(changeDir) {
18
+ const p = join(changeDir, VERIFICATION_FILE);
19
+ if (!existsSync(p)) return null;
20
+ try { return JSON.parse(readFileSync(p, 'utf-8')); } catch { return 'malformed'; }
21
+ }
22
+
23
+ // verdict → 门禁映射(设计 §L3,写死):
24
+ // must+satisfied / should+satisfied → PASS
25
+ // not-applicable → 须过 reason 判据(≥10 字符或含控制点序号),否则视同 uncovered
26
+ // uncovered / 缺席 → must=FAIL,should=WARN(不阻断)
27
+ function evaluateVerdicts(baseline, scope, verification) {
28
+ const failures = [];
29
+ const warnings = [];
30
+ const byId = new Map((verification.items || []).map(i => [Number(i.id), i]));
31
+ const reasonOk = (v, id) => {
32
+ const r = (v.reason || '').trim();
33
+ return r.length >= 10 || r.includes(String(id));
34
+ };
35
+
36
+ for (const id of scope.code_item_ids) {
37
+ const item = baseline.items.find(b => b.id === id);
38
+ const v = byId.get(id);
39
+ const must = item?.severity === 'must';
40
+ if (!v) {
41
+ // 完备性对账:适用项缺席 = uncovered(防空/残缺 items 空过,设计 §L3)
42
+ (must ? failures : warnings).push(`#${id} ${item?.title ?? ''} 缺席于 verification(=uncovered)`);
43
+ continue;
44
+ }
45
+ if (v.verdict === 'satisfied') continue;
46
+ if (v.verdict === 'not-applicable') {
47
+ if (!reasonOk(v, id)) {
48
+ failures.push(`#${id} not-applicable 的 reason 不合格(需 ≥10 字符或含序号)`);
49
+ }
50
+ continue;
51
+ }
52
+ // uncovered 或未知 verdict(未知按 uncovered,fail-closed)
53
+ if (must) failures.push(`#${id} ${item?.title ?? ''} uncovered(must)`);
54
+ else warnings.push(`#${id} ${item?.title ?? ''} uncovered(should,WARN 不阻断)`);
55
+ }
56
+ return { failures, warnings };
57
+ }
58
+
59
+ /**
60
+ * 核心求值。mode = 'full'(查 verification)| 'light'(跳过逐条核验)
61
+ */
62
+ export function evaluateSecurityBaseline(changeDir, mode) {
63
+ const state = readState(changeDir);
64
+
65
+ // 步 1:存量分档(单一判据 = 无 schema_version → WARN 软着陆,设计 §二.3)
66
+ if (state.schema_version == null) {
67
+ return { pass: true, failures: [], reason: 'legacy soft-land: no schema_version (存量 change,安全基线不阻断)' };
68
+ }
69
+
70
+ // 步 2:现场推导(写盘留痕;推导失败 fail-closed)
71
+ let scope;
72
+ try {
73
+ scope = deriveSecurityScope(changeDir, { source: 'guard' });
74
+ } catch (e) {
75
+ return { pass: false, failures: [`security scope derive crashed: ${e.message}`] };
76
+ }
77
+ if (scope.error) {
78
+ return {
79
+ pass: false,
80
+ failures: [`scope derive degraded (error=${scope.error}) — 无法推导适用范围,fail-closed;检查 git 基线(tf state init 打戳 base_sha)后重试 tf security scope`],
81
+ };
82
+ }
83
+
84
+ // 步 3:无适用触发类别 → PASS(已落盘留痕,可区分"无触发"与"门禁未生效")
85
+ if (scope.triggered_categories.length === 0) {
86
+ return { pass: true, failures: [], reason: `no applicable trigger categories (${scope.files_scanned} surface files scanned); scope written for audit` };
87
+ }
88
+
89
+ // 步 4:skip 短路(最先于实质检查;hash 不匹配 = 范围已变 → skip 失效继续)
90
+ const skipped = state.security_baseline_skipped === true || state.security_baseline_skipped === 'true';
91
+ if (skipped) {
92
+ const reason = (state.security_baseline_skip_reason || '').trim();
93
+ if (!reason || reason === 'null') {
94
+ return { pass: false, failures: ['security_baseline_skipped=true but no skip reason — set security_baseline_skip_reason via tf state set'] };
95
+ }
96
+ if (state.security_scope_hash && state.security_scope_hash === scope.scope_hash) {
97
+ return { pass: true, failures: [], reason: `skipped: ${reason}` };
98
+ }
99
+ // hash 缺失/不匹配 → skip 失效(fail-closed 方向),继续实质检查
100
+ }
101
+
102
+ // 步 5:档位分流
103
+ if (mode === 'full') {
104
+ const ver = readVerification(changeDir);
105
+ if (!ver) {
106
+ return {
107
+ pass: false,
108
+ failures: [`.security-verification.json missing — 派发 code-reviewer(末 wave / planned 最终审查)产核验 JSON;无适用 code 项时亦须落盘(空 items + 全 N/A 说明)`],
109
+ };
110
+ }
111
+ if (ver === 'malformed') {
112
+ return { pass: false, failures: ['.security-verification.json malformed (JSON parse failed)'] };
113
+ }
114
+ const baseline = loadSecurityBaseline();
115
+ const { failures, warnings } = evaluateVerdicts(baseline, scope, ver);
116
+ if (failures.length) {
117
+ return { pass: false, failures, ...(warnings.length ? { warnings } : {}) };
118
+ }
119
+ if (warnings.length) {
120
+ return { pass: true, failures: [], reason: `pass with WARN: ${warnings.join('; ')}` };
121
+ }
122
+ }
123
+ // light 档(quick/lightweight):不查 verification(direct 档无审查 receipt,设计 §八.5)
124
+
125
+ // 步 6:infra/config/process 交接检查(检查对象 = handoff.md,台账不进 guard)
126
+ if (scope.infra_item_ids.length > 0) {
127
+ const handoffPath = join(changeDir, HANDOFF_REL);
128
+ if (!existsSync(handoffPath) || !readFileSync(handoffPath, 'utf-8').trim()) {
129
+ return {
130
+ pass: false,
131
+ failures: [`infra-scope items ${scope.infra_item_ids.join('/')} apply but security/handoff.md missing or empty — write the handoff BEFORE tf state transition closing (设计 §L5 时序)`],
132
+ };
133
+ }
134
+ }
135
+
136
+ return { pass: true, failures: [] };
137
+ }
138
+
139
+ export function checkSecurityBaseline(changeDir) {
140
+ return evaluateSecurityBaseline(changeDir, 'full');
141
+ }
@@ -17,6 +17,9 @@ import { checkCompoundCaptured } from './checks/compound-captured.mjs';
17
17
  import { checkTestMatrixComplete } from './checks/test-matrix-complete.mjs';
18
18
  import { checkTestMatrixReady } from './checks/test-matrix-ready.mjs';
19
19
  import { checkGatesProbed } from './checks/gates-probed.mjs';
20
+ // agent-governance P1-1:接活前双维度(履历 experimental + clarity 跨层管道)
21
+ import { checkPrdClarityState } from './checks/prd-clarity-state.mjs';
22
+ import { checkHistoryRisk } from './checks/history-risk.mjs';
20
23
  import { checkArchReadiness } from './checks/arch-readiness.mjs';
21
24
  import { checkArchSnapshot } from './checks/arch-snapshot.mjs';
22
25
  import { checkArchMerged } from './checks/arch-merged.mjs';
@@ -33,6 +36,9 @@ import { checkCompoundWritebackLight } from './checks/compound-writeback-light.m
33
36
  import { checkTestMergedLight } from './checks/test-merged-light.mjs';
34
37
  import { checkCrossChangeConsistencyLight } from './checks/cross-change-consistency-light.mjs';
35
38
  import { checkExecutionReviewsPassedLight } from './checks/execution-reviews-passed-light.mjs';
39
+ // security-baseline-integration-design v1.3:安全基线双维度(executing→closing)
40
+ import { checkSecurityBaseline } from './checks/security-baseline.mjs';
41
+ import { checkSecurityBaselineLight } from './checks/security-baseline-light.mjs';
36
42
 
37
43
  // Transition matrix: <from>:<to> → required check dimensions
38
44
  const TRANSITION_CHECKS = {
@@ -57,10 +63,12 @@ const TRANSITION_CHECKS = {
57
63
  // 「闸门已实测可跑」文字自证物化为机械证据。检查五件:evidence 存在 / 首行 RED 基线 /
58
64
  // CONTRACT_HASH 值新鲜 / 契约 Gate Registry 的 id 逐 id 覆盖 / **段缺失即 FAIL(fail-closed)**。
59
65
  // hotfix 走 WORKFLOW_TRANSITION_CHECKS 自有覆盖(下方),tweak 由 skip 回退阀放行。
60
- 'bridging:approved-for-build': ['artifacts-exist', 'schema-valid', 'contract-fresh', 'dp-gate-passed', 'dp3-approved', 'gates-probed'],
66
+ // agent-governance P1-1:prd-clarity + history-risk 挂接活门(full 档)——
67
+ // 「接活前验规格 + 履历」;两维缺失态均中性(无记录/INDEX 空 = 放行),存量零变化。
68
+ 'bridging:approved-for-build': ['artifacts-exist', 'schema-valid', 'contract-fresh', 'dp-gate-passed', 'dp3-approved', 'gates-probed', 'prd-clarity', 'history-risk'],
61
69
  // v0.13 §49:test-matrix-ready 门禁前移——full 模式进入 executing 前强制测试准备度
62
70
  //(矩阵存在非空 OR 显式 skip 附理由;legacy 豁免)。hotfix/tweak 沿用 §45.3 豁免,不挂。
63
- 'approved-for-build:executing': ['artifacts-exist', 'contract-fresh', 'dp-gate-passed', 'execution-plan-ready', 'test-matrix-ready'],
71
+ 'approved-for-build:executing': ['artifacts-exist', 'contract-fresh', 'dp-gate-passed', 'execution-plan-ready', 'test-matrix-ready', 'prd-clarity', 'history-risk'],
64
72
  // v0.35.0 (v0.14 §59.4): arch-snapshot 新增——"先快照后回写"的强制化(legacy 豁免)。
65
73
  // 仅在 full workflow 挂;hotfix/tweak 沿用 §59.4 豁免(紧急/微调不挂)。
66
74
  //
@@ -77,7 +85,12 @@ const TRANSITION_CHECKS = {
77
85
  // artifacts_hash)→ **伪绿静默通过**。本维度直接比对 state.artifacts_hash 与制品实算值,
78
86
  // refresh-hash 无法清屏(它只改 plan JSON,不碰 state)——故为 refresh-hash 之外的独立锚。
79
87
  // hotfix/tweak 不挂:二者跳过 spec-writer、无 planning 四件,冻结机制本身 N/A(设计 §3.9)。
80
- 'executing:closing': ['contract-fresh', 'tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured', 'test-matrix-complete', 'arch-snapshot', 'delegation-status', 'arch-merged'],
88
+ // v1.3(security-baseline-integration-design):security-baseline 新增——
89
+ // 44 项企业安全基线的机械闸门(现场推导 scope + verification 完备性 + infra 交接)。
90
+ // full(legacy variant)走本基表;hotfix 走 WORKFLOW 表、planned 走 PLANNED 表、
91
+ // quick/lightweight 走 QUICK 表挂 -light 变体、tweak 天然不挂(豁免)。
92
+ // 存量 change(无 schema_version)WARN 软着陆,冷启动零行为变化。
93
+ 'executing:closing': ['contract-fresh', 'tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured', 'test-matrix-complete', 'arch-snapshot', 'delegation-status', 'arch-merged', 'security-baseline'],
81
94
 
82
95
  // Debugging side-path
83
96
  'executing:debugging': [],
@@ -111,7 +124,9 @@ const WORKFLOW_TRANSITION_CHECKS = {
111
124
  'bridging:approved-for-build': ['contract-current', 'dp3-approved'],
112
125
  'approved-for-build:executing': ['contract-current', 'dp3-approved', 'execution-plan-ready'],
113
126
  // hotfix exemption: no test-matrix-complete (v0.12 §45.3)
114
- 'executing:closing': ['tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured'],
127
+ // v1.3:security-baseline 重档——hotfix 走逐 wave 审查(execution-reviews-passed 已挂),
128
+ // code-reviewer 派发即 verification 生产者,重档不死锁(P1.5 R3-C1 裁定回滚)。
129
+ 'executing:closing': ['tasks-complete', 'tests-passing', 'specs-merged', 'execution-plan-ready', 'execution-reviews-passed', 'compound-captured', 'security-baseline'],
115
130
  },
116
131
  tweak: {
117
132
  'exploring:approved-for-build': [],
@@ -134,16 +149,17 @@ const TRANSITION_WORKFLOW_REQUIREMENTS = {
134
149
  // 必须显式列出关键跳——未列出的 key 会回落 full 基表(contract-fresh 等对轻路径恒 FAIL)。
135
150
  const QUICK_TRANSITION_CHECKS = {
136
151
  quick: {
137
- 'exploring:approved-for-build': ['direct-short-path'],
138
- 'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready'],
139
- 'executing:closing': ['direct-short-path', 'direct-test-result', 'test-matrix-complete'],
152
+ 'exploring:approved-for-build': ['direct-short-path', 'prd-clarity', 'history-risk'],
153
+ 'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready', 'prd-clarity', 'history-risk'],
154
+ // v1.3:security-baseline-light——direct 档无审查 receipt,省逐条 verdict(设计 §八.5)
155
+ 'executing:closing': ['direct-short-path', 'direct-test-result', 'test-matrix-complete', 'security-baseline-light'],
140
156
  'executing:debugging': [],
141
157
  'debugging:executing': ['direct-test-result'],
142
158
  },
143
159
  lightweight: {
144
- 'exploring:approved-for-build': ['direct-short-path'],
145
- 'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready'],
146
- 'executing:closing': ['direct-short-path', 'direct-test-result', 'lightweight-completion-evidence', 'test-matrix-complete'],
160
+ 'exploring:approved-for-build': ['direct-short-path', 'prd-clarity', 'history-risk'],
161
+ 'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready', 'prd-clarity', 'history-risk'],
162
+ 'executing:closing': ['direct-short-path', 'direct-test-result', 'lightweight-completion-evidence', 'test-matrix-complete', 'security-baseline-light'],
147
163
  'executing:debugging': [],
148
164
  'debugging:executing': ['direct-test-result'],
149
165
  },
@@ -155,12 +171,14 @@ const PLANNED_TRANSITION_CHECKS = {
155
171
  // 注:不挂 schema-valid——该 checker 强制 canonical specs/<capability>/spec.md,
156
172
  // 与 planned「specs 按需」冲突(P1 实测修正,同步方案 §4.5);反空壳由 artifacts-planned
157
173
  // (proposal≥10 行 + checkbox)+ 最终审查内容契约承担。
158
- 'exploring:approved-for-build': ['artifacts-planned'],
159
- 'approved-for-build:executing': ['execution-plan-ready', 'test-matrix-ready'],
174
+ 'exploring:approved-for-build': ['artifacts-planned', 'prd-clarity', 'history-risk'],
175
+ 'approved-for-build:executing': ['execution-plan-ready', 'test-matrix-ready', 'prd-clarity', 'history-risk'],
160
176
  'executing:closing': [
161
177
  'execution-plan-ready', 'execution-reviews-passed-light', 'test-matrix-complete',
162
178
  'tests-passing', 'arch-merged-light', 'compound-writeback-light', 'test-merged-light',
163
179
  'cross-change-consistency-light', 'delegation-status', 'specs-merged', 'tasks-complete',
180
+ // v1.3:security-baseline 重档(planned 有最终审查,verification 有生产者)
181
+ 'security-baseline',
164
182
  ],
165
183
  'executing:debugging': [],
166
184
  'debugging:executing': ['execution-plan-ready', 'tests-passing'],
@@ -293,6 +311,9 @@ async function main() {
293
311
  'test-matrix-complete': (dir) => checkTestMatrixComplete(dir, CTX),
294
312
  'test-matrix-ready': (dir) => checkTestMatrixReady(dir, CTX),
295
313
  'gates-probed': (dir) => checkGatesProbed(dir),
314
+ // agent-governance P1-1:接活前双维度。缺失态中性——冷启动零行为变化。
315
+ 'prd-clarity': (dir) => checkPrdClarityState(dir),
316
+ 'history-risk': (dir) => checkHistoryRisk(dir),
296
317
  'arch-readiness': (dir) => checkArchReadiness(dir),
297
318
  'arch-snapshot': (dir) => checkArchSnapshot(dir),
298
319
  'arch-merged': (dir) => checkArchMerged(dir),
@@ -309,6 +330,10 @@ async function main() {
309
330
  'test-merged-light': (dir) => checkTestMergedLight(dir),
310
331
  'cross-change-consistency-light': (dir) => checkCrossChangeConsistencyLight(dir),
311
332
  'execution-reviews-passed-light': (dir) => checkExecutionReviewsPassedLight(dir),
333
+ // security-baseline-integration-design v1.3(35 维):安全基线双维度。
334
+ // 重档(full/hotfix/planned)查 verification 完备性;轻档(quick/lightweight)省逐条核验。
335
+ 'security-baseline': (dir) => checkSecurityBaseline(dir),
336
+ 'security-baseline-light': (dir) => checkSecurityBaselineLight(dir),
312
337
  };
313
338
 
314
339
  const checks = [];
@@ -55,6 +55,11 @@ function hasKeyword(text, patterns) {
55
55
 
56
56
  function inferMode(changeDir) {
57
57
  const state = readState(changeDir);
58
+ // agent-governance P1-1 双信号(轻量分值化):
59
+ // clarity = 产品级 prd-clarity 跨层记录(pass|fail|null=null 中性——见 prd-clarity-state.mjs);
60
+ // risk = 0.64 双通道信号的分档聚合(low|medium|high|null——显式档位/无产物时 null)。
61
+ // 消费方 = workflow-start 路由推荐(建议级;阻断由 guard 维度承担,这里只影响推荐倾向)。
62
+ const clarity = state.prd_clarity_result ?? null;
58
63
 
59
64
  // Explicit override: honor any non-auto, non-null workflow value
60
65
  // v0.64.0(§3.2 F1):VALID_WORKFLOWS 扩为五值,quick/lightweight 亦为合法显式档位。
@@ -66,6 +71,8 @@ function inferMode(changeDir) {
66
71
  // 双通道(§3.2):显式 workflow 已定时不再建议前门(显式 > 推断)
67
72
  suggested_path: null,
68
73
  explicit: true,
74
+ clarity,
75
+ risk: null, // 显式档位不推断风险分档
69
76
  reason: `workflow explicitly set to '${state.workflow}' in .team-flow.yaml; skipping auto-detection`,
70
77
  };
71
78
  }
@@ -101,6 +108,9 @@ function inferMode(changeDir) {
101
108
  const archPathSignal = hasSchemaChange || hasArchAggregate;
102
109
  // 跨模块 / 高不确定性 → full(D1:full 只留重场景)
103
110
  const fullSignal = hasCrossModule || (taskCount > 4 && fileCount > 6);
111
+ // P1-1 risk 分档:四个布尔信号计数(0=low / 1=medium / ≥2=high)
112
+ const riskSignals = [hasSchemaChange, hasArchAggregate, hasCrossModule, fullSignal].filter(Boolean).length;
113
+ const risk = riskSignals >= 2 ? 'high' : riskSignals === 1 ? 'medium' : 'low';
104
114
 
105
115
  const allExts = files.map(f => {
106
116
  const parts = f.split('.');
@@ -115,6 +125,8 @@ function inferMode(changeDir) {
115
125
  mode: 'full',
116
126
  suggested_path: null,
117
127
  explicit: false,
128
+ clarity,
129
+ risk,
118
130
  reason: 'no planning artifacts detected → full (safe default)',
119
131
  };
120
132
  }
@@ -125,6 +137,8 @@ function inferMode(changeDir) {
125
137
  mode: 'hotfix',
126
138
  suggested_path: 'direct',
127
139
  explicit: false,
140
+ clarity,
141
+ risk,
128
142
  reason: `≤2 tasks, ≤2 files, no schema/API/new-module keywords → hotfix (trivial → suggest path direct; hotfix retained when reusing an existing contract)`,
129
143
  };
130
144
  }
@@ -135,6 +149,8 @@ function inferMode(changeDir) {
135
149
  mode: 'tweak',
136
150
  suggested_path: 'direct',
137
151
  explicit: false,
152
+ clarity,
153
+ risk,
138
154
  reason: `≤4 tasks, only config/doc files, no schema/API/new-module keywords → tweak (trivial → suggest path direct)`,
139
155
  };
140
156
  }
@@ -145,6 +161,8 @@ function inferMode(changeDir) {
145
161
  mode: 'full',
146
162
  suggested_path: 'planned',
147
163
  explicit: false,
164
+ clarity,
165
+ risk,
148
166
  reason: `${taskCount} tasks, ${fileCount} files${hasSchemaChange ? ', schema/API change detected' : ''}${hasArchAggregate ? ', arch/aggregate keywords detected' : ''} → suggest path planned (light architecture); front door: workflow start --path planned (new change) or tf state upgrade <dir> planned (existing change)`,
149
167
  };
150
168
  }
@@ -154,6 +172,8 @@ function inferMode(changeDir) {
154
172
  mode: 'full',
155
173
  suggested_path: null,
156
174
  explicit: false,
175
+ clarity,
176
+ risk,
157
177
  reason: `${taskCount} tasks, ${fileCount} files${codeFileCount > 0 ? ` (${codeFileCount} code files)` : ''}${hasSchemaChange ? ', schema/API change detected' : ''}${hasNewModule ? ', new module detected' : ''}${hasCrossModule ? ', cross-module detected' : ''} → full`,
158
178
  };
159
179
  }
@@ -382,9 +382,9 @@ function checkSolutions(root) {
382
382
  for (const line of readFileSync(indexPath, 'utf-8').split('\n')) {
383
383
  if (!line.startsWith('|') || line.startsWith('| date') || line.startsWith('|--')) continue;
384
384
  const cells = parseTableRow(line);
385
- // v0.57.0 P4:`source` 列新增后列数为 7(存量 INDEX)或 8(本版重建);
385
+ // v0.57.0 P4:`source` 列新增后列数为 7(存量)/ 8;P2-2 `flags` 列后 9(本版重建);
386
386
  // 另校验 `file` 列形状——列数在界内仍可能因未转义的 `|` 而错位。
387
- if (cells.length < 7 || cells.length > 8) continue;
387
+ if (cells.length < 7 || cells.length > 9) continue;
388
388
  if (!/^[a-z][a-z-]*\/.+\.md$/.test(cells[6])) continue;
389
389
  indexRows.set(cells[6], { date: cells[0], phase: cells[1], domain: cells[2], type: cells[3], severity: cells[4] });
390
390
  }
@@ -14,7 +14,9 @@
14
14
  // 供主会话 Phase 0.0 的阻塞提问(G1)取判据——**判据不在 skill 散文里另写一套**。
15
15
 
16
16
  import fs from 'node:fs';
17
+ import { existsSync, mkdirSync } from 'node:fs';
17
18
  import path from 'node:path';
19
+ import { createHash } from 'node:crypto';
18
20
  import { parseArgs } from 'node:util';
19
21
  import { loadConfig, resolveConfigWritePath } from './config-loader.mjs';
20
22
  import {
@@ -23,6 +25,7 @@ import {
23
25
  compareAgainstAck,
24
26
  shortHash,
25
27
  } from './template-hash.mjs';
28
+ import { checkPrdClarity } from '../guard/checks/prd-clarity.mjs';
26
29
 
27
30
  const VALID_MODES = ['adopt_latest', 'keep_legacy'];
28
31
 
@@ -48,6 +51,7 @@ export async function run(args) {
48
51
  const sub = positionals[0];
49
52
  if (sub === 'ack') return ack(positionals.slice(1), values);
50
53
  if (sub === 'check') return check(values);
54
+ if (sub === 'check-clarity') return checkClarity(positionals.slice(1));
51
55
  usage();
52
56
  }
53
57
 
@@ -58,11 +62,90 @@ function usage() {
58
62
  ' tf prd ack set --mode adopt_latest --in-use-hash … [--plugin-hash …] [--spec-hash …]\n' +
59
63
  ' tf prd ack show # 查看当前 ack\n' +
60
64
  ' tf prd ack clear # 清除 ack(下次将重新提问)\n' +
61
- ' tf prd check [--template-path <p>] [--plugin-root <p>] # 比对 hash 与 ack,输出 STATUS'
65
+ ' tf prd check [--template-path <p>] [--plugin-root <p>] # 比对 hash 与 ack,输出 STATUS\n' +
66
+ ' tf prd check-clarity <dir|prd.md> # 清晰度机械门(弱词/段/§8.4 漂移),末行 STATUS: PASS | FAIL,FAIL exit 1'
62
67
  );
63
68
  process.exit(2);
64
69
  }
65
70
 
71
+ /**
72
+ * `tf prd check-clarity`:产品级 PRD 清晰度机械门(agent-governance P0-1,DEC-8a)。
73
+ *
74
+ * 入参 = 目录(自动定位 requirement/vN/prd.md,多个 vN 取最大)或 prd.md 文件路径。
75
+ * 输出末行固定 `STATUS: PASS | FAIL`;**FAIL 时 exit 1**(fail-closed——本命令是门,
76
+ * 不同于 `tf prd check` 的判据工具语义;SKILL 冻结步骤 MUST 读 STATUS 且不得忽略非零退出码)。
77
+ * 判定细节见 scripts/guard/checks/prd-clarity.mjs 头注(规则源 / 豁免边界 / 校准纪律)。
78
+ */
79
+ function checkClarity(positionalArgs) {
80
+ const target = positionalArgs[0];
81
+ if (!target) usage();
82
+
83
+ let prdPath = target;
84
+ if (fs.existsSync(target) && fs.statSync(target).isDirectory()) {
85
+ prdPath = resolvePrdInDir(target);
86
+ if (!prdPath) {
87
+ console.error(`no requirement/vN/prd.md found under: ${target}`);
88
+ console.log('STATUS: FAIL');
89
+ process.exit(1);
90
+ }
91
+ }
92
+ if (!fs.existsSync(prdPath)) {
93
+ console.error(`PRD file not found: ${prdPath}`);
94
+ console.log('STATUS: FAIL');
95
+ process.exit(1);
96
+ }
97
+
98
+ const { pass, failures, stats } = checkPrdClarity(prdPath);
99
+ console.log('prd :', prdPath);
100
+ console.log('weak_hits :', stats.weakHits, '| drift_cells:', stats.driftCells,
101
+ '| missing_sections:', stats.sectionsMissing.length ? stats.sectionsMissing.join(',') : '(none)');
102
+ for (const f of failures) console.log(f);
103
+ console.log(`STATUS: ${pass ? 'PASS' : 'FAIL'}`);
104
+ recordClarityResult(pass, prdPath); // P1-1 跨层管道:落产品级记录供 tf state init 拷贝
105
+ if (!pass) process.exit(1);
106
+ }
107
+
108
+ /**
109
+ * P1-1 clarity 跨层管道的写侧:落 `<cwd>/.team-flow/prd-clarity.json`。
110
+ * 消费链 = `tf state init` 拷入 change state(prd_clarity_result/prd_clarity_hash)
111
+ * → guard 维度 `prd-clarity`(checks/prd-clarity-state.mjs)在接活前转换消费。
112
+ * PASS/FAIL 都落盘(fail 记录让绕过 S2 的 direct change 也在变更级被拦——双保险)。
113
+ */
114
+ function recordClarityResult(pass, prdPath) {
115
+ try {
116
+ const dir = path.join(process.cwd(), '.team-flow');
117
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
118
+ const content = fs.readFileSync(prdPath, 'utf-8');
119
+ const sha = createHash('sha256').update(content).digest('hex');
120
+ fs.writeFileSync(path.join(dir, 'prd-clarity.json'), `${JSON.stringify({
121
+ status: pass ? 'pass' : 'fail',
122
+ prd: prdPath,
123
+ prd_hash: `sha256:${sha}`,
124
+ checked_at: new Date().toISOString(),
125
+ }, null, 2)}\n`);
126
+ } catch {
127
+ // 落盘失败不吞主判定——STATUS 已输出;管道缺失 = 变更级维度走中性,fail-closed 不受影响
128
+ }
129
+ }
130
+
131
+ /**
132
+ * 目录内定位 prd.md:<dir>/prd.md、requirement/ 下最大 vN、或老布局 prd/ 下最大 vN
133
+ * (存量项目用 `prd/vN/prd.md`——2026-09-25 存量校准时实测补上,防新门卡死老布局)。
134
+ */
135
+ function resolvePrdInDir(dir) {
136
+ const direct = path.join(dir, 'prd.md');
137
+ if (fs.existsSync(direct)) return direct;
138
+ for (const base of ['requirement', 'prd']) {
139
+ const reqDir = path.join(dir, base);
140
+ if (!fs.existsSync(reqDir)) continue;
141
+ const versions = fs.readdirSync(reqDir)
142
+ .filter((n) => fs.existsSync(path.join(reqDir, n, 'prd.md')))
143
+ .sort((a, b) => parseInt(a.replace(/\D/g, ''), 10) - parseInt(b.replace(/\D/g, ''), 10)); // 数值序:v10 > v9(字典序会取错)
144
+ if (versions.length > 0) return path.join(reqDir, versions[versions.length - 1], 'prd.md');
145
+ }
146
+ return null;
147
+ }
148
+
66
149
  /** 读原始配置文件(未与 DEFAULTS 合并),用于区分「用户显式配置」与「默认值」。 */
67
150
  function readRawConfig() {
68
151
  const p = resolveConfigWritePath(process.cwd());