@xulthekl/team-flow 0.44.0 → 0.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/.claude/always/phase-guard.md +1 -1
  2. package/.claude-plugin/marketplace.json +1 -1
  3. package/.claude-plugin/plugin.json +1 -1
  4. package/.codex-plugin/plugin.json +1 -1
  5. package/.cursor-plugin/marketplace.json +1 -1
  6. package/.cursor-plugin/plugin.json +1 -1
  7. package/.github/plugin/marketplace.json +2 -2
  8. package/CHANGELOG.md +16 -0
  9. package/GEMINI.md +1 -1
  10. package/INSTALL.md +1 -1
  11. package/README.md +1 -1
  12. package/docs/README_en.md +1 -1
  13. package/docs/solutions/INDEX.md +1 -0
  14. package/docs/solutions/cross-phase/2026-08-18-no-summary.md +17 -0
  15. package/gemini-extension.json +1 -1
  16. package/hooks/session-start +2 -2
  17. package/llms.txt +1 -1
  18. package/package.json +1 -1
  19. package/plugin.json +1 -1
  20. package/scripts/guard/checks/delegation-status.mjs +56 -0
  21. package/scripts/guard/checks/test-gate-exemptions.mjs +13 -0
  22. package/scripts/guard/checks/test-matrix-complete.mjs +6 -0
  23. package/scripts/guard/checks/test-matrix-ready.mjs +6 -0
  24. package/scripts/guard/checks/tests-passing.mjs +6 -1
  25. package/scripts/guard/guard.mjs +3 -1
  26. package/scripts/lib/cmd-state.mjs +3 -0
  27. package/scripts/lib/config-loader.mjs +11 -0
  28. package/scripts/lib/conventions-generator.mjs +91 -0
  29. package/scripts/lib/execution-plan.mjs +2 -1
  30. package/scripts/lib/execution-recommendation.mjs +20 -3
  31. package/scripts/lib/glaf4-delegation.mjs +488 -0
  32. package/scripts/lib/glaf4-evidence-export.mjs +267 -0
  33. package/scripts/lib/state-loader.mjs +13 -0
  34. package/scripts/lib/test-matrix-export.mjs +151 -8
  35. package/scripts/team-flow.mjs +6 -0
  36. package/skills/contract-builder/SKILL.md +2 -2
  37. package/skills/contract-builder/references/glaf4-delegation.md +130 -0
  38. package/skills/workflow-start/SKILL.md +1 -1
  39. package/skills/workflow-start/references/routing-rules.md +12 -12
  40. package/templates/execution-contract.md +13 -1
@@ -0,0 +1,488 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * glaf4-delegation — GLAF4 委托协议 CLI(v2.1 §6.6/§6.3/§6.7,P2 实施委托全链路)
4
+ *
5
+ * 用法:
6
+ * tf glaf4-delegation detect <root> [--delegation-mode <mode>] [--json]
7
+ * 运行时探测(v2.1 §4 接口①,C1 修复接线):三态检测 + 写 .team-flow/team-flow.config.json 的 glaf4_dev 段。
8
+ * contract-builder 产出 execution-contract 时(DP-4 前)显式调用此命令,recommend 才能读到 glaf4_dev。
9
+ * tf glaf4-delegation confirm <change-dir> --mode <mode> --write-set <json> [--json]
10
+ * DP-4 委托方案确认:mode 枚举标量入 state,write_set_boundary 落 .superpowers/glaf4/write-set.json,
11
+ * state 只存引用 hash(writeState 硬编码字段列表 + parseYaml 仅标量,文件清单数组无法往返)。
12
+ * tf glaf4-delegation verify-write-set <change-dir> --run-dir <run-dir> [--target-root <root>] [--json]
13
+ * write-set 对账(门禁级 FAIL,阻断 closing):glaf4-dev 实际写入 ⊆ change 边界 + 允许集。
14
+ * tf glaf4-delegation verify-tasks <change-dir> [--json]
15
+ * task 完成度对账(审计输出 WARN):tasks.md targets 注解 vs delegation_status。
16
+ * tf glaf4-delegation record-partial <change-dir> --run-dir <run-dir> [--json]
17
+ * 6.7 partial writes 落盘清单:BLOCKED 时扫 run 实际写入写 delegation_partial_writes(人工核对从扫盘降为确认清单)。
18
+ * tf glaf4-delegation reset <change-dir> [--json]
19
+ * 6.7 官方降级:删 plan 重走 DP-4(删除 .superpowers/sdd/execution-plan.json + 清理 execution_plan_hash + dp_4/delegation 字段)。
20
+ *
21
+ * 允许集(v2.1 §6.3 write-set 对账):pom.xml/build.gradle、src/test/java/**、src/test/resources/**、
22
+ * .glaf4-dev/runs/**、test-environment.json。这是唯一约束 change 边界的机械机制。
23
+ */
24
+
25
+ import { createHash } from 'node:crypto';
26
+ import { execFileSync } from 'node:child_process';
27
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
28
+ import { join, isAbsolute, relative, resolve, normalize, sep } from 'node:path';
29
+ import { readState, writeState } from './state-loader.mjs';
30
+ import { getOverlayPaths } from './sdd-overlay.mjs';
31
+ import { detectGlaf4Delegation, writeGlaf4DevConfig } from './conventions-generator.mjs';
32
+
33
+ // DP-4 委托模式合法枚举(对应 glaf4-dev 七模式中可委托 change 的六种)
34
+ export const GLAF4_DELEGATION_MODES = [
35
+ 'FEATURE_TDD',
36
+ 'BUGFIX_REGRESSION',
37
+ 'REFACTOR_PROTECTED',
38
+ 'COMPLETE_UNIT_TESTS',
39
+ 'COMPLETE_INTEGRATION_TESTS',
40
+ 'TEST_BOOTSTRAP',
41
+ ];
42
+
43
+ // write-set 对账允许集(v2.1 §6.3)——change 边界之外、允许 glaf4-dev 写入的附加区域
44
+ const WRITE_SET_ALLOWED = [
45
+ 'pom.xml',
46
+ 'build.gradle',
47
+ 'build.gradle.kts',
48
+ 'src/test/java',
49
+ 'src/test/resources',
50
+ '.glaf4-dev/runs',
51
+ 'test-environment.json',
52
+ ];
53
+
54
+ /**
55
+ * write-set.json 的固定落盘路径(.superpowers/glaf4/write-set.json)
56
+ */
57
+ function writeSetPath(changeDir) {
58
+ return join(changeDir, '.superpowers', 'glaf4', 'write-set.json');
59
+ }
60
+
61
+ /**
62
+ * 计算 write-set 引用 hash(state 只存 hash,文件清单不落 YAML)
63
+ */
64
+ export function hashWriteSet(writeSet) {
65
+ const stable = JSON.stringify(writeSet, null, 2);
66
+ return `sha256:${createHash('sha256').update(stable).digest('hex')}`;
67
+ }
68
+
69
+ /**
70
+ * 运行时探测并写 config(v2.1 §4 接口①,C1 修复接线)
71
+ * @param {string} root 工程根目录
72
+ * @param {string} [delegationMode] 覆盖 delegation_mode(auto/manual/off)
73
+ * @returns {object} { ok, is_glaf4_java, glaf4_dev_available, glaf4_dev_version, delegation_mode, configPath }
74
+ */
75
+ export function detectAndWriteConfig(root, delegationMode) {
76
+ const detection = detectGlaf4Delegation(root, { delegationMode });
77
+
78
+ // 复评 C1 嵌套工程根盲区(2026-08-18):git root ≠ target_root(monorepo 布局)时,
79
+ // loadConfig(changeDir) 只查 changeDir→git root→home,不查 target_root → 委托链静默断裂。
80
+ // 写 git root 冗余副本保证从任意 changeDir 都能读到探测结果。gitRoot 提前解析供 mode 优先级使用。
81
+ let gitRoot = null;
82
+ try {
83
+ gitRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], {
84
+ cwd: root, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'],
85
+ }).trim();
86
+ } catch {
87
+ // 非 git 仓库,跳过
88
+ }
89
+
90
+ // 第四轮复评 Important(2026-08-18):delegation_mode 优先级链补全——显式 > 目标 root 已有 > git root 已有 > auto。
91
+ // 上轮只查目标 root,非 Java 服务无本地 config → 默认 auto → 覆盖 git root 副本的 off(用户 off 意图被静默翻转)。
92
+ let effectiveMode = delegationMode;
93
+ if (!effectiveMode) {
94
+ effectiveMode = readGlaf4DelegationMode(root)
95
+ || (gitRoot ? readGlaf4DelegationMode(gitRoot) : null)
96
+ || 'auto';
97
+ }
98
+ const info = {
99
+ enabled: detection.is_glaf4_java && detection.glaf4_dev_available,
100
+ version: detection.glaf4_dev_version,
101
+ delegationMode: effectiveMode,
102
+ };
103
+ writeGlaf4DevConfig(root, info);
104
+
105
+ let gitRootConfig = null;
106
+ if (gitRoot && gitRoot !== root) {
107
+ // 第三轮复评 Important①(2026-08-18):多服务混合 monorepo 下 git root 副本 last-write-wins 污染——
108
+ // 非 Java 服务 detect 会把 Java 服务的 enabled=true 覆盖成 false。改为 OR 聚合:
109
+ // 项目级能力一旦声明(任一服务 enabled=true),不因后续非 Java 服务探测而丢失。
110
+ const existingEnabled = readGitRootGlaf4Enabled(gitRoot);
111
+ const aggregatedEnabled = info.enabled || existingEnabled;
112
+ // 第四轮复评:git root 副本 mode 保留已有值(显式传参才覆盖)——用户 off 意图不因新探测静默翻转。
113
+ const gitRootMode = readGlaf4DelegationMode(gitRoot);
114
+ const gitRootEffectiveMode = delegationMode || gitRootMode || effectiveMode;
115
+ writeGlaf4DevConfig(gitRoot, { ...info, enabled: aggregatedEnabled, delegationMode: gitRootEffectiveMode });
116
+ gitRootConfig = join(gitRoot, '.team-flow', 'team-flow.config.json');
117
+ }
118
+
119
+ return {
120
+ ok: true,
121
+ ...detection,
122
+ configPath: join(root, '.team-flow', 'team-flow.config.json'),
123
+ gitRootConfig,
124
+ };
125
+ }
126
+
127
+ /**
128
+ * 读 git root 现有 config 的 glaf4_dev.enabled(第三轮复评 Important① OR 聚合用)
129
+ * @param {string} gitRoot
130
+ * @returns {boolean}
131
+ */
132
+ function readGitRootGlaf4Enabled(gitRoot) {
133
+ const cfgPath = join(gitRoot, '.team-flow', 'team-flow.config.json');
134
+ if (!existsSync(cfgPath)) return false;
135
+ try {
136
+ const cfg = JSON.parse(readFileSync(cfgPath, 'utf-8'));
137
+ return cfg?.glaf4_dev?.enabled === true;
138
+ } catch {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * 读目标 root 现有 config 的 glaf4_dev.delegation_mode(第三轮复评 Minor:保留已有值)
145
+ * @param {string} root
146
+ * @returns {string|null}
147
+ */
148
+ function readGlaf4DelegationMode(root) {
149
+ const cfgPath = join(root, '.team-flow', 'team-flow.config.json');
150
+ if (!existsSync(cfgPath)) return null;
151
+ try {
152
+ const cfg = JSON.parse(readFileSync(cfgPath, 'utf-8'));
153
+ const mode = cfg?.glaf4_dev?.delegation_mode;
154
+ return typeof mode === 'string' ? mode : null;
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * 解析 execution-contract.md `## GLAF4 Delegation` 段的 write_set_boundary 列表(v2.1 §6.2 权威源)
162
+ * @param {string} changeDir
163
+ * @returns {string[]|null} 契约段声明的边界文件清单(无段/无 write_set_boundary 时 null)
164
+ */
165
+ export function readContractWriteSetBoundary(changeDir) {
166
+ const contractPath = join(changeDir, 'execution-contract.md');
167
+ if (!existsSync(contractPath)) return null;
168
+ const content = readFileSync(contractPath, 'utf-8');
169
+ // 复评 I1 表头盲区(2026-08-18):模板表头为 `## GLAF4 Delegation(v2.1 §6.1/§6.2,…)`(带全角括号后缀)。
170
+ // 第四轮 Minor-2(2026-08-18):精确表头锚定——表头仅允许裸 `GLAF4 Delegation` 或带全角/半角括号注释,
171
+ // 排除 `## GLAF4 Delegation Notes` 这类前缀标题劫持(Notes 非括号注释 → 不匹配)。
172
+ const headerRe = /##\s*GLAF4 Delegation(?:\s*[((][^))\n]*[))])?\s*\n/;
173
+ const sections = [...content.matchAll(new RegExp(headerRe.source + '([\\s\\S]*?)(?=\\n##\\s|\\n#\\s|$)', 'g'))];
174
+ for (const section of sections) {
175
+ const boundaryMatch = section[1].match(/- write_set_boundary:\s*\n((?:[ \t]+- .+\n?)+)/);
176
+ if (boundaryMatch) {
177
+ return boundaryMatch[1].split('\n')
178
+ .map(l => l.trim().replace(/^- /, ''))
179
+ .filter(Boolean);
180
+ }
181
+ }
182
+ return null;
183
+ }
184
+
185
+ /**
186
+ * DP-4 委托方案确认
187
+ * @param {string} changeDir change 目录
188
+ * @param {object} input { mode, writeSet }
189
+ * @returns {object} { ok, dp_4_glaf4_mode, dp_4_write_set_hash, writeSetPath, contractBoundaryMismatch, preModeWarning }
190
+ */
191
+ export function confirmDelegation(changeDir, { mode, writeSet }) {
192
+ if (!GLAF4_DELEGATION_MODES.includes(mode)) {
193
+ throw new Error(`--mode 必须为 GLAF4_DELEGATION_MODES 之一: ${GLAF4_DELEGATION_MODES.join(', ')}`);
194
+ }
195
+ if (!writeSet || !Array.isArray(writeSet.files) || writeSet.files.length === 0) {
196
+ throw new Error('--write-set 必须为 { files: string[] },files 为 change 边界文件清单');
197
+ }
198
+
199
+ // L2(2026-08-18):前置校验——确认前应已选 glaf4-delegation 执行模式(tf execution plan)
200
+ const state = readState(changeDir);
201
+ let preModeWarning = null;
202
+ if (state.execution_mode !== 'glaf4-delegation') {
203
+ preModeWarning = `state.execution_mode=${state.execution_mode || 'null'},非 glaf4-delegation——请先执行 `
204
+ + '`tf execution plan --mode glaf4-delegation --confirm` 选定委托模式(v2.1 §6.6)';
205
+ }
206
+
207
+ const path = writeSetPath(changeDir);
208
+ mkdirSync(join(path, '..'), { recursive: true });
209
+ writeFileSync(path, JSON.stringify(writeSet, null, 2), 'utf-8');
210
+
211
+ // I1(2026-08-18):契约段 write_set_boundary vs JSON diff,不一致 WARN(JSON 为机械权威,契约段为展示层)
212
+ const contractBoundary = readContractWriteSetBoundary(changeDir);
213
+ const contractBoundaryMismatch = [];
214
+ if (contractBoundary) {
215
+ const jsonFiles = (writeSet.files || []).map(f => normalize(f).replace(/\\/g, '/'));
216
+ const contractFiles = contractBoundary.map(f => normalize(f).replace(/\\/g, '/'));
217
+ for (const f of contractFiles) {
218
+ if (!jsonFiles.includes(f)) contractBoundaryMismatch.push(f);
219
+ }
220
+ for (const f of jsonFiles) {
221
+ if (!contractFiles.includes(f)) contractBoundaryMismatch.push(f);
222
+ }
223
+ }
224
+
225
+ const writeSetHash = hashWriteSet(writeSet);
226
+ state.dp_4_glaf4_mode = mode;
227
+ state.dp_4_write_set_hash = writeSetHash;
228
+ writeState(changeDir, state);
229
+
230
+ return {
231
+ ok: true,
232
+ dp_4_glaf4_mode: mode,
233
+ dp_4_write_set_hash: writeSetHash,
234
+ writeSetPath: path,
235
+ contractBoundaryMismatch: contractBoundaryMismatch.length ? contractBoundaryMismatch : null,
236
+ preModeWarning,
237
+ };
238
+ }
239
+
240
+ /**
241
+ * 从 run-dir 收集 glaf4-dev 实际写入文件(draft-result.json outputs.actual_files / claimed_files)
242
+ * @param {string} runDir glaf4-dev run 目录
243
+ * @returns {string[]} 相对 target_root 的文件路径(去重)
244
+ */
245
+ export function collectActualWrites(runDir) {
246
+ const files = new Set();
247
+ const stagesDir = join(runDir, 'stages');
248
+ if (!existsSync(stagesDir)) return [...files];
249
+
250
+ const stages = readdirSync(stagesDir);
251
+ for (const stage of stages) {
252
+ const attemptsDir = join(stagesDir, stage, 'attempts');
253
+ if (!existsSync(attemptsDir)) continue;
254
+ const attempts = readdirSync(attemptsDir);
255
+ for (const attempt of attempts) {
256
+ const draftPath = join(attemptsDir, attempt, 'draft-result.json');
257
+ if (!existsSync(draftPath)) continue;
258
+ try {
259
+ const draft = JSON.parse(readFileSync(draftPath, 'utf-8'));
260
+ const actual = draft?.outputs?.actual_files || [];
261
+ const claimed = draft?.claimed_files || [];
262
+ for (const f of [...actual, ...claimed]) {
263
+ if (typeof f === 'string' && f.trim()) files.add(f.trim());
264
+ }
265
+ } catch {
266
+ // 损坏的 draft 忽略
267
+ }
268
+ }
269
+ }
270
+ return [...files];
271
+ }
272
+
273
+ /**
274
+ * 判断写入文件是否在允许集内(v2.1 §6.3)
275
+ * @param {string} file 相对 target_root 的文件路径(正斜杠分隔)
276
+ * @returns {boolean}
277
+ */
278
+ export function isInAllowedSet(file) {
279
+ const norm = normalize(file).replace(/\\/g, '/');
280
+ for (const allowed of WRITE_SET_ALLOWED) {
281
+ const a = normalize(allowed).replace(/\\/g, '/');
282
+ if (norm === a || norm.startsWith(`${a}/`)) return true;
283
+ }
284
+ return false;
285
+ }
286
+
287
+ /**
288
+ * write-set 对账(门禁级):glaf4-dev 实际写入 ⊆ change 边界 + 允许集
289
+ * @param {string} changeDir change 目录
290
+ * @param {string} runDir glaf4-dev run 目录
291
+ * @returns {object} { ok, violations[], actualWrites[], inBoundary, inAllowed }
292
+ */
293
+ export function verifyWriteSet(changeDir, runDir) {
294
+ const wsPath = writeSetPath(changeDir);
295
+ if (!existsSync(wsPath)) {
296
+ return { ok: false, violations: ['write-set.json 缺失——先执行 glaf4-delegation confirm(DP-4 委托确认)'], actualWrites: [] };
297
+ }
298
+ const writeSet = JSON.parse(readFileSync(wsPath, 'utf-8'));
299
+ const boundary = new Set((writeSet.files || []).map(f => normalize(f).replace(/\\/g, '/')));
300
+
301
+ const actualWrites = collectActualWrites(runDir);
302
+ const violations = [];
303
+ let inBoundary = 0;
304
+ let inAllowed = 0;
305
+ for (const file of actualWrites) {
306
+ const norm = normalize(file).replace(/\\/g, '/');
307
+ if (boundary.has(norm) || [...boundary].some(b => norm.startsWith(`${b}/`))) {
308
+ inBoundary++;
309
+ } else if (isInAllowedSet(norm)) {
310
+ inAllowed++;
311
+ } else {
312
+ violations.push(norm);
313
+ }
314
+ }
315
+
316
+ return { ok: violations.length === 0, violations, actualWrites, inBoundary, inAllowed };
317
+ }
318
+
319
+ /**
320
+ * I4(2026-08-18):partial writes 落盘清单(v2.1 §6.7)
321
+ * BLOCKED 终局时扫 run 实际写入写 delegation_partial_writes(JSON 字符串),
322
+ * 人工核对从"扫盘"降为"确认清单"。不改 delegation_status(由编排决定置 failed/其他)。
323
+ * @param {string} changeDir change 目录
324
+ * @param {string} runDir glaf4-dev run 目录
325
+ * @returns {object} { ok, partialWrites[], count }
326
+ */
327
+ export function recordPartialWrites(changeDir, runDir) {
328
+ const partialWrites = collectActualWrites(runDir);
329
+ const state = readState(changeDir);
330
+ state.delegation_partial_writes = JSON.stringify(partialWrites);
331
+ writeState(changeDir, state);
332
+ return { ok: true, partialWrites, count: partialWrites.length };
333
+ }
334
+
335
+ /**
336
+ * task 完成度对账(审计输出):tasks.md targets 注解统计
337
+ * @param {string} changeDir change 目录
338
+ * @returns {object} { ok, totalTasks, doneTasks, annotatedTasks, missingAnnotation }
339
+ */
340
+ export function verifyTasks(changeDir) {
341
+ const tasksPath = join(changeDir, 'tasks.md');
342
+ if (!existsSync(tasksPath)) {
343
+ return { ok: false, totalTasks: 0, doneTasks: 0, annotatedTasks: 0, missingAnnotation: 0 };
344
+ }
345
+ const content = readFileSync(tasksPath, 'utf-8');
346
+ const taskLines = content.split('\n').filter(l => /^- \[[ xX]\] /.test(l));
347
+ const totalTasks = taskLines.length;
348
+ const doneTasks = taskLines.filter(l => /^- \[[xX]\] /.test(l)).length;
349
+ const annotatedTasks = taskLines.filter(l => /·\s*targets:\s*/.test(l)).length;
350
+
351
+ return {
352
+ ok: doneTasks === totalTasks && totalTasks > 0,
353
+ totalTasks,
354
+ doneTasks,
355
+ annotatedTasks,
356
+ missingAnnotation: totalTasks - annotatedTasks,
357
+ };
358
+ }
359
+
360
+ /**
361
+ * 6.7 官方降级:删 plan 重走 DP-4(revise 只允许 sdd,无法切换 → 删除 plan + 清理 execution_plan_hash)
362
+ * @param {string} changeDir change 目录
363
+ * @returns {object} { ok, deletedPlan, cleanedHash }
364
+ */
365
+ export function resetDelegation(changeDir) {
366
+ const overlay = getOverlayPaths(changeDir);
367
+ const planPath = overlay.executionPlan;
368
+ let deletedPlan = false;
369
+ if (existsSync(planPath)) {
370
+ rmSync(planPath, { force: true });
371
+ deletedPlan = true;
372
+ }
373
+ // 同时清理 execution-recommendation receipt(删 plan 后需重新 recommend)
374
+ if (overlay.executionRecommendation && existsSync(overlay.executionRecommendation)) {
375
+ rmSync(overlay.executionRecommendation, { force: true });
376
+ }
377
+
378
+ const state = readState(changeDir);
379
+ state.execution_plan_hash = null;
380
+ state.execution_plan_revision = null;
381
+ state.execution_mode = null;
382
+ // L3(2026-08-18):清理 dp_4 委托决策与 delegation 状态位,避免 failed 残留继续阻断 closing
383
+ state.dp_4_glaf4_mode = null;
384
+ state.dp_4_write_set_hash = null;
385
+ state.delegation_status = null;
386
+ state.delegation_retry_count = null;
387
+ state.delegation_partial_writes = null;
388
+ writeState(changeDir, state);
389
+
390
+ return { ok: true, deletedPlan, cleanedHash: state.execution_plan_hash === null };
391
+ }
392
+
393
+ // ── CLI ─────────────────────────────────────────────────────────────────────
394
+
395
+ export async function run(args) {
396
+ const positionals = args.filter(a => !a.startsWith('--'));
397
+ const flags = new Set(args.filter(a => a.startsWith('--') && a !== '--mode' && a !== '--write-set' && a !== '--run-dir' && a !== '--target-root' && a !== '--json'));
398
+ const opt = (name) => {
399
+ const i = args.indexOf(`--${name}`);
400
+ return i >= 0 ? args[i + 1] : undefined;
401
+ };
402
+ const subcommand = positionals[0];
403
+ const changeDir = positionals[1];
404
+ const json = flags.has('--json') || args.includes('--json');
405
+
406
+ const usage = `Usage:
407
+ tf glaf4-delegation detect <root> [--delegation-mode <auto|manual|off>]
408
+ tf glaf4-delegation confirm <change-dir> --mode <mode> --write-set <json-file>
409
+ tf glaf4-delegation verify-write-set <change-dir> --run-dir <run-dir>
410
+ tf glaf4-delegation verify-tasks <change-dir>
411
+ tf glaf4-delegation record-partial <change-dir> --run-dir <run-dir>
412
+ tf glaf4-delegation reset <change-dir>`;
413
+
414
+ if (!subcommand || !changeDir) {
415
+ console.error(usage);
416
+ process.exit(2);
417
+ }
418
+
419
+ const print = (ok, data, msg) => {
420
+ if (json) console.log(JSON.stringify({ ok, ...data }));
421
+ else console.log(`${ok ? '✅' : '❌'} ${msg}`);
422
+ };
423
+
424
+ try {
425
+ if (subcommand === 'detect') {
426
+ const mode = opt('delegation-mode');
427
+ // 第四轮复评 Minor-1(2026-08-18):枚举校验——非法值(如 'foo')等同 auto 放行会让用户配置失真
428
+ if (mode !== undefined && !['auto', 'manual', 'off'].includes(mode)) {
429
+ console.error(`--delegation-mode 必须为 auto/manual/off 之一(实际: ${mode})`);
430
+ process.exit(2);
431
+ }
432
+ const result = detectAndWriteConfig(changeDir, mode);
433
+ const msg = result.glaf4_dev_available
434
+ ? `探测: is_glaf4_java=${result.is_glaf4_java}, glaf4-dev v${result.glaf4_dev_version} 可用, delegation_mode=${result.delegation_mode} → config 已写 ${result.configPath}`
435
+ : `探测: is_glaf4_java=${result.is_glaf4_java}, glaf4-dev 不可用 → config 已写 ${result.configPath}`;
436
+ print(result.ok, result, msg);
437
+ } else if (subcommand === 'confirm') {
438
+ const mode = opt('mode');
439
+ const writeSetFile = opt('write-set');
440
+ if (!mode || !writeSetFile) { console.error('confirm 需要 --mode 与 --write-set'); process.exit(2); }
441
+ const writeSet = JSON.parse(readFileSync(resolve(writeSetFile), 'utf-8'));
442
+ const result = confirmDelegation(changeDir, { mode, writeSet });
443
+ print(result.ok, result, `DP-4 委托确认: mode=${result.dp_4_glaf4_mode}, write_set_hash=${result.dp_4_write_set_hash.slice(0, 12)}…`);
444
+ if (result.preModeWarning) {
445
+ console.warn(`⚠️ ${result.preModeWarning}`);
446
+ }
447
+ if (result.contractBoundaryMismatch) {
448
+ console.warn(`⚠️ execution-contract GLAF4 Delegation 段 write_set_boundary 与 write-set.json 不一致:\n ${result.contractBoundaryMismatch.join('\n ')}\n 建议同步契约段(JSON 为机械权威源)`);
449
+ }
450
+ } else if (subcommand === 'record-partial') {
451
+ const runDir = opt('run-dir');
452
+ if (!runDir) { console.error('record-partial 需要 --run-dir'); process.exit(2); }
453
+ const result = recordPartialWrites(changeDir, resolve(runDir));
454
+ print(result.ok, result, `partial writes 清单已记录 ${result.count} 个文件 → delegation_partial_writes`);
455
+ } else if (subcommand === 'verify-write-set') {
456
+ const runDir = opt('run-dir');
457
+ if (!runDir) { console.error('verify-write-set 需要 --run-dir'); process.exit(2); }
458
+ const result = verifyWriteSet(changeDir, resolve(runDir));
459
+ const msg = result.ok
460
+ ? `write-set 对账通过: ${result.inBoundary} 个边界内写入, ${result.inAllowed} 个允许集写入`
461
+ : `write-set 对账 FAIL: ${result.violations.length} 个越界文件\n ${result.violations.join('\n ')}`;
462
+ print(result.ok, result, msg);
463
+ if (!result.ok) process.exitCode = 1;
464
+ } else if (subcommand === 'verify-tasks') {
465
+ const result = verifyTasks(changeDir);
466
+ const msg = `task 审计: ${result.doneTasks}/${result.totalTasks} 完成, ${result.annotatedTasks} 含 targets 注解, ${result.missingAnnotation} 缺注解`;
467
+ print(result.ok, result, msg);
468
+ } else if (subcommand === 'reset') {
469
+ const result = resetDelegation(changeDir);
470
+ print(result.ok, result, `6.7 降级: plan ${result.deletedPlan ? '已删除' : '不存在'}, execution_plan_hash 已清理 → 重走 DP-4`);
471
+ } else {
472
+ console.error(`未知子命令: ${subcommand}`);
473
+ console.error(usage);
474
+ process.exit(2);
475
+ }
476
+ } catch (err) {
477
+ console.error(`❌ glaf4-delegation error: ${err.message}`);
478
+ process.exit(1);
479
+ }
480
+ }
481
+
482
+ // 直接运行
483
+ if (import.meta.url === `file://${process.argv[1]}`) {
484
+ run(process.argv.slice(2)).catch(err => {
485
+ console.error(err);
486
+ process.exit(1);
487
+ });
488
+ }