@xulthekl/team-flow 0.67.0 → 0.69.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 (40) 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 +50 -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 +15 -4
  13. package/agents/code-reviewer.md +11 -4
  14. package/agents/prd-completeness-reviewer.md +18 -9
  15. package/docs/README_en.md +1 -1
  16. 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" +7 -5
  17. package/gemini-extension.json +1 -1
  18. package/hooks/session-start +2 -2
  19. package/llms.txt +1 -1
  20. package/package.json +5 -1
  21. package/plugin.json +1 -1
  22. package/scripts/guard/checks/security-baseline-light.mjs +9 -0
  23. package/scripts/guard/checks/security-baseline.mjs +141 -0
  24. package/scripts/guard/guard.mjs +22 -5
  25. package/scripts/lib/cmd-security.mjs +61 -0
  26. package/scripts/lib/cmd-state.mjs +5 -0
  27. package/scripts/lib/security-scope.mjs +188 -0
  28. package/scripts/lib/state-loader.mjs +12 -0
  29. package/scripts/team-flow.mjs +7 -0
  30. package/scripts/validate-mermaid.mjs +240 -0
  31. package/skills/ce-brainstorm/references/security-baseline.json +50 -0
  32. package/skills/ce-brainstorm/references/security-baseline.md +80 -0
  33. package/skills/ce-compound/references/agents/security-sentinel.md +5 -0
  34. package/skills/ce-plan/references/agents/security-sentinel.md +5 -0
  35. package/skills/code-reviewer/SKILL.md +5 -1
  36. package/skills/code-reviewer/code-reviewer-prompt.md +10 -0
  37. package/skills/release-archivist/SKILL.md +21 -2
  38. package/skills/spec-writer/SKILL.md +5 -0
  39. package/skills/workflow-start/SKILL.md +8 -3
  40. package/skills/workflow-start/references/routing-rules.md +1 -0
@@ -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
+ }
@@ -36,6 +36,9 @@ import { checkCompoundWritebackLight } from './checks/compound-writeback-light.m
36
36
  import { checkTestMergedLight } from './checks/test-merged-light.mjs';
37
37
  import { checkCrossChangeConsistencyLight } from './checks/cross-change-consistency-light.mjs';
38
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';
39
42
 
40
43
  // Transition matrix: <from>:<to> → required check dimensions
41
44
  const TRANSITION_CHECKS = {
@@ -82,7 +85,12 @@ const TRANSITION_CHECKS = {
82
85
  // artifacts_hash)→ **伪绿静默通过**。本维度直接比对 state.artifacts_hash 与制品实算值,
83
86
  // refresh-hash 无法清屏(它只改 plan JSON,不碰 state)——故为 refresh-hash 之外的独立锚。
84
87
  // hotfix/tweak 不挂:二者跳过 spec-writer、无 planning 四件,冻结机制本身 N/A(设计 §3.9)。
85
- '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'],
86
94
 
87
95
  // Debugging side-path
88
96
  'executing:debugging': [],
@@ -116,7 +124,9 @@ const WORKFLOW_TRANSITION_CHECKS = {
116
124
  'bridging:approved-for-build': ['contract-current', 'dp3-approved'],
117
125
  'approved-for-build:executing': ['contract-current', 'dp3-approved', 'execution-plan-ready'],
118
126
  // hotfix exemption: no test-matrix-complete (v0.12 §45.3)
119
- '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'],
120
130
  },
121
131
  tweak: {
122
132
  'exploring:approved-for-build': [],
@@ -141,14 +151,15 @@ const QUICK_TRANSITION_CHECKS = {
141
151
  quick: {
142
152
  'exploring:approved-for-build': ['direct-short-path', 'prd-clarity', 'history-risk'],
143
153
  'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready', 'prd-clarity', 'history-risk'],
144
- 'executing:closing': ['direct-short-path', 'direct-test-result', 'test-matrix-complete'],
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'],
145
156
  'executing:debugging': [],
146
157
  'debugging:executing': ['direct-test-result'],
147
158
  },
148
159
  lightweight: {
149
160
  'exploring:approved-for-build': ['direct-short-path', 'prd-clarity', 'history-risk'],
150
161
  'approved-for-build:executing': ['direct-short-path', 'test-matrix-ready', 'prd-clarity', 'history-risk'],
151
- 'executing:closing': ['direct-short-path', 'direct-test-result', 'lightweight-completion-evidence', 'test-matrix-complete'],
162
+ 'executing:closing': ['direct-short-path', 'direct-test-result', 'lightweight-completion-evidence', 'test-matrix-complete', 'security-baseline-light'],
152
163
  'executing:debugging': [],
153
164
  'debugging:executing': ['direct-test-result'],
154
165
  },
@@ -166,6 +177,8 @@ const PLANNED_TRANSITION_CHECKS = {
166
177
  'execution-plan-ready', 'execution-reviews-passed-light', 'test-matrix-complete',
167
178
  'tests-passing', 'arch-merged-light', 'compound-writeback-light', 'test-merged-light',
168
179
  'cross-change-consistency-light', 'delegation-status', 'specs-merged', 'tasks-complete',
180
+ // v1.3:security-baseline 重档(planned 有最终审查,verification 有生产者)
181
+ 'security-baseline',
169
182
  ],
170
183
  'executing:debugging': [],
171
184
  'debugging:executing': ['execution-plan-ready', 'tests-passing'],
@@ -298,7 +311,7 @@ async function main() {
298
311
  'test-matrix-complete': (dir) => checkTestMatrixComplete(dir, CTX),
299
312
  'test-matrix-ready': (dir) => checkTestMatrixReady(dir, CTX),
300
313
  'gates-probed': (dir) => checkGatesProbed(dir),
301
- // agent-governance P1-1(33 维):接活前双维度。缺失态中性——冷启动零行为变化。
314
+ // agent-governance P1-1:接活前双维度。缺失态中性——冷启动零行为变化。
302
315
  'prd-clarity': (dir) => checkPrdClarityState(dir),
303
316
  'history-risk': (dir) => checkHistoryRisk(dir),
304
317
  'arch-readiness': (dir) => checkArchReadiness(dir),
@@ -317,6 +330,10 @@ async function main() {
317
330
  'test-merged-light': (dir) => checkTestMergedLight(dir),
318
331
  'cross-change-consistency-light': (dir) => checkCrossChangeConsistencyLight(dir),
319
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),
320
337
  };
321
338
 
322
339
  const checks = [];
@@ -0,0 +1,61 @@
1
+ // scripts/lib/cmd-security.mjs — tf security 子命令
2
+ // v1.3(security-baseline-integration-design):`tf security scope <change-dir> [--json]`
3
+ // 运行共享推导模块(scripts/lib/security-scope.mjs 唯一实现),落盘 .security-scope.json
4
+ // 并打印 scope_hash。调用点:① L3 派发前(主会话取适用子集随 dispatch prompt 传参)
5
+ // ② 人工刷新(guard 对账 FAIL 后重跑)。LLM/人从不手写 scope JSON(设计 §二.6)。
6
+ import { parseArgs } from 'node:util';
7
+ import { deriveSecurityScope, loadSecurityBaseline, SCOPE_FILE } from './security-scope.mjs';
8
+
9
+ export async function run(args) {
10
+ const { positionals, values } = parseArgs({
11
+ args,
12
+ options: { json: { type: 'boolean', default: false } },
13
+ allowPositionals: true,
14
+ });
15
+
16
+ const sub = positionals[0];
17
+
18
+ if (sub === 'baseline') {
19
+ // tf security baseline [--json] — 查看基线双载体(人类可读路径 + 版本锚)
20
+ const data = loadSecurityBaseline();
21
+ if (values.json) {
22
+ console.log(JSON.stringify({
23
+ baseline_version: data.baseline_version,
24
+ items: data.items.length,
25
+ path: new URL('../../skills/ce-brainstorm/references/security-baseline.json', import.meta.url).pathname,
26
+ }, null, 2));
27
+ } else {
28
+ console.log(`security-baseline ${data.baseline_version} — ${data.items.length} items (md+json 双载体,见 skills/ce-brainstorm/references/security-baseline.md)`);
29
+ }
30
+ return 0;
31
+ }
32
+
33
+ if (sub !== 'scope') {
34
+ console.error('Usage: tf security <scope|baseline> <args>\n' +
35
+ ' scope <change-dir> [--json] 推导适用触发类别并落盘 .security-scope.json\n' +
36
+ ' baseline [--json] 查看 44 项基线版本锚');
37
+ return 2;
38
+ }
39
+
40
+ const changeDir = positionals[1];
41
+ if (!changeDir) {
42
+ console.error('Usage: tf security scope <change-dir> [--json]');
43
+ return 2;
44
+ }
45
+
46
+ const result = deriveSecurityScope(changeDir, { source: 'cli' });
47
+
48
+ if (values.json) {
49
+ console.log(JSON.stringify(result, null, 2));
50
+ } else {
51
+ if (result.error) {
52
+ console.error(`scope derive degraded (error=${result.error}) — 基线/diff 不可用,gate 侧将 fail-closed`);
53
+ }
54
+ console.log(`Scope written: ${SCOPE_FILE}`);
55
+ console.log(` triggered: ${result.triggered_categories.length ? result.triggered_categories.join(', ') : '(none — 静默 PASS 类别)'}`);
56
+ console.log(` applicable items: ${result.applicable_items.length} (code: ${result.code_item_ids.length}, infra-only: ${result.infra_item_ids.length})`);
57
+ console.log(` scope_hash: ${result.scope_hash}`);
58
+ }
59
+ // 推导失败(无基线/diff 失败)按 fail-closed 返回非零,供调用方判断
60
+ return result.error ? 1 : 0;
61
+ }
@@ -78,6 +78,11 @@ const SETTABLE_FIELDS = [
78
78
  'gates_probed_skipped', 'gates_probed_skip_reason', 'gates_probed_na',
79
79
  // v0.64.0(§4 arch-design-light):轻架构说明显式跳过键(须附理由)
80
80
  'arch_design_light_skipped', 'arch_design_light_skip_reason',
81
+ // security-baseline-integration-design v1.3(§L2 三环第 3 环):安全基线跳过三键。
82
+ // 三键必须与 state-loader BUILTIN_DEFAULTS + writeState 序列化分支同步注册,
83
+ // 缺任一环即「回显成功却零写入」。skip 写入规范:设置 skipped 必须同批带
84
+ // security_scope_hash(值取最近 tf security scope 输出),hash 缺失时 guard 判 skip 失效。
85
+ 'security_baseline_skipped', 'security_baseline_skip_reason', 'security_scope_hash',
81
86
  ];
82
87
 
83
88
  export async function run(args) {
@@ -0,0 +1,188 @@
1
+ // scripts/lib/security-scope.mjs — Scope Deriver(安全基线范围推导,唯一实现)
2
+ // 设计:docs/plan/security-baseline-integration-design.md v1.3 §三
3
+ // 单一真相源 = skills/ce-brainstorm/references/security-baseline.json(与 .md 双载体,
4
+ // 一致性由测试断言锁)。本模块是推导的唯一实现——CLI `tf security scope` 与 guard
5
+ // 检查器都只经此调用,杜绝多处各自实现漂移(§二.6)。
6
+ //
7
+ // 输出(写盘 <change-dir>/.security-scope.json):
8
+ // { base_sha, triggered_categories[], applicable_items[], code_item_ids[],
9
+ // infra_item_ids[], scope_hash, derived_at, derived_by, files_scanned, error }
10
+ // scope_hash 预像 = 规范化(排序)后的 triggered_categories + applicable_items id
11
+ // (排除 derived_at 等易变字段)——见设计 §三 Scope Deriver 段。
12
+ import { createHash } from 'node:crypto';
13
+ import { existsSync, readFileSync, writeFileSync, realpathSync } from 'node:fs';
14
+ import { execFileSync } from 'node:child_process';
15
+ import { join, resolve, relative } from 'node:path';
16
+ import { fileURLToPath } from 'node:url';
17
+ import { gitChangedFiles } from './surface-scan.mjs';
18
+ import { readState } from './state-loader.mjs';
19
+
20
+ export const SCOPE_FILE = '.security-scope.json';
21
+
22
+ const BASELINE_JSON_URL = new URL(
23
+ '../../skills/ce-brainstorm/references/security-baseline.json', import.meta.url,
24
+ );
25
+
26
+ export function loadSecurityBaseline() {
27
+ const raw = readFileSync(fileURLToPath(BASELINE_JSON_URL), 'utf-8');
28
+ const data = JSON.parse(raw);
29
+ if (!Array.isArray(data.items) || data.items.length !== 44) {
30
+ throw new Error(`security-baseline.json malformed: expected 44 items, got ${data.items?.length}`);
31
+ }
32
+ return data;
33
+ }
34
+
35
+ // --- 触发推导规则(路径启发式 + 文档关键词;规则改动须同步设计文档 §三) ---
36
+
37
+ // 不构成暴露面的文件(文档 / 测试 / 元数据)——纯此类变更 → 无触发类别 → 静默 PASS
38
+ const NON_SURFACE = /\.(md|txt|png|jpe?g|gif|svg|pdf|lock|sum|ipynb|css|scss|html?)$/i;
39
+ const TEST_PATH = /(^|\/)(test|tests|__tests__|spec|e2e)(\/|$)|\.(test|spec)\.[jt]sx?$/i;
40
+
41
+ // 源码扩展名 → 基础触发类别(改代码即查基础安全判据)
42
+ const SOURCE_EXT = /\.(java|kt|kts|ts|tsx|js|jsx|mjs|cjs|vue|py|go|rs|cs|php|rb|scala|swift|sql|sh|groovy|xml|yaml|yml|json|toml|properties|conf|ini|dockerfile)$/i;
43
+ const SOURCE_BASE_TRIGGERS = ['code-quality', 'input', 'output'];
44
+
45
+ // 路径启发式 → 追加触发类别(对路径与文件名匹配,大小写不敏感)
46
+ const PATH_RULES = [
47
+ { re: /upload|attachment|multipart|file[_-]?(io|controller|service|handler)/i, cat: 'file-io' },
48
+ { re: /auth|login|session|token|jwt|oauth|permission|rbac|role|sso|passwd|password/i, cat: 'authn-authz' },
49
+ { re: /redirect|forward|callback|goto|跳转|重定向/i, cat: 'redirect' },
50
+ { re: /audit|operat(ion|ing)_?log|审计/i, cat: 'audit' },
51
+ { re: /ingress|gateway|nginx|tls|ssl|cert|https|deploy|k8s|kubernetes/i, cat: 'transport' },
52
+ { re: /xml|soap|xxe|jaxb/i, cat: 'processing' },
53
+ { re: /pom\.xml$|package\.json$|requirements.*\.txt$|go\.mod$|build\.gradle$|gemfile$|composer\.json$/i, cat: 'deps' },
54
+ ];
55
+
56
+ // 部署/依赖清单路径 → deps(与 SOURCE_EXT 重叠时两者都算)
57
+ const DEPLOY_PATH = /(^|\/)(\.github|\.gitlab-ci|deploy|helm|charts|docker|k8s|infrastructure)(\/|$)|Dockerfile|docker-compose/i;
58
+
59
+ // spec/arch 文档关键词 → 追加触发类别(文本扫描,中文+英文)
60
+ const TEXT_RULES = [
61
+ { re: /文件上传|上传附件|multipart|file\s*upload/i, cat: 'file-io' },
62
+ { re: /登录|认证|鉴权|权限|会话|token|jwt|oauth/i, cat: 'authn-authz' },
63
+ { re: /跳转|重定向|redirect/i, cat: 'redirect' },
64
+ { re: /审计日志|操作日志|审计/i, cat: 'audit' },
65
+ { re: /https|tls|hsts|ssl|传输加密/i, cat: 'transport' },
66
+ { re: /xxe|xml\s*实体|soap/i, cat: 'processing' },
67
+ { re: /依赖升级|组件版本|中间件版本|cve/i, cat: 'deps' },
68
+ { re: /响应头|content-security-policy|x-frame|nosniff/i, cat: 'output' },
69
+ { re: /sql\s*注入|参数化|查询注入/i, cat: 'code-quality' },
70
+ ];
71
+
72
+ const DOC_TARGETS = ['proposal.md', 'design.md', 'specs', 'architecture'];
73
+
74
+ function scanDocText(changeDir) {
75
+ const texts = [];
76
+ for (const t of DOC_TARGETS) {
77
+ const p = join(changeDir, t);
78
+ if (!existsSync(p)) continue;
79
+ try {
80
+ const st = readFileSync(p, 'utf-8');
81
+ texts.push(st);
82
+ } catch { /* skip */ }
83
+ }
84
+ return texts.join('\n');
85
+ }
86
+
87
+ /**
88
+ * 推导安全基线适用范围。
89
+ * @param {string} changeDir
90
+ * @param {object} [opts]
91
+ * @param {'cli'|'guard'} [opts.source] 落盘 provenance 标记
92
+ * @param {boolean} [opts.write=true] 是否写盘 .security-scope.json
93
+ * @returns 推导结果对象(含 error 字段:'no-baseline'|'diff-failed'|null)
94
+ */
95
+ export function deriveSecurityScope(changeDir, opts = {}) {
96
+ const { source = 'cli', write = true } = opts;
97
+ const state = readState(changeDir);
98
+ const baseline = loadSecurityBaseline();
99
+
100
+ const changed = gitChangedFiles(changeDir, state);
101
+ const error = changed.error; // no-baseline / diff-failed / null
102
+ // 排除 change 目录自身(.team-flow.yaml / .security-*.json / .superpowers/ 等
103
+ // 流程产物会被 git status 三源扫到——它们不是变更代码的暴露面)。
104
+ const changeRoot = relativeRepoPrefix(changeDir);
105
+ const files = (changed.files || []).filter(f =>
106
+ !(changeRoot && f.startsWith(changeRoot)) && !f.startsWith('.superpowers/'));
107
+
108
+ const triggered = new Set();
109
+ let filesScanned = 0;
110
+
111
+ for (const f of files) {
112
+ if (TEST_PATH.test(f)) continue;
113
+ if (NON_SURFACE.test(f) && !DEPLOY_PATH.test(f)) continue;
114
+ filesScanned++;
115
+ if (SOURCE_EXT.test(f) || DEPLOY_PATH.test(f)) {
116
+ for (const c of SOURCE_BASE_TRIGGERS) triggered.add(c);
117
+ }
118
+ for (const rule of PATH_RULES) {
119
+ if (rule.re.test(f)) triggered.add(rule.cat);
120
+ }
121
+ if (DEPLOY_PATH.test(f)) triggered.add('deps');
122
+ }
123
+
124
+ const docText = scanDocText(changeDir);
125
+ if (docText) {
126
+ for (const rule of TEXT_RULES) {
127
+ if (rule.re.test(docText)) triggered.add(rule.cat);
128
+ }
129
+ }
130
+
131
+ const triggeredList = [...triggered].sort();
132
+ const applicable = baseline.items
133
+ .filter(i => triggeredList.includes(i.trigger))
134
+ .sort((a, b) => a.id - b.id);
135
+
136
+ const hashPreimage = JSON.stringify({
137
+ triggered: triggeredList,
138
+ items: applicable.map(i => i.id),
139
+ });
140
+ const scopeHash = createHash('sha256').update(hashPreimage).digest('hex').slice(0, 16);
141
+
142
+ const result = {
143
+ base_sha: changed.base ?? state.base_sha ?? null,
144
+ triggered_categories: triggeredList,
145
+ applicable_items: applicable.map(i => i.id),
146
+ code_item_ids: applicable.filter(i => i.locus.includes('code')).map(i => i.id),
147
+ infra_item_ids: applicable.filter(i => !i.locus.includes('code')).map(i => i.id),
148
+ scope_hash: scopeHash,
149
+ derived_at: new Date().toISOString(),
150
+ derived_by: source,
151
+ files_scanned: filesScanned,
152
+ error,
153
+ };
154
+
155
+ if (write) {
156
+ writeFileSync(join(changeDir, SCOPE_FILE), JSON.stringify(result, null, 2) + '\n', 'utf-8');
157
+ }
158
+ return result;
159
+ }
160
+
161
+ /** changeDir 在其 git 仓库内的相对前缀(带尾 /),用于过滤流程产物自身。
162
+ * 双侧 realpath 归一——macOS /var → /private/var 符号链接会让字符串前缀比较失效。 */
163
+ function relativeRepoPrefix(changeDir) {
164
+ try {
165
+ // stdio 收窄:非 git 目录时 git 会往 stderr 打 fatal 原文(报错不指向真因),
166
+ // 由调用侧按 error=no-baseline 给结构化提示
167
+ const root = realpathSync(execFileSync('git', ['rev-parse', '--show-toplevel'], {
168
+ cwd: changeDir, encoding: 'utf-8', timeout: 5000, stdio: ['ignore', 'pipe', 'ignore'],
169
+ }).trim());
170
+ if (!root) return null;
171
+ const abs = realpathSync(resolve(changeDir));
172
+ if (!abs.startsWith(root)) return null;
173
+ const rel = relative(root, abs);
174
+ return rel && !rel.startsWith('..') ? rel.replace(/\\/g, '/') + '/' : '';
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
179
+
180
+ export function readScopeFile(changeDir) {
181
+ const p = join(changeDir, SCOPE_FILE);
182
+ if (!existsSync(p)) return null;
183
+ try {
184
+ return JSON.parse(readFileSync(p, 'utf-8'));
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
@@ -114,6 +114,13 @@ const BUILTIN_DEFAULTS = {
114
114
  // 与 arch_merge_skipped / test_matrix_skipped 同型:跳过必须附理由留痕)
115
115
  arch_design_light_skipped: null,
116
116
  arch_design_light_skip_reason: null,
117
+ // Security baseline gate (security-baseline-integration-design v1.3 §L2)
118
+ // 三键全量三环(BUILTIN_DEFAULTS + writeState 序列化 + cmd-state SETTABLE_FIELDS),
119
+ // 缺任一环即「回显成功却零写入」(v0.59.0 同型教训)。security_scope_hash 绑定
120
+ // 最近一次推导的 scope_hash——范围变化(hash 不匹配)时 skip 自动失效(防跨轮固化)。
121
+ security_baseline_skipped: null,
122
+ security_baseline_skip_reason: null,
123
+ security_scope_hash: null,
117
124
  // 注意:schema_version 故意不在 BUILTIN_DEFAULTS 中(v0.13 §48.1)——
118
125
  // 它只由 `tf state init` 在 change 创建时打戳,字段缺失本身就是"存量 change"信号。
119
126
  };
@@ -281,6 +288,11 @@ export function writeState(changeDir, state) {
281
288
  lines.push('# === Light architecture design gate (v0.64.0) ===');
282
289
  lines.push(`arch_design_light_skipped: ${state.arch_design_light_skipped ?? 'null'}`);
283
290
  lines.push(`arch_design_light_skip_reason: ${state.arch_design_light_skip_reason ?? 'null'}`);
291
+ lines.push('');
292
+ lines.push('# === Security baseline gate (security-baseline-integration-design v1.3) ===');
293
+ lines.push(`security_baseline_skipped: ${state.security_baseline_skipped ?? 'null'}`);
294
+ lines.push(`security_baseline_skip_reason: ${state.security_baseline_skip_reason ?? 'null'}`);
295
+ lines.push(`security_scope_hash: ${state.security_scope_hash ?? 'null'}`);
284
296
 
285
297
  fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
286
298
  }
@@ -44,6 +44,8 @@ const COMMANDS = {
44
44
  'glaf4-evidence-export': () => import('./lib/glaf4-evidence-export.mjs'),
45
45
  'glaf4-delegation': () => import('./lib/glaf4-delegation.mjs'),
46
46
  'repo-layout': () => import('./lib/cmd-repo-layout.mjs'),
47
+ // security-baseline-integration-design v1.3:安全基线范围推导(Scope Deriver CLI 入口)
48
+ security: () => import('./lib/cmd-security.mjs'),
47
49
  test: () => import('./lib/test-record.mjs'),
48
50
  };
49
51
 
@@ -94,6 +96,11 @@ Commands:
94
96
  config [options] Display or modify configuration
95
97
  config --resolve-model <profile> Resolve a configured model profile without switching models
96
98
  state <sub> <dir> Manage .team-flow.yaml state (init|check|transition|get|rebuild|set|upgrade)
99
+ security scope <change-dir> [--json]
100
+ Derive security-baseline applicability (trigger categories + items),
101
+ write .security-scope.json, print scope_hash (v1.3 Scope Deriver CLI)
102
+ security baseline [--json]
103
+ Show 44-item enterprise security baseline version anchor (md+json 双载体)
97
104
  inject <dir> Generate phase-guard artifacts; use --platforms <name|all> when platform is ambiguous
98
105
  audit <dir> Generate decision-point-audit.md from .team-flow.yaml
99
106
  checkpoint save <change-dir> --task <id> --next <text>