@haaaiawd/loom 0.8.0 → 0.10.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.
package/cli/bin/loom.js CHANGED
@@ -12,15 +12,15 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
12
12
 
13
13
  import { getNextIntent, getStatus, getDependencyGraph, getIntent, loadIntentMap, updateIntentStatus, getNarrative } from '../src/intent-map.js';
14
14
  import { getPhilosophy, listPhilosophyFiles, validateInspirationSources, validatePartDecomposition } from '../src/philosophy.js';
15
- import { writeVerification, getVerificationHistory, getPendingVerifications, listVerifications, getVerificationContract } from '../src/verify.js';
15
+ import { writeVerification, createQuickVerification, getVerificationHistory, getPendingVerifications, listVerifications, getVerificationContract } from '../src/verify.js';
16
16
  import { initProject } from '../src/init.js';
17
17
  import { activateRole } from '../src/activate.js';
18
18
  import { listVersions, newVersion, useVersion, diffVersions } from '../src/version.js';
19
19
  import { doctor, contextSummary, traceIntent, reverseDep, reverseRef } from '../src/diagnostics.js';
20
20
  import { getHelpTopic, listHelpTopics } from '../src/help.js';
21
21
  import { guideProject } from '../src/guide.js';
22
- import { isAutoOn, autoOn, autoOff, autoStatus } from '../src/auto.js';
23
- import { generatePreviewPrompt, getPreviewStatus } from '../src/preview.js';
22
+ import { isAutoOn, autoOn, autoOff, autoStatus, getAutoMode } from '../src/auto.js';
23
+ import { generatePreviewPrompt, getPreviewStatus } from '../src/preview.js';
24
24
 
25
25
  // ─── 路径解析 ──────────────────────────────────────────
26
26
  // findLoomRoot / findVersionDir / readCurrentPointer 已提取到 shared/paths.js
@@ -126,8 +126,37 @@ try {
126
126
  console.log(`${id} status 已更新为 ${newStatus}`);
127
127
  break;
128
128
  }
129
+ case 'done': {
130
+ // loom intent done <id> — 自动走 pending→in_progress→completed,检查验证记录
131
+ const id = rest[0];
132
+ if (!id) die('用法: loom intent done <id>');
133
+ const verificationsDir = getVerificationsDir(versionDir);
134
+ // 检查有没有验证记录
135
+ const history = getVerificationHistory(verificationsDir, id);
136
+ if (!history || history.records.length === 0) {
137
+ die(`${id} 没有验证记录。先跑: loom verify pass ${id} --summary "..."`);
138
+ }
139
+ // 检查最新验证记录是否 passed
140
+ const latest = history.records[history.records.length - 1];
141
+ if (latest.verdict !== 'passed') {
142
+ die(`${id} 最新验证记录是 ${latest.verdict}(非 passed)。只有 passed 的 Intent 才能 done。`);
143
+ }
144
+ // 获取当前状态,自动走两步
145
+ const intent = getIntent(versionDir, id);
146
+ const currentStatus = intent.status;
147
+ if (currentStatus === 'completed') {
148
+ console.log(`${id} 已经是 completed,无需操作`);
149
+ break;
150
+ }
151
+ if (currentStatus === 'pending') {
152
+ updateIntentStatus(versionDir, id, 'in_progress');
153
+ }
154
+ updateIntentStatus(versionDir, id, 'completed');
155
+ console.log(`${id} 已完成(${currentStatus} → completed)`);
156
+ break;
157
+ }
129
158
  default:
130
- die(`未知子命令: intent ${sub}\n用法: loom intent [next|status|graph|get <id>|narrative <id>|validate|trace <id>|reverse-dep <id>|reverse-ref <anchor>|update <id> --status <...>]`);
159
+ die(`未知子命令: intent ${sub}\n用法: loom intent [next|status|graph|get <id>|narrative <id>|validate|trace <id>|reverse-dep <id>|reverse-ref <anchor>|update <id> --status <...>|done <id>]`);
131
160
  }
132
161
  break;
133
162
  }
@@ -135,11 +164,10 @@ try {
135
164
  case 'init': {
136
165
  const result = initProject(cwd());
137
166
  console.log('LOOM 项目已初始化');
138
- console.log(` 创建: ${result.created.length} 项`);
139
- for (const c of result.created) console.log(` + ${c}`);
140
- if (result.skipped.length) {
141
- console.log(` 跳过(已存在): ${result.skipped.length} 项`);
142
- for (const s of result.skipped) console.log(` - ${s}`);
167
+ for (const c of result.created) console.log(` [created] ${c}`);
168
+ for (const s of result.skipped) console.log(` [skipped] ${s} (already exists)`);
169
+ if (result.created.length === 0 && result.skipped.length > 0) {
170
+ console.log(' 所有文件已存在,无需操作。');
143
171
  }
144
172
  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
145
173
  console.log('To Agent: 运行 loom guide 诊断当前阶段,按引导执行');
@@ -187,6 +215,17 @@ try {
187
215
  const allIssues = [...inspiration.issues, ...decomposition.issues];
188
216
  const allPassed = inspiration.passed && decomposition.passed;
189
217
 
218
+ // --json: 结构化输出,供 Agent 程序化消费
219
+ if (argv.includes('--json')) {
220
+ output({
221
+ passed: allPassed,
222
+ issues: allIssues,
223
+ sources: inspiration.sources.map(({ file, sources }) => ({ file, count: sources.length })),
224
+ parts: decomposition.parts,
225
+ });
226
+ exit(allPassed ? 0 : 1);
227
+ }
228
+
190
229
  if (allPassed) {
191
230
  console.log('✓ 哲学文档校验通过');
192
231
  console.log(' 灵感来源:');
@@ -271,8 +310,34 @@ try {
271
310
  }
272
311
  break;
273
312
  }
313
+ case 'pass':
314
+ case 'fail': {
315
+ // loom verify pass <id> --summary "..." [--reproduction-command "..."]
316
+ // loom verify fail <id> --summary "..." [--deviation "..."] [--reproduction-command "..."]
317
+ const id = rest[0];
318
+ if (!id) die(`用法: loom verify ${sub} <id> --summary "..." [--reproduction-command "..."]${sub === 'fail' ? ' [--deviation "..."]' : ''}`);
319
+ const summaryIdx = argv.indexOf('--summary');
320
+ const reproIdx = argv.indexOf('--reproduction-command');
321
+ const deviationIdx = argv.indexOf('--deviation');
322
+ const summary = summaryIdx !== -1 ? argv[summaryIdx + 1] : null;
323
+ if (!summary) die(`缺少 --summary: loom verify ${sub} ${id} --summary "..."`);
324
+ const extras = {};
325
+ if (reproIdx !== -1 && argv[reproIdx + 1]) extras.reproduction_command = argv[reproIdx + 1];
326
+ if (sub === 'fail' && deviationIdx !== -1 && argv[deviationIdx + 1]) extras.deviation_detail = argv[deviationIdx + 1];
327
+ const verdict = sub === 'pass' ? 'passed' : 'deviated';
328
+ const result = createQuickVerification(verificationsDir, id, verdict, summary, extras);
329
+ console.log(`验证记录已写入: ${result.filePath}`);
330
+ console.log(` 轮次: ${result.round}, verdict: ${verdict}`);
331
+ if (verdict === 'deviated') {
332
+ console.log(` deviated 累计: ${result.deviated_count} 轮`);
333
+ if (result.should_escalate) {
334
+ console.log(` ⚠ 达到 3 轮上限,应升级为 blocked——Keeper 应执行: loom intent update ${id} --status blocked`);
335
+ }
336
+ }
337
+ break;
338
+ }
274
339
  default:
275
- die(`未知子命令: verify ${sub}\n用法: loom verify [contract <id>|history <id>|pending|list|write --json-file <path>|--json <string>]`);
340
+ die(`未知子命令: verify ${sub}\n用法: loom verify [contract <id>|history <id>|pending|list|write --json-file <path>|--json <string>|pass <id> --summary "..."|fail <id> --summary "..."]`);
276
341
  }
277
342
  break;
278
343
  }
@@ -337,7 +402,11 @@ try {
337
402
  for (const issue of issues) {
338
403
  const icon = issue.severity === 'fatal' ? '☠' : issue.severity === 'high' ? '⚠' : '·';
339
404
  console.log(` ${icon} [${issue.severity}] ${issue.type}: ${issue.msg}`);
405
+ if (issue.fix_hint) {
406
+ console.log(` → 修复: ${issue.fix_hint}`);
407
+ }
340
408
  }
409
+ console.log(`\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/PART_DECOMPOSITION.md + dimensions/SEARCH_METHODOLOGY.md。`);
341
410
  }
342
411
  break;
343
412
  }
@@ -368,22 +437,39 @@ try {
368
437
  break;
369
438
  }
370
439
 
371
- case 'guide': {
372
- const dryRun = argv.includes('--dry-run');
373
- const result = guideProject(cwd(), { dryRun });
374
- console.log(`阶段 ${result.stage_num}: ${result.stage}`);
375
- if (dryRun) {
376
- console.log('诊断: dry-run(不写 heartbeat)');
377
- }
378
- if (result.auto) {
379
- console.log(`模式: AUTO(自动执行,不等确认)`);
380
- } else {
381
- console.log(`模式: 手动(每步需用户确认)`);
440
+ case 'guide': {
441
+ const dryRun = argv.includes('--dry-run');
442
+ const jsonOut = argv.includes('--json');
443
+ const result = guideProject(cwd(), { dryRun });
444
+ if (jsonOut) {
445
+ output(result);
446
+ break;
447
+ }
448
+ console.log(`阶段 ${result.stage_num}: ${result.stage}`);
449
+ if (dryRun) {
450
+ console.log('诊断: dry-run(不写 heartbeat)');
382
451
  }
452
+ const modeDesc = {
453
+ 'manual': '手动(每步需确认)',
454
+ 'auto-loop': 'AUTO auto-loop(设计阶段需 review,Intent Loop 自动)',
455
+ 'auto-design': 'AUTO auto-design(全部自动)',
456
+ };
457
+ console.log(`模式: ${modeDesc[result.auto_mode] || result.auto_mode}`);
383
458
  console.log(`\n${result.message}`);
384
459
  console.log(`\n下一步: ${result.next_action}`);
385
460
  console.log(` → ${result.next_command}`);
386
- if (!result.auto && result.stage_num > 0 && result.stage_num < 6) {
461
+ if (result.inputs && result.inputs.length > 0) {
462
+ console.log(`\n需要读取:`);
463
+ for (const f of result.inputs) console.log(` - ${f}`);
464
+ }
465
+ if (result.outputs && result.outputs.length > 0) {
466
+ console.log(`\n需要产出:`);
467
+ for (const f of result.outputs) console.log(` - ${f}`);
468
+ }
469
+ if (result.verify_command) {
470
+ console.log(`\n完成后校验: ${result.verify_command}`);
471
+ }
472
+ if (result.auto_mode === 'manual' && result.stage_num > 0 && result.stage_num < 6) {
387
473
  console.log(`\n提示: 开启 AUTO 模式可自动连续执行 — loom auto on`);
388
474
  }
389
475
  break;
@@ -392,76 +478,93 @@ try {
392
478
  case 'auto': {
393
479
  const loomRoot = findLoomRoot();
394
480
  switch (sub) {
395
- case 'on':
396
- autoOn(loomRoot);
397
- console.log('AUTO 模式已开启。Agent 将自动连续执行,不等用户确认。');
481
+ case 'on': {
482
+ // loom auto on → auto-loop(默认)
483
+ // loom auto on --design → auto-design
484
+ const wantDesign = argv.includes('--design');
485
+ const mode = wantDesign ? 'auto-design' : 'auto-loop';
486
+ autoOn(loomRoot, mode);
487
+ if (mode === 'auto-design') {
488
+ console.log('AUTO 模式: auto-design(全部自动,含哲学/愿景/架构)');
489
+ console.log('Agent 自动连续执行所有阶段,不等人类确认。');
490
+ } else {
491
+ console.log('AUTO 模式: auto-loop(Intent Loop 自动,设计阶段需 review)');
492
+ console.log(' - stage 1-3(哲学/愿景/架构):自动执行但需人类 review');
493
+ console.log(' - stage 4+(Intent Loop):自动连续执行');
494
+ }
398
495
  console.log('核心契约: 持续运行,除非出意外否则不允许私自停止。');
399
496
  console.log(' - L3 human_review 由 Keeper 自主判定,不停下等人类');
400
497
  console.log(' - 唯一允许停下的情况: blocked(依赖阻塞/契约无法判定/连续 3 轮 deviated 升级)');
401
- console.log('关闭: loom auto off');
498
+ console.log('切换: loom auto on --design | loom auto off');
402
499
  break;
500
+ }
403
501
  case 'off':
404
502
  autoOff(loomRoot);
405
- console.log('AUTO 模式已关闭。每步需要用户确认。');
503
+ console.log('AUTO 模式: manual(每步需人类确认)');
406
504
  break;
407
505
  case 'status': {
408
506
  const status = autoStatus(loomRoot);
409
- if (status.on) {
410
- console.log(`AUTO 模式: 开启(自 ${status.since})`);
411
- console.log(' 规则: stage 1-3(哲学/愿景/架构)需人类 review,stage 4+(Intent Loop)自动执行');
412
- if (status.heartbeat) {
413
- const hb = status.heartbeat;
414
- console.log(` 心跳: ${hb.timestamp}`);
415
- console.log(` 阶段: ${hb.stage} (stage ${hb.stage_num})`);
416
- console.log(` 下一步: ${hb.next_action}`);
417
- console.log(` 命令: ${hb.next_command}`);
418
- } else {
419
- console.log(' 心跳: 尚未记录(运行 loom guide 后生成)');
420
- }
421
- } else {
422
- console.log('AUTO 模式: 关闭(所有阶段都需人类确认)');
507
+ const modeDesc = {
508
+ 'manual': 'manual(每步需人类确认)',
509
+ 'auto-loop': 'auto-loop(Intent Loop 自动,设计阶段需 review)',
510
+ 'auto-design': 'auto-design(全部自动,含哲学/愿景/架构)',
511
+ };
512
+ console.log(`AUTO 模式: ${modeDesc[status.mode] || status.mode}`);
513
+ if (status.mode === 'auto-loop') {
514
+ console.log(' 规则: stage 1-3 需人类 review,stage 4+ 自动执行');
515
+ } else if (status.mode === 'auto-design') {
516
+ console.log(' 规则: 全部阶段自动执行,不需人类 review');
517
+ }
518
+ if (status.heartbeat) {
519
+ const hb = status.heartbeat;
520
+ console.log(` 心跳: ${hb.timestamp}`);
521
+ console.log(` 阶段: ${hb.stage} (stage ${hb.stage_num})`);
522
+ console.log(` 下一步: ${hb.next_action}`);
523
+ console.log(` 命令: ${hb.next_command}`);
524
+ } else if (status.mode !== 'manual') {
525
+ console.log(' 心跳: 尚未记录(运行 loom guide 后生成)');
423
526
  }
424
527
  break;
425
528
  }
426
529
  default:
427
- die(`未知子命令: auto ${sub}\n用法: loom auto [on|off|status]`);
530
+ die(`未知子命令: auto ${sub}\n用法: loom auto [on [--design]|off|status]`);
428
531
  }
429
532
  break;
430
533
  }
431
534
 
432
- case 'preview': {
433
- const previewFile = join(cwd(), 'loom-preview.html');
434
- if (argv.includes('--help') || argv.includes('-h')) {
435
- console.log(`用法:
436
- loom preview 打开新鲜 preview;过期时提示重新生成
437
- loom preview --regen 输出生成提示词,让 Agent 重写 loom-preview.html
438
- loom preview status 检查 preview 是否存在、是否新鲜
439
- loom preview --stale 强行打开过期 preview
440
- loom preview --help 显示本帮助`);
441
- break;
442
- }
443
- const status = getPreviewStatus(cwd());
444
- const hasPreview = status.exists;
445
- const regenOnly = argv.includes('--regen') || argv.includes('-r');
446
- const openStale = argv.includes('--stale');
447
-
448
- if (sub === 'status') {
449
- output(status);
450
- break;
451
- }
452
-
453
- // 已有 HTML 且没指定 --regen:直接打开浏览器
454
- if (hasPreview && !regenOnly) {
455
- if (!status.fresh && !openStale) {
456
- console.log('preview 已过期:.loom 源文件比 loom-preview.html 更新。');
457
- console.log(` preview: ${status.preview_mtime || '未知'}`);
458
- console.log(` 最新源: ${status.source_latest_mtime || '未知'} ${status.latest_source_file ? `(${status.latest_source_file})` : ''}`);
459
- console.log('\n下一步: loom preview --regen');
460
- console.log('强行打开旧 preview: loom preview --stale');
461
- break;
462
- }
463
- const { spawn } = await import('node:child_process');
464
- const target = previewFile.replace(/\\/g, '/');
535
+ case 'preview': {
536
+ const previewFile = join(cwd(), 'loom-preview.html');
537
+ if (argv.includes('--help') || argv.includes('-h')) {
538
+ console.log(`用法:
539
+ loom preview 打开新鲜 preview;过期时提示重新生成
540
+ loom preview --regen 输出生成提示词,让 Agent 重写 loom-preview.html
541
+ loom preview status 检查 preview 是否存在、是否新鲜
542
+ loom preview --stale 强行打开过期 preview
543
+ loom preview --help 显示本帮助`);
544
+ break;
545
+ }
546
+ const status = getPreviewStatus(cwd());
547
+ const hasPreview = status.exists;
548
+ const regenOnly = argv.includes('--regen') || argv.includes('-r');
549
+ const openStale = argv.includes('--stale');
550
+
551
+ if (sub === 'status') {
552
+ output(status);
553
+ break;
554
+ }
555
+
556
+ // 已有 HTML 且没指定 --regen:直接打开浏览器
557
+ if (hasPreview && !regenOnly) {
558
+ if (!status.fresh && !openStale) {
559
+ console.log('preview 已过期:.loom 源文件比 loom-preview.html 更新。');
560
+ console.log(` preview: ${status.preview_mtime || '未知'}`);
561
+ console.log(` 最新源: ${status.source_latest_mtime || '未知'} ${status.latest_source_file ? `(${status.latest_source_file})` : ''}`);
562
+ console.log('\n下一步: loom preview --regen');
563
+ console.log('强行打开旧 preview: loom preview --stale');
564
+ break;
565
+ }
566
+ const { spawn } = await import('node:child_process');
567
+ const target = previewFile.replace(/\\/g, '/');
465
568
  if (process.platform === 'win32') {
466
569
  spawn('cmd', ['/c', 'start', target], { detached: true, stdio: 'ignore' }).unref();
467
570
  } else if (process.platform === 'darwin') {
@@ -470,9 +573,9 @@ try {
470
573
  spawn('xdg-open', [target], { detached: true, stdio: 'ignore' }).unref();
471
574
  }
472
575
  console.log(`已打开浏览器: ${previewFile}`);
473
- console.log(status.fresh ? `重新生成: loom preview --regen` : `已打开旧 preview。重新生成: loom preview --regen`);
474
- break;
475
- }
576
+ console.log(status.fresh ? `重新生成: loom preview --regen` : `已打开旧 preview。重新生成: loom preview --regen`);
577
+ break;
578
+ }
476
579
 
477
580
  // 没有 HTML 或指定 --regen:输出提示词让 AI 生成
478
581
  const prompt = generatePreviewPrompt();
@@ -508,10 +611,10 @@ To Human:
508
611
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
509
612
 
510
613
  用法:
511
- loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
512
- loom guide 诊断当前阶段,输出下一步引导
513
- loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
514
- loom auto on|off|status AUTO 模式开关(on 时 Agent 自动连续执行)
614
+ loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
615
+ loom guide 诊断当前阶段,输出下一步引导
616
+ loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
617
+ loom auto on|off|status AUTO 模式开关(on 时 Agent 自动连续执行)
515
618
  loom activate <role> 输出角色激活提示词(weaver|visionary|architect|forge|keeper)
516
619
 
517
620
  loom version list 列出所有版本(* 标记当前)
@@ -533,11 +636,11 @@ To Human:
533
636
 
534
637
  loom doctor 项目健康检查(一致性+孤儿引用+循环依赖+僵尸)
535
638
  loom context 上下文摘要(进度+下一步+待验证+风险)
536
- loom preview 打开新鲜 HTML;过期则提示重新生成
537
- loom preview status 检查 preview 是否存在、是否新鲜
538
- loom preview --regen 强制重新输出提示词(让 AI 重新生成 HTML)
539
- loom preview --stale 强行打开过期 preview
540
- loom help <topic> 分层指南(workflow|concepts|loop|version|doctor|preview)
639
+ loom preview 打开新鲜 HTML;过期则提示重新生成
640
+ loom preview status 检查 preview 是否存在、是否新鲜
641
+ loom preview --regen 强制重新输出提示词(让 AI 重新生成 HTML)
642
+ loom preview --stale 强行打开过期 preview
643
+ loom help <topic> 分层指南(workflow|concepts|loop|version|doctor|preview)
541
644
 
542
645
  loom philosophy get <anchor> 按锚点加载哲学章节
543
646
  loom philosophy list 列出哲学文档文件
package/cli/src/auto.js CHANGED
@@ -1,31 +1,54 @@
1
1
  // auto — AUTO 模式开关 + 心跳机制
2
- // 存储机制:.loom/auto 文件存在 = on,不存在 = off
2
+ // 存储机制:.loom/auto 文件内容 = 模式名(auto-loop / auto-design),不存在 = manual
3
3
  // 心跳:每次 guide 调用时写 .loom/heartbeat.json(时间戳 + stage + next_command)
4
- // AUTO on(默认):stage 4+(Intent Loop)自动跑,stage 1-3(哲学/愿景/架构)仍需人类 review
5
- // AUTO off:所有阶段都需人类确认,每步拆得更碎
4
+ //
5
+ // 三种模式:
6
+ // manual — 每步停,所有阶段需人类 review
7
+ // auto-loop — 只 Intent Loop(stage 4+)自动,stage 1-3 仍需人类 review(默认)
8
+ // auto-design — 哲学/愿景/架构也自动,全部阶段不需人类 review
6
9
 
7
10
  import { existsSync, writeFileSync, unlinkSync, readFileSync } from 'node:fs';
8
11
  import { join } from 'node:path';
9
12
 
13
+ const VALID_MODES = ['auto-loop', 'auto-design'];
14
+
10
15
  /**
11
- * 检查 AUTO 模式是否开启。
16
+ * 读取 .loom/auto 文件内容,返回模式名。
17
+ * 向后兼容:空文件 / 旧时间戳 / 未知内容 → auto-loop
18
+ * @param {string} loomRoot — .loom 目录路径
19
+ * @returns {string} 'manual' | 'auto-loop' | 'auto-design'
20
+ */
21
+ export function getAutoMode(loomRoot) {
22
+ const path = join(loomRoot, 'auto');
23
+ if (!existsSync(path)) return 'manual';
24
+ const content = readFileSync(path, 'utf-8').trim();
25
+ if (VALID_MODES.includes(content)) return content;
26
+ return 'auto-loop'; // 旧格式兼容
27
+ }
28
+
29
+ /**
30
+ * 检查 AUTO 模式是否开启(非 manual)。
12
31
  * @param {string} loomRoot — .loom 目录路径
13
32
  * @returns {boolean}
14
33
  */
15
34
  export function isAutoOn(loomRoot) {
16
- return existsSync(join(loomRoot, 'auto'));
35
+ return getAutoMode(loomRoot) !== 'manual';
17
36
  }
18
37
 
19
38
  /**
20
39
  * 开启 AUTO 模式。
21
40
  * @param {string} loomRoot — .loom 目录路径
41
+ * @param {string} mode — 'auto-loop' | 'auto-design'
22
42
  */
23
- export function autoOn(loomRoot) {
24
- writeFileSync(join(loomRoot, 'auto'), new Date().toISOString(), 'utf-8');
43
+ export function autoOn(loomRoot, mode = 'auto-loop') {
44
+ if (!VALID_MODES.includes(mode)) {
45
+ throw new Error(`非法 AUTO 模式: "${mode}" (合法: ${VALID_MODES.join(' | ')})`);
46
+ }
47
+ writeFileSync(join(loomRoot, 'auto'), mode, 'utf-8');
25
48
  }
26
49
 
27
50
  /**
28
- * 关闭 AUTO 模式。
51
+ * 关闭 AUTO 模式(切换到 manual)。
29
52
  * @param {string} loomRoot — .loom 目录路径
30
53
  */
31
54
  export function autoOff(loomRoot) {
@@ -36,14 +59,12 @@ export function autoOff(loomRoot) {
36
59
  /**
37
60
  * 获取 AUTO 状态描述。
38
61
  * @param {string} loomRoot — .loom 目录路径
39
- * @returns {{ on: boolean, since: string|null, heartbeat: object|null }}
62
+ * @returns {{ mode: string, heartbeat: object|null }}
40
63
  */
41
64
  export function autoStatus(loomRoot) {
42
- const path = join(loomRoot, 'auto');
43
- if (!existsSync(path)) return { on: false, since: null, heartbeat: null };
44
- const since = readFileSync(path, 'utf-8').trim();
65
+ const mode = getAutoMode(loomRoot);
45
66
  const heartbeat = readHeartbeat(loomRoot);
46
- return { on: true, since, heartbeat };
67
+ return { mode, heartbeat };
47
68
  }
48
69
 
49
70
  /**
@@ -79,15 +100,17 @@ export function readHeartbeat(loomRoot) {
79
100
 
80
101
  /**
81
102
  * 判断当前阶段是否需要人类 review。
82
- * AUTO on 时:stage 1-3(哲学/愿景/架构)需要人类 review,stage 4+ 自动跑
83
- * AUTO off 时:所有阶段都需要人类 review
103
+ * manual:全部需 review
104
+ * auto-loop:stage 1-3 review,stage 4+ 自动
105
+ * auto-design:全部自动
84
106
  * @param {string} loomRoot — .loom 目录路径
85
107
  * @param {number} stageNum — 阶段号
86
108
  * @returns {boolean} 是否需要人类 review
87
109
  */
88
110
  export function needsHumanReview(loomRoot, stageNum) {
89
- const autoOn = isAutoOn(loomRoot);
90
- if (!autoOn) return true; // AUTO off:所有阶段都需人类 review
91
- // AUTO on:stage 1-3 需人类 review,stage 4+ 自动
111
+ const mode = getAutoMode(loomRoot);
112
+ if (mode === 'manual') return true;
113
+ if (mode === 'auto-design') return false;
114
+ // auto-loop:stage 1-3 需 review,stage 4+ 自动
92
115
  return stageNum > 0 && stageNum < 4;
93
116
  }