@haaaiawd/loom 0.10.0 → 1.1.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 (58) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/LICENSE +21 -0
  3. package/README.md +147 -74
  4. package/cli/bin/loom.js +428 -105
  5. package/cli/help/asset.md +36 -0
  6. package/cli/help/atelier.md +37 -0
  7. package/cli/help/capability.md +68 -0
  8. package/cli/help/concepts.md +100 -72
  9. package/cli/help/doctor.md +71 -121
  10. package/cli/help/loop.md +120 -135
  11. package/cli/help/patch.md +33 -0
  12. package/cli/help/preview.md +2 -1
  13. package/cli/help/proposals.md +21 -0
  14. package/cli/help/version.md +92 -16
  15. package/cli/help/workflow.md +101 -100
  16. package/cli/src/activate.js +349 -73
  17. package/cli/src/asset-library.js +384 -0
  18. package/cli/src/atelier.js +331 -0
  19. package/cli/src/capability-graph.js +351 -0
  20. package/cli/src/capability-proposals.js +225 -0
  21. package/cli/src/diagnostics.js +240 -44
  22. package/cli/src/guide.js +155 -40
  23. package/cli/src/init.js +58 -32
  24. package/cli/src/intent-draft.js +303 -0
  25. package/cli/src/intent-map.js +560 -54
  26. package/cli/src/patch.js +214 -0
  27. package/cli/src/philosophy.js +177 -154
  28. package/cli/src/preview-prompt.md +13 -6
  29. package/cli/src/preview.js +1 -0
  30. package/cli/src/shared/intent-ref.js +38 -0
  31. package/cli/src/shared/proof-reference.js +19 -0
  32. package/cli/src/shared/verification-method.js +32 -0
  33. package/cli/src/verify.js +202 -62
  34. package/cli/src/version.js +5 -4
  35. package/dimensions/AUTHORSHIP.md +45 -0
  36. package/dimensions/PART_DECOMPOSITION.md +42 -203
  37. package/dimensions/SEARCH_METHODOLOGY.md +101 -97
  38. package/dimensions/examples/AGENT_SYSTEM/README.md +1 -1
  39. package/dimensions/examples/CLI_TOOL/README.md +1 -1
  40. package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +28 -77
  41. package/dimensions/universal/ENGINEERING_CREED.md +30 -74
  42. package/dimensions/universal/PRODUCT_PHILOSOPHY.md +32 -70
  43. package/meta/BASELINE.md +91 -276
  44. package/meta/INTENT_LOOP.md +289 -737
  45. package/meta/PHILOSOPHY_WEAVER.md +110 -343
  46. package/meta/ROLE_ACTIVATION.md +109 -267
  47. package/package.json +13 -7
  48. package/roles/architect.md +84 -111
  49. package/roles/forge.md +105 -126
  50. package/roles/keeper.md +109 -223
  51. package/roles/visionary.md +57 -86
  52. package/templates/ASSET_LIBRARY_MANIFEST_TEMPLATE.json +10 -0
  53. package/templates/ATELIER_RECORD_TEMPLATE.json +48 -0
  54. package/templates/CAPABILITY_BRIEF_TEMPLATE.md +34 -0
  55. package/templates/CAPABILITY_GRAPH_TEMPLATE.json +11 -0
  56. package/templates/INTENT_MAP_TEMPLATE.json +26 -10
  57. package/templates/PHILOSOPHY_TEMPLATE.md +44 -75
  58. package/templates/VISION_TEMPLATE.md +44 -67
package/cli/bin/loom.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // loom — LOOM 框架的 CLI 传感器层
3
3
  // Agent 通过这个 CLI 访问 Intent Map / 哲学 / 验证记录,不直接读文件。
4
4
 
5
- import { argv, cwd, exit } from 'node:process';
5
+ import { argv, cwd, env, exit } from 'node:process';
6
6
  import { resolve, join, dirname } from 'node:path';
7
7
  import { existsSync, readdirSync, readFileSync } from 'node:fs';
8
8
  import { fileURLToPath } from 'node:url';
@@ -10,9 +10,9 @@ 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, createQuickVerification, 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';
@@ -20,7 +20,14 @@ import { doctor, contextSummary, traceIntent, reverseDep, reverseRef } from '../
20
20
  import { getHelpTopic, listHelpTopics } from '../src/help.js';
21
21
  import { guideProject } from '../src/guide.js';
22
22
  import { isAutoOn, autoOn, autoOff, autoStatus, getAutoMode } from '../src/auto.js';
23
- import { generatePreviewPrompt, getPreviewStatus } from '../src/preview.js';
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';
27
+ import { compileCapabilityInputs, getCapabilityCoverage, getCapabilityFrontier, getCapabilityGraphProjection, getCapabilityNode } from '../src/capability-graph.js';
28
+ import { getAsset, importAsset, listAssets, recoverAssetImportTransaction, searchAssets, validateAssetLibrary } from '../src/asset-library.js';
29
+ import { closeCapabilityProposal, decideCapabilityProposal, getCapabilityProposal, listCapabilityProposals, submitCapabilityProposal } from '../src/capability-proposals.js';
30
+ import { getAtelierRecord, initAtelierRecord, validateAtelierRecord } from '../src/atelier.js';
24
31
 
25
32
  // ─── 路径解析 ──────────────────────────────────────────
26
33
  // findLoomRoot / findVersionDir / readCurrentPointer 已提取到 shared/paths.js
@@ -55,18 +62,197 @@ const [cmd, sub, ...rest] = argv.slice(2);
55
62
 
56
63
  try {
57
64
  switch (cmd) {
58
- case '--version':
65
+ case '--version':
59
66
  case '-v': {
60
67
  // 从根 package.json 读版本号(cli/bin -> cli -> LOOM root)
61
68
  const pkgPath = resolve(__dirname, '..', '..', 'package.json');
62
69
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
63
70
  console.log(`loom ${pkg.version}`);
64
- break;
65
- }
66
-
67
- case 'intent': {
68
- const versionDir = findVersionDir();
69
- switch (sub) {
71
+ break;
72
+ }
73
+
74
+ case 'capability': {
75
+ const versionDir = findVersionDir();
76
+ switch (sub) {
77
+ case 'graph':
78
+ output(getCapabilityGraphProjection(versionDir));
79
+ break;
80
+ case 'frontier':
81
+ output(getCapabilityFrontier(versionDir));
82
+ break;
83
+ case 'get': {
84
+ const id = rest[0];
85
+ if (!id) die('用法: loom capability get <node-id>');
86
+ output(getCapabilityNode(versionDir, id));
87
+ break;
88
+ }
89
+ case 'coverage':
90
+ output(getCapabilityCoverage(versionDir));
91
+ break;
92
+ case 'compile': {
93
+ const intentId = rest[0];
94
+ if (!intentId) die('用法: loom capability compile <intent-id>');
95
+ output(compileCapabilityInputs(versionDir, intentId));
96
+ break;
97
+ }
98
+ case 'proposal': {
99
+ const action = rest[0];
100
+ if (action === 'list') output(listCapabilityProposals(versionDir));
101
+ else if (action === 'get') {
102
+ if (!rest[1]) die('用法: loom capability proposal get <CGP-ID>');
103
+ output(getCapabilityProposal(versionDir, rest[1]));
104
+ } else if (action === 'submit') {
105
+ const fileIndex = argv.indexOf('--json-file');
106
+ const path = fileIndex === -1 ? null : argv[fileIndex + 1];
107
+ if (!path) die('用法: loom capability proposal submit --json-file <proposal.json>');
108
+ output(submitCapabilityProposal(versionDir, JSON.parse(readFileSync(path, 'utf-8'))));
109
+ } else if (action === 'decide') {
110
+ const rationaleIndex = argv.indexOf('--rationale');
111
+ if (!rest[1] || !rest[2] || rationaleIndex === -1 || !argv[rationaleIndex + 1]) die('用法: loom capability proposal decide <CGP-ID> <decision> --rationale <text>');
112
+ output(decideCapabilityProposal(versionDir, rest[1], rest[2], argv[rationaleIndex + 1]));
113
+ } else if (action === 'close') {
114
+ const resolutionIndex = argv.indexOf('--resolution-file');
115
+ if (!rest[1] || resolutionIndex === -1 || !argv[resolutionIndex + 1]) die('用法: loom capability proposal close <CGP-ID> --resolution-file <resolution.json>');
116
+ output(closeCapabilityProposal(versionDir, rest[1], JSON.parse(readFileSync(argv[resolutionIndex + 1], 'utf-8'))));
117
+ } else die('用法: loom capability proposal [list|get|submit|decide|close]');
118
+ break;
119
+ }
120
+ default:
121
+ die(`未知 capability 子命令: ${sub}\n用法: loom capability [graph|frontier|get|coverage|compile|proposal]`);
122
+ }
123
+ break;
124
+ }
125
+
126
+ case 'asset': {
127
+ const versionDir = findVersionDir();
128
+ switch (sub) {
129
+ case 'import': {
130
+ const filePath = rest[0];
131
+ const option = (name) => {
132
+ const index = argv.indexOf(name);
133
+ return index === -1 ? undefined : argv[index + 1];
134
+ };
135
+ if (!filePath || filePath.startsWith('--')) die('用法: loom asset import <本地文件> --tags <标签,...> --source <来源> --author <作者> --license <许可> --approval approved [--kind image] [--evidence <节点,...>]');
136
+ output(importAsset(versionDir, filePath, {
137
+ tags: option('--tags'), source: option('--source'), author: option('--author'), license: option('--license'),
138
+ approval: option('--approval'), kind: option('--kind'), evidenceRefs: option('--evidence'),
139
+ failureInjection: env.NODE_ENV === 'test' && option('--test-fail-after') === 'manifest' ? 'after_manifest'
140
+ : env.NODE_ENV === 'test' && option('--test-fail-after') === 'crash-manifest' ? 'crash_after_manifest' : undefined,
141
+ }));
142
+ break;
143
+ }
144
+ case 'list': output(listAssets(versionDir)); break;
145
+ case 'search': {
146
+ const query = rest[0];
147
+ if (!query) die('用法: loom asset search <查询>');
148
+ output(searchAssets(versionDir, query));
149
+ break;
150
+ }
151
+ case 'get': {
152
+ const id = rest[0];
153
+ if (!id) die('用法: loom asset get <asset-id>');
154
+ output(getAsset(versionDir, id));
155
+ break;
156
+ }
157
+ case 'validate': {
158
+ const recovery = recoverAssetImportTransaction(versionDir);
159
+ output({ valid: true, assets: Object.keys(validateAssetLibrary(versionDir).assets).length, recovered_transaction: recovery.recovered ? recovery : null });
160
+ break;
161
+ }
162
+ default: die(`未知 asset 子命令: ${sub}\n用法: loom asset [import|list|search|get|validate]`);
163
+ }
164
+ break;
165
+ }
166
+
167
+ case 'atelier': {
168
+ const versionDir = findVersionDir();
169
+ const intentId = rest[0];
170
+ if (!intentId) die(`用法: loom atelier ${sub || '<init|get|validate>'} <intent-id>`);
171
+ switch (sub) {
172
+ case 'init':
173
+ output(initAtelierRecord(versionDir, intentId));
174
+ break;
175
+ case 'get':
176
+ output(getAtelierRecord(versionDir, intentId));
177
+ break;
178
+ case 'validate':
179
+ output(validateAtelierRecord(versionDir, intentId));
180
+ break;
181
+ default:
182
+ die(`未知 atelier 子命令: ${sub}\n用法: loom atelier [init|get|validate] <intent-id>`);
183
+ }
184
+ break;
185
+ }
186
+
187
+ case 'intent': {
188
+ const versionDir = findVersionDir();
189
+ switch (sub) {
190
+ case 'add': {
191
+ const titleIdx = argv.indexOf('--title');
192
+ const dependsIdx = argv.indexOf('--depends-on');
193
+ const title = titleIdx !== -1 ? argv[titleIdx + 1] : null;
194
+ const dependencies = dependsIdx !== -1 && argv[dependsIdx + 1]
195
+ ? argv[dependsIdx + 1].split(',').map((id) => id.trim()).filter(Boolean)
196
+ : [];
197
+ if (!title) die('用法: loom intent add --title <text> [--depends-on INT-001,INT-002]');
198
+ output(addIntentDraft(versionDir, title, dependencies));
199
+ break;
200
+ }
201
+ case 'revise': {
202
+ const id = rest[0];
203
+ const reasonIdx = argv.indexOf('--reason');
204
+ const reason = reasonIdx !== -1 ? argv[reasonIdx + 1] : null;
205
+ if (!id || !reason) die('用法: loom intent revise <id> --reason <text>');
206
+ output(reviseIntentDraft(versionDir, id, reason));
207
+ break;
208
+ }
209
+ case 'draft': {
210
+ const id = rest[0];
211
+ if (!id) die('用法: loom intent draft <id>');
212
+ output(getIntentDraft(versionDir, id));
213
+ break;
214
+ }
215
+ case 'finalize': {
216
+ const id = rest[0];
217
+ if (!id) die('用法: loom intent finalize <id>');
218
+ const parseIds = (name) => {
219
+ const index = argv.indexOf(name);
220
+ if (index === -1) return [];
221
+ const value = argv[index + 1];
222
+ if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
223
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
224
+ };
225
+ output(finalizeIntentDraft(versionDir, id, {
226
+ review: parseIds('--review'),
227
+ unaffected: parseIds('--unaffected'),
228
+ }));
229
+ break;
230
+ }
231
+ case 'deprecate': {
232
+ const id = rest[0];
233
+ const readFlag = (name) => {
234
+ const index = argv.indexOf(name);
235
+ return index === -1 ? null : argv[index + 1];
236
+ };
237
+ const parseIds = (name) => {
238
+ const value = readFlag(name);
239
+ if (value === null) return [];
240
+ if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
241
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
242
+ };
243
+ const reason = readFlag('--reason');
244
+ if (!id || !reason || reason.startsWith('--')) die('用法: loom intent deprecate <id> --reason <text> [--confirm [--replacement <id>] [--review <ids>] [--unaffected <ids>]]');
245
+ const replacement = readFlag('--replacement');
246
+ if (argv.includes('--replacement') && (!replacement || replacement.startsWith('--'))) die('--replacement 需要当前版本的 Intent ID');
247
+ output(deprecateIntent(versionDir, id, {
248
+ reason,
249
+ confirm: argv.includes('--confirm'),
250
+ replacement,
251
+ review: parseIds('--review'),
252
+ unaffected: parseIds('--unaffected'),
253
+ }));
254
+ break;
255
+ }
70
256
  case 'next':
71
257
  output(getNextIntent(versionDir) ?? '没有可执行的 Intent');
72
258
  break;
@@ -76,25 +262,36 @@ try {
76
262
  console.log(`进度: ${s.counts.completed}/${s.counts.total} 完成`);
77
263
  console.log(` pending: ${s.counts.pending} ${fmt(s.ids.pending)}`);
78
264
  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;
265
+ console.log(` completed: ${s.counts.completed} ${fmt(s.ids.completed)}`);
266
+ console.log(` blocked: ${s.counts.blocked} ${fmt(s.ids.blocked)}`);
267
+ console.log(` needs_review: ${s.counts.needs_review} ${fmt(s.ids.needs_review)}`);
268
+ console.log(` deprecated: ${s.counts.deprecated} ${fmt(s.deprecated)}`);
269
+ break;
82
270
  }
83
271
  case 'graph':
84
272
  output(getDependencyGraph(versionDir));
85
273
  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
- }
274
+ case 'get': {
275
+ const id = rest[0];
276
+ if (!id) die('用法: loom intent get <id>');
277
+ const resolved = resolveIntentRef(versionDir, id);
278
+ output(getIntent(resolved.versionDir, resolved.intentId));
279
+ break;
280
+ }
92
281
  case 'narrative': {
93
282
  const id = rest[0];
94
283
  if (!id) die('用法: loom intent narrative <id>');
95
- output(getNarrative(versionDir, id));
96
- break;
97
- }
284
+ const resolved = resolveIntentRef(versionDir, id);
285
+ output(getNarrative(resolved.versionDir, resolved.intentId));
286
+ break;
287
+ }
288
+ case 'diff': {
289
+ const from = rest[0];
290
+ const to = rest[1];
291
+ if (!from || !to) die('用法: loom intent diff <v1> <v2>');
292
+ output(diffIntentVersions(findLoomRoot(), from, to));
293
+ break;
294
+ }
98
295
  case 'validate':
99
296
  loadIntentMap(versionDir);
100
297
  console.log('Intent Map 校验通过');
@@ -102,7 +299,8 @@ try {
102
299
  case 'trace': {
103
300
  const id = rest[0];
104
301
  if (!id) die('用法: loom intent trace <id>');
105
- output(traceIntent(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir), id));
302
+ const resolved = resolveIntentRef(versionDir, id);
303
+ output(traceIntent(resolved.versionDir, getVerificationsDir(resolved.versionDir), getPhilosophyDir(resolved.versionDir), resolved.intentId));
106
304
  break;
107
305
  }
108
306
  case 'reverse-dep': {
@@ -136,14 +334,28 @@ try {
136
334
  if (!history || history.records.length === 0) {
137
335
  die(`${id} 没有验证记录。先跑: loom verify pass ${id} --summary "..."`);
138
336
  }
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;
337
+ // 最新记录必须针对当前 revision 且通过,旧 revision 的通过不能闭合 Intent。
338
+ const latest = history.records[history.records.length - 1];
339
+ if (latest.verdict !== 'passed') {
340
+ die(`${id} 最新验证记录是 ${latest.verdict}(非 passed)。只有 passed 的 Intent 才能 done。`);
341
+ }
342
+ // 获取当前状态,自动走两步
343
+ const intent = getIntent(versionDir, id);
344
+ if (!isVerificationCurrent(intent, latest)) {
345
+ die(`${id} 最新 passed 验证不属于当前 Intent revision ${intent.revision ?? 1}。先重新验证。`);
346
+ }
347
+ if (intent.quality_strategy === 'atelier') {
348
+ const atelier = validateAtelierRecord(versionDir, id);
349
+ if (!['selected', 'baseline_retained'].includes(atelier.status)) {
350
+ die(`${id} 的 Atelier Record 尚未完成选择(当前: ${atelier.status})`);
351
+ }
352
+ if (latest.atelier?.record_ref !== `09_ATELIER/${id}.json`
353
+ || latest.atelier?.stance_revision !== atelier.stance_revision
354
+ || latest.atelier?.status !== atelier.status) {
355
+ die(`${id} 最新 passed 未绑定当前 Atelier Record 与 stance_revision。请在新的 Keeper task 中重新验证。`);
356
+ }
357
+ }
358
+ const currentStatus = intent.status;
147
359
  if (currentStatus === 'completed') {
148
360
  console.log(`${id} 已经是 completed,无需操作`);
149
361
  break;
@@ -156,13 +368,17 @@ try {
156
368
  break;
157
369
  }
158
370
  default:
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>]`);
371
+ 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]`);
160
372
  }
161
373
  break;
162
374
  }
163
375
 
164
- case 'init': {
165
- const result = initProject(cwd());
376
+ case 'init': {
377
+ if (sub === '--help' || sub === '-h') {
378
+ console.log('用法: loom init\\n\\n在当前目录创建 LOOM 项目骨架。若目录已有 .loom/,不会覆盖现有文件。');
379
+ break;
380
+ }
381
+ const result = initProject(cwd());
166
382
  console.log('LOOM 项目已初始化');
167
383
  for (const c of result.created) console.log(` [created] ${c}`);
168
384
  for (const s of result.skipped) console.log(` [skipped] ${s} (already exists)`);
@@ -179,7 +395,7 @@ try {
179
395
  }
180
396
 
181
397
  case 'activate': {
182
- const role = sub;
398
+ const role = sub;
183
399
  if (!role) die('用法: loom activate <role>\n角色: weaver | visionary | architect | forge | keeper');
184
400
  // weaver 不需要 versionDir(项目还没初始化时也能激活)
185
401
  let versionDir = null;
@@ -191,7 +407,10 @@ try {
191
407
  if (!String(e.message).includes('找不到 .loom')) throw e;
192
408
  }
193
409
  }
194
- const prompt = activateRole(role, versionDir);
410
+ const intentIdx = argv.indexOf('--intent');
411
+ const intentId = intentIdx !== -1 ? argv[intentIdx + 1] : null;
412
+ if (intentIdx !== -1 && !intentId) die('用法: loom activate <role> --intent <id>');
413
+ const prompt = activateRole(role, versionDir, intentId);
195
414
  output(prompt);
196
415
  break;
197
416
  }
@@ -205,24 +424,54 @@ try {
205
424
  output(getPhilosophy(getPhilosophyDir(versionDir), anchor));
206
425
  break;
207
426
  }
208
- case 'list':
209
- output(listPhilosophyFiles(getPhilosophyDir(versionDir)));
210
- break;
427
+ case 'list':
428
+ output(listPhilosophyFiles(getPhilosophyDir(versionDir)));
429
+ break;
430
+ case 'impact': {
431
+ const anchor = rest[0];
432
+ if (!anchor) die('用法: loom philosophy impact <anchor>');
433
+ output(assessPhilosophyImpact(versionDir, anchor));
434
+ break;
435
+ }
436
+ case 'revise': {
437
+ const anchor = rest[0];
438
+ const readFlag = (name) => {
439
+ const index = argv.indexOf(name);
440
+ return index === -1 ? null : argv[index + 1];
441
+ };
442
+ const parseIds = (name) => {
443
+ const value = readFlag(name);
444
+ if (value === null) return [];
445
+ if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
446
+ return value.split(',').map((item) => item.trim()).filter(Boolean);
447
+ };
448
+ const classification = readFlag('--classification');
449
+ const reason = readFlag('--reason');
450
+ if (!anchor || !classification || classification.startsWith('--') || !reason || reason.startsWith('--')) {
451
+ die('用法: loom philosophy revise <anchor> --classification <clarification|minor|major> --reason <text> [--confirm --review <ids> --unaffected <ids>]');
452
+ }
453
+ output(revisePhilosophy(versionDir, anchor, {
454
+ classification,
455
+ reason,
456
+ confirm: argv.includes('--confirm'),
457
+ review: parseIds('--review'),
458
+ unaffected: parseIds('--unaffected'),
459
+ }));
460
+ break;
461
+ }
211
462
  case 'check': {
212
- const philDir = getPhilosophyDir(versionDir);
213
- const inspiration = validateInspirationSources(philDir);
214
- const decomposition = validatePartDecomposition(philDir);
215
- const allIssues = [...inspiration.issues, ...decomposition.issues];
216
- const allPassed = inspiration.passed && decomposition.passed;
463
+ const philDir = getPhilosophyDir(versionDir);
464
+ const inspiration = validateInspirationSources(philDir);
465
+ const allIssues = inspiration.issues;
466
+ const allPassed = inspiration.passed;
217
467
 
218
468
  // --json: 结构化输出,供 Agent 程序化消费
219
469
  if (argv.includes('--json')) {
220
470
  output({
221
- passed: allPassed,
222
- issues: allIssues,
223
- sources: inspiration.sources.map(({ file, sources }) => ({ file, count: sources.length })),
224
- parts: decomposition.parts,
225
- });
471
+ passed: allPassed,
472
+ issues: allIssues,
473
+ sources: inspiration.sources.map(({ file, sources }) => ({ file, count: sources.length })),
474
+ });
226
475
  exit(allPassed ? 0 : 1);
227
476
  }
228
477
 
@@ -232,10 +481,6 @@ try {
232
481
  for (const { file, sources } of inspiration.sources) {
233
482
  console.log(` ${file}: ${sources.length} 个源`);
234
483
  }
235
- console.log(' 实现部分拆解:');
236
- for (const part of decomposition.parts) {
237
- console.log(` - ${part}`);
238
- }
239
484
  } else {
240
485
  const high = allIssues.filter((i) => i.severity === 'high').length;
241
486
  const medium = allIssues.filter((i) => i.severity === 'medium').length;
@@ -244,13 +489,13 @@ try {
244
489
  const icon = issue.severity === 'high' ? '⚠' : '·';
245
490
  console.log(` ${icon} [${issue.severity}] ${issue.msg}`);
246
491
  }
247
- console.log('\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/PART_DECOMPOSITION.md + dimensions/SEARCH_METHODOLOGY.md。');
492
+ console.log('\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/SEARCH_METHODOLOGY.md。');
248
493
  exit(1);
249
494
  }
250
495
  break;
251
496
  }
252
497
  default:
253
- die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list|check]`);
498
+ die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list|check|impact <anchor>|revise <anchor> --classification <clarification|minor|major> --reason <text>]`);
254
499
  }
255
500
  break;
256
501
  }
@@ -265,11 +510,16 @@ try {
265
510
  output(getVerificationContract(versionDir, id));
266
511
  break;
267
512
  }
268
- case 'history': {
269
- const id = rest[0];
270
- if (!id) die('用法: loom verify history <id>');
271
- const history = getVerificationHistory(verificationsDir, id);
272
- output(history ?? `没有 ${id} 的验证记录`);
513
+ case 'history': {
514
+ const id = rest[0];
515
+ if (!id) die('用法: loom verify history <id>');
516
+ if (argv.includes('--across-versions')) {
517
+ output(getAcrossVersionVerificationHistory(versionDir, id));
518
+ } else {
519
+ const resolved = resolveIntentRef(versionDir, id);
520
+ const history = getVerificationHistory(getVerificationsDir(resolved.versionDir), resolved.intentId);
521
+ output(history ?? `没有 ${resolved.ref} 的验证记录`);
522
+ }
273
523
  break;
274
524
  }
275
525
  case 'pending':
@@ -299,41 +549,54 @@ try {
299
549
  } else {
300
550
  die('用法: loom verify write --json-file <path> | --json <json-string>');
301
551
  }
302
- const result = writeVerification(verificationsDir, record);
552
+ const result = writeVerification(versionDir, verificationsDir, record);
303
553
  console.log(`验证记录已写入: ${result.filePath}`);
304
554
  console.log(` 轮次: ${result.round}, verdict: ${record.verdict}`);
305
- if (record.verdict === 'deviated') {
306
- console.log(` deviated 累计: ${result.deviated_count} 轮`);
307
- if (result.should_escalate) {
308
- console.log(` ⚠ 达到 3 轮上限,应升级为 blocked——Keeper 应执行: loom intent update ${record.intent_id} --status blocked`);
309
- }
310
- }
555
+ if (record.verdict === 'deviated') {
556
+ console.log(` deviated 累计: ${result.deviated_count} 轮`);
557
+ if (result.should_escalate) {
558
+ updateIntentStatus(versionDir, record.intent_id, 'blocked');
559
+ console.log(` ⚠ 已达到 3 轮上限,${record.intent_id} 已自动升级为 blocked`);
560
+ }
561
+ }
311
562
  break;
312
563
  }
313
564
  case 'pass':
314
565
  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];
566
+ // loom verify pass <id> --summary "..." [--reproduction-command "..."] [--preservation-evidence "..."] [--quality-proof "..."]
567
+ // loom verify fail <id> --summary "..." [--deviation "..."] [--reproduction-command "..."]
568
+ const id = rest[0];
569
+ if (!id) die(`用法: loom verify ${sub} <id> --summary "..." [--reproduction-command "..."]${sub === 'pass' ? ' [--preservation-evidence "..."] [--quality-proof "..."]' : ' [--deviation "..."]'}`);
570
+ const summaryIdx = argv.indexOf('--summary');
571
+ const reproIdx = argv.indexOf('--reproduction-command');
572
+ const deviationIdx = argv.indexOf('--deviation');
573
+ const qualityProofIdx = argv.indexOf('--quality-proof');
574
+ const preservationIdx = argv.indexOf('--preservation-evidence');
575
+ const summary = summaryIdx !== -1 ? argv[summaryIdx + 1] : null;
576
+ if (!summary) die(`缺少 --summary: loom verify ${sub} ${id} --summary "..."`);
577
+ const intent = getIntent(versionDir, id);
578
+ if (sub === 'pass' && intent.continuity_required && !(preservationIdx !== -1 && argv[preservationIdx + 1])) {
579
+ die(`Intent ${id} 声明了 continuity_required;通过前必须提供 --preservation-evidence,证明旧状态 → 新操作后的完整序列未发生未授权丢失。`);
580
+ }
581
+ if (sub === 'pass' && intent.quality_contract && !(qualityProofIdx !== -1 && argv[qualityProofIdx + 1])) {
582
+ die(`Intent ${id} 声明了 quality_contract;通过前必须提供 --quality-proof,指向项目内真实的 Quality Proof Markdown 锚点。`);
583
+ }
584
+ const extras = {};
585
+ if (reproIdx !== -1 && argv[reproIdx + 1]) extras.reproduction_command = argv[reproIdx + 1];
586
+ if (sub === 'fail' && deviationIdx !== -1 && argv[deviationIdx + 1]) extras.deviation_detail = argv[deviationIdx + 1];
587
+ if (sub === 'pass' && qualityProofIdx !== -1 && argv[qualityProofIdx + 1]) extras.quality_proof_ref = argv[qualityProofIdx + 1];
588
+ if (sub === 'pass' && preservationIdx !== -1 && argv[preservationIdx + 1]) extras.preservation_evidence = argv[preservationIdx + 1];
327
589
  const verdict = sub === 'pass' ? 'passed' : 'deviated';
328
- const result = createQuickVerification(verificationsDir, id, verdict, summary, extras);
590
+ const result = createQuickVerification(versionDir, verificationsDir, id, verdict, summary, extras);
329
591
  console.log(`验证记录已写入: ${result.filePath}`);
330
592
  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
- }
593
+ if (verdict === 'deviated') {
594
+ console.log(` deviated 累计: ${result.deviated_count} 轮`);
595
+ if (result.should_escalate) {
596
+ updateIntentStatus(versionDir, id, 'blocked');
597
+ console.log(` ⚠ 已达到 3 轮上限,${id} 已自动升级为 blocked`);
598
+ }
599
+ }
337
600
  break;
338
601
  }
339
602
  default:
@@ -342,7 +605,7 @@ try {
342
605
  break;
343
606
  }
344
607
 
345
- case 'version': {
608
+ case 'version': {
346
609
  const loomRoot = findLoomRoot();
347
610
  switch (sub) {
348
611
  case 'list': {
@@ -389,8 +652,42 @@ try {
389
652
  default:
390
653
  die(`未知子命令: version ${sub}\n用法: loom version [list|current|new|use <v>|diff <v1> <v2>]`);
391
654
  }
392
- break;
393
- }
655
+ break;
656
+ }
657
+
658
+ case 'patch': {
659
+ const versionDir = findVersionDir();
660
+ switch (sub) {
661
+ case 'record': {
662
+ const fileFlagIdx = argv.indexOf('--json-file');
663
+ const inputPath = fileFlagIdx !== -1 ? argv[fileFlagIdx + 1] : null;
664
+ if (!inputPath) die('用法: loom patch record --json-file <path>');
665
+ let record;
666
+ try {
667
+ record = JSON.parse(readFileSync(inputPath, 'utf-8'));
668
+ } catch (e) {
669
+ die(`JSON 文件解析失败: ${inputPath}\n原因: ${e.message}`);
670
+ }
671
+ output(recordPatch(versionDir, record));
672
+ break;
673
+ }
674
+ case 'list':
675
+ output(listPatches(versionDir));
676
+ break;
677
+ case 'get': {
678
+ const id = rest[0];
679
+ if (!id) die('用法: loom patch get <id>');
680
+ output(getPatch(versionDir, id));
681
+ break;
682
+ }
683
+ case 'validate':
684
+ output(validatePatches(versionDir));
685
+ break;
686
+ default:
687
+ die(`未知子命令: patch ${sub}\n用法: loom patch [record --json-file <path>|list|get <id>|validate]`);
688
+ }
689
+ break;
690
+ }
394
691
 
395
692
  case 'doctor': {
396
693
  const versionDir = findVersionDir();
@@ -406,7 +703,7 @@ try {
406
703
  console.log(` → 修复: ${issue.fix_hint}`);
407
704
  }
408
705
  }
409
- console.log(`\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/PART_DECOMPOSITION.md + dimensions/SEARCH_METHODOLOGY.md。`);
706
+ console.log(`\n参见 meta/PHILOSOPHY_WEAVER.md + dimensions/SEARCH_METHODOLOGY.md。`);
410
707
  }
411
708
  break;
412
709
  }
@@ -614,25 +911,46 @@ To Human:
614
911
  loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
615
912
  loom guide 诊断当前阶段,输出下一步引导
616
913
  loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
617
- loom auto on|off|status AUTO 模式开关(on Agent 自动连续执行)
618
- loom activate <role> 输出角色激活提示词(weaver|visionary|architect|forge|keeper)
914
+ loom auto on|off|status AUTO 编排协议开关(由 Agent/runtime 消费,不由 CLI 自行执行)
915
+ loom activate <role> [--intent <id>] 输出普通或 Intent-scoped 角色激活提示词
619
916
 
620
917
  loom version list 列出所有版本(* 标记当前)
621
918
  loom version current 显示当前版本
622
919
  loom version new 创建 v{N+1} + 自动切换为当前
623
920
  loom version use <v> 切换当前版本
624
- loom version diff <v1> <v2> 对比两个版本的文件差异
625
-
626
- loom intent next 返回下一个可执行 Intent
921
+ loom version diff <v1> <v2> 对比两个版本的文件差异
922
+
923
+ loom patch record --json-file <path> 记录 Patch 并生成 Markdown 投影
924
+ loom patch list 列出当前版本的 Patch
925
+ loom patch get <id> 返回指定 Patch
926
+ loom patch validate 校验 Patch ledger 和 Markdown 投影
927
+
928
+ loom capability graph 输出 Capability Graph 的 Mermaid 投影与摘要
929
+ loom capability frontier 列出尚未路由的高影响节点
930
+ loom capability get <id> 返回节点、关系、Brief 与 Intent 回链
931
+ loom capability coverage 检查图谱覆盖、Brief 和 Intent 回链
932
+ loom capability compile <id> 只读显示会进入该 Intent 的能力输入
933
+ loom atelier init <id> 为 atelier Intent 创建唯一创作记录
934
+ loom atelier get <id> 返回并校验当前 Atelier Record
935
+ loom atelier validate <id> 校验 Record、revision、候选与证据引用
936
+
937
+ loom intent next 返回下一个可执行 Intent
938
+ loom intent add --title <text> [--depends-on <ids>] 创建新增 draft
939
+ loom intent revise <id> --reason <text> 创建修订 draft 并报告反向依赖
940
+ loom intent draft <id> 查看 draft
941
+ loom intent finalize <id> 校验并原子写入官方 Intent Map
942
+ loom intent deprecate <id> --reason <text> 只读评估;加 --confirm 才弃用
627
943
  loom intent status 返回进度概览
628
944
  loom intent graph 输出 Mermaid 依赖图
629
945
  loom intent get <id> 返回某 Intent 完整信息
630
- loom intent narrative <id> 返回某 Intent 的意图叙事(解析 narrative_ref)
946
+ loom intent narrative <id> 返回某 Intent 的意图叙事(解析 narrative_ref)
947
+ loom intent diff <v1> <v2> 按显式 lineage 对比 Intent 语义
631
948
  loom intent validate 校验 Intent Map 结构
632
949
  loom intent trace <id> 返回某 Intent 的完整追溯链(依赖+验证+哲学+叙事)
633
950
  loom intent reverse-dep <id> 返回依赖某 Intent 的所有 Intent(变更影响评估)
634
951
  loom intent reverse-ref <anchor> 返回引用某哲学锚点的所有 Intent
635
- loom intent update <id> --status <s> 更新 Intent 状态(Keeper 用)
952
+ loom intent update <id> --status <s> 更新 Intent 状态(Keeper 用)
953
+ loom intent done <id> 当前 revision 验证通过后闭合 Intent
636
954
 
637
955
  loom doctor 项目健康检查(一致性+孤儿引用+循环依赖+僵尸)
638
956
  loom context 上下文摘要(进度+下一步+待验证+风险)
@@ -640,18 +958,23 @@ To Human:
640
958
  loom preview status 检查 preview 是否存在、是否新鲜
641
959
  loom preview --regen 强制重新输出提示词(让 AI 重新生成 HTML)
642
960
  loom preview --stale 强行打开过期 preview
643
- loom help <topic> 分层指南(workflow|concepts|loop|version|doctor|preview)
961
+ loom help <topic> 分层指南(含 patch 工作流)
644
962
 
645
- loom philosophy get <anchor> 按锚点加载哲学章节
646
- loom philosophy list 列出哲学文档文件
647
- loom philosophy check 校验灵感来源质量(源数量/多样性/理由)
963
+ loom philosophy get <anchor> 按锚点加载哲学章节
964
+ loom philosophy list 列出哲学文档文件
965
+ loom philosophy check 校验灵感来源质量(源数量/多样性/理由)
966
+ loom philosophy impact <anchor> 只读分析直接引用和传递影响
967
+ loom philosophy revise <anchor> --classification <type> --reason <text> 只读评估;加 --confirm 才记录 clarification/minor
648
968
 
649
969
  loom verify contract <id> 返回某 Intent 的验收契约(解析引用)
650
- loom verify history <id> 返回某 Intent 验证历史
970
+ loom verify history <id> 返回某 Intent 本地验证历史
971
+ loom verify history <ref> --across-versions 递归返回 lineage 各版本的本地历史
651
972
  loom verify pending 返回待验证的 Intent
652
973
  loom verify list 列出所有验证记录
653
974
  loom verify write --json-file <path> 从文件读入并写入验证记录
654
- loom verify write --json <string> 从命令行字符串写入验证记录
975
+ loom verify write --json <string> 从命令行字符串写入验证记录
976
+ loom verify pass|fail <id> --summary <text> 快捷写入验证结果
977
+ --quality-proof <ref> 声明相对质量提升时,指向基线比较与稳定性证据
655
978
 
656
979
  参数:
657
980
  --loom-dir <path> 指定 .loom/v{N} 目录(默认读 .loom/current 指针)`);