@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 +190 -87
- package/cli/src/auto.js +41 -18
- package/cli/src/diagnostics.js +270 -191
- package/cli/src/guide.js +92 -25
- package/cli/src/philosophy.js +51 -17
- package/cli/src/verify.js +38 -8
- package/package.json +4 -4
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(`
|
|
139
|
-
for (const
|
|
140
|
-
if (result.skipped.length) {
|
|
141
|
-
console.log(
|
|
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
|
|
374
|
-
|
|
375
|
-
if (
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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 (
|
|
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
|
-
|
|
397
|
-
|
|
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('
|
|
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
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
console.log(
|
|
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
|
|
2
|
+
// 存储机制:.loom/auto 文件内容 = 模式名(auto-loop / auto-design),不存在 = manual
|
|
3
3
|
// 心跳:每次 guide 调用时写 .loom/heartbeat.json(时间戳 + stage + next_command)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
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
|
-
*
|
|
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
|
|
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
|
-
|
|
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 {{
|
|
62
|
+
* @returns {{ mode: string, heartbeat: object|null }}
|
|
40
63
|
*/
|
|
41
64
|
export function autoStatus(loomRoot) {
|
|
42
|
-
const
|
|
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 {
|
|
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
|
-
*
|
|
83
|
-
*
|
|
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
|
|
90
|
-
if (
|
|
91
|
-
|
|
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
|
}
|