@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,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
+ }
@@ -21,6 +21,8 @@ export async function run(args) {
21
21
  severity: { type: 'string' },
22
22
  summary: { type: 'string' },
23
23
  source: { type: 'string' },
24
+ // agent-governance P2-1:纠错条目的来源事件锚(HOLD 的 msg_id / 决策日志行)
25
+ 'source-event': { type: 'string' },
24
26
  // v0.57.0 §4.1:inject 的展示条数控制与 backfill 的预演
25
27
  limit: { type: 'string' },
26
28
  all: { type: 'boolean' },
@@ -52,6 +54,7 @@ export async function run(args) {
52
54
  severity: values.severity,
53
55
  summary: values.summary,
54
56
  source: values.source,
57
+ source_event: values['source-event'],
55
58
  dir: values.dir,
56
59
  });
57
60
  return;
@@ -1,7 +1,7 @@
1
1
  // scripts/lib/cmd-state.mjs — tf state subcommand handler
2
2
  import { parseArgs } from 'node:util';
3
3
  import { spawnSync } from 'node:child_process';
4
- import { existsSync, mkdirSync } from 'node:fs';
4
+ import { existsSync, mkdirSync, readFileSync } from 'node:fs';
5
5
  import path, { dirname, join } from 'node:path';
6
6
  import { fileURLToPath } from 'node:url';
7
7
  // VALID_STATES 统一从 state-loader.mjs 引入(状态机合法值唯一真相源),不再本地硬编码。
@@ -11,6 +11,26 @@ import { computeArtifactsHash, computeContractHash, computeTestMatrixHash } from
11
11
 
12
12
  const __dirname = dirname(fileURLToPath(import.meta.url));
13
13
 
14
+ /**
15
+ * P1-1:自 changeDir 向上(限 6 层)找 `<root>/.team-flow/prd-clarity.json` 并解析。
16
+ * 找不到 / 解析失败 → null(= 中性,init 不打字段)。
17
+ */
18
+ function findProductClarityRecord(changeDir) {
19
+ let dir = path.resolve(changeDir);
20
+ for (let i = 0; i < 6; i += 1) {
21
+ const rec = path.join(dir, '.team-flow', 'prd-clarity.json');
22
+ if (existsSync(rec)) {
23
+ const parsed = JSON.parse(readFileSync(rec, 'utf-8'));
24
+ if (parsed && (parsed.status === 'pass' || parsed.status === 'fail')) return parsed;
25
+ return null;
26
+ }
27
+ const parent = path.dirname(dir);
28
+ if (parent === dir) break;
29
+ dir = parent;
30
+ }
31
+ return null;
32
+ }
33
+
14
34
  // v0.13 §50(BUG-A 政策修正):test_result 移出可手工设置字段——测试证据必须经
15
35
  // tf test record 程序化记录,手工自述值不再被 tests-passing 门禁接受。
16
36
  // v0.13 §48.2:test_matrix_skip_reason 可设置(显式 skip 的理由留痕)。
@@ -58,6 +78,11 @@ const SETTABLE_FIELDS = [
58
78
  'gates_probed_skipped', 'gates_probed_skip_reason', 'gates_probed_na',
59
79
  // v0.64.0(§4 arch-design-light):轻架构说明显式跳过键(须附理由)
60
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',
61
86
  ];
62
87
 
63
88
  export async function run(args) {
@@ -121,6 +146,16 @@ export async function run(args) {
121
146
  const state = readState(changeDir);
122
147
  if (!stateFileExisted) {
123
148
  state.schema_version = 1;
149
+ // P1-1 clarity 跨层管道:拷贝产品级记录(唯一写入路径——state 字段不在
150
+ // SETTABLE_FIELDS,防自助放行)。find 向上找 <root>/.team-flow/prd-clarity.json。
151
+ // 记录 fail 也拷:绕过 S2 冻结的 direct change 在变更级被 guard 拦下(双保险)。
152
+ try {
153
+ const rec = findProductClarityRecord(changeDir);
154
+ if (rec) {
155
+ state.prd_clarity_result = rec.status === 'fail' ? 'fail' : 'pass';
156
+ state.prd_clarity_hash = rec.prd_hash ?? null;
157
+ }
158
+ } catch { /* 无记录 = 中性,不阻断 init */ }
124
159
  // v0.64.0 P0(§4 扫描基线):init 打戳 base_sha(架构 surface 扫描的 git 基线)。
125
160
  // 非 git 环境 / 尚无提交 → 保持 null,扫描时按 §4 退化参照(origin → FAIL)。
126
161
  try {
@@ -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
+ }
@@ -42,6 +42,10 @@ export function run(args = {}) {
42
42
  const severity = args.severity || 'medium';
43
43
  const summary = args.summary || '(no summary)';
44
44
  const source = args.source || '';
45
+ // agent-governance P2-1:来源事件锚——correction 条目的毕业条件消费它
46
+ //(P2-2「锚未标撤销」判定;无锚条目按未毕业处理)。HOLD 场景 = escalation msg_id
47
+ // 或决策日志行;非空才写入 frontmatter。
48
+ const sourceEvent = args.source_event || '';
45
49
  const dir = args.dir || 'docs/solutions';
46
50
 
47
51
  // 注:process.exit 后补 return——真实 CLI 下 exit 即终止;测试环境 mock exit 时
@@ -79,6 +83,7 @@ type: ${type}
79
83
  severity: ${severity}
80
84
  date: ${date}
81
85
  source: ${source}
86
+ ${sourceEvent ? `source_event: ${sourceEvent}` : ''}
82
87
  title: ${title}
83
88
  ---
84
89
 
@@ -41,6 +41,34 @@ import { parseFrontmatter, listSolutionEntries } from './solutions-entry.mjs';
41
41
  /** INDEX 保留的最大**条目数**(非行数——文件还含 4 行头部) */
42
42
  export const MAX_INDEX_ENTRIES = 150;
43
43
 
44
+ /**
45
+ * agent-governance P2-2(earned automation 见习):毕业阈值。
46
+ * `flags` 列派生规则(**纯派生、随重建重算**——INDEX 是投影):
47
+ * - 条目有 `source_event` 锚(P2-1 起的 correction 条目):confirmations ≥ 本阈值 → `auto-ok`,
48
+ * 否则 → `probation`(见习:不进自动注入上下文)。
49
+ * 「锚被标撤销」的毕业否决暂无实现(撤销动作未建,如实声明——source_event 锚已就位,
50
+ * 撤销标记是其消费侧的未来项)。
51
+ * - 无锚存量条目 → `auto-ok`(**grandfather**:它们已在注入通道,见习化 = 行为回退,不采纳;
52
+ * 与 schema_version 存量豁免同构)。
53
+ * - 红区硬排除不在此处:红区判定是**消费侧**职责(inject 按 summary/domain 关键词排除),
54
+ * flags 表示毕业状态、不表示区域。
55
+ */
56
+ export const GRADUATED_MIN_CONFIRMATIONS = 3;
57
+
58
+ /** 解析 frontmatter confirmations 计数(宽松:仅计数,promote 的严格解析不在此复用)。 */
59
+ function countConfirmations(fm) {
60
+ const raw = fm?.confirmations;
61
+ if (raw == null || raw === '') return 0;
62
+ return String(raw).replace(/[[\]]/g, '').split(',').map((s) => s.trim()).filter(Boolean).length;
63
+ }
64
+
65
+ /** 派生 flags(见上注释的规则表)。 */
66
+ export function deriveFlags(fm) {
67
+ const hasAnchor = Boolean(fm?.source_event && String(fm.source_event).trim());
68
+ if (!hasAnchor) return 'auto-ok';
69
+ return countConfirmations(fm) >= GRADUATED_MIN_CONFIRMATIONS ? 'auto-ok' : 'probation';
70
+ }
71
+
44
72
  /**
45
73
  * 解析条目文件的 frontmatter(扁平键值)——**收敛到共享层**(v0.57.0 P4 二轮)。
46
74
  *
@@ -111,6 +139,8 @@ export function refreshIndex(dir, { phases = SOLUTION_PHASES, maxEntries = MAX_I
111
139
  file: `${phase}/${file}`,
112
140
  // v0.57.0 P4:source 进 INDEX 供 inject 做同源聚类(此前只存在于条目文件,消费方读不到)
113
141
  source: fm.source || '',
142
+ // P2-2:见习/毕业派生状态(规则见 GRADUATED_MIN_CONFIRMATIONS 注释)
143
+ flags: deriveFlags(fm),
114
144
  });
115
145
  }
116
146
  }
@@ -133,10 +163,10 @@ export function refreshIndex(dir, { phases = SOLUTION_PHASES, maxEntries = MAX_I
133
163
  // "只读 INDEX、不读条目文件",故聚类键必须进索引。消费方对 7/8 列都容错(见 solutions-inject)。
134
164
  let index = '# Solutions Index\n';
135
165
  index += `<!-- 每条一行,排序 severity 降序 → date 降序 → file 兜底,≤${maxEntries} 条上限;summary/source 内的 | 转义为 \\| -->\n`;
136
- index += '| date | phase | domain | type | severity | summary | file | source |\n';
137
- index += '|------|-------|--------|------|----------|---------|------|--------|\n';
166
+ index += '| date | phase | domain | type | severity | summary | file | source | flags |\n';
167
+ index += '|------|-------|--------|------|----------|---------|------|--------|-------|\n';
138
168
  for (const e of kept) {
139
- index += `| ${escapeCell(e.date)} | ${escapeCell(e.phase)} | ${escapeCell(e.domain)} | ${escapeCell(e.type)} | ${escapeCell(e.severity)} | ${escapeCell(e.summary)} | ${escapeCell(e.file)} | ${escapeCell(e.source)} |\n`;
169
+ index += `| ${escapeCell(e.date)} | ${escapeCell(e.phase)} | ${escapeCell(e.domain)} | ${escapeCell(e.type)} | ${escapeCell(e.severity)} | ${escapeCell(e.summary)} | ${escapeCell(e.file)} | ${escapeCell(e.source)} | ${escapeCell(e.flags)} |\n`;
140
170
  }
141
171
 
142
172
  writeFileSync(join(dir, 'INDEX.md'), index, 'utf-8');
@@ -37,16 +37,31 @@ import { severityRank } from './severity.mjs';
37
37
  import { parseTableRow } from './md-normalize.mjs';
38
38
 
39
39
  /**
40
- * INDEX 表列数范围(date | phase | domain | type | severity | summary | file [| source])。
41
- * v0.57.0 P4:`source` 列是本版新增 ⇒ 7(存量 INDEX)与 8(本版重建)都要接受。
40
+ * INDEX 表列数范围(date | phase | domain | type | severity | summary | file | source | flags)。
41
+ * v0.57.0 P4:`source` 列新增 ⇒ 7/8 都要接受;
42
+ * agent-governance P2-2:`flags` 列(见习/毕业)新增 ⇒ 8(上版重建)与 9(本版)都要接受,
43
+ * **7 列最旧存量同样接受**(flags 缺失 = 无锚存量 = grandfather auto-ok,见 index-gen deriveFlags)。
42
44
  */
43
45
  const INDEX_COLUMNS_MIN = 7;
44
- const INDEX_COLUMNS_MAX = 8;
46
+ const INDEX_COLUMNS_MAX = 9;
45
47
  /** `file` 列的形状(`<phase>/<name>.md`)——语义校验,见下方列解析注释 */
46
48
  const ENTRY_FILE_RE = /^[a-z][a-z-]*\/.+\.md$/;
47
49
  /** 默认展示条数 */
48
50
  const DEFAULT_LIMIT = 5;
49
51
 
52
+ /**
53
+ * P2-2 红区硬排除:DP-A / Code Landing / publish 相关的复利建议**永不自动注入**
54
+ * (memo §2.4 红区护栏——红区永久人工,连"建议"也不自动进上下文,防复利通道
55
+ * 变相给红区动作背书)。宁可少注入(安全方向)不误放。
56
+ */
57
+ const RED_ZONE_RE = /\bDP-A\b|Code Landing|\bpublish\b|发布闸|代码落地/i;
58
+
59
+ /** P2-2 见习判定:flags 列非 auto-ok(probation 或未知新值)即不自动注入。 */
60
+ function isProbation(flags) {
61
+ const f = String(flags || '').toLowerCase();
62
+ return f !== '' && f !== 'auto-ok';
63
+ }
64
+
50
65
  export function run(args = {}) {
51
66
  const phase = args.phase || 'cross-phase';
52
67
  const domain = args.domain || '';
@@ -63,6 +78,8 @@ export function run(args = {}) {
63
78
 
64
79
  const content = readFileSync(indexPath, 'utf-8');
65
80
  const entries = [];
81
+ // P2-2 门控计数(可观察性:跳过多少 = 门在工作的证据)
82
+ const excluded = { probation: 0, redZone: 0 };
66
83
 
67
84
  for (const line of content.split('\n')) {
68
85
  if (!line.startsWith('|') || line.startsWith('| date') || line.startsWith('|--')) continue;
@@ -70,7 +87,7 @@ export function run(args = {}) {
70
87
  const cells = parseTableRow(line);
71
88
  // 上下界同时校验:只查下界无法发现"summary 含 `|` 导致列数变多、file 列错位"(v0.38.0 test-merge 同型缺陷)
72
89
  if (cells.length < INDEX_COLUMNS_MIN || cells.length > INDEX_COLUMNS_MAX) continue;
73
- const [date, ePhase, eDomain, type, severity, summary, file, source = ''] = cells;
90
+ const [date, ePhase, eDomain, type, severity, summary, file, source = '', flags = ''] = cells;
74
91
  // file 列语义校验(v0.57.0 P4):列数在界内仍可能错位——例如 summary 含**未转义**的 `|`
75
92
  // 时列数恰好落进 7/8 区间,摘要后半段会顶到 file 位。形状不符即丢弃,避免注入错位的路径。
76
93
  if (!ENTRY_FILE_RE.test(file)) continue;
@@ -79,7 +96,10 @@ export function run(args = {}) {
79
96
  const domainMatch = !domain || !eDomain || eDomain === 'general' || eDomain === domain;
80
97
 
81
98
  if (phaseMatch && domainMatch) {
82
- entries.push({ date, phase: ePhase, domain: eDomain, type, severity, summary, file, source, isNative: ePhase === phase });
99
+ // P2-2 两道消费侧门控(跳过 = 不注入,落 excluded 计数可观察)
100
+ if (isProbation(flags)) { excluded.probation += 1; continue; }
101
+ if (RED_ZONE_RE.test(`${summary} ${eDomain}`)) { excluded.redZone += 1; continue; }
102
+ entries.push({ date, phase: ePhase, domain: eDomain, type, severity, summary, file, source, flags, isNative: ePhase === phase });
83
103
  }
84
104
  }
85
105
 
@@ -119,12 +139,17 @@ export function run(args = {}) {
119
139
  const shown = all ? clustered : clustered.slice(0, limit);
120
140
 
121
141
  if (shown.length === 0) {
122
- console.log(`<!-- No matching solutions found (phase=${phase}, domain=${domain || 'any'}) -->`);
123
- return { entries: [] };
142
+ // P2-2:「被门控排除成空」与「本来无匹配」语义不同——前者必须可见,否则门 = 隐形
143
+ const excl = (excluded.probation + excluded.redZone) > 0
144
+ ? `(P2-2 门控排除 ${excluded.probation} 条见习 + ${excluded.redZone} 条红区)` : '';
145
+ console.log(`<!-- No matching solutions found (phase=${phase}, domain=${domain || 'any'})${excl} -->`);
146
+ return { entries: [], excluded };
124
147
  }
125
148
 
126
149
  const foldedNote = foldedCount > 0 ? `, ${foldedCount} 条同源已折叠` : '';
127
- console.log(`## Solutions Context (phase=${phase}, domain=${domain || 'any'}, showing ${shown.length}/${clustered.length}${foldedNote})`);
150
+ const excludedNote = (excluded.probation + excluded.redZone) > 0
151
+ ? `, P2-2 门控排除 ${excluded.probation} 条见习 + ${excluded.redZone} 条红区` : '';
152
+ console.log(`## Solutions Context (phase=${phase}, domain=${domain || 'any'}, showing ${shown.length}/${clustered.length}${foldedNote}${excludedNote})`);
128
153
  console.log('');
129
154
  for (const e of shown) {
130
155
  console.log(`- [${e.severity}] ${e.summary} (${e.phase}/${e.domain}, ${e.date}) → ${e.file}`);
@@ -140,7 +165,7 @@ export function run(args = {}) {
140
165
  console.log('');
141
166
  }
142
167
 
143
- return { entries: shown, total: clustered.length, folded: foldedCount, hidden };
168
+ return { entries: shown, total: clustered.length, folded: foldedCount, hidden, excluded };
144
169
  }
145
170
 
146
171
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
@@ -89,6 +89,11 @@ const BUILTIN_DEFAULTS = {
89
89
  test_matrix_skip_reason: null,
90
90
  // Test evidence (v0.13 §50:tf test record 落盘的 runner 输出证据路径)
91
91
  test_evidence_path: null,
92
+ // Product-level PRD clarity record(agent-governance P1-1 跨层管道):
93
+ // 唯一写入路径 = tf state init 从 <root>/.team-flow/prd-clarity.json 拷贝;
94
+ // **有意不在 cmd-state SETTABLE_FIELDS**——可 set 就是自助放行通道(gate-hint-is-a-bypass)。
95
+ prd_clarity_result: null,
96
+ prd_clarity_hash: null,
92
97
  // Tasks gate (v0.22 §85:hotfix/tweak 跳过 spec-writer 时显式跳过 tasks.md)
93
98
  tasks_skipped: null,
94
99
  tasks_skip_reason: null,
@@ -109,6 +114,13 @@ const BUILTIN_DEFAULTS = {
109
114
  // 与 arch_merge_skipped / test_matrix_skipped 同型:跳过必须附理由留痕)
110
115
  arch_design_light_skipped: null,
111
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,
112
124
  // 注意:schema_version 故意不在 BUILTIN_DEFAULTS 中(v0.13 §48.1)——
113
125
  // 它只由 `tf state init` 在 change 创建时打戳,字段缺失本身就是"存量 change"信号。
114
126
  };
@@ -184,6 +196,14 @@ export function writeState(changeDir, state) {
184
196
  lines.push('# === Hashes (fast staleness detection) ===');
185
197
  lines.push(`artifacts_hash: ${state.artifacts_hash ?? 'null'}`);
186
198
  lines.push(`contract_hash: ${state.contract_hash ?? 'null'}`);
199
+ // P1-1 clarity 管道:条件序列化(同 schema_version 模式)——存量 change 无记录不追加,
200
+ // readState 缺失回退 BUILTIN_DEFAULTS 的 null(= 变更级维度中性)。
201
+ if (state.prd_clarity_result != null) {
202
+ lines.push(`prd_clarity_result: ${state.prd_clarity_result}`);
203
+ }
204
+ if (state.prd_clarity_hash != null) {
205
+ lines.push(`prd_clarity_hash: ${state.prd_clarity_hash}`);
206
+ }
187
207
  lines.push('');
188
208
  lines.push('# === Execution progress ===');
189
209
  lines.push(`execution_mode: ${state.execution_mode ?? 'null'}`);
@@ -268,6 +288,11 @@ export function writeState(changeDir, state) {
268
288
  lines.push('# === Light architecture design gate (v0.64.0) ===');
269
289
  lines.push(`arch_design_light_skipped: ${state.arch_design_light_skipped ?? 'null'}`);
270
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'}`);
271
296
 
272
297
  fs.writeFileSync(filePath, lines.join('\n') + '\n', 'utf-8');
273
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>
@@ -195,13 +195,13 @@ For detailed routing logic, read `references/phase0-routing.md`. Summary:
195
195
  - **0.1b Classify**: software → continue; non-software → `references/universal-brainstorming.md`; neither → respond directly
196
196
  - **0.1c Verdict**: verdict-shaped requests → offer `/ce-pov` handoff via `references/verdict-routing.md`
197
197
  - **0.2 Assess**: clear requirements → skip to Phase 2.5; ambiguous → full brainstorm
198
- - **0.3 Scope**: Lightweight / Standard / Deep (+ feature vs product); arm visual-probe and blindspot tripwires
198
+ - **0.3 Scope**: Lightweight / Standard / Deep (+ feature vs product); arm visual-probe and blindspot tripwires. **Scope 推荐降阈值(agent-governance P1-2 · A 档)**:需求一句话可述、无跨模块疑点、`arch_baseline` 已建档时,**默认推荐 Lightweight**(跳 BP-1~3 + Path A 轻确认),用户确认即走;含糊/多义/涉新模块 → 推荐 Standard(含糊才升 Deep)。推荐是建议非决定,用户可任选档位;**判定不清时按 Standard 起步**(宁多问不漏问)。
199
199
  - **0.4 Spine**: create 5-task tracking spine for Standard/Deep
200
200
  - **0.5 Iteration**: resolve `ITERATION_VERSION` from `requirement/` directory (legacy: fallback `prd/`)
201
201
 
202
202
  ### Phase 1: Understand the Idea
203
203
 
204
- **1.1 Context Scan** — For Standard/Deep, dispatch grounding scout sub-agent (extraction-tier) to produce a grounding dossier. For the scout prompt, scratch directory setup, and Slack routing, read `references/grounding.md`. Two rules: **verify before claiming** (check infrastructure exists) and **defer design to planning**.
204
+ **1.1 Context Scan** — For Standard/Deep, dispatch grounding scout sub-agent (extraction-tier) to produce a grounding dossier. For the scout prompt, scratch directory setup, and Slack routing, read `references/grounding.md`. Two rules: **verify before claiming** (check infrastructure exists) and **defer design to planning**. **复利注入(P2-3 顶层调用,不限 scope)**:Phase 1 开始前跑 `tf solutions inject --phase prd --limit 5`(CLI 内含见习/红区门控;**Lightweight 同样注入**——它是轻命令,不随 scout 一起被 scope 门掉,防 A 档默认轻档路径零注入);空结果静默跳过——冷启动不计收益 0。
205
205
 
206
206
  **1.2 Pressure Test** — Scan opening for rigor gaps (evidence, specificity, counterfactual, attachment). Read `references/product-pressure-test.md` for per-tier lens catalog. Session-settled decisions count as already-probed.
207
207
 
@@ -273,7 +273,7 @@ Read `references/brainstorm-sections.md` for doc-warranted criteria. If warrante
273
273
 
274
274
  ### QA-4: PRD Quality Check
275
275
 
276
- Fires after Phase 3 (or Phase 3.5 if prototype loop ran). Read `references/evidence-chain-validation.md` for QA-4 criteria. Evaluates PRD completeness and traceability against business analysis artifacts. **冻结前完整性评审(6 维,含 §8.4 信息齐备性与业务可读形态 D6)的派发点已归并**:standalone 路径由 Phase 3.5(`references/prototype-loop.md` §3.5.5)派发 prd-completeness-reviewer;orchestrated 路径由 orchestrator S2 派发(见 `workflow-orchestrator/references/s2-prd-prototype-loop.md`);**QA-4 自身不重复派发**,仅按评审结果判定是否回 Phase 1.3/Phase 3 修订。
276
+ Fires after Phase 3 (or Phase 3.5 if prototype loop ran). Read `references/evidence-chain-validation.md` for QA-4 criteria. Evaluates PRD completeness and traceability against business analysis artifacts. **冻结前完整性评审(6 维,含 §8.4 信息齐备性与业务可读形态 D6)的派发点已归并**:standalone 路径由 Phase 3.5(`references/prototype-loop.md` §3.5.5)派发 prd-completeness-reviewer;orchestrated 路径由 orchestrator S2 派发(见 `workflow-orchestrator/references/s2-prd-prototype-loop.md`);**QA-4 自身不重复派发**,仅按评审结果判定是否回 Phase 1.3/Phase 3 修订。**冻结前先过清晰度机械门 `tf prd check-clarity`(P0-1,checker 先于 reviewer,FAIL 则回改不派发)**——调用细则见 `references/prototype-loop.md` §3.5.5 前置段(standalone)/ `s2-prd-prototype-loop.md` step 3.4(orchestrated)。
277
277
 
278
278
  ### Phase 3.6: PRD ↔ Scenario/Process Bidirectional Validation
279
279
 
@@ -9,7 +9,7 @@ Detailed context scanning logic for Phase 1.1. The main SKILL.md describes the h
9
9
  **Standard and Deep** — Two passes:
10
10
 
11
11
  *Constraint Check (inline)* — Use the project's active instructions and conventions already in your context. Read `STRATEGY.md` if it exists for product direction and `CONCEPTS.md` if it exists for canonical vocabulary — it lives at **`docs/architecture/CONCEPTS.md`** (the repo-root location was retired in v0.23.0, so a root-only probe misses bootstrapped projects). Use canonical names in dialogue, approaches, and the Product Contract.
12
- - **Solutions index (v0.5)**: Read `docs/solutions/INDEX.md` if it exists. Filter entries where `phase = prd OR phase = cross-phase` and `domain` matches the current topic. Inject the **top 5** summaries as context constraints, ordered **severity → phase-match → date** (the CLI's ordering). Widen the window with `tf solutions inject --phase prd --limit <n>` when cross-phase entries saturate it — otherwise this stage's own entries are unreachable. If INDEX.md does not exist or is empty, skip silently — a missing index makes the CLI print one WARN, which is not an error.
12
+ - **Solutions index (v0.5)**: Run `tf solutions inject --phase prd --limit 5` first(CLI 内含 P2-2 见习/红区门控); only if the CLI is unavailable, fall back to reading `docs/solutions/INDEX.md` manually — filter `phase = prd OR cross-phase` + domain match, top 5, ordered **severity → phase-match → date**. **手动降级不降门(P2-2)**:跳过 `flags=probation`(见习)行与红区关键词行(`DP-A`/`Code Landing`/`publish`/`发布闸`/`代码落地`)。If INDEX.md does not exist or is empty, skip silently — a missing index makes the CLI print one WARN, which is not an error(冷启动不计收益 0)。
13
13
 
14
14
  ## Topic Scan (Grounding Scout)
15
15