agentic-flow-kit 0.4.0 → 0.6.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 (117) hide show
  1. package/README.md +3 -0
  2. package/modules/gates/dotnet-ca/check-architecture.sh +23 -5
  3. package/modules/hosts/omp/agents/implementer.md +47 -6
  4. package/modules/hosts/omp/agents/independent-reviewer.md +44 -5
  5. package/modules/hosts/omp/agents/ui-verifier.md +43 -6
  6. package/modules/hosts/opencode/agents/implementer.md +47 -1
  7. package/modules/hosts/opencode/agents/independent-reviewer.md +44 -1
  8. package/modules/hosts/opencode/agents/ui-verifier.md +43 -1
  9. package/modules/hosts/opencode/commands/build.md +74 -7
  10. package/modules/hosts/opencode/commands/deploy.md +51 -6
  11. package/modules/hosts/opencode/commands/design.md +36 -6
  12. package/modules/hosts/opencode/commands/gate-checklist.md +77 -0
  13. package/modules/hosts/opencode/commands/maintain.md +70 -6
  14. package/modules/hosts/opencode/commands/new-task.md +59 -6
  15. package/modules/hosts/opencode/commands/plan.md +35 -6
  16. package/modules/hosts/opencode/commands/review.md +41 -6
  17. package/modules/hosts/opencode/commands/source-sync-check.md +89 -0
  18. package/modules/hosts/opencode/commands/sync-hosts.md +66 -0
  19. package/modules/hosts/opencode/commands/test.md +58 -8
  20. package/modules/hosts/trae/agents/implementer.md +46 -2
  21. package/modules/hosts/trae/agents/independent-reviewer.md +44 -1
  22. package/modules/hosts/trae/agents/ui-verifier.md +41 -3
  23. package/modules/hosts/trae/commands/wf-build.md +74 -7
  24. package/modules/hosts/trae/commands/wf-deploy.md +51 -6
  25. package/modules/hosts/trae/commands/wf-design.md +36 -6
  26. package/modules/hosts/trae/commands/wf-gate-checklist.md +78 -0
  27. package/modules/hosts/trae/commands/wf-maintain.md +70 -6
  28. package/modules/hosts/trae/commands/wf-new-task.md +59 -6
  29. package/modules/hosts/trae/commands/wf-plan.md +35 -6
  30. package/modules/hosts/trae/commands/wf-review.md +41 -6
  31. package/modules/hosts/trae/commands/wf-source-sync-check.md +90 -0
  32. package/modules/hosts/trae/commands/wf-sync-hosts.md +67 -0
  33. package/modules/hosts/trae/commands/wf-test.md +58 -7
  34. package/modules/hosts/trae/hooks/post-edit-check.cjs +3 -2
  35. package/modules/hosts/trae/hooks/pre-shell-check.cjs +40 -9
  36. package/modules/hosts/trae/hooks.json +1 -1
  37. package/modules/hosts/zcode/agents/implementer.md +47 -6
  38. package/modules/hosts/zcode/agents/independent-reviewer.md +44 -5
  39. package/modules/hosts/zcode/agents/ui-verifier.md +43 -5
  40. package/package.json +6 -3
  41. package/src/add-gate.mjs +5 -3
  42. package/src/cli.mjs +15 -2
  43. package/src/doctor.mjs +245 -15
  44. package/src/gate-dotnet-ca.test.mjs +124 -0
  45. package/src/init.mjs +79 -17
  46. package/src/init.test.mjs +84 -2
  47. package/src/pack.test.mjs +24 -0
  48. package/src/render.mjs +64 -4
  49. package/src/run-tests.mjs +7 -11
  50. package/src/sync-hosts.mjs +194 -0
  51. package/src/sync-hosts.test.mjs +170 -0
  52. package/src/sync.mjs +65 -20
  53. package/src/sync.test.mjs +52 -0
  54. package/src/trae-hooks.test.mjs +115 -0
  55. package/templates/AGENTS.md +2 -1
  56. package/templates/_agents/board/index.html +32 -3
  57. package/templates/_agents/commands/build.md +22 -1
  58. package/templates/_agents/commands/design.md +2 -0
  59. package/templates/_agents/commands/gate-checklist.md +91 -0
  60. package/templates/_agents/commands/maintain.md +2 -3
  61. package/templates/_agents/commands/plan.md +4 -1
  62. package/templates/_agents/commands/review.md +1 -0
  63. package/templates/_agents/commands/source-sync-check.md +103 -0
  64. package/templates/_agents/commands/sync-hosts.md +79 -0
  65. package/templates/_agents/commands/test.md +4 -2
  66. package/templates/_agents/hooks/check-pairing-incremental.sh +20 -7
  67. package/templates/_agents/notes/runtime-env.md +1 -1
  68. package/templates/_agents/roles/implementer.md +2 -0
  69. package/templates/_agents/roles/independent-reviewer.md +2 -0
  70. package/templates/_agents/roles/ui-verifier.md +2 -0
  71. package/templates/_agents/rule-budgets.txt +1 -1
  72. package/templates/_agents/scripts/agg-delegations.cjs +39 -30
  73. package/templates/_agents/scripts/agg-delegations.test.mjs +32 -0
  74. package/templates/_agents/scripts/check-loop.mjs +763 -0
  75. package/templates/_agents/scripts/check-loop.sh +5 -808
  76. package/templates/_agents/scripts/check-loop.test.mjs +1102 -0
  77. package/templates/_agents/scripts/confirm-doc.mjs +228 -0
  78. package/templates/_agents/scripts/confirm-doc.test.mjs +321 -0
  79. package/templates/_agents/scripts/doctor.test.mjs +371 -0
  80. package/templates/_agents/scripts/ensure-board.mjs +30 -3
  81. package/templates/_agents/scripts/fill-intent.mjs +59 -0
  82. package/templates/_agents/scripts/fill-intent.test.mjs +73 -0
  83. package/templates/_agents/scripts/fill-plan.mjs +60 -0
  84. package/templates/_agents/scripts/fill-plan.test.mjs +67 -0
  85. package/templates/_agents/scripts/fill-spec.mjs +52 -0
  86. package/templates/_agents/scripts/fill-spec.test.mjs +61 -0
  87. package/templates/_agents/scripts/gate-checklist.mjs +194 -0
  88. package/templates/_agents/scripts/gate-checklist.test.mjs +204 -0
  89. package/templates/_agents/scripts/gen-wiki-board.mjs +38 -24
  90. package/templates/_agents/scripts/gen-wiki-board.test.mjs +18 -0
  91. package/templates/_agents/scripts/gen-workflow-index.mjs +4 -2
  92. package/templates/_agents/scripts/gen-workflow-metrics.mjs +12 -3
  93. package/templates/_agents/scripts/kb-search.mjs +25 -9
  94. package/templates/_agents/scripts/kb-search.test.mjs +38 -8
  95. package/templates/_agents/scripts/source-sync-check.mjs +145 -0
  96. package/templates/_agents/scripts/source-sync-check.test.mjs +244 -0
  97. package/templates/_agents/scripts/verify-wiki-consistency.mjs +46 -23
  98. package/templates/_agents/scripts/verify.mjs +8 -10
  99. package/templates/_agents/scripts/verify.test.mjs +13 -1
  100. package/templates/_agents/scripts/wf-journal.mjs +158 -0
  101. package/templates/_agents/scripts/wf-journal.test.mjs +150 -0
  102. package/templates/_agents/scripts/workflow-board-server.mjs +73 -17
  103. package/templates/_agents/scripts/workflow-board-server.test.mjs +136 -0
  104. package/templates/_agents/scripts/workflow-enums.mjs +70 -0
  105. package/templates/_agents/scripts/workflow-enums.test.mjs +110 -0
  106. package/templates/_agents/scripts/workflows-check.mjs +260 -0
  107. package/templates/_agents/scripts/workflows-check.test.mjs +267 -0
  108. package/templates/_agents/workflow-enums.txt +19 -0
  109. package/templates/_agents/workflows/_TEMPLATE.md +75 -0
  110. package/templates/_agents/workflows/pipeline-closing.md +29 -0
  111. package/templates/_agents/workflows/source-sync-repair.md +30 -0
  112. package/templates/_agents/workflows/steps//347/244/272/344/276/213-/351/203/250/347/275/262/351/252/214/350/257/201.md +10 -0
  113. package/templates/_agents/workflows//347/244/272/344/276/213-/345/271/266/350/241/214/345/256/236/347/216/260/350/257/204/345/256/241.md +16 -0
  114. package/templates/_githooks/pre-commit +16 -1
  115. package/templates/workflow/README.md +1 -1
  116. package/templates/_agents/cache/kb-index.json +0 -1
  117. package/templates/_agents/scripts/check-loop.test.sh +0 -1042
@@ -0,0 +1,260 @@
1
+ // flow-kit workflows-check:编排脚本机器 lint(2026-09-25 workflows-linter,审查改进方向 1)
2
+ // 目的:`.agents/workflows/_TEMPLATE.md`「解析校验先行」此前是纯 prose——role ∈ .agents/roles/、
3
+ // step ∈ steps/、after 引用存在且无环,全靠宿主 AI 自觉;而编排脚本的书写者与执行者是同一
4
+ // 宿主 AI,缺外部裁决者。本工具把该纪律变成机器门:解析 .agents/workflows/*.md 的 stages 表
5
+ // 逐行校验,error 级 exit 1(可挂 doctor / CI),告警级 advisory 不阻断。
6
+ //
7
+ // 检查项(error → exit 1):
8
+ // E1 缺 frontmatter(name/description/concurrency 口径见 _TEMPLATE.md)
9
+ // E2 concurrency 非正整数(缺省 = 2)
10
+ // E3 缺 stages 表(表头须含 id/after/gate 列)
11
+ // E4 stage 缺 id 或 id 重复
12
+ // E5 after 引用不存在的 stage
13
+ // E6 after 依赖成环
14
+ // E7 role 不在 .agents/roles/(词表 = 目录内 *.md 文件名)
15
+ // E8 step 不在 .agents/workflows/steps/(文件名即步骤名——目录即注册表)
16
+ // E9 四形态(role/step/gate/human)不互斥:全空或同时 ≥2 个非空(`—`/`-`/空白 均视为空;human 列可选,缺列恒空)
17
+ // E10 retries 非非负整数(空 = 0,口径同 _TEMPLATE「失败且 retries > 0 → 重派」)
18
+ // 告警(advisory,不阻断):
19
+ // W1 gate 含 {a,b} 花括号展开——仅 bash 展开,PowerShell/cmd 按字面路径失败(跨宿主可移植性)
20
+ // W2 两个 role stage 的授权文件 token 完全相同且互相无 after 先序——并行 fan-out 同文件交叉写
21
+ // W3 frontmatter 缺 name(编排脚本标识)
22
+ // W4 human 确认门行填了 retries——用户驳回不能重派,无重试语义(视作 0)
23
+ //
24
+ // 排除:`_` 前缀文件(_TEMPLATE.md 是机制文档,正文含示例表——示例 step 名「部署验证」不在 steps/,
25
+ // 机制文档不按可执行件校验);steps/ 子目录(是扩展点注册表,非编排脚本)。
26
+ // 表解析:取首个表头含 id+after+gate 的表格,列名归一(task / params → task、files(授权)→ files),
27
+ // 到首个非 `|` 行截止;仅此一张表——编排脚本按机制文档只允许一张 stages 表。
28
+ // 用法:node .agents/scripts/workflows-check.mjs [--root <目标根>] [--json]
29
+ // 默认根 = process.cwd()(doctor 以 cwd=target 调用;仓库根直跑即自扫)
30
+ // 零依赖;只报告不修复(B-b 决策口径,同 source-sync-check)
31
+ // 测试:node templates/_agents/scripts/workflows-check.test.mjs(fixture + 真实仓库 baseline)
32
+ import fs from 'node:fs';
33
+ import path from 'node:path';
34
+
35
+ function fail(msg) { console.error('❌ ' + msg); process.exit(1); }
36
+
37
+ // 空值规范:em/en dash、裸连字符、空白均视为「未填」(stages 表通用约定)
38
+ const EMPTY_CELLS = new Set(['—', '–', '-', '']);
39
+ const normCell = (v) => { const s = String(v ?? '').trim(); return EMPTY_CELLS.has(s) ? '' : s; };
40
+
41
+ function listMdNames(dir) {
42
+ if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) return [];
43
+ return fs.readdirSync(dir).filter((f) => f.endsWith('.md')).map((f) => f.slice(0, -3)).sort();
44
+ }
45
+
46
+ function parseFm(text) {
47
+ const m = String(text).match(/^---\r?\n([\s\S]*?)\r?\n---/);
48
+ if (!m) return null;
49
+ const fm = {};
50
+ for (const line of m[1].split(/\r?\n/)) {
51
+ const kv = line.match(/^(\S+):\s*(.*)$/);
52
+ if (kv) fm[kv[1].trim()] = kv[2].trim();
53
+ }
54
+ return fm;
55
+ }
56
+
57
+ // splitRow:`| a | b |` → ['a','b'](去首尾竖线后按 | 切)
58
+ function splitRow(line) {
59
+ return line.trim().replace(/^\|/, '').replace(/\|$/, '').split('|').map((c) => c.trim());
60
+ }
61
+
62
+ // cellName:表头格 → 列名(小写取首个英文词:task / params → task、files(授权) → files)
63
+ function cellName(c) {
64
+ const m = c.toLowerCase().match(/^[a-z]+/);
65
+ return m ? m[0] : '';
66
+ }
67
+
68
+ // parseStages:返回 stage 行数组(id/after/role/step/files/gate/human/retries + _line 行号),无表返回 null。
69
+ // human 列可选(表头缺列 = 恒空,既有 9 列脚本零改动兼容);导出供 wf-journal.mjs 复用同一解析口径。
70
+ export function parseStages(text) {
71
+ const lines = String(text).split(/\r?\n/);
72
+ let headerIdx = -1;
73
+ let colIdx = null;
74
+ for (let i = 0; i < lines.length; i++) {
75
+ if (!/^\s*\|/.test(lines[i])) continue;
76
+ const names = splitRow(lines[i]).map(cellName);
77
+ if (names.includes('id') && names.includes('after') && names.includes('gate')) {
78
+ headerIdx = i;
79
+ colIdx = {};
80
+ names.forEach((n, k) => { if (n && !(n in colIdx)) colIdx[n] = k; });
81
+ break;
82
+ }
83
+ }
84
+ if (headerIdx === -1) return null;
85
+ const COLS = ['id', 'after', 'role', 'step', 'files', 'gate', 'human', 'retries'];
86
+ const rows = [];
87
+ for (let i = headerIdx + 1; i < lines.length; i++) {
88
+ if (!/^\s*\|/.test(lines[i])) break;
89
+ const cells = splitRow(lines[i]);
90
+ if (cells.every((c) => /^[-: ]*$/.test(c))) continue; // 分隔行 |----|
91
+ if (cells.every((c) => normCell(c) === '')) continue; // 全空行
92
+ const row = { _line: i + 1 };
93
+ for (const c of COLS) row[c] = colIdx[c] !== undefined ? normCell(cells[colIdx[c]] ?? '') : '';
94
+ rows.push(row);
95
+ }
96
+ return rows;
97
+ }
98
+
99
+ // lintWorkflowText:单文件校验(pure,词表以 Set 传入)
100
+ export function lintWorkflowText({ file, text, roles, steps }) {
101
+ const errors = [];
102
+ const warnings = [];
103
+ const fm = parseFm(text);
104
+ if (!fm) {
105
+ errors.push('E1 缺 frontmatter(--- name / description / concurrency ---,口径见 _TEMPLATE.md)');
106
+ } else {
107
+ if (fm.concurrency !== undefined && fm.concurrency !== '' && !/^[1-9]\d*$/.test(fm.concurrency)) {
108
+ errors.push(`E2 concurrency 须为正整数(现「${fm.concurrency}」,缺省 = 2)`);
109
+ }
110
+ if (!fm.name) warnings.push('W3 frontmatter 缺 name(编排脚本标识,_TEMPLATE.md 口径)');
111
+ }
112
+
113
+ const rows = parseStages(text);
114
+ if (!rows) {
115
+ errors.push('E3 缺 stages 表(表头须含 id / after / gate 列——三形态各至少一行的可执行编排才有效)');
116
+ return { file, errors, warnings };
117
+ }
118
+
119
+ const byId = new Map();
120
+ for (const r of rows) {
121
+ if (!r.id) { errors.push(`E4 L${r._line} stage 缺 id`); continue; }
122
+ if (byId.has(r.id)) errors.push(`E4 id 重复:${r.id}(L${byId.get(r.id)._line} 与 L${r._line})`);
123
+ else byId.set(r.id, r);
124
+ const forms = [r.role && 'role', r.step && 'step', r.gate && 'gate', r.human && 'human'].filter(Boolean);
125
+ if (forms.length === 0) errors.push(`E9 L${r._line} ${r.id} 四形态(role/step/gate/human)全空——须恰填一项`);
126
+ if (forms.length > 1) errors.push(`E9 L${r._line} ${r.id} 四形态互斥违例:同时填了 ${forms.join(' + ')}`);
127
+ if (r.role && !roles.has(r.role)) errors.push(`E7 L${r._line} ${r.id} role「${r.role}」不在 .agents/roles/(现有:${[...roles].join('、') || '无'})`);
128
+ if (r.step && !steps.has(r.step)) errors.push(`E8 L${r._line} ${r.id} step「${r.step}」不在 steps/(现有:${[...steps].join('、') || '无'})`);
129
+ if (r.retries && !/^\d+$/.test(r.retries)) errors.push(`E10 L${r._line} ${r.id} retries 须为非负整数(现「${r.retries}」,空 = 0)`);
130
+ if (r.gate && /\{[^}]+,[^}]+\}/.test(r.gate)) warnings.push(`W1 L${r._line} ${r.id} gate 含 {a,b} 花括号展开——仅 bash 展开,PowerShell/cmd 按字面路径失败:${r.gate}`);
131
+ if (r.human && r.retries) warnings.push(`W4 L${r._line} ${r.id} human 确认门行填了 retries「${r.retries}」——用户驳回不能重派,无重试语义(视作 0)`);
132
+ }
133
+
134
+ // after 引用存在性 + 成环(DFS 三色)
135
+ const ids = new Set(byId.keys());
136
+ const deps = new Map();
137
+ for (const [id, r] of byId) {
138
+ deps.set(id, r.after ? r.after.split(/[,,、\s]+/).filter(Boolean) : []);
139
+ }
140
+ for (const [id, ds] of deps) {
141
+ for (const d of ds) if (!ids.has(d)) errors.push(`E5 ${id} after 引用不存在的 stage:${d}`);
142
+ }
143
+ const color = new Map();
144
+ let cycle = '';
145
+ const dfs = (id) => {
146
+ color.set(id, 1);
147
+ for (const d of deps.get(id) || []) {
148
+ if (!ids.has(d)) continue;
149
+ const c = color.get(d) || 0;
150
+ if (c === 1) { cycle = `${d} → ${id}`; return true; }
151
+ if (c === 0 && dfs(d)) return true;
152
+ }
153
+ color.set(id, 2);
154
+ return false;
155
+ };
156
+ for (const id of ids) {
157
+ if (!color.get(id) && dfs(id)) { errors.push(`E6 after 依赖成环:${cycle}(闭环上一对)`); break; }
158
+ }
159
+
160
+ // W2 授权文件交叉:role stage 间完全相同 token 且互相无 after 先序(有先序 = 串行接力,不算交叉)
161
+ if (!errors.some((e) => e.startsWith('E5') || e.startsWith('E6'))) {
162
+ const depAll = new Map();
163
+ const depClosure = (id) => {
164
+ if (depAll.has(id)) return depAll.get(id);
165
+ const s = new Set();
166
+ depAll.set(id, s); // 先占位防递归环(E6 已排除,稳妥起见)
167
+ for (const d of deps.get(id) || []) {
168
+ if (!ids.has(d)) continue;
169
+ s.add(d);
170
+ for (const x of depClosure(d)) s.add(x);
171
+ }
172
+ return s;
173
+ };
174
+ const tokensOf = (r) => new Set((r.files || '').split(/[,,、\s]+/).map((t) => t.trim()).filter(Boolean));
175
+ const roleRows = [...byId.values()].filter((r) => r.role && r.files);
176
+ for (let i = 0; i < roleRows.length; i++) {
177
+ for (let j = i + 1; j < roleRows.length; j++) {
178
+ const a = roleRows[i];
179
+ const b = roleRows[j];
180
+ const shared = [...tokensOf(a)].filter((t) => tokensOf(b).has(t));
181
+ if (!shared.length) continue;
182
+ if (depClosure(a.id).has(b.id) || depClosure(b.id).has(a.id)) continue;
183
+ warnings.push(`W2 role stage ${a.id} 与 ${b.id} 授权文件交叉且无 after 先序(并行 fan-out 同文件交叉写):${shared.join('、')}`);
184
+ }
185
+ }
186
+ }
187
+
188
+ return { file, errors, warnings };
189
+ }
190
+
191
+ // lintWorkflows:扫 <root>/.agents/workflows/(pure function,测试/doctor 共用)
192
+ // 返回 { root, roles, steps, files: [{file, errors, warnings}], errorCount, warningCount }
193
+ export function lintWorkflows({ root }) {
194
+ const wfRoot = path.join(root, '.agents', 'workflows');
195
+ const roles = new Set(listMdNames(path.join(root, '.agents', 'roles')));
196
+ const steps = new Set(listMdNames(path.join(wfRoot, 'steps')));
197
+ const files = (fs.existsSync(wfRoot) && fs.statSync(wfRoot).isDirectory())
198
+ ? fs.readdirSync(wfRoot, { withFileTypes: true })
199
+ .filter((e) => e.isFile() && e.name.endsWith('.md') && !e.name.startsWith('_'))
200
+ .map((e) => e.name)
201
+ .sort()
202
+ : [];
203
+ const results = files.map((f) => lintWorkflowText({
204
+ file: f,
205
+ text: fs.readFileSync(path.join(wfRoot, f), 'utf8'),
206
+ roles,
207
+ steps,
208
+ }));
209
+ return {
210
+ root,
211
+ roles: [...roles],
212
+ steps: [...steps],
213
+ files: results,
214
+ errorCount: results.reduce((n, r) => n + r.errors.length, 0),
215
+ warningCount: results.reduce((n, r) => n + r.warnings.length, 0),
216
+ };
217
+ }
218
+
219
+ function printResult(result) {
220
+ console.log('▶ flow-kit workflows-check(.agents/workflows/ 编排脚本 lint)');
221
+ console.log(` 编排脚本 ${result.files.length} 份 | roles 词表 ${result.roles.length} | steps 词表 ${result.steps.length}`);
222
+ for (const f of result.files) {
223
+ if (!f.errors.length && !f.warnings.length) {
224
+ console.log(` ✅ ${f.file}`);
225
+ } else {
226
+ console.log(` ${f.errors.length ? '❌' : '⚠️ '} ${f.file}(${f.errors.length} error / ${f.warnings.length} warning)`);
227
+ for (const e of f.errors) console.log(` - ${e}`);
228
+ for (const w of f.warnings) console.log(` - ⚠️ ${w}`);
229
+ }
230
+ }
231
+ const tail = result.errorCount
232
+ ? `❌ ${result.errorCount} error / ${result.warningCount} warning——error 须修复后才可执行编排(解析校验先行)`
233
+ : `${result.warningCount ? `⚠️ 0 error / ${result.warningCount} warning(advisory 不阻断)` : '✅ 0 error / 0 warning'}`;
234
+ console.log(` ${tail}`);
235
+ }
236
+
237
+ const isMain = process.argv[1] && process.argv[1].endsWith('workflows-check.mjs');
238
+ if (isMain) {
239
+ let root = process.cwd();
240
+ let json = false;
241
+ for (let i = 2; i < process.argv.length; i++) {
242
+ const a = process.argv[i];
243
+ if (a === '--root') root = path.resolve(process.argv[++i] ?? fail('--root 缺少值'));
244
+ else if (a === '--json') json = true;
245
+ else fail(`未知选项:${a}(flow-kit workflows-check --root <dir> / --json)`);
246
+ }
247
+ const result = lintWorkflows({ root });
248
+ if (json) {
249
+ process.stdout.write(JSON.stringify({
250
+ root: result.root,
251
+ files: result.files,
252
+ errorCount: result.errorCount,
253
+ warningCount: result.warningCount,
254
+ }, null, 2) + '\n');
255
+ } else {
256
+ printResult(result);
257
+ }
258
+ process.exit(result.errorCount ? 1 : 0);
259
+ }
260
+ export default lintWorkflows;
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ // workflows-check.test.mjs — 编排脚本 lint 测试(2026-09-25 workflows-linter)
3
+ // 方法:fixture 临时目录(.agents/workflows/*.md + roles/ + steps/ 词表)调 lintWorkflows() 拿结构化结果,
4
+ // 不依赖真实仓库文件;末两个场景对真实仓库自扫做 baseline 断言。
5
+ // 判据:error 10 类各至少一场景命中;advisory 3 类各一场景;_ 前缀排除;真实仓库 0 error。
6
+ // 用法:node templates/_agents/scripts/workflows-check.test.mjs(npm test 随跑)
7
+ import fs from 'node:fs';
8
+ import os from 'node:os';
9
+ import path from 'node:path';
10
+ import { fileURLToPath } from 'node:url';
11
+ import { lintWorkflows } from './workflows-check.mjs';
12
+
13
+ let pass = 0;
14
+ let fail = 0;
15
+ const check = (desc, cond, detail = '') => {
16
+ if (cond) { pass++; console.log(`PASS ${desc}`); }
17
+ else { fail++; console.log(`FAIL ${desc}${detail ? '\n ' + detail : ''}`); }
18
+ };
19
+
20
+ const HEAD = '| id | after | role | step | task / params | files(授权) | accept(验收判据) | gate | retries |';
21
+ const SEP = '|----|-------|------|------|---------------|--------------|-------------------|------|---------|';
22
+
23
+ // mkfix:建临时根 + 词表(roles: implementer/reviewer;steps: deploy)+ 指定编排脚本集合
24
+ const mkfix = (scripts) => {
25
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'wf-lint-test-'));
26
+ fs.mkdirSync(path.join(root, '.agents', 'workflows', 'steps'), { recursive: true });
27
+ fs.mkdirSync(path.join(root, '.agents', 'roles'), { recursive: true });
28
+ for (const r of ['implementer', 'independent-reviewer']) {
29
+ fs.writeFileSync(path.join(root, '.agents', 'roles', `${r}.md`), `# ${r}\n`);
30
+ }
31
+ fs.writeFileSync(path.join(root, '.agents', 'workflows', 'steps', 'deploy.md'), '# deploy\n');
32
+ for (const [name, body] of Object.entries(scripts)) {
33
+ fs.writeFileSync(path.join(root, '.agents', 'workflows', name), body);
34
+ }
35
+ return root;
36
+ };
37
+ const fm = (extra = '') => `---\nname: t\ndescription: t\n${extra}---\n\n# w\n\n`;
38
+
39
+ // ---- S1 全绿:双 role 并行 + 汇聚 gate → 0 error 0 warning ----
40
+ {
41
+ const root = mkfix({
42
+ 'ok.md': fm() + HEAD + '\n' + SEP + '\n' +
43
+ '| a | — | implementer | — | 做 A | src/a/** | 测试过 | | 1 |\n' +
44
+ '| b | — | implementer | — | 做 B | src/b/** | 构建过 | | 0 |\n' +
45
+ '| g | a,b | — | — | — | — | — | npm test | |\n',
46
+ });
47
+ const r = lintWorkflows({ root });
48
+ check('S1 全绿(并行 role + 汇聚 gate)→ 0 error 0 warning',
49
+ r.errorCount === 0 && r.warningCount === 0 && r.files.length === 1,
50
+ JSON.stringify(r.files[0]));
51
+ fs.rmSync(root, { recursive: true, force: true });
52
+ }
53
+
54
+ // ---- S2 after 悬空引用 → E5 ----
55
+ {
56
+ const root = mkfix({
57
+ 'dangling.md': fm() + HEAD + '\n' + SEP + '\n' +
58
+ '| a | ghost | — | — | — | — | — | npm test | |\n',
59
+ });
60
+ const r = lintWorkflows({ root });
61
+ check('S2 after 引用不存在 → E5',
62
+ r.errorCount === 1 && r.files[0].errors[0].startsWith('E5') && r.files[0].errors[0].includes('ghost'),
63
+ JSON.stringify(r.files[0].errors));
64
+ fs.rmSync(root, { recursive: true, force: true });
65
+ }
66
+
67
+ // ---- S3 after 成环 → E6 ----
68
+ {
69
+ const root = mkfix({
70
+ 'cycle.md': fm() + HEAD + '\n' + SEP + '\n' +
71
+ '| a | b | — | — | — | — | — | npm test | |\n' +
72
+ '| b | a | — | — | — | — | — | npm test | |\n',
73
+ });
74
+ const r = lintWorkflows({ root });
75
+ check('S3 after 依赖成环 → E6',
76
+ r.errorCount === 1 && r.files[0].errors[0].startsWith('E6'),
77
+ JSON.stringify(r.files[0].errors));
78
+ fs.rmSync(root, { recursive: true, force: true });
79
+ }
80
+
81
+ // ---- S4 未知 role → E7(词表只有 implementer / independent-reviewer)----
82
+ {
83
+ const root = mkfix({
84
+ 'role.md': fm() + HEAD + '\n' + SEP + '\n' +
85
+ '| a | — | hacker | — | 做 A | src/** | 过 | | |\n',
86
+ });
87
+ const r = lintWorkflows({ root });
88
+ check('S4 role 不在 .agents/roles/ → E7',
89
+ r.errorCount === 1 && r.files[0].errors[0].startsWith('E7') && r.files[0].errors[0].includes('hacker'),
90
+ JSON.stringify(r.files[0].errors));
91
+ fs.rmSync(root, { recursive: true, force: true });
92
+ }
93
+
94
+ // ---- S5 未知 step → E8(steps/ 只有 deploy)----
95
+ {
96
+ const root = mkfix({
97
+ 'step.md': fm() + HEAD + '\n' + SEP + '\n' +
98
+ '| a | — | — | rollback | env=prod | | exit 0 | | 0 |\n',
99
+ });
100
+ const r = lintWorkflows({ root });
101
+ check('S5 step 不在 steps/ → E8',
102
+ r.errorCount === 1 && r.files[0].errors[0].startsWith('E8') && r.files[0].errors[0].includes('rollback'),
103
+ JSON.stringify(r.files[0].errors));
104
+ fs.rmSync(root, { recursive: true, force: true });
105
+ }
106
+
107
+ // ---- S6 三形态全空 / 双填 → E9 ×2 ----
108
+ {
109
+ const root = mkfix({
110
+ 'forms.md': fm() + HEAD + '\n' + SEP + '\n' +
111
+ '| nothing | — | — | — | 只有任务没有形态 | src/** | 过 | | |\n' +
112
+ '| both | — | implementer | deploy | 形态双填 | src/** | 过 | npm test | |\n',
113
+ });
114
+ const r = lintWorkflows({ root });
115
+ const e9 = r.files[0].errors.filter((e) => e.startsWith('E9'));
116
+ check('S6 三形态全空与双填 → E9 ×2',
117
+ r.errorCount === 2 && e9.length === 2 && e9[0].includes('nothing') && e9[1].includes('both'),
118
+ JSON.stringify(r.files[0].errors));
119
+ fs.rmSync(root, { recursive: true, force: true });
120
+ }
121
+
122
+ // ---- S7 retries 非法 / concurrency 非法 → E10 + E2 ----
123
+ {
124
+ const root = mkfix({
125
+ 'nums.md': fm('concurrency: x\n') + HEAD + '\n' + SEP + '\n' +
126
+ '| a | — | — | — | — | — | — | npm test | 重试 |\n',
127
+ });
128
+ const r = lintWorkflows({ root });
129
+ const codes = r.files[0].errors.map((e) => (e.match(/^E\d+/) || [''])[0]);
130
+ check('S7 retries 非数字 → E10 且 concurrency 非法 → E2',
131
+ r.errorCount === 2 && codes.includes('E10') && codes.includes('E2'),
132
+ JSON.stringify(r.files[0].errors));
133
+ fs.rmSync(root, { recursive: true, force: true });
134
+ }
135
+
136
+ // ---- S8 gate 花括号展开 → W1(0 error,advisory)----
137
+ {
138
+ const root = mkfix({
139
+ 'brace.md': fm() + HEAD + '\n' + SEP + '\n' +
140
+ '| g | — | — | — | — | — | — | node scripts/fill-{intent,plan}.test.mjs | |\n',
141
+ });
142
+ const r = lintWorkflows({ root });
143
+ check('S8 gate 含 {a,b} 花括号展开 → W1 且不计 error',
144
+ r.errorCount === 0 && r.warningCount === 1 && r.files[0].warnings[0].startsWith('W1'),
145
+ JSON.stringify({ e: r.files[0].errors, w: r.files[0].warnings }));
146
+ fs.rmSync(root, { recursive: true, force: true });
147
+ }
148
+
149
+ // ---- S9 授权文件交叉:无先序告警 / 有 after 先序不告警 ----
150
+ {
151
+ const root = mkfix({
152
+ 'parallel-cross.md': fm() + HEAD + '\n' + SEP + '\n' +
153
+ '| a | — | implementer | — | 做 A | src/shared/** | 过 | | |\n' +
154
+ '| b | — | implementer | — | 做 B | src/shared/** | 过 | | |\n',
155
+ 'ordered-ok.md': fm() + HEAD + '\n' + SEP + '\n' +
156
+ '| a | — | implementer | — | 做 A | src/shared/** | 过 | | |\n' +
157
+ '| b | a | implementer | — | 做 B | src/shared/** | 过 | | |\n',
158
+ });
159
+ const r = lintWorkflows({ root });
160
+ const cross = r.files.find((f) => f.file === 'parallel-cross.md');
161
+ const ordered = r.files.find((f) => f.file === 'ordered-ok.md');
162
+ check('S9 授权文件相同且无先序 → W2;有 after 先序 → 不告警',
163
+ r.errorCount === 0 && cross.warnings.length === 1 && cross.warnings[0].startsWith('W2') && ordered.warnings.length === 0,
164
+ JSON.stringify({ cross: cross.warnings, ordered: ordered.warnings }));
165
+ fs.rmSync(root, { recursive: true, force: true });
166
+ }
167
+
168
+ // ---- S10 _ 前缀机制文档不进扫描面(内含坏表也不报)----
169
+ {
170
+ const root = mkfix({
171
+ '_TEMPLATE.md': fm() + HEAD + '\n' + SEP + '\n' +
172
+ '| bad | ghost | hacker | — | — | — | — | — | 重试 |\n',
173
+ });
174
+ const r = lintWorkflows({ root });
175
+ check('S10 _TEMPLATE.md 排除(机制文档含示例表,不按可执行件校验)',
176
+ r.files.length === 0 && r.errorCount === 0 && r.warningCount === 0,
177
+ JSON.stringify(r.files.map((f) => f.file)));
178
+ fs.rmSync(root, { recursive: true, force: true });
179
+ }
180
+
181
+ // ---- S11 缺 frontmatter → E1;缺 stages 表 → E3 ----
182
+ {
183
+ const root = mkfix({
184
+ 'nofm.md': '# 没有 frontmatter\n\n只有正文\n',
185
+ 'notable.md': fm() + '# 有 frontmatter 没有 stages 表\n',
186
+ });
187
+ const r = lintWorkflows({ root });
188
+ const nofm = r.files.find((f) => f.file === 'nofm.md');
189
+ const notable = r.files.find((f) => f.file === 'notable.md');
190
+ check('S11 缺 frontmatter → E1 且缺表 → E3',
191
+ nofm.errors.some((e) => e.startsWith('E1')) && notable.errors.some((e) => e.startsWith('E3')),
192
+ JSON.stringify({ nofm: nofm.errors, notable: notable.errors }));
193
+ fs.rmSync(root, { recursive: true, force: true });
194
+ }
195
+
196
+ // ---- S12 id 重复 → E4;id 空 → E4 ----
197
+ {
198
+ const root = mkfix({
199
+ 'dup.md': fm() + HEAD + '\n' + SEP + '\n' +
200
+ '| a | — | — | — | — | — | — | npm test | |\n' +
201
+ '| a | — | — | — | — | — | — | npm test | |\n' +
202
+ '| — | — | — | — | 缺 id 行 | — | — | — | |\n',
203
+ });
204
+ const r = lintWorkflows({ root });
205
+ const e4 = r.files[0].errors.filter((e) => e.startsWith('E4'));
206
+ check('S12 id 重复与缺 id → E4 ×2',
207
+ e4.length === 2 && e4.some((e) => e.includes('重复')) && e4.some((e) => e.includes('缺 id')),
208
+ JSON.stringify(r.files[0].errors));
209
+ fs.rmSync(root, { recursive: true, force: true });
210
+ }
211
+
212
+ // ---- S13 step 引用 + gate + 空值规范(— / 空 retries / 空 gate)混排全绿 ----
213
+ {
214
+ const root = mkfix({
215
+ 'mixed.md': fm('concurrency: 3\n') + HEAD + '\n' + SEP + '\n' +
216
+ '| build | — | implementer | — | 构建 | src/** | 测试过 | | |\n' +
217
+ '| verify | build | — | deploy | env=staging | | exit 0 | | 2 |\n' +
218
+ '| final | verify | — | — | — | — | — | npm test | |\n',
219
+ });
220
+ const r = lintWorkflows({ root });
221
+ check('S13 step 行 / gate 行 / em-dash 空值 / concurrency=3 / retries=2 混排全绿',
222
+ r.errorCount === 0 && r.warningCount === 0,
223
+ JSON.stringify(r.files[0]));
224
+ fs.rmSync(root, { recursive: true, force: true });
225
+ }
226
+
227
+ // ---- S14 真实仓库自扫 baseline:0 error(_TEMPLATE 排除后三份可执行件全过)----
228
+ {
229
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', '..');
230
+ const r = lintWorkflows({ root: ROOT });
231
+ const names = r.files.map((f) => f.file).join('、');
232
+ check('S14 真实仓库 .agents/workflows/ 自扫 0 error(扫描面:' + names + ')',
233
+ r.errorCount === 0 && r.files.length >= 3,
234
+ JSON.stringify(r.files.filter((f) => f.errors.length).map((f) => ({ f: f.file, e: f.errors }))));
235
+ }
236
+
237
+ // ---- S14 human 确认门(第四形态):单形态全绿 / 双填 E9 / ×retries W4 / 缺列兼容 ----
238
+ {
239
+ const HEAD10 = '| id | after | role | step | task / params | files(授权) | accept(验收判据) | gate | human | retries |';
240
+ const SEP10 = '|----|-------|------|------|---------------|--------------|-------------------|------|-------|---------|';
241
+ const root = mkfix({
242
+ 'human-ok.md': fm() + HEAD10 + '\n' + SEP10 + '\n' +
243
+ '| a | — | implementer | — | 做 A | src/a/** | 测试过 | | | 0 |\n' +
244
+ '| ask | a | — | — | 用户确认可继续 | — | 一句「可以」 | | 用户确认 | |\n' +
245
+ '| b | ask | — | — | — | — | — | npm test | | |\n',
246
+ 'human-bad.md': fm() + HEAD10 + '\n' + SEP10 + '\n' +
247
+ '| both | — | — | — | 双填 | — | — | npm test | 用户确认 | |\n' +
248
+ '| retry | — | — | — | 确认 | — | 一句可以 | | 用户确认 | 2 |\n',
249
+ 'no-human-col.md': fm() + HEAD + '\n' + SEP + '\n' +
250
+ '| a | — | — | — | — | — | — | npm test | |\n',
251
+ });
252
+ const r = lintWorkflows({ root });
253
+ const ok = r.files.find((f) => f.file === 'human-ok.md');
254
+ const bad = r.files.find((f) => f.file === 'human-bad.md');
255
+ const legacy = r.files.find((f) => f.file === 'no-human-col.md');
256
+ check('S14 human 单形态 10 列全绿;human+gate 双填 E9;human×retries W4;9 列无 human 表全过',
257
+ r.errorCount === 1
258
+ && ok.errors.length === 0 && ok.warnings.length === 0
259
+ && bad.errors.length === 1 && bad.errors[0].startsWith('E9') && bad.errors[0].includes('gate + human')
260
+ && bad.warnings.length === 1 && bad.warnings[0].startsWith('W4')
261
+ && legacy.errors.length === 0 && legacy.warnings.length === 0,
262
+ JSON.stringify(r.files.map((f) => ({ f: f.file, e: f.errors, w: f.warnings }))));
263
+ fs.rmSync(root, { recursive: true, force: true });
264
+ }
265
+
266
+ console.log(`\n合计: PASS ${pass} / FAIL ${fail}`);
267
+ process.exit(fail ? 1 : 0);
@@ -0,0 +1,19 @@
1
+ # workflow 文档枚举单源(2026-09-25 enum-single-source)
2
+ # 本文件会被 core.autocrlf 检出为 CRLF——读取方显式去 \r(node parseEnums),禁止依赖行尾格式(rule-budgets.txt 同约定)。
3
+ # 消费方:check-loop.mjs / workflow-board-server.mjs / kb-search.mjs / gen-workflow-index.mjs /
4
+ # fill-{intent,plan,spec}.mjs(均经 scripts/workflow-enums.mjs 读取器)——改枚举改这里,别改消费方。
5
+ # 格式:key=value value value(空格分隔;# 注释行忽略;键重复 / 值重复 / 缺键 = 消费方 fail-loud)
6
+ # 语义:doc = intent/spec/plan 三件;incident = 事故件。
7
+ # all 全生命周期状态
8
+ # confirmed 已确认(check-loop「状态未确认」放行口径 = 看板枚举告警口径)
9
+ # active 活跃(INDEX 活跃层 / kb-search 活跃 ×1.3 排序)
10
+ # terminal 终态(看板「入口已 done 而 plan 未终态」告警口径)
11
+ # abandoned 放弃留档(L3 确认三件豁免口径)
12
+ doc.status.all=draft approved done superseded cancelled
13
+ doc.status.confirmed=approved done superseded cancelled
14
+ doc.status.active=draft approved
15
+ doc.status.terminal=done superseded cancelled
16
+ doc.status.abandoned=superseded cancelled
17
+ incident.status.all=open fixed closed
18
+ incident.status.active=open
19
+ level.all=L0 L1 L2 L3
@@ -0,0 +1,75 @@
1
+ # Workflow 编排机制 — 编排脚本 + steps 扩展点
2
+
3
+ > kit 的跨宿主编排机制:`.agents/workflows/` 下的编排脚本承载编排语义(依赖分层 / 并行 / 重试 / gate / 用户确认门 / run journal),**宿主 AI 经 AGENTS.md 常驻指令自动加载并按语义执行**——有子智能体的宿主并行 fan-out,没有的顺序自做,验收不变。零宿主协议适配:机制只依赖所有宿主共有的四样本领(读 AGENTS.md、读仓库文件、跑终端命令、子智能体)。
4
+
5
+ ## 自动加载(AGENTS.md 常驻指令)
6
+
7
+ > 多工作包编排——用户要跑编排、或 plan 执行多工作包时:读 `.agents/workflows/` 编排脚本按本文档语义执行;自定义步骤在 `steps/`。
8
+
9
+ ## 编排脚本(`.agents/workflows/<主题>.md`)
10
+
11
+ frontmatter(`name` / `description` / `concurrency` 默认 2)+ 一张 stages 表(`human` 列可选——不用确认门的可省该列,保持 9 列):
12
+
13
+ ```md
14
+ ---
15
+ name: 并行修两域
16
+ description: 后端+前端并行实现 → 汇聚过测试 → 部署验证(step)→ 独立评审
17
+ concurrency: 2
18
+ ---
19
+ | id | after | role | step | task / params | files(授权) | accept(验收判据) | gate | human | retries |
20
+ |----|-------|------|------|---------------|--------------|-------------------|------|-------|---------|
21
+ | backend | — | implementer | — | 实现后端 X | src/api/** | 项目测试过 | | | 1 |
22
+ | frontend | — | implementer | — | 改前端 Y | web/** | 构建过 | | | 0 |
23
+ | gate-all | backend,frontend | — | — | — | — | — | npm test | | |
24
+ | confirm-deploy | gate-all | — | — | 用户对话内确认可上 staging | — | 一句「可以」 | | 用户确认 | |
25
+ | deploy-check | confirm-deploy | — | 部署验证 | env=staging | | 部署命令 exit 0 | | | 0 |
26
+ | review | deploy-check | independent-reviewer | — | 评审两域改动 | | 符合 plan 与判据 | | | 0 |
27
+ ```
28
+
29
+ **stage 四形态**(互斥,恰填一项):
30
+
31
+ | 形态 | 判据 | 执行 |
32
+ |------|------|------|
33
+ | role 派单行 | `role` 非空 | 派子智能体(`task` 目标 / `files` 授权清单 / `accept` 验收判据写进派单头 + 红线行) |
34
+ | step 行 | `step` 非空 | 宿主 AI 读 `steps/<step>.md` 按指引执行,`task / params` 列传参 |
35
+ | gate 行 | `gate` 非空(role/step/human 空) | 终端跑命令,非零即中止 |
36
+ | human 确认门行 | `human` 非空(其余形态列空) | 宿主 AI **停下向用户询问**(`task` 列 = 问什么、`accept` 列 = 通过判据),通过才派下层;驳回 → 中止汇总。**不派子智能体**(机器判据层面落实「子智能体不跨确认门」,取代早前 role 行伪装确认门的写法) |
37
+
38
+ **编排语义**(执行口径,全宿主同一):`after` 空 = 第 0 层;一层全 ok 才派依赖它的下一层;层内按 `concurrency` 分批——有子智能体的宿主并行 fan-out,没有的顺序自做(验收标准不变);失败且 `retries` > 0 → 重派(全新上下文);重试超限 / gate 非零 / human 驳回 → 中止后续、汇总报告失败项。human 行无重试语义(retries 视作 0)。
39
+
40
+ **run journal**(断点续跑与重试计量):每个 stage 每次尝试出结果(pass / fail / blocked)即记一行;中止后重跑先看 status——已 pass 的 stage 不重跑,从就绪层续:
41
+
42
+ ```bash
43
+ node .agents/scripts/wf-journal.mjs add --wf <编排名> --stage <id> --status pass --form human # fail/blocked 同式;--form ∈ role|step|gate|human
44
+ node .agents/scripts/wf-journal.mjs status --wf <编排名> # 已完成 / 就绪(含重试就绪×N)/ 待定
45
+ ```
46
+
47
+ journal 落 `.agents/cache/orchestration-runs.jsonl`(本地运行态,可丢弃;同 run 同 stage 多行按末次生效,attempt 自动计次;`--new-run` 新起一轮)。
48
+
49
+ ## steps 扩展点(`.agents/workflows/steps/<名>.md`)
50
+
51
+ kit 定义的扩展机制:文件名即步骤名(steps 目录即注册表),项目把自己的领域动作(部署 / 迁移 / 审计上报…)注册进来,编排脚本 step 行引用即用。
52
+
53
+ ```md
54
+ ---
55
+ params: env(目标环境);timeout-min(可选,默认 10)
56
+ ---
57
+ # 步骤 — 部署验证
58
+
59
+ 1. 跑终端命令:`npm run deploy -- --env <env>`,非零即步骤失败并回报 stderr 尾段
60
+ 2. 跑冒烟检查:`curl -fsS https://<env>.example.com/healthz`
61
+ 3. 回报:部署版本号 + 冒烟结果
62
+ ```
63
+
64
+ - frontmatter `params`:参数约定(自由文本列明名称与含义);调用方在编排脚本 `task / params` 列传参(`k=v`,空格分隔多个)。
65
+ - 正文:执行指引(编号步骤)——可指使命令、子智能体、检查动作;步骤失败按编排脚本行 `retries` 重试。
66
+
67
+ ## 纪律(先读)
68
+
69
+ - **声明随 plan 确认后方可执行**:编排脚本是 plan 任务拆解的执行器,随 plan 草稿一并全文过目。
70
+ - 子智能体不跨确认门、不 commit / push(派单红线行:只改授权文件;缺输入或需偏离 → `BLOCKER:` 停下)。确认门一律用 human 形态行表达,不得派子智能体代行。
71
+ - 收敛复核:派单返回后核对 diff 与授权文件范围(与 build.md 委派约定同口径);BLOCKER 停住待处置。
72
+ - **run journal 三条**:① 每个 stage 每次尝试出结果即 `wf-journal.mjs add` 记一行(含重试与 human 门结论);② 中止后重跑先 `status`——已 pass 不重跑,从就绪层续;③ 收尾可把本 run 的 status 汇总粘 `workflow/delegations.md` 留痕(聚合脚本不读 journal)。
73
+ - **journal 是自报态,非验证态**:run journal 由执行者(宿主 AI)自己记——concurrency / retry / 断点续跑语义的保证取决于记账纪律,编排层无运行时派发器强制(wf-run 已废,执行按本机制文档语义);中止续跑时对「已 pass」结论保留人工核对权(2026-09-27 audit-gate-hardening 边界声明)。
74
+ - 留痕:每个派单 / step stage 完成后向 `workflow/delegations.md` 委派结果表追加一行(`一次通过` / `返工×N` / `返工待修`,口径与 agg-delegations.cjs 对齐)。
75
+ - 解析校验先行(机器门):`node .agents/scripts/workflows-check.mjs`——role ∈ `.agents/roles/`、step ∈ `steps/`、after 引用存在且无环、四形态恰填一项、retries/concurrency 数值合法;任一不过即拒,不执行。
@@ -0,0 +1,29 @@
1
+ ---
2
+ name: pipeline-closing
3
+ description: 推广环节 6 阶段闭环——起草 intent + plan(双写)→ 用户对话内确认 → 按 plan 改动面实施 → 3 关静态门(source-sync-check / npm test / doctor)→ 关单留痕(intent + plan → done + docs commit)。可复用于本项目所有 L1 推广环节(新增工具 / 微改动 / 协议修补)
4
+ concurrency: 2
5
+ ---
6
+ # workflow — pipeline-closing(推广环节 6 阶段闭环)
7
+
8
+ > 把 5 个已闭环推广环节(E/A/D/C/F/B)的标准节奏固化为可复用编排。stage 用 role 派单 / gate 终端 / human 确认门三种形态——不引新 step(`_TEMPLATE.md` 解析校验要求 `step ∈ steps/`,新增 step 留给后续项目注册)。
9
+
10
+ | id | after | role | step | task / params | files(授权) | accept(验收判据) | gate | human | retries |
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 |
13
+ | gate-doc | author | — | — | — | — | — | node .agents/scripts/fill-intent.test.mjs && node .agents/scripts/fill-plan.test.mjs | | |
14
+ | confirm | gate-doc | — | — | plan 草稿全文过目,用户对话内一句"可以" → plan 状态 draft → approved + 回填「确认与复核」节 | — | plan 状态 approved + 「确认结果:approved(YYYY-MM-DD 用户对话内确认)」 | | 用户确认(plan 确认门) | |
15
+ | plan-commit | confirm | implementer | — | 提交 plan 立项 commit(docs(workflow)) | workflow/intents/, workflow/plans/, workflow/INDEX.md | pre-commit 钩子全过;commit message 含 plan 改动面摘要 | | | 0 |
16
+ | build | plan-commit | implementer | — | 按 plan §改动面 执行(含双源纪律:包源改了装副本手动同步) | 见 plan | plan 改动面全部完成;双源文件 sha 一致 | | | 1 |
17
+ | gate-sync | build | — | — | — | — | — | node .agents/scripts/source-sync-check.mjs --diff(必须 0 差异) | | |
18
+ | gate-test | build | — | — | — | — | — | npm test(必须全绿) | | |
19
+ | gate-doctor | build | — | — | — | — | — | node bin/flow-kit.mjs doctor(必须 0 FAIL) | | |
20
+ | feat-commit | gate-sync,gate-test,gate-doctor | implementer | — | 提交 feat commit(feat(模块名)) | 工作区所有变更文件 | pre-commit 钩子全过;commit message 含 build 改动面摘要 | | | 0 |
21
+ | closeout | feat-commit | implementer | — | 补 intent 8 条验收勾验 + plan → done + 关单 docs commit 留痕;同步 INDEX.md(如适用) | workflow/intents/<date>-<topic>.md, workflow/plans/<date>-<topic>.md, workflow/INDEX.md | intent + plan 状态均 done;8 条验收勾验含 commit SHA 证据;commit message 含关单 commit + 8 条勾验摘要 | | | 0 |
22
+
23
+ **编排语义**:第 0 层 author → 第 1 层 gate-doc + confirm(confirm 为 human 确认门形态——宿主 AI 停下询问用户,不派子智能体;通过才派下层)→ 第 2 层 plan-commit → 第 3 层 build → 第 4 层 3 关静态门(并行 fan-out)→ 第 5 层 feat-commit → 第 6 层 closeout。失败且 retries > 0 → 重派(全新上下文);gate 非零 / human 驳回 → 中止后续、汇总报告失败项。每个 stage 每次尝试出结果记 `wf-journal.mjs add`;中止续跑先 `wf-journal.mjs status --wf pipeline-closing`(已 pass 不重跑)。
24
+
25
+ **纪律**(先读):
26
+ - 声明随 plan 确认后方可执行——本 workflow 是 plan 任务拆解的执行器;plan 草稿先经用户对话内确认。
27
+ - 子智能体不跨确认门、不 commit / push(派单红线行:只改授权文件;缺输入或需偏离 → `BLOCKER:` 停下)。确认门一律 human 形态行表达(confirm 行即此),不得派子智能体代行。
28
+ - 留痕:每个派单 stage 完成后向 `workflow/delegations.md` 委派结果表追加一行(`一次通过` / `返工×N` / `返工待修`)。
29
+ - 解析校验先行(机器门 `node .agents/scripts/workflows-check.mjs`):role ∈ `.agents/roles/`、after 引用存在且无环、四形态恰填一项——任一不过即拒,不执行。