agentic-flow-kit 0.8.0 → 0.9.1

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 (141) hide show
  1. package/README.md +93 -69
  2. package/modules/gates/README.md +24 -0
  3. package/modules/gates/generated-readonly/README.md +13 -0
  4. package/modules/gates/generated-readonly/check-generated.mjs +61 -0
  5. package/modules/gates/generated-readonly/local-pre-commit.line +1 -0
  6. package/modules/gates/node-layer/README.md +9 -0
  7. package/modules/gates/node-layer/check-node-layer.mjs +29 -0
  8. package/modules/gates/node-layer/local-pre-commit.line +1 -0
  9. package/modules/gates/py-import/README.md +9 -0
  10. package/modules/gates/py-import/check-py-import.mjs +30 -0
  11. package/modules/gates/py-import/local-pre-commit.line +1 -0
  12. package/modules/hosts/claude/agents/implementer.md +52 -0
  13. package/modules/hosts/claude/agents/independent-reviewer.md +49 -0
  14. package/modules/hosts/claude/agents/ui-verifier.md +48 -0
  15. package/modules/hosts/claude/commands/wf-build.md +79 -0
  16. package/modules/hosts/claude/commands/wf-deploy.md +57 -0
  17. package/modules/hosts/claude/commands/wf-design.md +42 -0
  18. package/modules/hosts/claude/commands/wf-gate-checklist.md +77 -0
  19. package/modules/hosts/claude/commands/wf-maintain.md +76 -0
  20. package/modules/hosts/claude/commands/wf-new-task.md +65 -0
  21. package/modules/hosts/claude/commands/wf-plan.md +41 -0
  22. package/modules/hosts/claude/commands/wf-review.md +47 -0
  23. package/modules/hosts/claude/commands/wf-source-sync-check.md +89 -0
  24. package/modules/hosts/claude/commands/wf-sync-hosts.md +66 -0
  25. package/modules/hosts/claude/commands/wf-test.md +66 -0
  26. package/modules/hosts/codex/agents/implementer.md +52 -0
  27. package/modules/hosts/codex/agents/independent-reviewer.md +49 -0
  28. package/modules/hosts/codex/agents/ui-verifier.md +48 -0
  29. package/modules/hosts/codex/commands/wf-build.md +79 -0
  30. package/modules/hosts/codex/commands/wf-deploy.md +57 -0
  31. package/modules/hosts/codex/commands/wf-design.md +42 -0
  32. package/modules/hosts/codex/commands/wf-gate-checklist.md +77 -0
  33. package/modules/hosts/codex/commands/wf-maintain.md +76 -0
  34. package/modules/hosts/codex/commands/wf-new-task.md +65 -0
  35. package/modules/hosts/codex/commands/wf-plan.md +41 -0
  36. package/modules/hosts/codex/commands/wf-review.md +47 -0
  37. package/modules/hosts/codex/commands/wf-source-sync-check.md +89 -0
  38. package/modules/hosts/codex/commands/wf-sync-hosts.md +66 -0
  39. package/modules/hosts/codex/commands/wf-test.md +66 -0
  40. package/modules/hosts/codex/skills/flow-kit/SKILL.md +14 -0
  41. package/modules/hosts/cursor/agents/implementer.md +52 -0
  42. package/modules/hosts/cursor/agents/independent-reviewer.md +49 -0
  43. package/modules/hosts/cursor/agents/ui-verifier.md +48 -0
  44. package/modules/hosts/cursor/commands/wf-build.md +79 -0
  45. package/modules/hosts/cursor/commands/wf-deploy.md +57 -0
  46. package/modules/hosts/cursor/commands/wf-design.md +42 -0
  47. package/modules/hosts/cursor/commands/wf-gate-checklist.md +77 -0
  48. package/modules/hosts/cursor/commands/wf-maintain.md +76 -0
  49. package/modules/hosts/cursor/commands/wf-new-task.md +65 -0
  50. package/modules/hosts/cursor/commands/wf-plan.md +41 -0
  51. package/modules/hosts/cursor/commands/wf-review.md +47 -0
  52. package/modules/hosts/cursor/commands/wf-source-sync-check.md +89 -0
  53. package/modules/hosts/cursor/commands/wf-sync-hosts.md +66 -0
  54. package/modules/hosts/cursor/commands/wf-test.md +66 -0
  55. package/modules/hosts/omp/agents/implementer.md +1 -1
  56. package/modules/hosts/omp/agents/independent-reviewer.md +1 -1
  57. package/modules/hosts/omp/agents/ui-verifier.md +1 -1
  58. package/modules/hosts/opencode/agents/implementer.md +1 -1
  59. package/modules/hosts/opencode/agents/independent-reviewer.md +1 -1
  60. package/modules/hosts/opencode/agents/ui-verifier.md +1 -1
  61. package/modules/hosts/opencode/commands/wf-build.md +3 -4
  62. package/modules/hosts/opencode/commands/wf-design.md +2 -2
  63. package/modules/hosts/opencode/commands/wf-maintain.md +1 -1
  64. package/modules/hosts/opencode/commands/wf-plan.md +3 -3
  65. package/modules/hosts/opencode/commands/wf-review.md +1 -1
  66. package/modules/hosts/opencode/commands/wf-source-sync-check.md +1 -1
  67. package/modules/hosts/opencode/commands/wf-test.md +3 -1
  68. package/modules/hosts/trae/agents/implementer.md +1 -1
  69. package/modules/hosts/trae/agents/independent-reviewer.md +1 -1
  70. package/modules/hosts/trae/agents/ui-verifier.md +1 -1
  71. package/modules/hosts/trae/commands/wf-build.md +3 -4
  72. package/modules/hosts/trae/commands/wf-design.md +2 -2
  73. package/modules/hosts/trae/commands/wf-maintain.md +1 -1
  74. package/modules/hosts/trae/commands/wf-plan.md +3 -3
  75. package/modules/hosts/trae/commands/wf-review.md +1 -1
  76. package/modules/hosts/trae/commands/wf-source-sync-check.md +1 -1
  77. package/modules/hosts/trae/commands/wf-test.md +3 -1
  78. package/modules/hosts/zcode/agents/implementer.md +1 -1
  79. package/modules/hosts/zcode/agents/independent-reviewer.md +1 -1
  80. package/modules/hosts/zcode/agents/ui-verifier.md +1 -1
  81. package/package.json +45 -45
  82. package/src/cli.mjs +16 -4
  83. package/src/doctor.mjs +49 -1
  84. package/src/fresh-init.test.mjs +36 -0
  85. package/src/gates.test.mjs +126 -0
  86. package/src/init.mjs +8 -1
  87. package/src/init.test.mjs +7 -1
  88. package/src/profiles.mjs +10 -4
  89. package/src/run-tests.mjs +3 -0
  90. package/src/stack-profile.test.mjs +89 -0
  91. package/src/sync-hosts.test.mjs +26 -6
  92. package/templates/AGENTS.md +4 -4
  93. package/templates/_agents/board/index.html +3 -3
  94. package/templates/_agents/commands/build.md +4 -5
  95. package/templates/_agents/commands/design.md +4 -4
  96. package/templates/_agents/commands/maintain.md +1 -1
  97. package/templates/_agents/commands/new-task.md +13 -2
  98. package/templates/_agents/commands/plan.md +4 -4
  99. package/templates/_agents/commands/review.md +1 -1
  100. package/templates/_agents/commands/source-sync-check.md +1 -1
  101. package/templates/_agents/commands/test.md +4 -2
  102. package/templates/_agents/hooks/commit-check.cjs +45 -24
  103. package/templates/_agents/lane-surfaces.txt +14 -0
  104. package/templates/_agents/roles/implementer.md +1 -1
  105. package/templates/_agents/roles/independent-reviewer.md +1 -1
  106. package/templates/_agents/roles/ui-verifier.md +1 -1
  107. package/templates/_agents/scripts/check-lane-surface.mjs +164 -0
  108. package/templates/_agents/scripts/check-lane-surface.test.mjs +200 -0
  109. package/templates/_agents/scripts/check-loop-rev.test.mjs +337 -0
  110. package/templates/_agents/scripts/check-loop.mjs +460 -268
  111. package/templates/_agents/scripts/check-loop.test.mjs +677 -17
  112. package/templates/_agents/scripts/check-metric-claims.mjs +275 -0
  113. package/templates/_agents/scripts/commit-check-trigger.test.mjs +64 -1
  114. package/templates/_agents/scripts/confirm-doc.mjs +159 -15
  115. package/templates/_agents/scripts/confirm-doc.test.mjs +362 -10
  116. package/templates/_agents/scripts/doctor.test.mjs +102 -1
  117. package/templates/_agents/scripts/fill-intent.mjs +8 -7
  118. package/templates/_agents/scripts/fill-intent.test.mjs +12 -3
  119. package/templates/_agents/scripts/fill-plan.mjs +27 -11
  120. package/templates/_agents/scripts/fill-plan.test.mjs +148 -8
  121. package/templates/_agents/scripts/fill-spec.mjs +18 -3
  122. package/templates/_agents/scripts/fill-spec.test.mjs +198 -1
  123. package/templates/_agents/scripts/gate-checklist.mjs +4 -1
  124. package/templates/_agents/scripts/gen-workflow-index.mjs +6 -2
  125. package/templates/_agents/scripts/gen-workflow-index.test.mjs +12 -0
  126. package/templates/_agents/scripts/policy.mjs +46 -0
  127. package/templates/_agents/scripts/pre-push.test.mjs +82 -0
  128. package/templates/_agents/scripts/solidify-task.mjs +107 -0
  129. package/templates/_agents/scripts/solidify-task.test.mjs +126 -0
  130. package/templates/_agents/scripts/stage-gates.mjs +256 -0
  131. package/templates/_agents/scripts/trust-mode.mjs +67 -0
  132. package/templates/_agents/scripts/wf-journal.mjs +71 -2
  133. package/templates/_agents/scripts/wf-journal.test.mjs +66 -0
  134. package/templates/_agents/scripts/workflow-board-server.mjs +18 -15
  135. package/templates/_agents/scripts/workflow-board-server.test.mjs +30 -1
  136. package/templates/_agents/workflows/pipeline-closing.md +1 -1
  137. package/templates/_githooks/pre-commit +33 -2
  138. package/templates/_githooks/pre-push +58 -10
  139. package/templates/workflow/README.md +22 -3
  140. package/templates/workflow/incidents/_TEMPLATE.md +4 -0
  141. package/templates/workflow/intents/_TEMPLATE.md +1 -0
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ // trust-mode: AI 自治信任等级管理(2026-09-30 ai-autonomy-trust;同日 hybrid-governance-risk-lanes 加名称双轨)
3
+ // 管理 .agents/trust-mode.json,授权 AI 对 L0/L1 文档进行自治放行(--auto)。
4
+ // 等级语义(名称 ↔ 数值双轨:confirm-doc.mjs 读数值 enabled/level,人机接口名称与数值皆收):
5
+ // Strict (level 0, enabled=false): AI 自治关闭(默认)——confirm-doc --auto 一律拒绝
6
+ // Standard (level 1): AI 可自批 L0/L1 draft -> approved
7
+ // Trusted (level 2): AI 可自批 L0/L1 draft -> approved 及 approved -> done(全闭环)
8
+ // 安全默认值:未开启(enabled: false)时,confirm-doc.mjs --auto 一律拒绝。
9
+ import fs from 'node:fs';
10
+ import path from 'node:path';
11
+
12
+ const CONFIG_PATH = path.join(process.cwd(), '.agents', 'trust-mode.json');
13
+ const NAME_BY_LEVEL = { 0: 'Strict', 1: 'Standard', 2: 'Trusted' };
14
+ const LEVEL_BY_NAME = { Strict: 0, Standard: 1, Trusted: 2 };
15
+
16
+ function readConfig() {
17
+ try {
18
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8'));
19
+ } catch {
20
+ return { enabled: false, level: 0, name: 'Strict', enabledAt: null };
21
+ }
22
+ }
23
+
24
+ function writeConfig(config) {
25
+ fs.mkdirSync(path.dirname(CONFIG_PATH), { recursive: true });
26
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
27
+ }
28
+
29
+ function main() {
30
+ const args = process.argv.slice(2);
31
+ const config = readConfig();
32
+
33
+ if (args.includes('--enable')) {
34
+ const levelIdx = args.indexOf('--level');
35
+ const raw = levelIdx !== -1 ? String(args[levelIdx + 1] ?? '') : '1';
36
+ const level = LEVEL_BY_NAME[raw] ?? (['0', '1', '2'].includes(raw) ? Number(raw) : NaN);
37
+ if (![0, 1, 2].includes(level)) {
38
+ console.error('❌ --level 必须为 Strict|Standard|Trusted(或 0|1|2)');
39
+ process.exit(1);
40
+ }
41
+ if (level === 0) {
42
+ writeConfig({ enabled: false, level: 0, name: 'Strict', enabledAt: null });
43
+ console.log('🔒 AI 自治模式已关闭(Strict)');
44
+ return;
45
+ }
46
+ writeConfig({ enabled: true, level, name: NAME_BY_LEVEL[level], enabledAt: new Date().toISOString() });
47
+ console.log(`✅ AI 自治模式已开启 (${NAME_BY_LEVEL[level]} / Level ${level})`);
48
+ console.log(level === 1 ? ' 授权范围:L0/L1 文档 draft -> approved' : ' 授权范围:L0/L1 文档全闭环 (approved + done)');
49
+ return;
50
+ }
51
+
52
+ if (args.includes('--disable')) {
53
+ writeConfig({ enabled: false, level: 0, name: 'Strict', enabledAt: null });
54
+ console.log('🔒 AI 自治模式已关闭 (Strict)');
55
+ return;
56
+ }
57
+
58
+ if (args.includes('--status')) {
59
+ console.log(JSON.stringify({ name: NAME_BY_LEVEL[config.level] || 'Strict', ...config }, null, 2));
60
+ return;
61
+ }
62
+
63
+ console.log('用法: node .agents/scripts/trust-mode.mjs [--enable --level <Strict|Standard|Trusted|1|2> | --disable | --status]');
64
+ process.exit(config.enabled ? 0 : 1);
65
+ }
66
+
67
+ main();
@@ -10,7 +10,7 @@
10
10
  // node .agents/scripts/wf-journal.mjs add --wf <编排名> --stage <id> --status <pass|fail|blocked>
11
11
  // [--form role|step|gate|human] [--attempt N] [--note <文本>] [--run <runId>] [--new-run]
12
12
  // [--journal <文件>](默认 .agents/cache/orchestration-runs.jsonl,按 cwd)
13
- // node .agents/scripts/wf-journal.mjs status --wf <编排名> [--run <runId>] [--journal <文件>] [--wf-root <目录>]
13
+ // node .agents/scripts/wf-journal.mjs status --wf <编排名> [--run <runId>] [--journal <文件>] [--wf-root <目录>] [--delegations <文件>]
14
14
  // 测试:node templates/_agents/scripts/wf-journal.test.mjs(fixture + 真实仓库场景)
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
@@ -151,8 +151,77 @@ if (isMain) {
151
151
  console.log(` ▶ 就绪(${plan.ready.length}):${plan.ready.map((s) => (s.retry ? `${s.id}(重试就绪×${s.retry})` : s.id)).join('、') || '无'}`);
152
152
  console.log(` ⏸ 待定(${plan.pending.length}):${plan.pending.map((s) => s.id + (s.depBlocked ? '(依赖中止)' : '')).join('、') || '无'}`);
153
153
  console.log(` ⛔ 中止(${plan.blocked.length}):${plan.blocked.map((s) => `${s.id}${s.note ? `(${s.note})` : ''}`).join('、') || '无'}`);
154
+ const wfLines = fs.readFileSync(wfFile, 'utf8').split(/\r?\n/);
155
+ const gaps = unmatchedPasses({
156
+ stages, state, wfLines,
157
+ delegationsText: readDelegationsText(typeof a.delegations === 'string' ? a.delegations : null),
158
+ });
159
+ if (gaps.length) console.log(` 委派台账未记录(${gaps.length}):${gaps.join('、')}`);
154
160
  process.exit(0);
155
161
  }
156
162
  fail(`未知命令:${a.cmd ?? ''}(add | status,用法见文件头注释)`);
157
163
  }
158
- export default { replayRun, planStages, latestRun, readJournalLines };
164
+ function readDelegationsText(file) {
165
+ const p = file || path.join(process.cwd(), 'workflow', 'delegations.md');
166
+ try { return fs.readFileSync(p, 'utf8'); } catch { return ''; }
167
+ }
168
+
169
+ function delegationSectionRows(text) {
170
+ const lines = String(text || '').split(/\r?\n/);
171
+ let inSec = false;
172
+ const rows = [];
173
+ for (const line of lines) {
174
+ if (/^##\s+/.test(line)) {
175
+ if (inSec) break;
176
+ if (/^##\s+委派结果\s*$/.test(line)) inSec = true;
177
+ continue;
178
+ }
179
+ if (!inSec || !/^\s*\|/.test(line)) continue;
180
+ const cells = line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
181
+ if (!cells.length || cells.every((c) => /^[-: ]*$/.test(c))) continue;
182
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(cells[0])) continue;
183
+ rows.push({ date: cells[0], line });
184
+ }
185
+ return rows;
186
+ }
187
+
188
+ function mdBasenames(text) {
189
+ const out = [];
190
+ const re = /[^\s|`]+?\.md/g;
191
+ let m;
192
+ while ((m = re.exec(String(text || '')))) {
193
+ const base = m[0].replace(/\\/g, '/').split('/').pop();
194
+ if (base && !out.includes(base)) out.push(base);
195
+ }
196
+ return out;
197
+ }
198
+
199
+ function idInRow(id, line) {
200
+ const escaped = String(id).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
201
+ return new RegExp(`(^|[^A-Za-z0-9_-])${escaped}([^A-Za-z0-9_-]|$)`).test(line);
202
+ }
203
+
204
+ // unmatchedPasses:末次 pass 且 role 为 implementer 或 independent-reviewer、
205
+ // 委派结果表没有与 pass 的 UTC 日相同、且含阶段 id 或阶段上 .md 文件名的一行。
206
+ export function unmatchedPasses({ stages, state, wfLines, delegationsText }) {
207
+ const rows = delegationSectionRows(delegationsText);
208
+ const gaps = [];
209
+ for (const s of stages) {
210
+ if (s.role !== 'implementer' && s.role !== 'independent-reviewer') continue;
211
+ const st = state[s.id];
212
+ if (st?.status !== 'pass') continue;
213
+ const day = String(st.ts || '').slice(0, 10);
214
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(day)) continue;
215
+ const raw = wfLines[(s._line || 0) - 1] || '';
216
+ const names = [...mdBasenames(s.files), ...mdBasenames(st.note), ...mdBasenames(raw)];
217
+ const hit = rows.some((r) => {
218
+ if (r.date !== day) return false;
219
+ if (idInRow(s.id, r.line)) return true;
220
+ return names.some((name) => r.line.includes(name));
221
+ });
222
+ if (!hit) gaps.push(s.id);
223
+ }
224
+ return gaps;
225
+ }
226
+
227
+ export default { replayRun, planStages, latestRun, readJournalLines, unmatchedPasses };
@@ -146,5 +146,71 @@ const STAGES = parseStages(`| id | after | role | step | task | files | accept |
146
146
  }
147
147
  }
148
148
 
149
+ // ---- 委派台账只读对账(status;不改 add 必填)----
150
+ {
151
+ const missing = spawnSync(process.execPath, [JOURNAL_CLI, 'add', '--stage', 'a', '--status', 'pass'], { encoding: 'utf8' });
152
+ check('add 缺 --wf 仍失败', missing.status !== 0 && /必填/.test(missing.stderr || ''), missing.stderr || missing.stdout);
153
+
154
+ const wfOf = (id, role, files = '—') =>
155
+ '---\nname: demo\ndescription: d\n---\n\n'
156
+ + '| id | after | role | step | task | files | accept | gate | retries |\n'
157
+ + '|----|-------|------|------|------|-------|--------|------|---------|\n'
158
+ + `| ${id} | — | ${role} | — | — | ${files} | — | npm test | |\n`;
159
+ const row = (date, note) =>
160
+ `## 委派结果\n\n| 日期 | 被委派方 | 任务一句话 | 结果 | 备注 |\n|------|----------|------------|------|------|\n| ${date} | x | y | 一次通过 | ${note} |\n`;
161
+ const passLine = (stage, over = {}) => JSON.stringify({
162
+ ts: '2026-09-26T10:00:00Z', run: 'demo@1', wf: 'demo', stage, status: 'pass', attempt: 1, ...over,
163
+ }) + '\n';
164
+ const statusOf = ({ body, journal, delegations }) => {
165
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'wfj-del-'));
166
+ const wfRoot = path.join(tmp, 'workflows');
167
+ fs.mkdirSync(wfRoot);
168
+ fs.writeFileSync(path.join(wfRoot, 'demo.md'), body);
169
+ const j = path.join(tmp, 'j.jsonl');
170
+ if (journal != null) fs.writeFileSync(j, journal);
171
+ const d = path.join(tmp, 'delegations.md');
172
+ const args = ['status', '--wf', 'demo', '--wf-root', wfRoot, '--journal', j, '--delegations', d];
173
+ if (delegations != null) fs.writeFileSync(d, delegations);
174
+ const r = spawnSync(process.execPath, [JOURNAL_CLI, ...args], { encoding: 'utf8' });
175
+ fs.rmSync(tmp, { recursive: true, force: true });
176
+ return r;
177
+ };
178
+ const listed = (r, id) => r.status === 0
179
+ && (r.stdout || '').split(/\r?\n/).some((line) => line.trim() === `委派台账未记录(1):${id}`);
180
+
181
+ let r = statusOf({ body: wfOf('build', 'implementer'), journal: passLine('build'), delegations: null });
182
+ check('委派表为空且 role=implementer 的 pass:列出阶段 id', listed(r, 'build'), r.stdout);
183
+
184
+ r = statusOf({ body: wfOf('review', 'independent-reviewer'), journal: passLine('review'), delegations: '' });
185
+ check('role=independent-reviewer 的 pass:同样列出', listed(r, 'review'), r.stdout);
186
+
187
+ r = statusOf({ body: wfOf('ui', 'ui-verifier'), journal: passLine('ui'), delegations: '' });
188
+ check('role=ui-verifier:不打印委派台账未记录', r.status === 0 && !(r.stdout || '').includes('委派台账未记录'), r.stdout);
189
+
190
+ r = statusOf({ body: wfOf('build', 'implementer'), journal: passLine('build'), delegations: row('2026-09-26', 'build') });
191
+ check('同日行含阶段 id:该阶段不列出', r.status === 0 && !(r.stdout || '').includes('委派台账未记录'), r.stdout);
192
+
193
+ r = statusOf({
194
+ body: wfOf('impl', 'implementer', 'workflow/intents/2026-09-26-foo.md'),
195
+ journal: passLine('impl'),
196
+ delegations: row('2026-09-26', '2026-09-26-foo.md'),
197
+ });
198
+ check('同日行含 files 里的 .md 文件名:该阶段不列出', r.status === 0 && !(r.stdout || '').includes('委派台账未记录'), r.stdout);
199
+
200
+ r = statusOf({ body: wfOf('build', 'implementer'), journal: passLine('build'), delegations: row('2026-09-25', 'build') });
201
+ check('行日期与 pass 的 UTC 日不同:该阶段仍列出', listed(r, 'build'), r.stdout);
202
+
203
+ r = statusOf({ body: wfOf('build', 'implementer'), journal: passLine('build'), delegations: row('2026-09-26', 'rebuild') });
204
+ check('阶段 id 只是更长词的一段:仍列出', listed(r, 'build'), r.stdout);
205
+
206
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'wfj-norun-'));
207
+ const wfRoot = path.join(tmp, 'workflows');
208
+ fs.mkdirSync(wfRoot);
209
+ fs.writeFileSync(path.join(wfRoot, 'demo.md'), wfOf('build', 'implementer'));
210
+ r = spawnSync(process.execPath, [JOURNAL_CLI, 'status', '--wf', 'demo', '--wf-root', wfRoot, '--journal', path.join(tmp, 'none.jsonl')], { encoding: 'utf8' });
211
+ check('无 journal run:不打印委派台账未记录', r.status === 0 && /就绪/.test(r.stdout || '') && !(r.stdout || '').includes('委派台账未记录'), r.stdout);
212
+ fs.rmSync(tmp, { recursive: true, force: true });
213
+ }
214
+
149
215
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
150
216
  process.exit(fail ? 1 : 0);
@@ -53,37 +53,40 @@ function parseAcceptance(text) {
53
53
  }
54
54
  export { parseAcceptance };
55
55
 
56
- // ---- 配对断裂检测:按 slug 聚合同族(同名 intent/incident/plan/spec),异常卡附 alerts ----
57
- // 规则(口径对齐 check-loop.sh 的硬断档 + 状态枚举,看板为预警层、不阻断;枚举读单源 workflow-enums.txt):
56
+ // ---- 配对断裂检测:按完整文件名(含日期)聚合同族,异常卡附 alerts ----
57
+ // 与 check-loop 检查 1 的 basename 同一口径。slug(去掉日期)只给界面分组,不参与配对。
58
+ // 规则(看板为预警层、不阻断;枚举读单源 workflow-enums.txt):
58
59
  // 孤儿 spec / 孤儿 plan(无同名 intent·incident 入口);入口 done 但 plan 未终态(看板启发式);
59
60
  // 入口缺 plan(intent 一律要求;incident 需非 legacy 且级别 L1/L2/L3);L2/L3 入口缺同名 spec;
60
61
  // spec L3 确认三件缺失(确认结果 / 确认时间 / 正文独立复核行);
61
- // 状态不在枚举内或缺失(doc=confirmed 集;incident=all 集)
62
+ // 状态不在全量枚举内或缺失;draft 是合法起点,提示「尚未确认」
62
63
  const PLAN_TERMINAL = ENUMS['doc.status.terminal'];
63
- const STATUS_ENUM = {
64
- intents: ENUMS['doc.status.confirmed'],
65
- specs: ENUMS['doc.status.confirmed'],
66
- plans: ENUMS['doc.status.confirmed'],
64
+ const STATUS_ALL = {
65
+ intents: ENUMS['doc.status.all'],
66
+ specs: ENUMS['doc.status.all'],
67
+ plans: ENUMS['doc.status.all'],
67
68
  incidents: ENUMS['incident.status.all'],
68
69
  };
69
70
  const NO_STATUS = '(未填)';
70
71
  const L3_REVIEW = /^- 独立复核:[ \t]*[^<\s]/m; // 正文独立复核行须有实质内容(对照 check-loop 锚定)
71
72
 
72
- function detectAlerts(cards) {
73
- const bySlug = new Map();
73
+ export function detectAlerts(cards) {
74
+ const byName = new Map();
74
75
  for (const c of cards) {
75
- if (!bySlug.has(c.slug)) bySlug.set(c.slug, []);
76
- bySlug.get(c.slug).push(c);
76
+ if (!byName.has(c.name)) byName.set(c.name, []);
77
+ byName.get(c.name).push(c);
77
78
  }
78
79
  for (const c of cards) {
79
- const fam = bySlug.get(c.slug) || [];
80
+ const fam = byName.get(c.name) || [];
80
81
  const types = new Set(fam.map((x) => x.type));
81
82
  const alerts = [];
82
83
  const isEntry = c.type === 'intents' || c.type === 'incidents';
83
84
  // legacy 复盘件免配对类告警(口径同 check-loop);孤儿 spec / 孤儿 plan 两条仍按 spec·plan 自身判定
84
85
  const legacyIncident = c.type === 'incidents' && c.flow === 'legacy';
85
- if (!STATUS_ENUM[c.type].includes(c.status))
86
- alerts.push(`状态不在枚举内:须为 ${STATUS_ENUM[c.type].join('/')}(现 ${c.status === NO_STATUS ? '缺失' : c.status})`);
86
+ if (!STATUS_ALL[c.type].includes(c.status))
87
+ alerts.push(`状态不在枚举内:须为 ${STATUS_ALL[c.type].join('/')}(现 ${c.status === NO_STATUS ? '缺失' : c.status})`);
88
+ else if (c.status === 'draft')
89
+ alerts.push('尚未确认');
87
90
  if (c.type === 'specs' && !types.has('intents') && !types.has('incidents'))
88
91
  alerts.push('孤儿 spec:无同名 intent/incident 入口');
89
92
  if (c.type === 'plans' && !types.has('intents') && !types.has('incidents'))
@@ -97,7 +100,7 @@ function detectAlerts(cards) {
97
100
  alerts.push('入口缺 plan:无同名 plan');
98
101
  if (isEntry && !legacyIncident && ['L2', 'L3'].includes(c.level) && !types.has('specs'))
99
102
  alerts.push(`${c.level} 入口缺同名 spec`);
100
- if (c.type === 'specs' && c.level === 'L3' && !ENUMS['doc.status.abandoned'].includes(c.status)) {
103
+ if (c.type === 'specs' && c.level === 'L3' && c.status !== 'draft' && !ENUMS['doc.status.abandoned'].includes(c.status)) {
101
104
  if (c.confirm !== 'approved') alerts.push(`L3 确认缺失:确认结果须为 approved(现 ${c.confirm || '缺失'})`);
102
105
  if (!c.confirmTime) alerts.push('L3 确认缺失:须记录确认时间');
103
106
  if (!c.hasReview) alerts.push('L3 复核缺失:须记录新会话独立复核结论');
@@ -12,7 +12,7 @@ import os from 'node:os';
12
12
  import path from 'node:path';
13
13
  import { spawnSync } from 'node:child_process';
14
14
  import { fileURLToPath } from 'node:url';
15
- import { parseLoopHardBlocks, parseAcceptance } from './workflow-board-server.mjs';
15
+ import { parseLoopHardBlocks, parseAcceptance, detectAlerts } from './workflow-board-server.mjs';
16
16
  import { computeFingerprint } from './confirm-doc.mjs';
17
17
 
18
18
  let pass = 0;
@@ -132,5 +132,34 @@ const CHECK_LOOP = path.join(SCRIPT_DIR, 'check-loop.mjs');
132
132
  check('⑤markdown 语法不受剥标签影响', strip(md) === md);
133
133
  }
134
134
 
135
+ // ---- ⑥按完整文件名配对;draft 是合法起点 ----
136
+ {
137
+ const has = (card, text) => (card.alerts || []).some((a) => a.includes(text));
138
+ const card = (over) => ({
139
+ type: 'intents', name: '2026-09-01-topic', slug: 'topic', status: 'approved', level: 'L1',
140
+ ...over,
141
+ });
142
+ const paired = card();
143
+ const plan = card({ type: 'plans', status: 'approved' });
144
+ const other = card({ name: '2026-09-02-topic', status: 'draft' });
145
+ detectAlerts([paired, plan, other]);
146
+ check('⑥同 slug 不同日期:有同名 plan 的 intent 不报缺 plan', !has(paired, '入口缺 plan'));
147
+ check('⑥同 slug 不同日期:没有同名 plan 的 intent 报缺 plan', has(other, '入口缺 plan'), JSON.stringify(other.alerts));
148
+ check('⑥draft 提示尚未确认,不报非法枚举', has(other, '尚未确认') && !has(other, '状态不在枚举内'), JSON.stringify(other.alerts));
149
+ const okIntent = card({ name: '2026-09-03-keep', status: 'approved' });
150
+ const okPlan = card({ type: 'plans', name: '2026-09-03-keep', status: 'approved' });
151
+ detectAlerts([okIntent, okPlan]);
152
+ check('⑥approved 同名配对:无非法枚举、无尚未确认、无缺 plan',
153
+ !has(okIntent, '状态不在枚举内') && !has(okIntent, '尚未确认') && !has(okIntent, '入口缺 plan'),
154
+ JSON.stringify(okIntent.alerts));
155
+ const doneIntent = card({ name: '2026-09-04-done', status: 'done' });
156
+ const draftPlan = card({ type: 'plans', name: '2026-09-04-done', status: 'draft' });
157
+ detectAlerts([doneIntent, draftPlan]);
158
+ check('⑥入口 done 而同名 plan 仍是 draft:plan 报未终态', has(draftPlan, '未终态'), JSON.stringify(draftPlan.alerts));
159
+ const bogus = card({ name: '2026-09-05-bad', status: 'nope' });
160
+ detectAlerts([bogus]);
161
+ check('⑥未知状态仍报不在枚举内', has(bogus, '状态不在枚举内'), JSON.stringify(bogus.alerts));
162
+ }
163
+
135
164
  console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
136
165
  process.exit(fail ? 1 : 0);
@@ -9,7 +9,7 @@ concurrency: 2
9
9
 
10
10
  | id | after | role | step | task / params | files(授权) | accept(验收判据) | gate | human | retries |
11
11
  |----|-------|------|------|---------------|--------------|-------------------|------|-------|---------|
12
- | author | — | implementer | — | 起草 intent + plan(双写 fill-{intent,plan}.mjs 生成草稿 + AI 填实),状态 draft | workflow/intents/<date>-<topic>.md, workflow/plans/<date>-<topic>.md | intent frontmatter 5 字段齐 + 状态 draft + 验收标准 ≥ 5 条;plan L1 极简 2 节齐 | | | 1 |
12
+ | author | — | implementer | — | 起草 intent + plan(双写 fill-{intent,plan}.mjs 生成草稿 + AI 填实),状态 draft | workflow/intents/<date>-<topic>.md, workflow/plans/<date>-<topic>.md | intent frontmatter 6 字段齐(含 risk_level)+ 状态 draft + 验收标准 ≥ 5 条;plan L1 极简 2 节齐 | | | 1 |
13
13
  | gate-doc | author | — | — | — | — | — | node .agents/scripts/fill-intent.test.mjs && node .agents/scripts/fill-plan.test.mjs | | |
14
14
  | confirm | gate-doc | — | — | plan 草稿全文过目,用户对话内一句"可以" → plan 状态 draft → approved + 回填「确认与复核」节 | — | plan 状态 approved + 「确认结果:approved(YYYY-MM-DD 用户对话内确认)」 | | 用户确认(plan 确认门) | |
15
15
  | plan-commit | confirm | implementer | — | 提交 plan 立项 commit(docs(workflow)) | workflow/intents/, workflow/plans/, workflow/INDEX.md | pre-commit 钩子全过;commit message 含 plan 改动面摘要 | | | 0 |
@@ -1,6 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Git 原生 pre-commit hook(经 core.hooksPath 挂载,git 执行、不依赖 agent 客户端)— agentic-flow-kit 通用编排
3
- # 通用门禁序:闭环配对(增量) → wiki 台账(增量) → 常驻面体积预算(增量) → 双源一致性(增量) → managed 台账快检(包源恒跑) → 项目门禁(local-pre-commit) → 敏感信息扫描+条件构建(commit-check.cjs)
3
+ # 通用门禁序:闭环配对(增量) → 泳道完整性(增量) → wiki 台账(增量) → 常驻面体积预算(增量) → 双源一致性(增量) → managed 台账快检(包源恒跑) → 项目门禁(local-pre-commit) → 敏感信息扫描+条件构建(commit-check.cjs)
4
4
  # 项目专属门禁(架构红线/业务守卫等)放 .agents/hooks/local-pre-commit——存在即在本钩子内执行,返回非零即阻断提交
5
5
  # 合并提交场景由 .githooks/pre-merge-commit 原样转发到本脚本(合并时 git 调的是 pre-merge-commit,
6
6
  # 不是 pre-commit)——门禁清单只此一份,避免两处分叉
@@ -19,7 +19,38 @@ run_gate() {
19
19
  }
20
20
 
21
21
  # 闭环配对门禁(增量版,check-pairing-incremental.sh:本次暂存的 intents/incidents 入口须已有同名 plan)
22
- run_gate "闭环配对(增量)" .agents/hooks/check-pairing-incremental.sh
22
+ # experiment/* 泳道分支(2026-09-30 hybrid-governance-explore-hardening):降级为 advisory——快速 PoC
23
+ # 迭代不因闭环文档断档被拦(断档报告照出),转正合入 main 由 pre-push 加固门(check-loop --hardening)兜底。
24
+ # 仅降级闭环文档面:双源 / 台账 / 常驻面 / 敏感信息扫描等门禁不豁免。分支不可判(detached HEAD 等)走严格模式。
25
+ pairing_gate_mode=strict
26
+ case "$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" in
27
+ experiment/*) pairing_gate_mode=advisory ;;
28
+ esac
29
+ if [ "$pairing_gate_mode" = "advisory" ]; then
30
+ echo "pre-commit: experiment/* 泳道分支——闭环配对门禁降级为 advisory(报告不阻断;转正入 main 须过加固门)" >&2
31
+ if command -v sh >/dev/null 2>&1; then
32
+ sh .agents/hooks/check-pairing-incremental.sh || echo "pre-commit: (advisory)闭环配对断档已在上方列出——experiment 泳道不阻断提交" >&2
33
+ else
34
+ bash .agents/hooks/check-pairing-incremental.sh || echo "pre-commit: (advisory)闭环配对断档已在上方列出——experiment 泳道不阻断提交" >&2
35
+ fi
36
+ else
37
+ run_gate "闭环配对(增量)" .agents/hooks/check-pairing-incremental.sh
38
+ fi
39
+
40
+ # 泳道完整性(增量,check-lane-surface.mjs——2026-10-01 drift-hardening):
41
+ # 检查 A「触达面判低」——暂存命中项目 L2 触达面(.agents/lane-surfaces.txt,ERE 逐行;缺失/空=跳过,
42
+ # 装户无感)且活跃入口(draft/approved / open/fixed)全为 L0/L1 → 拦;有活跃 L2/L3 → 放行;无活跃入口 →
43
+ # 仅 advisory(docs/bootstrap 豁免类)。experiment/* 分支降级 advisory(转正由 pre-push 加固门兜底)。
44
+ # 检查 B「探索标记缺失」——experiment/* 分支暂存 intent 缺「阶段: exploring」→ 拦(标记零成本,
45
+ # advisory 免纪律不免标记——标记是加固门覆盖率的前提)。
46
+ # 旧装副本无脚本自然跳过;node 缺失时本门跳过(其后 commit-check 节点检查仍会拦提交,无空洞)。
47
+ # 注意:不经 run_gate(其实现为 sh "$@",会把 node 当脚本解释——直接 node 调用,模式同双源门禁块)。
48
+ if [ -f .agents/scripts/check-lane-surface.mjs ] && command -v node >/dev/null 2>&1; then
49
+ if ! node .agents/scripts/check-lane-surface.mjs; then
50
+ echo "pre-commit: 泳道完整性(增量)未通过,提交已阻止" >&2
51
+ exit 1
52
+ fi
53
+ fi
23
54
 
24
55
  # wiki 台账门禁(增量,check-wiki-ledger.sh:暂存区触及 wiki/ 时跑 verify-wiki-consistency 三方一致性;
25
56
  # 无 wiki/ 变更即跳过)
@@ -3,30 +3,78 @@
3
3
  # 跑 .agents/scripts/check-loop.sh:扫 workflow/ 闭环骨架断档
4
4
  # - 硬阻断(exit 1)= 配对断裂 / 回路断档 → push 立即阻断
5
5
  # - 警告(advisory)= 状态未确认 / 模板占位残留 / 引用漂移 等卫生项 → 仅打印提示
6
+ # stdin 有行时,每个非删除的本地 sha 用 --rev 扫那棵树。没有行时(手跑钩子)仍无参数扫工作区。
6
7
  #
7
- # 机器门:本地钩子(唯一本地机器门)——CI 配置为本地未入库件(.gitignore 排除),不构成跨环境保证。
8
+ # experiment/* 泳道(2026-09-30 hybrid-governance-explore-hardening):**目标分支**为 experiment/* 时,
9
+ # 闭环扫描降级 advisory(报告不阻断)——快速 PoC 推送不因闭环断档被拦;转正(目标为 main)由加固门兜底。
10
+ # 加固门(同日;2026-10-01 v09-review-defects 修正判定键):推送**目标**为 refs/heads/main 时以
11
+ # --hardening 运行(无论来源分支——experiment/* 直推 main 同样覆盖),exploring 任务须先收口
12
+ # (intent approved/done + 同名 plan 在场 + L2/L3 另须同名 spec;判据见 check-loop.mjs 加固门段),
13
+ # hard 失败即阻断 push,防「未文档化的探索件」进 main。
14
+ #
15
+ # 机器门:本地钩子(唯一本地机器门)——服务端兜底(如有)= 项目自有 CI;本 kit 不附带 CI 配置,裸装无跨环境保证。
16
+ # 本仓:.github/workflows/ci.yml 于 push main / 全部 PR 复跑 check-loop / doctor / source-sync-check --gate / workflows-check 四道门。
8
17
  # 不要用 `git push --no-verify` 绕开——发现 hard-block 应先修 workflow 文档再推。
9
18
 
10
19
  cd "$(git rev-parse --show-toplevel)" || exit 1
11
20
 
12
- # 读 stdin(pre-push 协议要求消费 stdin:本地 ref + 远端 ref + sha x2 每行)
13
- while read -r _ _ _ _; do :; done
14
-
15
21
  SCRIPT=".agents/scripts/check-loop.sh"
16
22
  if [ ! -f "$SCRIPT" ]; then
17
23
  echo "pre-push: 未找到 $SCRIPT,跳过闭环扫描" >&2
18
24
  exit 0
19
25
  fi
20
26
 
21
- # Windows 下 git 自带 bash 能直接执行 sh 脚本;其它环境 sh 兼容
27
+ run_loop() {
28
+ if command -v sh >/dev/null 2>&1; then
29
+ sh "$SCRIPT" "$@"
30
+ else
31
+ bash "$SCRIPT" "$@"
32
+ fi
33
+ }
34
+
35
+ saw=0
36
+ rc=0
37
+ while read -r local_ref local_sha remote_ref _remote_sha; do
38
+ saw=1
39
+ case "$local_sha" in
40
+ *[!0]*) ;;
41
+ *) continue ;;
42
+ esac
43
+ # 按目标分支三态分派(2026-10-01 v09-review-defects:判定键从 local_ref 改为 remote_ref——
44
+ # 修复 experiment/*→main 直推被 advisory continue 吞掉加固门的漏口):
45
+ # main → 加固门(必带 --hardening)
46
+ # experiment/* → advisory(报告不阻断)
47
+ # 其他 → 全量扫描
48
+ case "$remote_ref" in
49
+ refs/heads/main|main)
50
+ run_loop --rev "$local_sha" --hardening
51
+ ;;
52
+ refs/heads/experiment/*|experiment/*)
53
+ echo "pre-push: experiment/* 目标分支($remote_ref)——闭环扫描降级 advisory,不阻断" >&2
22
54
  if command -v sh >/dev/null 2>&1; then
23
- sh "$SCRIPT"
55
+ sh "$SCRIPT" --rev "$local_sha" >&2 || echo "pre-push: (advisory)闭环断档已在上方列出——experiment 泳道可推送,转正(推 main)须过加固门" >&2
24
56
  else
25
- bash "$SCRIPT"
57
+ bash "$SCRIPT" --rev "$local_sha" >&2 || echo "pre-push: (advisory)闭环断档已在上方列出——experiment 泳道可推送,转正(推 main)须过加固门" >&2
58
+ fi
59
+ ;;
60
+ *)
61
+ run_loop --rev "$local_sha"
62
+ ;;
63
+ esac
64
+ one=$?
65
+ if [ "$one" -ne 0 ] && [ "$rc" -eq 0 ]; then
66
+ rc=$one
26
67
  fi
27
- rc=$?
28
- if [ $rc -ne 0 ]; then
68
+ done
69
+
70
+ if [ "$saw" -eq 0 ]; then
71
+ run_loop
72
+ rc=$?
73
+ fi
74
+
75
+ if [ "$rc" -ne 0 ]; then
29
76
  echo "pre-push: 闭环骨架断档(hard-block)未通过,push 已阻断。修 workflow 文档后重推——不要用 --no-verify 绕过(本钩子是唯一机器门,无服务端兜底,见头部说明)" >&2
30
- exit $rc
77
+ echo "pre-push: 若为「加固未过」= experiment 泳道件转正前须收口:intent approved/done + 同名 plan(L2/L3 另须 spec)且状态非 draft" >&2
78
+ exit "$rc"
31
79
  fi
32
80
  exit 0
@@ -15,7 +15,7 @@
15
15
 
16
16
  **命名一律英文 kebab-case**(如 `2026-09-07-<主题>.md`),**禁中文文件名**——check-loop 对非 ASCII 文件名给 warning。文档内容(标题/正文)不受此限。
17
17
 
18
- **文档协议(frontmatter 受限子集)**:四类文档头部一律为 YAML frontmatter(每行 `键: 值`,机器字段唯一来源)——`状态`(intent/spec/plan:draft/approved/done/superseded/cancelled;incident:open/fixed/closed,**严格枚举,单源 `.agents/workflow-enums.txt`**——check-loop / 看板 / 检索 / fill-* 一律读它,改枚举改那边;附注写 `备注:` 键)、`级别`(L0-L3,同单源)、`日期`/`发现`(YYYY-MM-DD)、`模块`(**新建文档必填**,取值见 `.agents/workflow-modules.txt` 词表,单值取主导模块;存量不回填,AI 触碰时顺手补)、L3 spec 的 `确认结果`/`确认时间`、回填件 `流程: legacy`。**`状态` 迁移须经 `approved`**(确认环节的机器可见态,`done` 只在关单出现):新建的 spec/plan 若已 done 而 git 历史中从未出现行首 `状态: approved`,check-loop 报「确认态缺失」warning。check-loop 只扫 frontmatter 取机器字段(`fm_get` 字段断言),正文不再写「状态:/级别:」行;叙述性字段(独立复核/复盘三件套/验收勾验)仍留正文按节锚定。
18
+ **文档协议(frontmatter 受限子集)**:四类文档头部一律为 YAML frontmatter(每行 `键: 值`,机器字段唯一来源)——`状态`(intent/spec/plan:draft/approved/done/superseded/cancelled;incident:open/fixed/closed,**严格枚举,单源 `.agents/workflow-enums.txt`**——check-loop / 看板 / 检索 / fill-* 一律读它,改枚举改那边;附注写 `备注:` 键)、`级别`(L0-L3,同单源)、intent 的 `risk_level`(=级别,混合治理风险泳道选道字段,fill-intent 落)、`日期`/`发现`(YYYY-MM-DD)、`模块`(**新建文档必填**,取值见 `.agents/workflow-modules.txt` 词表,单值取主导模块;存量不回填,AI 触碰时顺手补)、L3 spec 的 `确认结果`/`确认时间`、回填件 `流程: legacy`。**`状态` 迁移须经 `approved`**(确认环节的机器可见态,`done` 只在关单出现):新建的 spec/plan 若已 done 而 git 历史中从未出现行首 `状态: approved`,check-loop 报「确认态缺失」warning。check-loop 只扫 frontmatter 取机器字段(`fm_get` 字段断言),正文不再写「状态:/级别:」行;叙述性字段(独立复核/复盘三件套/验收勾验)仍留正文按节锚定。
19
19
 
20
20
  各子目录内 `_TEMPLATE.md` 为起步模板,复制后填写,不直接改动模板本身。
21
21
 
@@ -32,19 +32,38 @@
32
32
  - **量化**:委派/自做结果记 `delegations.md`,聚合跑 `node .agents/scripts/agg-delegations.cjs`(**并发扩容门槛见该文件——数字达标前不扩并发**);语料与常驻面体积的**月度快照**跑 `node .agents/scripts/gen-workflow-metrics.mjs`(每月一行落 `metrics.md`,同月重跑即更新;明细看 stdout)
33
33
  - **追溯**:git 即审计——文档随代码同 commit、上线打 `release/<日期>` tag,`git log` 全链路可查;多人评审暂缓,git 历史即评审记录
34
34
  - **级别**:L0 例行 | L1 实现级(页面 / 交互 / 样式等,未命中 L2/L3)| L2 规则 / 契约(编码权威 / 共享契约 / 既有接口语义 / 全局横切口径,触达面闭集见 `.agents/commands/new-task.md` §级别判断)| L3 数据与运行时结构(schema / 迁移 SQL / DI 链 / 认证与中间件管线);混合改动就高不就低
35
+ - **风险泳道(混合治理)**:级别即风险级(L0 最低 → L3 最高),按级选道——**L0 / L1 协作道(敏捷优先)**:L0 豁免 intent 直接 commit;L1 已确认 intent + 极简 plan 即动工,独立复核不设同步前置,审计异步补(check-loop、事后 review 兜底);**L2 / L3 防御道(严谨优先)**:同步确认门——spec 确认通过方可起草 plan、plan 确认后方可动手(闭环规则 1-2),L3 加新会话独立复核,未确认不放行。intent frontmatter 以 `risk_level`(=级别)落机器可读标记;批量代录与 AI 自治(confirm-doc `--batch` / `--auto`,trust-mode Standard|Trusted)均限协作道 L0/L1,协作道勾触达红线会被 check-loop 判「红线判低」hard 拦截
36
+ - **探索泳道(experiment/ 前缀分支,Explore-to-Harden,2026-09-30 hybrid-governance-explore-hardening)**:experiment/ 前缀分支上的 PoC 任务在 intent frontmatter 加 `阶段: exploring` 标记——提交/推送门降级 advisory(pre-commit 闭环配对、pre-push 闭环扫描报告不阻断);L0/L1 的 draft intent 允许 spec/plan 起草与确认先于入口确认(stage-gates 起草门泳道豁免,「先起草后确认」)。**转正(合入 main)必须过加固门**:pre-push 对目标 refs/heads/main 以 `check-loop --hardening` 运行——exploring 任务的 intent 须为 approved/done、同名 plan 在场且收口(L2/L3 另须同名 spec),未收口 hard 阻断;探索作废走显式放弃态 superseded/cancelled(出列不加固)。诚实边界:分支名不进 commit——experiment/* 分支上暂存 intent 缺「阶段: exploring」由 pre-commit「泳道完整性」门禁拦截(2026-10-01 drift-hardening),残余暴露面仅剩绕过本地钩子的提交(`--no-verify` 禁令纪律覆盖)
37
+ - **Trusted 自动泳道(2026-09-30 hybrid-governance-explore-hardening)**:trust-mode.json = Trusted(level 2)时,confirm-doc 的非 TTY 单份调用对 L0/L1 文档**免旗标**(无需 `--delegated` 原话 / `--auto`)自动按 AI 自治放行(台账 source=ai-auto-trust-L2);L2/L3 与 incidents 仍须人工,Standard / Strict 不隐式(fail-closed)
35
38
 
36
39
  ## 执行模型
37
40
 
38
41
  - 默认宿主主智能体负责阶段路由、用户确认、关键判断和最终验收,不注册为额外角色。
39
42
  - 阶段命令仅在边界明确时委派 `.agents/roles/implementer.md`、`.agents/roles/independent-reviewer.md`、`.agents/roles/ui-verifier.md`。
40
- - 各 agent 宿主(OpenCode / Trae / ZCode 等)通过其适配目录(`.opencode/agents/`、`.trae/agents/`、`.zcode/agents/` 等)下的薄 Adapter 注册角色;角色行为只维护在 `.agents/roles/`。
43
+ - 各 agent 宿主通过其适配目录下的薄 Adapter 注册角色(`.claude/`、`.cursor/`、`.codex/`、`.opencode/`、`.trae/`、`.zcode/`、`.omp/`)。角色行为只维护在 `.agents/roles/`。Codex 自动读取的是仓库根 `AGENTS.md` 与 `.codex/skills/`;`.codex/commands` 与 `.codex/agents` 是同形薄转发,供显式引用。Cursor 自动加载 `.cursor/commands/`。Claude Code 自动加载 `.claude/commands/` 与 `.claude/agents/`。
41
44
  - 宿主不支持子智能体时按命令 frontmatter 的 `fallback` 执行;L2 / L3 独立复核不得回退为原主智能体自查。
42
45
  - 子智能体不得跨越用户确认门,也不得自行提交、合入或上线;所有结果由主智能体复核后交用户决策。
43
46
 
47
+ ## 硬规则
48
+
49
+ `kit.json` 的 `audit: false`(`init` 新装的默认值)时,check-loop 只阻断下面与文档闭环直接相关的项。`audit` 字段缺省时保持全量检查,已装仓库升级不会突然丢掉卫生警告。本字段设为 `true` 时卫生检查照常打印。豁免日期的唯一表是 `.agents/scripts/policy.mjs` 的 `policyVersion`(`kit.json` 同名字段;未知版本回退到 1)。
50
+
51
+ 1. **同名配对**:L1 有 plan;L2/L3 有 spec 与 plan。incident 的回路断档同样阻断。
52
+ 2. **验收勾验**:新建 intent 关到 done 时,验收标准未勾则阻断。
53
+ 3. **确认留痕**:approved / done / fixed / closed 须经 `confirm-doc.mjs`,指纹与台账一致。`--delegated` 原话仅限用户对话内明确确认的当次措辞——官方脚本/门禁不得自动代录或编造 quote(2026-10-01 v09-review-defects:solidify-task 曾自动伪造原话,已修复并纳入回归)。
54
+ 4. **敏感信息**:pre-commit 的 commit-check 扫描密钥。
55
+ 5. **双源与台账**:doctor 核对 managed / owned 的 sha;引擎改动改包源 `templates/` 再 `sync`。
56
+
57
+ 其余检查(占位符、引用、模块字段、常驻面预算、量化签名、阶段索引、适配器断线)只在 `audit` 不是 `false` 时出警告。
58
+
59
+ **发版草稿**:最近一次改动 `package.json` 的 `version` 的那次提交里,当时已经是 draft 或 approved 的 intent、spec、plan,若被扫描的树上仍是 draft 或 approved,则阻断。当时已是 draft 的不看版本锚。当时已是 approved 的,只在发版版本大于 `policy.mjs` 的 `check17UnclosedAfter` 时阻断;版本 1 没有该键。不带 `--rev` 时现在的状态读工作区,带 `--rev` 时读被推送的那棵树。那次提交之后新建的文件不在范围内。状态为 open 的 incident 不在此列。
60
+
61
+ **委派台账**:L2 或 L3 的 intent、spec、plan 状态为 done,或同级别 incident 状态为 fixed 或 closed 时,若 `workflow/delegations.md` 的「委派结果」表与「自做任务结果」表中均没有日期不早于该文档日期、且含该文件名(带 / 不带 `.md` 均匹配)的一行,check-loop 打出警告。incident 的日期取 frontmatter「发现」。`audit: false` 时这条警告与其他卫生警告一起被吞掉。退出码仍由阻断项决定。
62
+
44
63
  ## 闭环规则
45
64
 
46
65
  1. L1 以上新需求始于已确认的 intent;L1 以上修复始于已确认的 incident 草稿(作为 intent 等价入口)
47
- 2. L2 / L3 变更必须先有与入口文档同名的 spec 确认通过(L3 加新会话独立复核),方可起草 plan;L1 用极简 plan——改动面 + 验证方式两节起步,多文件多步骤再加任务拆解/执行顺序
66
+ 2. L2 / L3 变更必须先有与入口文档同名的 spec 确认通过(L3 加新会话独立复核),方可起草 plan;L1 用极简 plan——改动面 + 验证方式两节起步,多文件多步骤再加任务拆解/执行顺序。**逐阶段前置已机器强制**(2026-09-30 stage-gate-machine):fill-spec / fill-plan / confirm-doc 校验入口与 spec 前置未过即拒;顺序审计 = check-loop 检查 19
48
67
  3. 实现产物必须通过静态门(构建 + 测试 + 项目门禁)
49
68
  4. 线上 / 实测缺陷回落到 `incidents/`,复盘三件套(新 intent、防复发验证、规范条目)缺一不可;**根因属「门禁缺位 / 规范未落地 / 系统性问题」时,即使结构性修复已完成也必须立新 intent** 追踪系统性改进,禁止以「修复已完成」为由绕过 intent 回路
50
69
  5. 关单在 test:逐条勾验入口文档「验收标准」并补证据后 intent → done(不依赖是否上 prod)。done 仍有未勾项会被 check-loop 拦(新建 intent hard-block)。上 prod 另走 deploy(tag / 回滚 / 观察)
@@ -10,6 +10,10 @@
10
10
 
11
11
  ## 时间线
12
12
  - <发现 → 定位 → 止血 → 恢复,带时刻>
13
+ - 用户确认:草稿过目通过(<日期>)
14
+ <!-- 上行是**L2/L3 起草 spec 的机器前置**(fill-spec / confirm-doc 校验):须为「时间线」小节内的行首条目;
15
+ 接受形态 `- 用户确认:…` / `- **用户确认**:…` / `- <日期> 用户确认:…`(词前词后加粗、日期前缀均可);
16
+ 正文叙述句(如「用户确认了方案」)不算留痕。形态清单写在本注释内,避免样例行自含描述文字 -->
13
17
 
14
18
  ## 影响面
15
19
  - <页面 / 接口 / 数据范围>
@@ -8,6 +8,7 @@
8
8
  # INTENT — <主题>
9
9
  <!-- 复制本模板为 YYYY-MM-DD-<主题>.md 后填写;plans/ 下同名文件与本文件配对 -->
10
10
  <!-- frontmatter 受限子集(2026-09-13):每行 `键: 值`;状态∈draft/approved/done/superseded/cancelled(严格枚举,附注写备注键);级别∈L0/L1/L2/L3;check-loop 只扫 frontmatter 取机器字段,正文不再写状态/级别行 -->
11
+ <!-- 探索泳道(可选,2026-09-30 hybrid-governance-explore-hardening):experiment/ 前缀分支上的 PoC 任务在 frontmatter 加「阶段: exploring」——提交/推送门降级 advisory,spec/plan 可先于本入口确认起草(限 L0/L1);转正合入 main 前须收口(本档 approved/done + 同名 plan/spec 在场,check-loop --hardening 加固门),或显式放弃(superseded/cancelled) -->
11
12
 
12
13
  ## 背景与问题
13
14
  <为什么做;写明需求来源(工单 / 沟通记录 / 待办事项)>