@haaaiawd/loom 0.9.0 → 1.0.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 (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +87 -52
  3. package/cli/bin/loom.js +438 -149
  4. package/cli/help/concepts.md +93 -72
  5. package/cli/help/doctor.md +71 -121
  6. package/cli/help/loop.md +120 -135
  7. package/cli/help/patch.md +33 -0
  8. package/cli/help/preview.md +2 -1
  9. package/cli/help/version.md +92 -16
  10. package/cli/help/workflow.md +89 -100
  11. package/cli/src/activate.js +302 -73
  12. package/cli/src/auto.js +41 -18
  13. package/cli/src/diagnostics.js +223 -50
  14. package/cli/src/guide.js +127 -38
  15. package/cli/src/init.js +50 -29
  16. package/cli/src/intent-draft.js +303 -0
  17. package/cli/src/intent-map.js +540 -54
  18. package/cli/src/patch.js +214 -0
  19. package/cli/src/philosophy.js +181 -156
  20. package/cli/src/preview-prompt.md +13 -6
  21. package/cli/src/preview.js +1 -0
  22. package/cli/src/shared/intent-ref.js +38 -0
  23. package/cli/src/shared/proof-reference.js +19 -0
  24. package/cli/src/shared/verification-method.js +32 -0
  25. package/cli/src/verify.js +204 -51
  26. package/cli/src/version.js +5 -4
  27. package/dimensions/PART_DECOMPOSITION.md +42 -203
  28. package/dimensions/SEARCH_METHODOLOGY.md +101 -97
  29. package/dimensions/examples/AGENT_SYSTEM/README.md +1 -1
  30. package/dimensions/examples/CLI_TOOL/README.md +1 -1
  31. package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +28 -77
  32. package/dimensions/universal/ENGINEERING_CREED.md +30 -74
  33. package/dimensions/universal/PRODUCT_PHILOSOPHY.md +32 -70
  34. package/meta/BASELINE.md +91 -276
  35. package/meta/INTENT_LOOP.md +242 -737
  36. package/meta/PHILOSOPHY_WEAVER.md +110 -343
  37. package/meta/ROLE_ACTIVATION.md +103 -267
  38. package/package.json +4 -3
  39. package/roles/architect.md +71 -111
  40. package/roles/forge.md +87 -126
  41. package/roles/keeper.md +99 -223
  42. package/roles/visionary.md +57 -86
  43. package/templates/INTENT_MAP_TEMPLATE.json +24 -10
  44. package/templates/PHILOSOPHY_TEMPLATE.md +44 -75
  45. package/templates/VISION_TEMPLATE.md +44 -67
package/cli/bin/loom.js CHANGED
@@ -10,17 +10,20 @@ import { findLoomRoot, findVersionDir, readCurrentPointer } from '../src/shared/
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
12
 
13
- import { getNextIntent, getStatus, getDependencyGraph, getIntent, loadIntentMap, updateIntentStatus, getNarrative } from '../src/intent-map.js';
14
- import { getPhilosophy, listPhilosophyFiles, validateInspirationSources, validatePartDecomposition } from '../src/philosophy.js';
15
- import { writeVerification, getVerificationHistory, getPendingVerifications, listVerifications, getVerificationContract } from '../src/verify.js';
13
+ import { deprecateIntent, getNextIntent, getStatus, getDependencyGraph, getIntent, loadIntentMap, updateIntentStatus, getNarrative, diffIntentVersions } from '../src/intent-map.js';
14
+ import { assessPhilosophyImpact, getPhilosophy, listPhilosophyFiles, revisePhilosophy, validateInspirationSources } from '../src/philosophy.js';
15
+ import { writeVerification, createQuickVerification, getVerificationHistory, getAcrossVersionVerificationHistory, getPendingVerifications, listVerifications, getVerificationContract, isVerificationCurrent } 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';
22
+ import { isAutoOn, autoOn, autoOff, autoStatus, getAutoMode } from '../src/auto.js';
23
23
  import { generatePreviewPrompt, getPreviewStatus } from '../src/preview.js';
24
+ import { getPatch, listPatches, recordPatch, validatePatches } from '../src/patch.js';
25
+ import { addIntentDraft, finalizeIntentDraft, getIntentDraft, reviseIntentDraft } from '../src/intent-draft.js';
26
+ import { resolveIntentRef } from '../src/shared/intent-ref.js';
24
27
 
25
28
  // ─── 路径解析 ──────────────────────────────────────────
26
29
  // findLoomRoot / findVersionDir / readCurrentPointer 已提取到 shared/paths.js
@@ -64,9 +67,75 @@ try {
64
67
  break;
65
68
  }
66
69
 
67
- case 'intent': {
68
- const versionDir = findVersionDir();
69
- switch (sub) {
70
+ case 'intent': {
71
+ const versionDir = findVersionDir();
72
+ switch (sub) {
73
+ case 'add': {
74
+ const titleIdx = argv.indexOf('--title');
75
+ const dependsIdx = argv.indexOf('--depends-on');
76
+ const title = titleIdx !== -1 ? argv[titleIdx + 1] : null;
77
+ const dependencies = dependsIdx !== -1 && argv[dependsIdx + 1]
78
+ ? argv[dependsIdx + 1].split(',').map((id) => id.trim()).filter(Boolean)
79
+ : [];
80
+ if (!title) die('用法: loom intent add --title <text> [--depends-on INT-001,INT-002]');
81
+ output(addIntentDraft(versionDir, title, dependencies));
82
+ break;
83
+ }
84
+ case 'revise': {
85
+ const id = rest[0];
86
+ const reasonIdx = argv.indexOf('--reason');
87
+ const reason = reasonIdx !== -1 ? argv[reasonIdx + 1] : null;
88
+ if (!id || !reason) die('用法: loom intent revise <id> --reason <text>');
89
+ output(reviseIntentDraft(versionDir, id, reason));
90
+ break;
91
+ }
92
+ case 'draft': {
93
+ const id = rest[0];
94
+ if (!id) die('用法: loom intent draft <id>');
95
+ output(getIntentDraft(versionDir, id));
96
+ break;
97
+ }
98
+ case 'finalize': {
99
+ const id = rest[0];
100
+ if (!id) die('用法: loom intent finalize <id>');
101
+ const parseIds = (name) => {
102
+ const index = argv.indexOf(name);
103
+ if (index === -1) return [];
104
+ const value = argv[index + 1];
105
+ if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
106
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
107
+ };
108
+ output(finalizeIntentDraft(versionDir, id, {
109
+ review: parseIds('--review'),
110
+ unaffected: parseIds('--unaffected'),
111
+ }));
112
+ break;
113
+ }
114
+ case 'deprecate': {
115
+ const id = rest[0];
116
+ const readFlag = (name) => {
117
+ const index = argv.indexOf(name);
118
+ return index === -1 ? null : argv[index + 1];
119
+ };
120
+ const parseIds = (name) => {
121
+ const value = readFlag(name);
122
+ if (value === null) return [];
123
+ if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
124
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
125
+ };
126
+ const reason = readFlag('--reason');
127
+ if (!id || !reason || reason.startsWith('--')) die('用法: loom intent deprecate <id> --reason <text> [--confirm [--replacement <id>] [--review <ids>] [--unaffected <ids>]]');
128
+ const replacement = readFlag('--replacement');
129
+ if (argv.includes('--replacement') && (!replacement || replacement.startsWith('--'))) die('--replacement 需要当前版本的 Intent ID');
130
+ output(deprecateIntent(versionDir, id, {
131
+ reason,
132
+ confirm: argv.includes('--confirm'),
133
+ replacement,
134
+ review: parseIds('--review'),
135
+ unaffected: parseIds('--unaffected'),
136
+ }));
137
+ break;
138
+ }
70
139
  case 'next':
71
140
  output(getNextIntent(versionDir) ?? '没有可执行的 Intent');
72
141
  break;
@@ -76,25 +145,36 @@ try {
76
145
  console.log(`进度: ${s.counts.completed}/${s.counts.total} 完成`);
77
146
  console.log(` pending: ${s.counts.pending} ${fmt(s.ids.pending)}`);
78
147
  console.log(` in_progress: ${s.counts.in_progress} ${fmt(s.ids.in_progress)}`);
79
- console.log(` completed: ${s.counts.completed} ${fmt(s.ids.completed)}`);
80
- console.log(` blocked: ${s.counts.blocked} ${fmt(s.ids.blocked)}`);
81
- break;
148
+ console.log(` completed: ${s.counts.completed} ${fmt(s.ids.completed)}`);
149
+ console.log(` blocked: ${s.counts.blocked} ${fmt(s.ids.blocked)}`);
150
+ console.log(` needs_review: ${s.counts.needs_review} ${fmt(s.ids.needs_review)}`);
151
+ console.log(` deprecated: ${s.counts.deprecated} ${fmt(s.deprecated)}`);
152
+ break;
82
153
  }
83
154
  case 'graph':
84
155
  output(getDependencyGraph(versionDir));
85
156
  break;
86
- case 'get': {
87
- const id = rest[0];
88
- if (!id) die('用法: loom intent get <id>');
89
- output(getIntent(versionDir, id));
90
- break;
91
- }
157
+ case 'get': {
158
+ const id = rest[0];
159
+ if (!id) die('用法: loom intent get <id>');
160
+ const resolved = resolveIntentRef(versionDir, id);
161
+ output(getIntent(resolved.versionDir, resolved.intentId));
162
+ break;
163
+ }
92
164
  case 'narrative': {
93
165
  const id = rest[0];
94
166
  if (!id) die('用法: loom intent narrative <id>');
95
- output(getNarrative(versionDir, id));
96
- break;
97
- }
167
+ const resolved = resolveIntentRef(versionDir, id);
168
+ output(getNarrative(resolved.versionDir, resolved.intentId));
169
+ break;
170
+ }
171
+ case 'diff': {
172
+ const from = rest[0];
173
+ const to = rest[1];
174
+ if (!from || !to) die('用法: loom intent diff <v1> <v2>');
175
+ output(diffIntentVersions(findLoomRoot(), from, to));
176
+ break;
177
+ }
98
178
  case 'validate':
99
179
  loadIntentMap(versionDir);
100
180
  console.log('Intent Map 校验通过');
@@ -102,7 +182,8 @@ try {
102
182
  case 'trace': {
103
183
  const id = rest[0];
104
184
  if (!id) die('用法: loom intent trace <id>');
105
- output(traceIntent(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir), id));
185
+ const resolved = resolveIntentRef(versionDir, id);
186
+ output(traceIntent(resolved.versionDir, getVerificationsDir(resolved.versionDir), getPhilosophyDir(resolved.versionDir), resolved.intentId));
106
187
  break;
107
188
  }
108
189
  case 'reverse-dep': {
@@ -126,20 +207,55 @@ try {
126
207
  console.log(`${id} status 已更新为 ${newStatus}`);
127
208
  break;
128
209
  }
210
+ case 'done': {
211
+ // loom intent done <id> — 自动走 pending→in_progress→completed,检查验证记录
212
+ const id = rest[0];
213
+ if (!id) die('用法: loom intent done <id>');
214
+ const verificationsDir = getVerificationsDir(versionDir);
215
+ // 检查有没有验证记录
216
+ const history = getVerificationHistory(verificationsDir, id);
217
+ if (!history || history.records.length === 0) {
218
+ die(`${id} 没有验证记录。先跑: loom verify pass ${id} --summary "..."`);
219
+ }
220
+ // 最新记录必须针对当前 revision 且通过,旧 revision 的通过不能闭合 Intent。
221
+ const latest = history.records[history.records.length - 1];
222
+ if (latest.verdict !== 'passed') {
223
+ die(`${id} 最新验证记录是 ${latest.verdict}(非 passed)。只有 passed 的 Intent 才能 done。`);
224
+ }
225
+ // 获取当前状态,自动走两步
226
+ const intent = getIntent(versionDir, id);
227
+ if (!isVerificationCurrent(intent, latest)) {
228
+ die(`${id} 最新 passed 验证不属于当前 Intent revision ${intent.revision ?? 1}。先重新验证。`);
229
+ }
230
+ const currentStatus = intent.status;
231
+ if (currentStatus === 'completed') {
232
+ console.log(`${id} 已经是 completed,无需操作`);
233
+ break;
234
+ }
235
+ if (currentStatus === 'pending') {
236
+ updateIntentStatus(versionDir, id, 'in_progress');
237
+ }
238
+ updateIntentStatus(versionDir, id, 'completed');
239
+ console.log(`${id} 已完成(${currentStatus} → completed)`);
240
+ break;
241
+ }
129
242
  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 <...>]`);
243
+ die(`未知子命令: intent ${sub}\n用法: loom intent [add|revise|draft|finalize|deprecate|next|status|graph|get|narrative|diff|validate|trace|reverse-dep|reverse-ref|update|done]`);
131
244
  }
132
245
  break;
133
246
  }
134
247
 
135
- case 'init': {
136
- const result = initProject(cwd());
248
+ case 'init': {
249
+ if (sub === '--help' || sub === '-h') {
250
+ console.log('用法: loom init\\n\\n在当前目录创建 LOOM 项目骨架。若目录已有 .loom/,不会覆盖现有文件。');
251
+ break;
252
+ }
253
+ const result = initProject(cwd());
137
254
  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}`);
255
+ for (const c of result.created) console.log(` [created] ${c}`);
256
+ for (const s of result.skipped) console.log(` [skipped] ${s} (already exists)`);
257
+ if (result.created.length === 0 && result.skipped.length > 0) {
258
+ console.log(' 所有文件已存在,无需操作。');
143
259
  }
144
260
  console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
145
261
  console.log('To Agent: 运行 loom guide 诊断当前阶段,按引导执行');
@@ -151,7 +267,7 @@ try {
151
267
  }
152
268
 
153
269
  case 'activate': {
154
- const role = sub;
270
+ const role = sub;
155
271
  if (!role) die('用法: loom activate <role>\n角色: weaver | visionary | architect | forge | keeper');
156
272
  // weaver 不需要 versionDir(项目还没初始化时也能激活)
157
273
  let versionDir = null;
@@ -163,7 +279,10 @@ try {
163
279
  if (!String(e.message).includes('找不到 .loom')) throw e;
164
280
  }
165
281
  }
166
- const prompt = activateRole(role, versionDir);
282
+ const intentIdx = argv.indexOf('--intent');
283
+ const intentId = intentIdx !== -1 ? argv[intentIdx + 1] : null;
284
+ if (intentIdx !== -1 && !intentId) die('用法: loom activate <role> --intent <id>');
285
+ const prompt = activateRole(role, versionDir, intentId);
167
286
  output(prompt);
168
287
  break;
169
288
  }
@@ -177,15 +296,56 @@ try {
177
296
  output(getPhilosophy(getPhilosophyDir(versionDir), anchor));
178
297
  break;
179
298
  }
180
- case 'list':
181
- output(listPhilosophyFiles(getPhilosophyDir(versionDir)));
182
- break;
299
+ case 'list':
300
+ output(listPhilosophyFiles(getPhilosophyDir(versionDir)));
301
+ break;
302
+ case 'impact': {
303
+ const anchor = rest[0];
304
+ if (!anchor) die('用法: loom philosophy impact <anchor>');
305
+ output(assessPhilosophyImpact(versionDir, anchor));
306
+ break;
307
+ }
308
+ case 'revise': {
309
+ const anchor = rest[0];
310
+ const readFlag = (name) => {
311
+ const index = argv.indexOf(name);
312
+ return index === -1 ? null : argv[index + 1];
313
+ };
314
+ const parseIds = (name) => {
315
+ const value = readFlag(name);
316
+ if (value === null) return [];
317
+ if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
318
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
319
+ };
320
+ const classification = readFlag('--classification');
321
+ const reason = readFlag('--reason');
322
+ if (!anchor || !classification || classification.startsWith('--') || !reason || reason.startsWith('--')) {
323
+ die('用法: loom philosophy revise <anchor> --classification <clarification|minor|major> --reason <text> [--confirm --review <ids> --unaffected <ids>]');
324
+ }
325
+ output(revisePhilosophy(versionDir, anchor, {
326
+ classification,
327
+ reason,
328
+ confirm: argv.includes('--confirm'),
329
+ review: parseIds('--review'),
330
+ unaffected: parseIds('--unaffected'),
331
+ }));
332
+ break;
333
+ }
183
334
  case 'check': {
184
- const philDir = getPhilosophyDir(versionDir);
185
- const inspiration = validateInspirationSources(philDir);
186
- const decomposition = validatePartDecomposition(philDir);
187
- const allIssues = [...inspiration.issues, ...decomposition.issues];
188
- const allPassed = inspiration.passed && decomposition.passed;
335
+ const philDir = getPhilosophyDir(versionDir);
336
+ const inspiration = validateInspirationSources(philDir);
337
+ const allIssues = inspiration.issues;
338
+ const allPassed = inspiration.passed;
339
+
340
+ // --json: 结构化输出,供 Agent 程序化消费
341
+ if (argv.includes('--json')) {
342
+ output({
343
+ passed: allPassed,
344
+ issues: allIssues,
345
+ sources: inspiration.sources.map(({ file, sources }) => ({ file, count: sources.length })),
346
+ });
347
+ exit(allPassed ? 0 : 1);
348
+ }
189
349
 
190
350
  if (allPassed) {
191
351
  console.log('✓ 哲学文档校验通过');
@@ -193,10 +353,6 @@ try {
193
353
  for (const { file, sources } of inspiration.sources) {
194
354
  console.log(` ${file}: ${sources.length} 个源`);
195
355
  }
196
- console.log(' 实现部分拆解:');
197
- for (const part of decomposition.parts) {
198
- console.log(` - ${part}`);
199
- }
200
356
  } else {
201
357
  const high = allIssues.filter((i) => i.severity === 'high').length;
202
358
  const medium = allIssues.filter((i) => i.severity === 'medium').length;
@@ -205,13 +361,13 @@ try {
205
361
  const icon = issue.severity === 'high' ? '⚠' : '·';
206
362
  console.log(` ${icon} [${issue.severity}] ${issue.msg}`);
207
363
  }
208
- console.log('\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/PART_DECOMPOSITION.md + dimensions/SEARCH_METHODOLOGY.md。');
364
+ console.log('\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/SEARCH_METHODOLOGY.md。');
209
365
  exit(1);
210
366
  }
211
367
  break;
212
368
  }
213
369
  default:
214
- die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list|check]`);
370
+ die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list|check|impact <anchor>|revise <anchor> --classification <clarification|minor|major> --reason <text>]`);
215
371
  }
216
372
  break;
217
373
  }
@@ -226,11 +382,16 @@ try {
226
382
  output(getVerificationContract(versionDir, id));
227
383
  break;
228
384
  }
229
- case 'history': {
230
- const id = rest[0];
231
- if (!id) die('用法: loom verify history <id>');
232
- const history = getVerificationHistory(verificationsDir, id);
233
- output(history ?? `没有 ${id} 的验证记录`);
385
+ case 'history': {
386
+ const id = rest[0];
387
+ if (!id) die('用法: loom verify history <id>');
388
+ if (argv.includes('--across-versions')) {
389
+ output(getAcrossVersionVerificationHistory(versionDir, id));
390
+ } else {
391
+ const resolved = resolveIntentRef(versionDir, id);
392
+ const history = getVerificationHistory(getVerificationsDir(resolved.versionDir), resolved.intentId);
393
+ output(history ?? `没有 ${resolved.ref} 的验证记录`);
394
+ }
234
395
  break;
235
396
  }
236
397
  case 'pending':
@@ -260,24 +421,63 @@ try {
260
421
  } else {
261
422
  die('用法: loom verify write --json-file <path> | --json <json-string>');
262
423
  }
263
- const result = writeVerification(verificationsDir, record);
424
+ const result = writeVerification(versionDir, verificationsDir, record);
264
425
  console.log(`验证记录已写入: ${result.filePath}`);
265
426
  console.log(` 轮次: ${result.round}, verdict: ${record.verdict}`);
266
- if (record.verdict === 'deviated') {
267
- console.log(` deviated 累计: ${result.deviated_count} 轮`);
268
- if (result.should_escalate) {
269
- console.log(` ⚠ 达到 3 轮上限,应升级为 blocked——Keeper 应执行: loom intent update ${record.intent_id} --status blocked`);
270
- }
271
- }
427
+ if (record.verdict === 'deviated') {
428
+ console.log(` deviated 累计: ${result.deviated_count} 轮`);
429
+ if (result.should_escalate) {
430
+ updateIntentStatus(versionDir, record.intent_id, 'blocked');
431
+ console.log(` ⚠ 已达到 3 轮上限,${record.intent_id} 已自动升级为 blocked`);
432
+ }
433
+ }
434
+ break;
435
+ }
436
+ case 'pass':
437
+ case 'fail': {
438
+ // loom verify pass <id> --summary "..." [--reproduction-command "..."] [--preservation-evidence "..."] [--quality-proof "..."]
439
+ // loom verify fail <id> --summary "..." [--deviation "..."] [--reproduction-command "..."]
440
+ const id = rest[0];
441
+ if (!id) die(`用法: loom verify ${sub} <id> --summary "..." [--reproduction-command "..."]${sub === 'pass' ? ' [--preservation-evidence "..."] [--quality-proof "..."]' : ' [--deviation "..."]'}`);
442
+ const summaryIdx = argv.indexOf('--summary');
443
+ const reproIdx = argv.indexOf('--reproduction-command');
444
+ const deviationIdx = argv.indexOf('--deviation');
445
+ const qualityProofIdx = argv.indexOf('--quality-proof');
446
+ const preservationIdx = argv.indexOf('--preservation-evidence');
447
+ const summary = summaryIdx !== -1 ? argv[summaryIdx + 1] : null;
448
+ if (!summary) die(`缺少 --summary: loom verify ${sub} ${id} --summary "..."`);
449
+ const intent = getIntent(versionDir, id);
450
+ if (sub === 'pass' && intent.continuity_required && !(preservationIdx !== -1 && argv[preservationIdx + 1])) {
451
+ die(`Intent ${id} 声明了 continuity_required;通过前必须提供 --preservation-evidence,证明旧状态 → 新操作后的完整序列未发生未授权丢失。`);
452
+ }
453
+ if (sub === 'pass' && intent.quality_contract && !(qualityProofIdx !== -1 && argv[qualityProofIdx + 1])) {
454
+ die(`Intent ${id} 声明了 quality_contract;通过前必须提供 --quality-proof,指向项目内真实的 Quality Proof Markdown 锚点。`);
455
+ }
456
+ const extras = {};
457
+ if (reproIdx !== -1 && argv[reproIdx + 1]) extras.reproduction_command = argv[reproIdx + 1];
458
+ if (sub === 'fail' && deviationIdx !== -1 && argv[deviationIdx + 1]) extras.deviation_detail = argv[deviationIdx + 1];
459
+ if (sub === 'pass' && qualityProofIdx !== -1 && argv[qualityProofIdx + 1]) extras.quality_proof_ref = argv[qualityProofIdx + 1];
460
+ if (sub === 'pass' && preservationIdx !== -1 && argv[preservationIdx + 1]) extras.preservation_evidence = argv[preservationIdx + 1];
461
+ const verdict = sub === 'pass' ? 'passed' : 'deviated';
462
+ const result = createQuickVerification(versionDir, verificationsDir, id, verdict, summary, extras);
463
+ console.log(`验证记录已写入: ${result.filePath}`);
464
+ console.log(` 轮次: ${result.round}, verdict: ${verdict}`);
465
+ if (verdict === 'deviated') {
466
+ console.log(` deviated 累计: ${result.deviated_count} 轮`);
467
+ if (result.should_escalate) {
468
+ updateIntentStatus(versionDir, id, 'blocked');
469
+ console.log(` ⚠ 已达到 3 轮上限,${id} 已自动升级为 blocked`);
470
+ }
471
+ }
272
472
  break;
273
473
  }
274
474
  default:
275
- die(`未知子命令: verify ${sub}\n用法: loom verify [contract <id>|history <id>|pending|list|write --json-file <path>|--json <string>]`);
475
+ 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
476
  }
277
477
  break;
278
478
  }
279
479
 
280
- case 'version': {
480
+ case 'version': {
281
481
  const loomRoot = findLoomRoot();
282
482
  switch (sub) {
283
483
  case 'list': {
@@ -324,8 +524,42 @@ try {
324
524
  default:
325
525
  die(`未知子命令: version ${sub}\n用法: loom version [list|current|new|use <v>|diff <v1> <v2>]`);
326
526
  }
327
- break;
328
- }
527
+ break;
528
+ }
529
+
530
+ case 'patch': {
531
+ const versionDir = findVersionDir();
532
+ switch (sub) {
533
+ case 'record': {
534
+ const fileFlagIdx = argv.indexOf('--json-file');
535
+ const inputPath = fileFlagIdx !== -1 ? argv[fileFlagIdx + 1] : null;
536
+ if (!inputPath) die('用法: loom patch record --json-file <path>');
537
+ let record;
538
+ try {
539
+ record = JSON.parse(readFileSync(inputPath, 'utf-8'));
540
+ } catch (e) {
541
+ die(`JSON 文件解析失败: ${inputPath}\n原因: ${e.message}`);
542
+ }
543
+ output(recordPatch(versionDir, record));
544
+ break;
545
+ }
546
+ case 'list':
547
+ output(listPatches(versionDir));
548
+ break;
549
+ case 'get': {
550
+ const id = rest[0];
551
+ if (!id) die('用法: loom patch get <id>');
552
+ output(getPatch(versionDir, id));
553
+ break;
554
+ }
555
+ case 'validate':
556
+ output(validatePatches(versionDir));
557
+ break;
558
+ default:
559
+ die(`未知子命令: patch ${sub}\n用法: loom patch [record --json-file <path>|list|get <id>|validate]`);
560
+ }
561
+ break;
562
+ }
329
563
 
330
564
  case 'doctor': {
331
565
  const versionDir = findVersionDir();
@@ -337,7 +571,11 @@ try {
337
571
  for (const issue of issues) {
338
572
  const icon = issue.severity === 'fatal' ? '☠' : issue.severity === 'high' ? '⚠' : '·';
339
573
  console.log(` ${icon} [${issue.severity}] ${issue.type}: ${issue.msg}`);
574
+ if (issue.fix_hint) {
575
+ console.log(` → 修复: ${issue.fix_hint}`);
576
+ }
340
577
  }
578
+ console.log(`\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/SEARCH_METHODOLOGY.md。`);
341
579
  }
342
580
  break;
343
581
  }
@@ -368,22 +606,39 @@ try {
368
606
  break;
369
607
  }
370
608
 
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(`模式: 手动(每步需用户确认)`);
609
+ case 'guide': {
610
+ const dryRun = argv.includes('--dry-run');
611
+ const jsonOut = argv.includes('--json');
612
+ const result = guideProject(cwd(), { dryRun });
613
+ if (jsonOut) {
614
+ output(result);
615
+ break;
616
+ }
617
+ console.log(`阶段 ${result.stage_num}: ${result.stage}`);
618
+ if (dryRun) {
619
+ console.log('诊断: dry-run(不写 heartbeat)');
382
620
  }
621
+ const modeDesc = {
622
+ 'manual': '手动(每步需确认)',
623
+ 'auto-loop': 'AUTO auto-loop(设计阶段需 review,Intent Loop 自动)',
624
+ 'auto-design': 'AUTO auto-design(全部自动)',
625
+ };
626
+ console.log(`模式: ${modeDesc[result.auto_mode] || result.auto_mode}`);
383
627
  console.log(`\n${result.message}`);
384
628
  console.log(`\n下一步: ${result.next_action}`);
385
629
  console.log(` → ${result.next_command}`);
386
- if (!result.auto && result.stage_num > 0 && result.stage_num < 6) {
630
+ if (result.inputs && result.inputs.length > 0) {
631
+ console.log(`\n需要读取:`);
632
+ for (const f of result.inputs) console.log(` - ${f}`);
633
+ }
634
+ if (result.outputs && result.outputs.length > 0) {
635
+ console.log(`\n需要产出:`);
636
+ for (const f of result.outputs) console.log(` - ${f}`);
637
+ }
638
+ if (result.verify_command) {
639
+ console.log(`\n完成后校验: ${result.verify_command}`);
640
+ }
641
+ if (result.auto_mode === 'manual' && result.stage_num > 0 && result.stage_num < 6) {
387
642
  console.log(`\n提示: 开启 AUTO 模式可自动连续执行 — loom auto on`);
388
643
  }
389
644
  break;
@@ -392,76 +647,93 @@ try {
392
647
  case 'auto': {
393
648
  const loomRoot = findLoomRoot();
394
649
  switch (sub) {
395
- case 'on':
396
- autoOn(loomRoot);
397
- console.log('AUTO 模式已开启。Agent 将自动连续执行,不等用户确认。');
650
+ case 'on': {
651
+ // loom auto on → auto-loop(默认)
652
+ // loom auto on --design → auto-design
653
+ const wantDesign = argv.includes('--design');
654
+ const mode = wantDesign ? 'auto-design' : 'auto-loop';
655
+ autoOn(loomRoot, mode);
656
+ if (mode === 'auto-design') {
657
+ console.log('AUTO 模式: auto-design(全部自动,含哲学/愿景/架构)');
658
+ console.log('Agent 自动连续执行所有阶段,不等人类确认。');
659
+ } else {
660
+ console.log('AUTO 模式: auto-loop(Intent Loop 自动,设计阶段需 review)');
661
+ console.log(' - stage 1-3(哲学/愿景/架构):自动执行但需人类 review');
662
+ console.log(' - stage 4+(Intent Loop):自动连续执行');
663
+ }
398
664
  console.log('核心契约: 持续运行,除非出意外否则不允许私自停止。');
399
665
  console.log(' - L3 human_review 由 Keeper 自主判定,不停下等人类');
400
666
  console.log(' - 唯一允许停下的情况: blocked(依赖阻塞/契约无法判定/连续 3 轮 deviated 升级)');
401
- console.log('关闭: loom auto off');
667
+ console.log('切换: loom auto on --design | loom auto off');
402
668
  break;
669
+ }
403
670
  case 'off':
404
671
  autoOff(loomRoot);
405
- console.log('AUTO 模式已关闭。每步需要用户确认。');
672
+ console.log('AUTO 模式: manual(每步需人类确认)');
406
673
  break;
407
674
  case 'status': {
408
675
  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 模式: 关闭(所有阶段都需人类确认)');
676
+ const modeDesc = {
677
+ 'manual': 'manual(每步需人类确认)',
678
+ 'auto-loop': 'auto-loop(Intent Loop 自动,设计阶段需 review)',
679
+ 'auto-design': 'auto-design(全部自动,含哲学/愿景/架构)',
680
+ };
681
+ console.log(`AUTO 模式: ${modeDesc[status.mode] || status.mode}`);
682
+ if (status.mode === 'auto-loop') {
683
+ console.log(' 规则: stage 1-3 需人类 review,stage 4+ 自动执行');
684
+ } else if (status.mode === 'auto-design') {
685
+ console.log(' 规则: 全部阶段自动执行,不需人类 review');
686
+ }
687
+ if (status.heartbeat) {
688
+ const hb = status.heartbeat;
689
+ console.log(` 心跳: ${hb.timestamp}`);
690
+ console.log(` 阶段: ${hb.stage} (stage ${hb.stage_num})`);
691
+ console.log(` 下一步: ${hb.next_action}`);
692
+ console.log(` 命令: ${hb.next_command}`);
693
+ } else if (status.mode !== 'manual') {
694
+ console.log(' 心跳: 尚未记录(运行 loom guide 后生成)');
423
695
  }
424
696
  break;
425
697
  }
426
698
  default:
427
- die(`未知子命令: auto ${sub}\n用法: loom auto [on|off|status]`);
699
+ die(`未知子命令: auto ${sub}\n用法: loom auto [on [--design]|off|status]`);
428
700
  }
429
701
  break;
430
702
  }
431
703
 
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, '/');
704
+ case 'preview': {
705
+ const previewFile = join(cwd(), 'loom-preview.html');
706
+ if (argv.includes('--help') || argv.includes('-h')) {
707
+ console.log(`用法:
708
+ loom preview 打开新鲜 preview;过期时提示重新生成
709
+ loom preview --regen 输出生成提示词,让 Agent 重写 loom-preview.html
710
+ loom preview status 检查 preview 是否存在、是否新鲜
711
+ loom preview --stale 强行打开过期 preview
712
+ loom preview --help 显示本帮助`);
713
+ break;
714
+ }
715
+ const status = getPreviewStatus(cwd());
716
+ const hasPreview = status.exists;
717
+ const regenOnly = argv.includes('--regen') || argv.includes('-r');
718
+ const openStale = argv.includes('--stale');
719
+
720
+ if (sub === 'status') {
721
+ output(status);
722
+ break;
723
+ }
724
+
725
+ // 已有 HTML 且没指定 --regen:直接打开浏览器
726
+ if (hasPreview && !regenOnly) {
727
+ if (!status.fresh && !openStale) {
728
+ console.log('preview 已过期:.loom 源文件比 loom-preview.html 更新。');
729
+ console.log(` preview: ${status.preview_mtime || '未知'}`);
730
+ console.log(` 最新源: ${status.source_latest_mtime || '未知'} ${status.latest_source_file ? `(${status.latest_source_file})` : ''}`);
731
+ console.log('\n下一步: loom preview --regen');
732
+ console.log('强行打开旧 preview: loom preview --stale');
733
+ break;
734
+ }
735
+ const { spawn } = await import('node:child_process');
736
+ const target = previewFile.replace(/\\/g, '/');
465
737
  if (process.platform === 'win32') {
466
738
  spawn('cmd', ['/c', 'start', target], { detached: true, stdio: 'ignore' }).unref();
467
739
  } else if (process.platform === 'darwin') {
@@ -470,9 +742,9 @@ try {
470
742
  spawn('xdg-open', [target], { detached: true, stdio: 'ignore' }).unref();
471
743
  }
472
744
  console.log(`已打开浏览器: ${previewFile}`);
473
- console.log(status.fresh ? `重新生成: loom preview --regen` : `已打开旧 preview。重新生成: loom preview --regen`);
474
- break;
475
- }
745
+ console.log(status.fresh ? `重新生成: loom preview --regen` : `已打开旧 preview。重新生成: loom preview --regen`);
746
+ break;
747
+ }
476
748
 
477
749
  // 没有 HTML 或指定 --regen:输出提示词让 AI 生成
478
750
  const prompt = generatePreviewPrompt();
@@ -508,47 +780,64 @@ To Human:
508
780
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
509
781
 
510
782
  用法:
511
- loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
512
- loom guide 诊断当前阶段,输出下一步引导
513
- loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
514
- loom auto on|off|status AUTO 模式开关(on Agent 自动连续执行)
515
- loom activate <role> 输出角色激活提示词(weaver|visionary|architect|forge|keeper)
783
+ loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
784
+ loom guide 诊断当前阶段,输出下一步引导
785
+ loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
786
+ loom auto on|off|status AUTO 编排协议开关(由 Agent/runtime 消费,不由 CLI 自行执行)
787
+ loom activate <role> [--intent <id>] 输出普通或 Intent-scoped 角色激活提示词
516
788
 
517
789
  loom version list 列出所有版本(* 标记当前)
518
790
  loom version current 显示当前版本
519
791
  loom version new 创建 v{N+1} + 自动切换为当前
520
792
  loom version use <v> 切换当前版本
521
- loom version diff <v1> <v2> 对比两个版本的文件差异
522
-
523
- loom intent next 返回下一个可执行 Intent
793
+ loom version diff <v1> <v2> 对比两个版本的文件差异
794
+
795
+ loom patch record --json-file <path> 记录 Patch 并生成 Markdown 投影
796
+ loom patch list 列出当前版本的 Patch
797
+ loom patch get <id> 返回指定 Patch
798
+ loom patch validate 校验 Patch ledger 和 Markdown 投影
799
+
800
+ loom intent next 返回下一个可执行 Intent
801
+ loom intent add --title <text> [--depends-on <ids>] 创建新增 draft
802
+ loom intent revise <id> --reason <text> 创建修订 draft 并报告反向依赖
803
+ loom intent draft <id> 查看 draft
804
+ loom intent finalize <id> 校验并原子写入官方 Intent Map
805
+ loom intent deprecate <id> --reason <text> 只读评估;加 --confirm 才弃用
524
806
  loom intent status 返回进度概览
525
807
  loom intent graph 输出 Mermaid 依赖图
526
808
  loom intent get <id> 返回某 Intent 完整信息
527
- loom intent narrative <id> 返回某 Intent 的意图叙事(解析 narrative_ref)
809
+ loom intent narrative <id> 返回某 Intent 的意图叙事(解析 narrative_ref)
810
+ loom intent diff <v1> <v2> 按显式 lineage 对比 Intent 语义
528
811
  loom intent validate 校验 Intent Map 结构
529
812
  loom intent trace <id> 返回某 Intent 的完整追溯链(依赖+验证+哲学+叙事)
530
813
  loom intent reverse-dep <id> 返回依赖某 Intent 的所有 Intent(变更影响评估)
531
814
  loom intent reverse-ref <anchor> 返回引用某哲学锚点的所有 Intent
532
- loom intent update <id> --status <s> 更新 Intent 状态(Keeper 用)
815
+ loom intent update <id> --status <s> 更新 Intent 状态(Keeper 用)
816
+ loom intent done <id> 当前 revision 验证通过后闭合 Intent
533
817
 
534
818
  loom doctor 项目健康检查(一致性+孤儿引用+循环依赖+僵尸)
535
819
  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)
541
-
542
- loom philosophy get <anchor> 按锚点加载哲学章节
543
- loom philosophy list 列出哲学文档文件
544
- loom philosophy check 校验灵感来源质量(源数量/多样性/理由)
820
+ loom preview 打开新鲜 HTML;过期则提示重新生成
821
+ loom preview status 检查 preview 是否存在、是否新鲜
822
+ loom preview --regen 强制重新输出提示词(让 AI 重新生成 HTML)
823
+ loom preview --stale 强行打开过期 preview
824
+ loom help <topic> 分层指南(含 patch 工作流)
825
+
826
+ loom philosophy get <anchor> 按锚点加载哲学章节
827
+ loom philosophy list 列出哲学文档文件
828
+ loom philosophy check 校验灵感来源质量(源数量/多样性/理由)
829
+ loom philosophy impact <anchor> 只读分析直接引用和传递影响
830
+ loom philosophy revise <anchor> --classification <type> --reason <text> 只读评估;加 --confirm 才记录 clarification/minor
545
831
 
546
832
  loom verify contract <id> 返回某 Intent 的验收契约(解析引用)
547
- loom verify history <id> 返回某 Intent 验证历史
833
+ loom verify history <id> 返回某 Intent 本地验证历史
834
+ loom verify history <ref> --across-versions 递归返回 lineage 各版本的本地历史
548
835
  loom verify pending 返回待验证的 Intent
549
836
  loom verify list 列出所有验证记录
550
837
  loom verify write --json-file <path> 从文件读入并写入验证记录
551
- loom verify write --json <string> 从命令行字符串写入验证记录
838
+ loom verify write --json <string> 从命令行字符串写入验证记录
839
+ loom verify pass|fail <id> --summary <text> 快捷写入验证结果
840
+ --quality-proof <ref> 声明相对质量提升时,指向基线比较与稳定性证据
552
841
 
553
842
  参数:
554
843
  --loom-dir <path> 指定 .loom/v{N} 目录(默认读 .loom/current 指针)`);