@haaaiawd/loom 1.2.2 → 2.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 (82) hide show
  1. package/CHANGELOG.md +11 -58
  2. package/CONTRIBUTING.md +37 -0
  3. package/EVIL_EVAL.md +112 -0
  4. package/README.md +193 -446
  5. package/README.zh-CN.md +174 -0
  6. package/SECURITY.md +11 -0
  7. package/cli/bin/loom.js +170 -995
  8. package/cli/src/protocol.js +367 -0
  9. package/cli/src/store.js +626 -0
  10. package/design.md +194 -0
  11. package/docs/PROMPT_CATALOG.md +99 -0
  12. package/docs/RELEASE_CHECKLIST.md +53 -0
  13. package/docs/UX_FLOW.md +171 -0
  14. package/docs/brand/loom-mark.svg +18 -0
  15. package/docs/brand/loom-readme-header.svg +34 -0
  16. package/docs/brand/loom-readme-header.zh-CN.svg +29 -0
  17. package/docs/loom-eval-loop.drawio +21 -0
  18. package/docs/loom-eval-loop.svg +56 -0
  19. package/docs/loom-production-loop.drawio +41 -0
  20. package/docs/loom-production-loop.svg +92 -0
  21. package/package.json +43 -40
  22. package/EXTERNAL_ACQUISITION_DESIGN.md +0 -143
  23. package/cli/help/asset.md +0 -36
  24. package/cli/help/atelier.md +0 -37
  25. package/cli/help/capability.md +0 -73
  26. package/cli/help/concepts.md +0 -103
  27. package/cli/help/doctor.md +0 -73
  28. package/cli/help/expertise.md +0 -51
  29. package/cli/help/loop.md +0 -134
  30. package/cli/help/patch.md +0 -33
  31. package/cli/help/preview.md +0 -60
  32. package/cli/help/proposals.md +0 -21
  33. package/cli/help/version.md +0 -136
  34. package/cli/help/workflow.md +0 -114
  35. package/cli/src/activate.js +0 -473
  36. package/cli/src/asset-library.js +0 -384
  37. package/cli/src/atelier.js +0 -331
  38. package/cli/src/auto.js +0 -116
  39. package/cli/src/capability-graph.js +0 -430
  40. package/cli/src/capability-proposals.js +0 -225
  41. package/cli/src/diagnostics.js +0 -766
  42. package/cli/src/expertise-pack.js +0 -336
  43. package/cli/src/guide.js +0 -495
  44. package/cli/src/help.js +0 -41
  45. package/cli/src/init.js +0 -187
  46. package/cli/src/intent-draft.js +0 -303
  47. package/cli/src/intent-map.js +0 -747
  48. package/cli/src/patch.js +0 -214
  49. package/cli/src/philosophy.js +0 -331
  50. package/cli/src/preview-prompt.md +0 -337
  51. package/cli/src/preview.js +0 -73
  52. package/cli/src/shared/intent-ref.js +0 -38
  53. package/cli/src/shared/md-utils.js +0 -125
  54. package/cli/src/shared/paths.js +0 -73
  55. package/cli/src/shared/proof-reference.js +0 -19
  56. package/cli/src/shared/verification-method.js +0 -32
  57. package/cli/src/verify.js +0 -394
  58. package/cli/src/version.js +0 -134
  59. package/dimensions/AUTHORSHIP.md +0 -45
  60. package/dimensions/PART_DECOMPOSITION.md +0 -42
  61. package/dimensions/SEARCH_METHODOLOGY.md +0 -101
  62. package/dimensions/examples/AGENT_SYSTEM/README.md +0 -219
  63. package/dimensions/examples/CLI_TOOL/README.md +0 -163
  64. package/dimensions/universal/COLLABORATION_PHILOSOPHY.md +0 -28
  65. package/dimensions/universal/ENGINEERING_CREED.md +0 -30
  66. package/dimensions/universal/PRODUCT_PHILOSOPHY.md +0 -32
  67. package/meta/BASELINE.md +0 -91
  68. package/meta/INTENT_LOOP.md +0 -296
  69. package/meta/PHILOSOPHY_WEAVER.md +0 -110
  70. package/meta/ROLE_ACTIVATION.md +0 -114
  71. package/roles/architect.md +0 -86
  72. package/roles/forge.md +0 -112
  73. package/roles/keeper.md +0 -113
  74. package/roles/visionary.md +0 -57
  75. package/templates/ASSET_LIBRARY_MANIFEST_TEMPLATE.json +0 -10
  76. package/templates/ATELIER_RECORD_TEMPLATE.json +0 -48
  77. package/templates/CAPABILITY_BRIEF_TEMPLATE.md +0 -35
  78. package/templates/CAPABILITY_GRAPH_TEMPLATE.json +0 -11
  79. package/templates/EXPERTISE_PACK_TEMPLATE.json +0 -22
  80. package/templates/INTENT_MAP_TEMPLATE.json +0 -85
  81. package/templates/PHILOSOPHY_TEMPLATE.md +0 -44
  82. package/templates/VISION_TEMPLATE.md +0 -44
package/cli/bin/loom.js CHANGED
@@ -1,1031 +1,206 @@
1
1
  #!/usr/bin/env node
2
- // loom — LOOM 框架的 CLI 传感器层
3
- // Agent 通过这个 CLI 访问 Intent Map / 哲学 / 验证记录,不直接读文件。
4
2
 
5
- import { argv, cwd, env, exit } from 'node:process';
6
- import { resolve, join, dirname } from 'node:path';
7
- import { existsSync, readdirSync, readFileSync } from 'node:fs';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
8
5
  import { fileURLToPath } from 'node:url';
9
- import { findLoomRoot, findVersionDir, readCurrentPointer } from '../src/shared/paths.js';
10
-
11
- const __dirname = dirname(fileURLToPath(import.meta.url));
12
-
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
- import { initProject } from '../src/init.js';
17
- import { activateRole } from '../src/activate.js';
18
- import { listVersions, newVersion, useVersion, diffVersions } from '../src/version.js';
19
- import { doctor, contextSummary, traceIntent, reverseDep, reverseRef } from '../src/diagnostics.js';
20
- import { getHelpTopic, listHelpTopics } from '../src/help.js';
21
- import { guideProject } from '../src/guide.js';
22
- import { isAutoOn, autoOn, autoOff, autoStatus, getAutoMode } from '../src/auto.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';
31
- import { assertExpertiseReady, getExpertisePack, initExpertisePack, validateExpertisePack } from '../src/expertise-pack.js';
32
-
33
- // ─── 路径解析 ──────────────────────────────────────────
34
- // findLoomRoot / findVersionDir / readCurrentPointer 已提取到 shared/paths.js
35
- // 这里只保留目录辅助函数。
36
-
37
- function getPhilosophyDir(versionDir) {
38
- return join(versionDir, '00_PHILOSOPHY');
6
+ import {
7
+ blockTask,
8
+ checkProject,
9
+ compileContext,
10
+ completeTask,
11
+ createCapability,
12
+ createDesign,
13
+ getCapability,
14
+ getDesign,
15
+ getKeeperPrompt,
16
+ getTask,
17
+ importTasks,
18
+ initProject,
19
+ listCapabilities,
20
+ listDesigns,
21
+ loadProject,
22
+ markReady,
23
+ recordKeeper,
24
+ recordUnderstanding,
25
+ reopenTask,
26
+ scaffoldEval,
27
+ skipKeeper,
28
+ startTask,
29
+ taskSummary,
30
+ updateTask,
31
+ } from '../src/store.js';
32
+ import { promptCatalog } from '../src/protocol.js';
33
+
34
+ const argv = process.argv.slice(2);
35
+ const [command, subcommand, ...rest] = argv;
36
+
37
+ function output(value) {
38
+ console.log(typeof value === 'string' ? value : JSON.stringify(value, null, 2));
39
39
  }
40
40
 
41
- function getVerificationsDir(versionDir) {
42
- return join(versionDir, 'verifications');
41
+ function fail(message) {
42
+ console.error(`Error: ${message}`);
43
+ process.exitCode = 1;
43
44
  }
44
45
 
45
- // ─── 输出工具 ──────────────────────────────────────────
46
+ function option(name) {
47
+ const index = argv.indexOf(name);
48
+ return index === -1 ? undefined : argv[index + 1];
49
+ }
46
50
 
47
- function output(data) {
48
- if (typeof data === 'string') {
49
- console.log(data);
50
- } else {
51
- console.log(JSON.stringify(data, null, 2));
51
+ function jsonFile() {
52
+ const path = option('--json-file');
53
+ if (!path) throw new Error('This command requires --json-file <path>');
54
+ try {
55
+ return JSON.parse(readFileSync(resolve(path), 'utf8'));
56
+ } catch (error) {
57
+ throw new Error(`Cannot read JSON file ${path}: ${error.message}`);
52
58
  }
53
59
  }
54
60
 
55
- function die(msg, code = 1) {
56
- console.error(`错误: ${msg}`);
57
- exit(code);
61
+ function help() {
62
+ return `loom — continuity infrastructure for Agents
63
+
64
+ The human talks naturally to the Agent. The Agent uses these commands silently.
65
+
66
+ Start and resume
67
+ loom init
68
+ loom context [--task TASK-001] [--keeper]
69
+ loom check
70
+ loom prompts
71
+
72
+ Preserve understanding
73
+ loom record --json-file <update.json>
74
+ loom project ready
75
+ loom design add <slug> --title <text> --kind <product|experience|system|contract|verification|operations|research>
76
+ loom design list|get <slug>
77
+ loom capability add <slug> --title <professional-field>
78
+ loom capability list|get <slug>
79
+
80
+ Maintain the work map
81
+ loom task plan --json-file <tasks.json>
82
+ loom task status|next|get <id>
83
+ loom task start <id>
84
+ loom task update <id> --json-file <patch.json>
85
+ loom task block <id> --json-file <block.json>
86
+ loom task reopen <id> [--reason <reason>]
87
+ loom task done <id> --json-file <evidence.json>
88
+
89
+ One-time independent handoff
90
+ loom keeper prompt
91
+ loom keeper record --json-file <result.json>
92
+ loom keeper skip --reason <reason>
93
+
94
+ Evaluate LOOM itself
95
+ loom eval scaffold --json-file <scenario.json>
96
+
97
+ Use JSON files for structured writes so long content and shell quoting remain auditable.
98
+ Task completion JSON includes evidence plus one check for every exact done_when criterion.`;
58
99
  }
59
100
 
60
- // ─── 命令路由 ──────────────────────────────────────────
61
-
62
- const [cmd, sub, ...rest] = argv.slice(2);
63
-
64
101
  try {
65
- switch (cmd) {
66
- case '--version':
102
+ switch (command) {
103
+ case '--version':
67
104
  case '-v': {
68
- // 从根 package.json 读版本号(cli/bin -> cli -> LOOM root)
69
- const pkgPath = resolve(__dirname, '..', '..', 'package.json');
70
- const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
71
- console.log(`loom ${pkg.version}`);
72
- break;
73
- }
74
-
75
- case 'capability': {
76
- const versionDir = findVersionDir();
77
- switch (sub) {
78
- case 'graph':
79
- output(getCapabilityGraphProjection(versionDir));
80
- break;
81
- case 'frontier':
82
- output(getCapabilityFrontier(versionDir));
83
- break;
84
- case 'get': {
85
- const id = rest[0];
86
- if (!id) die('用法: loom capability get <node-id>');
87
- output(getCapabilityNode(versionDir, id));
88
- break;
89
- }
90
- case 'coverage':
91
- output(getCapabilityCoverage(versionDir));
92
- break;
93
- case 'compile': {
94
- const intentId = rest[0];
95
- if (!intentId) die('用法: loom capability compile <intent-id>');
96
- output(compileCapabilityInputs(versionDir, intentId));
97
- break;
98
- }
99
- case 'proposal': {
100
- const action = rest[0];
101
- if (action === 'list') output(listCapabilityProposals(versionDir));
102
- else if (action === 'get') {
103
- if (!rest[1]) die('用法: loom capability proposal get <CGP-ID>');
104
- output(getCapabilityProposal(versionDir, rest[1]));
105
- } else if (action === 'submit') {
106
- const fileIndex = argv.indexOf('--json-file');
107
- const path = fileIndex === -1 ? null : argv[fileIndex + 1];
108
- if (!path) die('用法: loom capability proposal submit --json-file <proposal.json>');
109
- output(submitCapabilityProposal(versionDir, JSON.parse(readFileSync(path, 'utf-8'))));
110
- } else if (action === 'decide') {
111
- const rationaleIndex = argv.indexOf('--rationale');
112
- if (!rest[1] || !rest[2] || rationaleIndex === -1 || !argv[rationaleIndex + 1]) die('用法: loom capability proposal decide <CGP-ID> <decision> --rationale <text>');
113
- output(decideCapabilityProposal(versionDir, rest[1], rest[2], argv[rationaleIndex + 1]));
114
- } else if (action === 'close') {
115
- const resolutionIndex = argv.indexOf('--resolution-file');
116
- if (!rest[1] || resolutionIndex === -1 || !argv[resolutionIndex + 1]) die('用法: loom capability proposal close <CGP-ID> --resolution-file <resolution.json>');
117
- output(closeCapabilityProposal(versionDir, rest[1], JSON.parse(readFileSync(argv[resolutionIndex + 1], 'utf-8'))));
118
- } else die('用法: loom capability proposal [list|get|submit|decide|close]');
119
- break;
120
- }
121
- default:
122
- die(`未知 capability 子命令: ${sub}\n用法: loom capability [graph|frontier|get|coverage|compile|proposal]`);
123
- }
124
- break;
125
- }
126
-
127
- case 'asset': {
128
- const versionDir = findVersionDir();
129
- switch (sub) {
130
- case 'import': {
131
- const filePath = rest[0];
132
- const option = (name) => {
133
- const index = argv.indexOf(name);
134
- return index === -1 ? undefined : argv[index + 1];
135
- };
136
- if (!filePath || filePath.startsWith('--')) die('用法: loom asset import <本地文件> --tags <标签,...> --source <来源> --author <作者> --license <许可> --approval approved [--kind image] [--evidence <节点,...>]');
137
- output(importAsset(versionDir, filePath, {
138
- tags: option('--tags'), source: option('--source'), author: option('--author'), license: option('--license'),
139
- approval: option('--approval'), kind: option('--kind'), evidenceRefs: option('--evidence'),
140
- failureInjection: env.NODE_ENV === 'test' && option('--test-fail-after') === 'manifest' ? 'after_manifest'
141
- : env.NODE_ENV === 'test' && option('--test-fail-after') === 'crash-manifest' ? 'crash_after_manifest' : undefined,
142
- }));
143
- break;
144
- }
145
- case 'list': output(listAssets(versionDir)); break;
146
- case 'search': {
147
- const query = rest[0];
148
- if (!query) die('用法: loom asset search <查询>');
149
- output(searchAssets(versionDir, query));
150
- break;
151
- }
152
- case 'get': {
153
- const id = rest[0];
154
- if (!id) die('用法: loom asset get <asset-id>');
155
- output(getAsset(versionDir, id));
156
- break;
157
- }
158
- case 'validate': {
159
- const recovery = recoverAssetImportTransaction(versionDir);
160
- output({ valid: true, assets: Object.keys(validateAssetLibrary(versionDir).assets).length, recovered_transaction: recovery.recovered ? recovery : null });
161
- break;
162
- }
163
- default: die(`未知 asset 子命令: ${sub}\n用法: loom asset [import|list|search|get|validate]`);
164
- }
165
- break;
166
- }
167
-
168
- case 'atelier': {
169
- const versionDir = findVersionDir();
170
- const intentId = rest[0];
171
- if (!intentId) die(`用法: loom atelier ${sub || '<init|get|validate>'} <intent-id>`);
172
- switch (sub) {
173
- case 'init':
174
- output(initAtelierRecord(versionDir, intentId));
175
- break;
176
- case 'get':
177
- output(getAtelierRecord(versionDir, intentId));
178
- break;
179
- case 'validate':
180
- output(validateAtelierRecord(versionDir, intentId));
181
- break;
182
- default:
183
- die(`未知 atelier 子命令: ${sub}\n用法: loom atelier [init|get|validate] <intent-id>`);
184
- }
185
- break;
186
- }
187
-
188
- case 'expertise': {
189
- const versionDir = findVersionDir();
190
- const intentId = rest[0];
191
- if (!intentId) die(`用法: loom expertise ${sub || '<init|get|validate>'} <intent-id>`);
192
- switch (sub) {
193
- case 'init':
194
- output(initExpertisePack(versionDir, intentId));
195
- break;
196
- case 'get':
197
- output(getExpertisePack(versionDir, intentId));
198
- break;
199
- case 'validate':
200
- output(validateExpertisePack(versionDir, intentId));
201
- break;
202
- default:
203
- die(`未知 expertise 子命令: ${sub}\n用法: loom expertise [init|get|validate] <intent-id>`);
204
- }
205
- break;
206
- }
207
-
208
- case 'intent': {
209
- const versionDir = findVersionDir();
210
- switch (sub) {
211
- case 'add': {
212
- const titleIdx = argv.indexOf('--title');
213
- const dependsIdx = argv.indexOf('--depends-on');
214
- const title = titleIdx !== -1 ? argv[titleIdx + 1] : null;
215
- const dependencies = dependsIdx !== -1 && argv[dependsIdx + 1]
216
- ? argv[dependsIdx + 1].split(',').map((id) => id.trim()).filter(Boolean)
217
- : [];
218
- if (!title) die('用法: loom intent add --title <text> [--depends-on INT-001,INT-002]');
219
- output(addIntentDraft(versionDir, title, dependencies));
220
- break;
221
- }
222
- case 'revise': {
223
- const id = rest[0];
224
- const reasonIdx = argv.indexOf('--reason');
225
- const reason = reasonIdx !== -1 ? argv[reasonIdx + 1] : null;
226
- if (!id || !reason) die('用法: loom intent revise <id> --reason <text>');
227
- output(reviseIntentDraft(versionDir, id, reason));
228
- break;
229
- }
230
- case 'draft': {
231
- const id = rest[0];
232
- if (!id) die('用法: loom intent draft <id>');
233
- output(getIntentDraft(versionDir, id));
234
- break;
235
- }
236
- case 'finalize': {
237
- const id = rest[0];
238
- if (!id) die('用法: loom intent finalize <id>');
239
- const parseIds = (name) => {
240
- const index = argv.indexOf(name);
241
- if (index === -1) return [];
242
- const value = argv[index + 1];
243
- if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
244
- return value.split(',').map((item) => item.trim()).filter(Boolean);
245
- };
246
- output(finalizeIntentDraft(versionDir, id, {
247
- review: parseIds('--review'),
248
- unaffected: parseIds('--unaffected'),
249
- }));
250
- break;
251
- }
252
- case 'deprecate': {
253
- const id = rest[0];
254
- const readFlag = (name) => {
255
- const index = argv.indexOf(name);
256
- return index === -1 ? null : argv[index + 1];
257
- };
258
- const parseIds = (name) => {
259
- const value = readFlag(name);
260
- if (value === null) return [];
261
- if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
262
- return value.split(',').map((item) => item.trim()).filter(Boolean);
263
- };
264
- const reason = readFlag('--reason');
265
- if (!id || !reason || reason.startsWith('--')) die('用法: loom intent deprecate <id> --reason <text> [--confirm [--replacement <id>] [--review <ids>] [--unaffected <ids>]]');
266
- const replacement = readFlag('--replacement');
267
- if (argv.includes('--replacement') && (!replacement || replacement.startsWith('--'))) die('--replacement 需要当前版本的 Intent ID');
268
- output(deprecateIntent(versionDir, id, {
269
- reason,
270
- confirm: argv.includes('--confirm'),
271
- replacement,
272
- review: parseIds('--review'),
273
- unaffected: parseIds('--unaffected'),
274
- }));
275
- break;
276
- }
277
- case 'next':
278
- {
279
- const next = getNextIntent(versionDir);
280
- output(next ? {
281
- ...next,
282
- suggested_flow: {
283
- start: `loom intent update ${next.id} --status in_progress`,
284
- forge_context: `loom activate forge --intent ${next.id}`,
285
- note: '先显式开始 Intent,再由 activate 命令装配该 Intent 的叙事、契约、图谱路由与专业输入。',
286
- },
287
- } : '没有可执行的 Intent');
288
- }
289
- break;
290
- case 'status': {
291
- const s = getStatus(versionDir);
292
- const fmt = (ids) => ids.map((id) => s.titles[id] ? `${id}(${s.titles[id]})` : id).join(', ') || '-';
293
- console.log(`进度: ${s.counts.completed}/${s.counts.total} 完成`);
294
- console.log(` pending: ${s.counts.pending} ${fmt(s.ids.pending)}`);
295
- console.log(` in_progress: ${s.counts.in_progress} ${fmt(s.ids.in_progress)}`);
296
- console.log(` completed: ${s.counts.completed} ${fmt(s.ids.completed)}`);
297
- console.log(` blocked: ${s.counts.blocked} ${fmt(s.ids.blocked)}`);
298
- console.log(` needs_review: ${s.counts.needs_review} ${fmt(s.ids.needs_review)}`);
299
- console.log(` deprecated: ${s.counts.deprecated} ${fmt(s.deprecated)}`);
300
- break;
301
- }
302
- case 'graph':
303
- output(getDependencyGraph(versionDir));
304
- break;
305
- case 'get': {
306
- const id = rest[0];
307
- if (!id) die('用法: loom intent get <id>');
308
- const resolved = resolveIntentRef(versionDir, id);
309
- output(getIntent(resolved.versionDir, resolved.intentId));
310
- break;
311
- }
312
- case 'narrative': {
313
- const id = rest[0];
314
- if (!id) die('用法: loom intent narrative <id>');
315
- const resolved = resolveIntentRef(versionDir, id);
316
- output(getNarrative(resolved.versionDir, resolved.intentId));
317
- break;
318
- }
319
- case 'diff': {
320
- const from = rest[0];
321
- const to = rest[1];
322
- if (!from || !to) die('用法: loom intent diff <v1> <v2>');
323
- output(diffIntentVersions(findLoomRoot(), from, to));
324
- break;
325
- }
326
- case 'validate':
327
- loadIntentMap(versionDir);
328
- console.log('Intent Map 校验通过');
329
- break;
330
- case 'trace': {
331
- const id = rest[0];
332
- if (!id) die('用法: loom intent trace <id>');
333
- const resolved = resolveIntentRef(versionDir, id);
334
- output(traceIntent(resolved.versionDir, getVerificationsDir(resolved.versionDir), getPhilosophyDir(resolved.versionDir), resolved.intentId));
335
- break;
336
- }
337
- case 'reverse-dep': {
338
- const id = rest[0];
339
- if (!id) die('用法: loom intent reverse-dep <id>');
340
- output(reverseDep(versionDir, id));
341
- break;
342
- }
343
- case 'reverse-ref': {
344
- const anchor = rest[0];
345
- if (!anchor) die('用法: loom intent reverse-ref <anchor>\n例: loom intent reverse-ref PRODUCT_PHILOSOPHY.md#core-belief');
346
- output(reverseRef(versionDir, anchor));
347
- break;
348
- }
349
- case 'update': {
350
- const id = rest[0];
351
- const statusFlagIdx = argv.indexOf('--status');
352
- const newStatus = statusFlagIdx !== -1 ? argv[statusFlagIdx + 1] : null;
353
- if (!id || !newStatus) die('用法: loom intent update <id> --status <pending|in_progress|completed|blocked|needs_review>');
354
- updateIntentStatus(versionDir, id, newStatus);
355
- console.log(`${id} status 已更新为 ${newStatus}`);
356
- break;
357
- }
358
- case 'done': {
359
- // loom intent done <id> — 自动走 pending→in_progress→completed,检查验证记录
360
- const id = rest[0];
361
- if (!id) die('用法: loom intent done <id>');
362
- const verificationsDir = getVerificationsDir(versionDir);
363
- // 检查有没有验证记录
364
- const history = getVerificationHistory(verificationsDir, id);
365
- if (!history || history.records.length === 0) {
366
- die(`${id} 没有验证记录。先跑: loom verify pass ${id} --summary "..."`);
367
- }
368
- // 最新记录必须针对当前 revision 且通过,旧 revision 的通过不能闭合 Intent。
369
- const latest = history.records[history.records.length - 1];
370
- if (latest.verdict !== 'passed') {
371
- die(`${id} 最新验证记录是 ${latest.verdict}(非 passed)。只有 passed 的 Intent 才能 done。`);
372
- }
373
- // 获取当前状态,自动走两步
374
- const intent = getIntent(versionDir, id);
375
- if (!isVerificationCurrent(intent, latest)) {
376
- die(`${id} 最新 passed 验证不属于当前 Intent revision ${intent.revision ?? 1}。先重新验证。`);
377
- }
378
- const expertise = assertExpertiseReady(versionDir, id);
379
- if (expertise && (latest.expertise?.record_ref !== `10_EXPERTISE_PACKS/${id}.json`
380
- || latest.expertise?.intent_revision !== expertise.intent_revision
381
- || latest.expertise?.source_count !== expertise.source_count
382
- || latest.expertise?.capsule_count !== expertise.capsule_count
383
- || latest.expertise?.pack_digest !== expertise.pack_digest)) {
384
- die(`${id} 最新 passed 未绑定当前 Expertise Pack。请在新的 Keeper task 中重新打开来源并验证。`);
385
- }
386
- if (intent.quality_strategy === 'atelier') {
387
- const atelier = validateAtelierRecord(versionDir, id);
388
- if (!['selected', 'baseline_retained'].includes(atelier.status)) {
389
- die(`${id} 的 Atelier Record 尚未完成选择(当前: ${atelier.status})`);
390
- }
391
- if (latest.atelier?.record_ref !== `09_ATELIER/${id}.json`
392
- || latest.atelier?.stance_revision !== atelier.stance_revision
393
- || latest.atelier?.status !== atelier.status) {
394
- die(`${id} 最新 passed 未绑定当前 Atelier Record 与 stance_revision。请在新的 Keeper task 中重新验证。`);
395
- }
396
- }
397
- const currentStatus = intent.status;
398
- if (currentStatus === 'completed') {
399
- console.log(`${id} 已经是 completed,无需操作`);
400
- break;
401
- }
402
- if (currentStatus === 'pending') {
403
- updateIntentStatus(versionDir, id, 'in_progress');
404
- }
405
- updateIntentStatus(versionDir, id, 'completed');
406
- console.log(`${id} 已完成(${currentStatus} → completed)`);
407
- break;
408
- }
409
- default:
410
- 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]`);
411
- }
105
+ const here = dirname(fileURLToPath(import.meta.url));
106
+ const pkg = JSON.parse(readFileSync(resolve(here, '..', '..', 'package.json'), 'utf8'));
107
+ output(`loom ${pkg.version}`);
412
108
  break;
413
109
  }
414
-
415
- case 'init': {
416
- if (sub === '--help' || sub === '-h') {
417
- console.log('用法: loom init\\n\\n在当前目录创建 LOOM 项目骨架。若目录已有 .loom/,不会覆盖现有文件。');
418
- break;
419
- }
420
- const result = initProject(cwd());
421
- console.log('LOOM 项目已初始化');
422
- for (const c of result.created) console.log(` [created] ${c}`);
423
- for (const s of result.skipped) console.log(` [skipped] ${s} (already exists)`);
424
- if (result.created.length === 0 && result.skipped.length > 0) {
425
- console.log(' 所有文件已存在,无需操作。');
426
- }
427
- console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
428
- console.log('To Agent: 运行 loom guide 诊断当前阶段,按引导执行');
429
- console.log('To Human: 把以下指令给你的 AI agent:');
430
- console.log(' "项目已用 LOOM 初始化。请运行 loom guide 看下一步,');
431
- console.log(' 然后激活 Weaver 角色织造产品哲学。"');
432
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
110
+ case '--help':
111
+ case '-h':
112
+ case undefined:
113
+ output(help());
433
114
  break;
434
- }
435
-
436
- case 'activate': {
437
- const role = sub;
438
- if (!role) die('用法: loom activate <role>\n角色: weaver | visionary | architect | forge | keeper');
439
- // weaver 不需要 versionDir(项目还没初始化时也能激活)
440
- let versionDir = null;
441
- if (role !== 'weaver') {
442
- try {
443
- versionDir = findVersionDir();
444
- } catch (e) {
445
- // 只吞"找不到 .loom 目录"——其他错误(权限、磁盘)向上抛
446
- if (!String(e.message).includes('找不到 .loom')) throw e;
447
- }
448
- }
449
- const intentIdx = argv.indexOf('--intent');
450
- const intentId = intentIdx !== -1 ? argv[intentIdx + 1] : null;
451
- if (intentIdx !== -1 && !intentId) die('用法: loom activate <role> --intent <id>');
452
- const prompt = activateRole(role, versionDir, intentId);
453
- output(prompt);
115
+ case 'init':
116
+ output(initProject());
454
117
  break;
455
- }
456
-
457
- case 'philosophy': {
458
- const versionDir = findVersionDir();
459
- switch (sub) {
460
- case 'get': {
461
- const anchor = rest[0];
462
- if (!anchor) die('用法: loom philosophy get <anchor>\n例: loom philosophy get PRODUCT_PHILOSOPHY.md#core-belief');
463
- output(getPhilosophy(getPhilosophyDir(versionDir), anchor));
464
- break;
465
- }
466
- case 'list':
467
- output(listPhilosophyFiles(getPhilosophyDir(versionDir)));
468
- break;
469
- case 'impact': {
470
- const anchor = rest[0];
471
- if (!anchor) die('用法: loom philosophy impact <anchor>');
472
- output(assessPhilosophyImpact(versionDir, anchor));
473
- break;
474
- }
475
- case 'revise': {
476
- const anchor = rest[0];
477
- const readFlag = (name) => {
478
- const index = argv.indexOf(name);
479
- return index === -1 ? null : argv[index + 1];
480
- };
481
- const parseIds = (name) => {
482
- const value = readFlag(name);
483
- if (value === null) return [];
484
- if (!value || value.startsWith('--')) die(`${name} 需要逗号分隔的 Intent ID`);
485
- return value.split(',').map((item) => item.trim()).filter(Boolean);
486
- };
487
- const classification = readFlag('--classification');
488
- const reason = readFlag('--reason');
489
- if (!anchor || !classification || classification.startsWith('--') || !reason || reason.startsWith('--')) {
490
- die('用法: loom philosophy revise <anchor> --classification <clarification|minor|major> --reason <text> [--confirm --review <ids> --unaffected <ids>]');
491
- }
492
- output(revisePhilosophy(versionDir, anchor, {
493
- classification,
494
- reason,
495
- confirm: argv.includes('--confirm'),
496
- review: parseIds('--review'),
497
- unaffected: parseIds('--unaffected'),
498
- }));
499
- break;
500
- }
501
- case 'check': {
502
- const philDir = getPhilosophyDir(versionDir);
503
- const inspiration = validateInspirationSources(philDir);
504
- const allIssues = inspiration.issues;
505
- const allPassed = inspiration.passed;
506
-
507
- // --json: 结构化输出,供 Agent 程序化消费
508
- if (argv.includes('--json')) {
509
- output({
510
- passed: allPassed,
511
- issues: allIssues,
512
- sources: inspiration.sources.map(({ file, sources }) => ({ file, count: sources.length })),
513
- });
514
- exit(allPassed ? 0 : 1);
515
- }
516
-
517
- if (allPassed) {
518
- console.log('✓ 哲学文档校验通过');
519
- console.log(' 灵感来源:');
520
- for (const { file, sources } of inspiration.sources) {
521
- console.log(` ${file}: ${sources.length} 个源`);
522
- }
523
- } else {
524
- const high = allIssues.filter((i) => i.severity === 'high').length;
525
- const medium = allIssues.filter((i) => i.severity === 'medium').length;
526
- console.log(`✗ 哲学文档校验未通过(${allIssues.length} 个问题: ${high} high, ${medium} medium)`);
527
- for (const issue of allIssues) {
528
- const icon = issue.severity === 'high' ? '⚠' : '·';
529
- console.log(` ${icon} [${issue.severity}] ${issue.msg}`);
530
- }
531
- console.log('\n下一步: 运行 loom activate weaver;命令会重新输出哲学阶段所需的 Context Pack。');
532
- exit(1);
533
- }
534
- break;
535
- }
536
- default:
537
- die(`未知子命令: philosophy ${sub}\n用法: loom philosophy [get <anchor>|list|check|impact <anchor>|revise <anchor> --classification <clarification|minor|major> --reason <text>]`);
538
- }
118
+ case 'context':
119
+ case 'resume':
120
+ output(compileContext({ taskId: option('--task'), keeper: argv.includes('--keeper') }));
539
121
  break;
540
- }
541
-
542
- case 'verify': {
543
- const versionDir = findVersionDir();
544
- const verificationsDir = getVerificationsDir(versionDir);
545
- switch (sub) {
546
- case 'contract': {
547
- const id = rest[0];
548
- if (!id) die('用法: loom verify contract <id>');
549
- output(getVerificationContract(versionDir, id));
550
- break;
551
- }
552
- case 'history': {
553
- const id = rest[0];
554
- if (!id) die('用法: loom verify history <id>');
555
- if (argv.includes('--across-versions')) {
556
- output(getAcrossVersionVerificationHistory(versionDir, id));
557
- } else {
558
- const resolved = resolveIntentRef(versionDir, id);
559
- const history = getVerificationHistory(getVerificationsDir(resolved.versionDir), resolved.intentId);
560
- output(history ?? `没有 ${resolved.ref} 的验证记录`);
561
- }
562
- break;
563
- }
564
- case 'pending':
565
- output(getPendingVerifications(versionDir, verificationsDir));
566
- break;
567
- case 'list':
568
- output(listVerifications(verificationsDir));
569
- break;
570
- case 'write': {
571
- // 支持两种输入方式:--json-file <path>(推荐)或 --json <string>
572
- // verdict 合法值: passed | deviated | blocked | pending_human
573
- const fileFlagIdx = argv.indexOf('--json-file');
574
- const jsonFlagIdx = argv.indexOf('--json');
575
- let record;
576
- if (fileFlagIdx !== -1 && argv[fileFlagIdx + 1]) {
577
- try {
578
- record = JSON.parse(readFileSync(argv[fileFlagIdx + 1], 'utf-8'));
579
- } catch (e) {
580
- die(`JSON 文件解析失败: ${argv[fileFlagIdx + 1]}\n原因: ${e.message}`);
581
- }
582
- } else if (jsonFlagIdx !== -1 && argv[jsonFlagIdx + 1]) {
583
- try {
584
- record = JSON.parse(argv[jsonFlagIdx + 1]);
585
- } catch (e) {
586
- die(`JSON 字符串解析失败: ${e.message}`);
587
- }
588
- } else {
589
- die('用法: loom verify write --json-file <path> | --json <json-string>');
590
- }
591
- const result = writeVerification(versionDir, verificationsDir, record);
592
- console.log(`验证记录已写入: ${result.filePath}`);
593
- console.log(` 轮次: ${result.round}, verdict: ${record.verdict}`);
594
- if (record.verdict === 'deviated') {
595
- console.log(` deviated 累计: ${result.deviated_count} 轮`);
596
- if (result.should_escalate) {
597
- updateIntentStatus(versionDir, record.intent_id, 'blocked');
598
- console.log(` ⚠ 已达到 3 轮上限,${record.intent_id} 已自动升级为 blocked`);
599
- }
600
- }
601
- break;
602
- }
603
- case 'pass':
604
- case 'fail': {
605
- // loom verify pass <id> --summary "..." --verified-by <id> --verification-context <independent_thread|human_review>
606
- // loom verify fail <id> --summary "..." [--deviation "..."] [--reproduction-command "..."]
607
- const id = rest[0];
608
- if (!id) die(`用法: loom verify ${sub} <id> --summary "..." [--reproduction-command "..."]${sub === 'pass' ? ' [--preservation-evidence "..."] [--quality-proof "..."]' : ' [--deviation "..."]'}`);
609
- const summaryIdx = argv.indexOf('--summary');
610
- const reproIdx = argv.indexOf('--reproduction-command');
611
- const deviationIdx = argv.indexOf('--deviation');
612
- const qualityProofIdx = argv.indexOf('--quality-proof');
613
- const preservationIdx = argv.indexOf('--preservation-evidence');
614
- const verifiedByIdx = argv.indexOf('--verified-by');
615
- const verificationContextIdx = argv.indexOf('--verification-context');
616
- const summary = summaryIdx !== -1 ? argv[summaryIdx + 1] : null;
617
- if (!summary) die(`缺少 --summary: loom verify ${sub} ${id} --summary "..."`);
618
- const intent = getIntent(versionDir, id);
619
- if (sub === 'pass' && intent.continuity_required && !(preservationIdx !== -1 && argv[preservationIdx + 1])) {
620
- die(`Intent ${id} 声明了 continuity_required;通过前必须提供 --preservation-evidence,证明旧状态 → 新操作后的完整序列未发生未授权丢失。`);
621
- }
622
- if (sub === 'pass' && intent.quality_contract && !(qualityProofIdx !== -1 && argv[qualityProofIdx + 1])) {
623
- die(`Intent ${id} 声明了 quality_contract;通过前必须提供 --quality-proof,指向项目内真实的 Quality Proof Markdown 锚点。`);
624
- }
625
- if (sub === 'pass' && !(verifiedByIdx !== -1 && argv[verifiedByIdx + 1] && verificationContextIdx !== -1 && argv[verificationContextIdx + 1])) {
626
- die(`Intent ${id} 通过前必须声明独立验证来源:--verified-by <thread/run/人类标识> --verification-context <independent_thread|human_review>。同一会话自检请记录为自检,不得写 passed。`);
627
- }
628
- const extras = {};
629
- if (reproIdx !== -1 && argv[reproIdx + 1]) extras.reproduction_command = argv[reproIdx + 1];
630
- if (sub === 'fail' && deviationIdx !== -1 && argv[deviationIdx + 1]) extras.deviation_detail = argv[deviationIdx + 1];
631
- if (sub === 'pass' && qualityProofIdx !== -1 && argv[qualityProofIdx + 1]) extras.quality_proof_ref = argv[qualityProofIdx + 1];
632
- if (sub === 'pass' && preservationIdx !== -1 && argv[preservationIdx + 1]) extras.preservation_evidence = argv[preservationIdx + 1];
633
- if (sub === 'pass') extras.verification_provenance = { verified_by: argv[verifiedByIdx + 1], context: argv[verificationContextIdx + 1] };
634
- const verdict = sub === 'pass' ? 'passed' : 'deviated';
635
- const result = createQuickVerification(versionDir, verificationsDir, id, verdict, summary, extras);
636
- console.log(`验证记录已写入: ${result.filePath}`);
637
- console.log(` 轮次: ${result.round}, verdict: ${verdict}`);
638
- if (verdict === 'deviated') {
639
- console.log(` deviated 累计: ${result.deviated_count} 轮`);
640
- if (result.should_escalate) {
641
- updateIntentStatus(versionDir, id, 'blocked');
642
- console.log(` ⚠ 已达到 3 轮上限,${id} 已自动升级为 blocked`);
643
- }
644
- }
645
- break;
646
- }
647
- default:
648
- die(`未知子命令: verify ${sub}\n用法: loom verify [contract <id>|history <id>|pending|list|write --json-file <path>|--json <string>|pass <id> --summary "..."|fail <id> --summary "..."]`);
649
- }
122
+ case 'prompts':
123
+ output(promptCatalog());
650
124
  break;
651
- }
652
-
653
- case 'version': {
654
- const loomRoot = findLoomRoot();
655
- switch (sub) {
656
- case 'list': {
657
- const { versions, current } = listVersions(loomRoot);
658
- for (const v of versions) {
659
- const mark = v === current ? ' *' : ' ';
660
- console.log(`${mark}${v}`);
661
- }
662
- if (current) console.log(`\n当前版本: ${current}`);
663
- break;
664
- }
665
- case 'current': {
666
- const current = readCurrentPointer(loomRoot);
667
- output(current ?? '没有版本目录');
668
- break;
669
- }
670
- case 'new': {
671
- const result = newVersion(cwd());
672
- console.log(`已创建新版本: ${result.version}`);
673
- console.log(` 创建: ${result.created.length} 项`);
674
- for (const c of result.created) console.log(` + ${c}`);
675
- if (result.skipped.length) {
676
- console.log(` 跳过(已存在): ${result.skipped.length} 项`);
677
- for (const s of result.skipped) console.log(` - ${s}`);
678
- }
679
- console.log(`\n当前版本已切换为 ${result.version}`);
680
- console.log('下一步: loom activate weaver(参考上一版本哲学织造新哲学)');
681
- break;
682
- }
683
- case 'use': {
684
- const v = rest[0];
685
- if (!v) die('用法: loom version use <v1|v2|...>');
686
- const switched = useVersion(loomRoot, v);
687
- console.log(`当前版本已切换为 ${switched}`);
688
- break;
689
- }
690
- case 'diff': {
691
- const v1 = rest[0];
692
- const v2 = rest[1];
693
- if (!v1 || !v2) die('用法: loom version diff <v1> <v2>');
694
- output(diffVersions(loomRoot, v1, v2));
695
- break;
696
- }
697
- default:
698
- die(`未知子命令: version ${sub}\n用法: loom version [list|current|new|use <v>|diff <v1> <v2>]`);
699
- }
700
- break;
701
- }
702
-
703
- case 'patch': {
704
- const versionDir = findVersionDir();
705
- switch (sub) {
706
- case 'record': {
707
- const fileFlagIdx = argv.indexOf('--json-file');
708
- const inputPath = fileFlagIdx !== -1 ? argv[fileFlagIdx + 1] : null;
709
- if (!inputPath) die('用法: loom patch record --json-file <path>');
710
- let record;
711
- try {
712
- record = JSON.parse(readFileSync(inputPath, 'utf-8'));
713
- } catch (e) {
714
- die(`JSON 文件解析失败: ${inputPath}\n原因: ${e.message}`);
715
- }
716
- output(recordPatch(versionDir, record));
717
- break;
718
- }
719
- case 'list':
720
- output(listPatches(versionDir));
721
- break;
722
- case 'get': {
723
- const id = rest[0];
724
- if (!id) die('用法: loom patch get <id>');
725
- output(getPatch(versionDir, id));
726
- break;
727
- }
728
- case 'validate':
729
- output(validatePatches(versionDir));
730
- break;
731
- default:
732
- die(`未知子命令: patch ${sub}\n用法: loom patch [record --json-file <path>|list|get <id>|validate]`);
733
- }
734
- break;
735
- }
736
-
737
- case 'doctor': {
738
- const versionDir = findVersionDir();
739
- const { issues, summary } = doctor(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir));
740
- if (summary.healthy) {
741
- console.log('✓ 项目健康,未发现问题');
742
- } else {
743
- console.log(`发现 ${summary.total_issues} 个问题(fatal: ${summary.fatal}, high: ${summary.high}, medium: ${summary.medium})`);
744
- for (const issue of issues) {
745
- const icon = issue.severity === 'fatal' ? '☠' : issue.severity === 'high' ? '⚠' : '·';
746
- console.log(` ${icon} [${issue.severity}] ${issue.type}: ${issue.msg}`);
747
- if (issue.fix_hint) {
748
- console.log(` → 修复: ${issue.fix_hint}`);
749
- }
750
- }
751
- console.log('\n下一步: 运行 loom guide;它会根据当前状态给出可执行动作与所需 Context Pack。');
752
- }
125
+ case 'record':
126
+ output(recordUnderstanding(jsonFile()));
753
127
  break;
754
- }
755
-
756
- case 'context': {
757
- const versionDir = findVersionDir();
758
- output(contextSummary(versionDir, getVerificationsDir(versionDir), getPhilosophyDir(versionDir)));
128
+ case 'check': {
129
+ const result = checkProject();
130
+ output(result);
131
+ if (!result.healthy) process.exitCode = 1;
759
132
  break;
760
133
  }
761
-
762
- case 'help': {
763
- // sub 是 topic(loom help workflow → sub='workflow')
764
- // 过滤掉 --help/-h 这种被路由进来的情况
765
- const topic = (sub && !sub.startsWith('-')) ? sub : null;
766
- if (!topic) {
767
- console.log('LOOM 指南 topics:');
768
- for (const t of listHelpTopics()) {
769
- console.log(` loom help ${t}`);
770
- }
771
- console.log('\n运行 loom --help 查看所有命令。');
772
- } else {
773
- const content = getHelpTopic(topic);
774
- if (!content) {
775
- die(`未知 topic: ${topic}\n可用 topics: ${listHelpTopics().join(', ')}`);
776
- }
777
- console.log(content);
778
- }
134
+ case 'project':
135
+ if (subcommand !== 'ready') throw new Error('Usage: loom project ready');
136
+ output(markReady());
137
+ break;
138
+ case 'design': {
139
+ if (subcommand === 'add') {
140
+ const slug = rest[0];
141
+ const title = option('--title');
142
+ const kind = option('--kind');
143
+ if (!slug) throw new Error('Usage: loom design add <slug> --title <text> --kind <kind>');
144
+ output(createDesign(slug, { title, kind }));
145
+ } else if (subcommand === 'list') output(listDesigns());
146
+ else if (subcommand === 'get') {
147
+ if (!rest[0]) throw new Error('Usage: loom design get <slug>');
148
+ output(getDesign(rest[0]));
149
+ } else throw new Error('Usage: loom design add|list|get');
779
150
  break;
780
151
  }
781
-
782
- case 'guide': {
783
- const dryRun = argv.includes('--dry-run');
784
- const jsonOut = argv.includes('--json');
785
- const result = guideProject(cwd(), { dryRun });
786
- if (jsonOut) {
787
- output(result);
788
- break;
789
- }
790
- console.log(`阶段 ${result.stage_num}: ${result.stage}`);
791
- if (dryRun) {
792
- console.log('诊断: dry-run(不写 heartbeat)');
793
- }
794
- const modeDesc = {
795
- 'manual': '手动(每步需确认)',
796
- 'auto-loop': 'AUTO auto-loop(设计阶段需 review,Intent Loop 自动)',
797
- 'auto-design': 'AUTO auto-design(全部自动)',
798
- };
799
- console.log(`模式: ${modeDesc[result.auto_mode] || result.auto_mode}`);
800
- console.log(`\n${result.message}`);
801
- console.log(`\n下一步: ${result.next_action}`);
802
- console.log(` → ${result.next_command}`);
803
- if (result.input_delivery === 'context_pack') {
804
- console.log('\n上下文: 直接运行这条命令;它会输出当前阶段所需的 Context Pack。');
805
- }
806
- if (result.verify_command) {
807
- console.log(`\n完成后校验: ${result.verify_command}`);
808
- }
809
- if (result.auto_mode === 'manual' && result.stage_num > 0 && result.stage_num < 6) {
810
- console.log(`\n提示: 开启 AUTO 模式可自动连续执行 — loom auto on`);
811
- }
152
+ case 'capability': {
153
+ if (subcommand === 'add') {
154
+ const slug = rest[0];
155
+ const title = option('--title');
156
+ if (!slug) throw new Error('Usage: loom capability add <slug> --title <text>');
157
+ output(createCapability(slug, { title }));
158
+ } else if (subcommand === 'list') output(listCapabilities());
159
+ else if (subcommand === 'get') {
160
+ if (!rest[0]) throw new Error('Usage: loom capability get <slug>');
161
+ output(getCapability(rest[0]));
162
+ } else throw new Error('Usage: loom capability add|list|get');
812
163
  break;
813
164
  }
814
-
815
- case 'auto': {
816
- const loomRoot = findLoomRoot();
817
- switch (sub) {
818
- case 'on': {
819
- // loom auto on → auto-loop(默认)
820
- // loom auto on --design → auto-design
821
- const wantDesign = argv.includes('--design');
822
- const mode = wantDesign ? 'auto-design' : 'auto-loop';
823
- autoOn(loomRoot, mode);
824
- if (mode === 'auto-design') {
825
- console.log('AUTO 模式: auto-design(全部自动,含哲学/愿景/架构)');
826
- console.log('Agent 自动连续执行所有阶段,不等人类确认。');
827
- } else {
828
- console.log('AUTO 模式: auto-loop(Intent Loop 自动,设计阶段需 review)');
829
- console.log(' - stage 1-3(哲学/愿景/架构):自动执行但需人类 review');
830
- console.log(' - stage 4+(Intent Loop):自动连续执行');
831
- }
832
- console.log('核心契约: 持续运行,除非出意外否则不允许私自停止。');
833
- console.log(' - L3 human_review Keeper 自主判定,不停下等人类');
834
- console.log(' - 唯一允许停下的情况: blocked(依赖阻塞/契约无法判定/连续 3 deviated 升级)');
835
- console.log('切换: loom auto on --design | loom auto off');
836
- break;
837
- }
838
- case 'off':
839
- autoOff(loomRoot);
840
- console.log('AUTO 模式: manual(每步需人类确认)');
841
- break;
842
- case 'status': {
843
- const status = autoStatus(loomRoot);
844
- const modeDesc = {
845
- 'manual': 'manual(每步需人类确认)',
846
- 'auto-loop': 'auto-loop(Intent Loop 自动,设计阶段需 review)',
847
- 'auto-design': 'auto-design(全部自动,含哲学/愿景/架构)',
848
- };
849
- console.log(`AUTO 模式: ${modeDesc[status.mode] || status.mode}`);
850
- if (status.mode === 'auto-loop') {
851
- console.log(' 规则: stage 1-3 需人类 review,stage 4+ 自动执行');
852
- } else if (status.mode === 'auto-design') {
853
- console.log(' 规则: 全部阶段自动执行,不需人类 review');
854
- }
855
- if (status.heartbeat) {
856
- const hb = status.heartbeat;
857
- console.log(` 心跳: ${hb.timestamp}`);
858
- console.log(` 阶段: ${hb.stage} (stage ${hb.stage_num})`);
859
- console.log(` 下一步: ${hb.next_action}`);
860
- console.log(` 命令: ${hb.next_command}`);
861
- } else if (status.mode !== 'manual') {
862
- console.log(' 心跳: 尚未记录(运行 loom guide 后生成)');
863
- }
864
- break;
865
- }
866
- default:
867
- die(`未知子命令: auto ${sub}\n用法: loom auto [on [--design]|off|status]`);
868
- }
165
+ case 'task': {
166
+ if (subcommand === 'plan') output(importTasks(jsonFile()));
167
+ else if (subcommand === 'status') output(taskSummary(loadProject().taskStore.tasks));
168
+ else if (subcommand === 'next') output(getTask());
169
+ else if (subcommand === 'get') {
170
+ if (!rest[0]) throw new Error('Usage: loom task get <id>');
171
+ output(getTask(rest[0]));
172
+ } else if (subcommand === 'start') {
173
+ if (!rest[0]) throw new Error('Usage: loom task start <id>');
174
+ output(startTask(rest[0]));
175
+ } else if (subcommand === 'update') {
176
+ if (!rest[0]) throw new Error('Usage: loom task update <id> --json-file <patch.json>');
177
+ output(updateTask(rest[0], jsonFile()));
178
+ } else if (subcommand === 'block') {
179
+ if (!rest[0]) throw new Error('Usage: loom task block <id> --json-file <block.json>');
180
+ output(blockTask(rest[0], jsonFile()));
181
+ } else if (subcommand === 'reopen') {
182
+ if (!rest[0]) throw new Error('Usage: loom task reopen <id>');
183
+ output(reopenTask(rest[0], { reason: option('--reason') }));
184
+ } else if (subcommand === 'done') {
185
+ if (!rest[0]) throw new Error('Usage: loom task done <id> --json-file <evidence.json>');
186
+ output(completeTask(rest[0], jsonFile()));
187
+ } else throw new Error('Usage: loom task plan|status|next|get|start|update|block|reopen|done');
869
188
  break;
870
189
  }
871
-
872
- case 'preview': {
873
- const previewFile = join(cwd(), 'loom-preview.html');
874
- if (argv.includes('--help') || argv.includes('-h')) {
875
- console.log(`用法:
876
- loom preview 打开新鲜 preview;过期时提示重新生成
877
- loom preview --regen 输出生成提示词,让 Agent 重写 loom-preview.html
878
- loom preview status 检查 preview 是否存在、是否新鲜
879
- loom preview --stale 强行打开过期 preview
880
- loom preview --help 显示本帮助`);
881
- break;
882
- }
883
- const status = getPreviewStatus(cwd());
884
- const hasPreview = status.exists;
885
- const regenOnly = argv.includes('--regen') || argv.includes('-r');
886
- const openStale = argv.includes('--stale');
887
-
888
- if (sub === 'status') {
889
- output(status);
890
- break;
891
- }
892
-
893
- // 已有 HTML 且没指定 --regen:直接打开浏览器
894
- if (hasPreview && !regenOnly) {
895
- if (!status.fresh && !openStale) {
896
- console.log('preview 已过期:.loom 源文件比 loom-preview.html 更新。');
897
- console.log(` preview: ${status.preview_mtime || '未知'}`);
898
- console.log(` 最新源: ${status.source_latest_mtime || '未知'} ${status.latest_source_file ? `(${status.latest_source_file})` : ''}`);
899
- console.log('\n下一步: loom preview --regen');
900
- console.log('强行打开旧 preview: loom preview --stale');
901
- break;
902
- }
903
- const { spawn } = await import('node:child_process');
904
- const target = previewFile.replace(/\\/g, '/');
905
- if (process.platform === 'win32') {
906
- spawn('cmd', ['/c', 'start', target], { detached: true, stdio: 'ignore' }).unref();
907
- } else if (process.platform === 'darwin') {
908
- spawn('open', [target], { detached: true, stdio: 'ignore' }).unref();
909
- } else {
910
- spawn('xdg-open', [target], { detached: true, stdio: 'ignore' }).unref();
911
- }
912
- console.log(`已打开浏览器: ${previewFile}`);
913
- console.log(status.fresh ? `重新生成: loom preview --regen` : `已打开旧 preview。重新生成: loom preview --regen`);
914
- break;
915
- }
916
-
917
- // 没有 HTML 或指定 --regen:输出提示词让 AI 生成
918
- const prompt = generatePreviewPrompt();
919
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
920
- console.log('To Agent: 按以下提示词读 .loom/ 文件并生成 loom-preview.html');
921
- console.log(' 生成完成后再次运行 loom preview 会自动打开浏览器');
922
- console.log('To Human: 把以下内容给你的 AI agent');
923
- console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
924
- console.log('');
925
- console.log(prompt);
190
+ case 'keeper': {
191
+ if (subcommand === 'prompt') output(getKeeperPrompt());
192
+ else if (subcommand === 'record') output(recordKeeper(jsonFile()));
193
+ else if (subcommand === 'skip') output(skipKeeper(option('--reason')));
194
+ else throw new Error('Usage: loom keeper prompt|record|skip');
926
195
  break;
927
196
  }
928
-
929
- case '--help':
930
- case '-h':
931
- case undefined:
932
- console.log(`loom — LOOM 框架 CLI
933
- 哲学驱动开发:哲学 → 愿景 → 架构 → Intent Loop
934
-
935
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
936
- To Agent:
937
- 你是 AI agent。本项目使用 LOOM 框架。
938
- 第一步: 运行 loom guide 诊断当前阶段
939
- 第二步: 按引导执行对应命令
940
- 学习: loom help <topic> 获取详细指南
941
- 状态: loom context 获取当前进度
942
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
943
- To Human:
944
- 请把以下指令给你的 AI agent:
945
- "请用 LOOM 框架初始化并推进这个项目。
946
- 先运行 loom --help 了解命令,再运行 loom guide 看当前阶段,
947
- 然后按引导一步步执行。"
948
- ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
949
-
950
- 用法:
951
- loom init 初始化项目(创建 .loom/v1/ 骨架 + 模板)
952
- loom guide 诊断当前阶段,输出下一步引导
953
- loom guide --dry-run 只读诊断当前阶段,不写 heartbeat
954
- loom auto on|off|status AUTO 编排协议开关(由 Agent/runtime 消费,不由 CLI 自行执行)
955
- loom activate <role> [--intent <id>] 输出普通或 Intent-scoped 角色激活提示词
956
-
957
- loom version list 列出所有版本(* 标记当前)
958
- loom version current 显示当前版本
959
- loom version new 创建 v{N+1} + 自动切换为当前
960
- loom version use <v> 切换当前版本
961
- loom version diff <v1> <v2> 对比两个版本的文件差异
962
-
963
- loom patch record --json-file <path> 记录 Patch 并生成 Markdown 投影
964
- loom patch list 列出当前版本的 Patch
965
- loom patch get <id> 返回指定 Patch
966
- loom patch validate 校验 Patch ledger 和 Markdown 投影
967
-
968
- loom capability graph 输出 Capability Graph 的 Mermaid 投影与摘要
969
- loom capability frontier 列出尚未路由的高影响节点
970
- loom capability get <id> 返回节点、关系、Brief 与 Intent 回链
971
- loom capability coverage 检查图谱覆盖、Brief 和 Intent 回链
972
- loom capability compile <id> 只读显示会进入该 Intent 的能力输入
973
- loom expertise init <id> 创建当前 Intent 的外部能力获取记录
974
- loom expertise get <id> 返回并校验当前 Expertise Pack
975
- loom expertise validate <id> 校验检索、来源、Capsule 与 revision
976
- loom atelier init <id> 为 atelier Intent 创建唯一创作记录
977
- loom atelier get <id> 返回并校验当前 Atelier Record
978
- loom atelier validate <id> 校验 Record、revision、候选与证据引用
979
-
980
- loom intent next 返回下一个可执行 Intent
981
- loom intent add --title <text> [--depends-on <ids>] 创建新增 draft
982
- loom intent revise <id> --reason <text> 创建修订 draft 并报告反向依赖
983
- loom intent draft <id> 查看 draft
984
- loom intent finalize <id> 校验并原子写入官方 Intent Map
985
- loom intent deprecate <id> --reason <text> 只读评估;加 --confirm 才弃用
986
- loom intent status 返回进度概览
987
- loom intent graph 输出 Mermaid 依赖图
988
- loom intent get <id> 返回某 Intent 完整信息
989
- loom intent narrative <id> 返回某 Intent 的意图叙事(解析 narrative_ref)
990
- loom intent diff <v1> <v2> 按显式 lineage 对比 Intent 语义
991
- loom intent validate 校验 Intent Map 结构
992
- loom intent trace <id> 返回某 Intent 的完整追溯链(依赖+验证+哲学+叙事)
993
- loom intent reverse-dep <id> 返回依赖某 Intent 的所有 Intent(变更影响评估)
994
- loom intent reverse-ref <anchor> 返回引用某哲学锚点的所有 Intent
995
- loom intent update <id> --status <s> 更新 Intent 状态(Keeper 用)
996
- loom intent done <id> 当前 revision 验证通过后闭合 Intent
997
-
998
- loom doctor 项目健康检查(一致性+孤儿引用+循环依赖+僵尸)
999
- loom context 上下文摘要(进度+下一步+待验证+风险)
1000
- loom preview 打开新鲜 HTML;过期则提示重新生成
1001
- loom preview status 检查 preview 是否存在、是否新鲜
1002
- loom preview --regen 强制重新输出提示词(让 AI 重新生成 HTML)
1003
- loom preview --stale 强行打开过期 preview
1004
- loom help <topic> 分层指南(含 patch 工作流)
1005
-
1006
- loom philosophy get <anchor> 按锚点加载哲学章节
1007
- loom philosophy list 列出哲学文档文件
1008
- loom philosophy check 校验灵感来源质量(源数量/多样性/理由)
1009
- loom philosophy impact <anchor> 只读分析直接引用和传递影响
1010
- loom philosophy revise <anchor> --classification <type> --reason <text> 只读评估;加 --confirm 才记录 clarification/minor
1011
-
1012
- loom verify contract <id> 返回某 Intent 的验收契约(解析引用)
1013
- loom verify history <id> 返回某 Intent 本地验证历史
1014
- loom verify history <ref> --across-versions 递归返回 lineage 各版本的本地历史
1015
- loom verify pending 返回待验证的 Intent
1016
- loom verify list 列出所有验证记录
1017
- loom verify write --json-file <path> 从文件读入并写入验证记录
1018
- loom verify write --json <string> 从命令行字符串写入验证记录
1019
- loom verify pass|fail <id> --summary <text> 快捷写入验证结果
1020
- --quality-proof <ref> 声明相对质量提升时,指向基线比较与稳定性证据
1021
-
1022
- 参数:
1023
- --loom-dir <path> 指定 .loom/v{N} 目录(默认读 .loom/current 指针)`);
197
+ case 'eval':
198
+ if (subcommand !== 'scaffold') throw new Error('Usage: loom eval scaffold --json-file <scenario.json>');
199
+ output(scaffoldEval(jsonFile()));
1024
200
  break;
1025
-
1026
201
  default:
1027
- die(`未知命令: ${cmd}\n运行 loom --help 查看用法`);
202
+ throw new Error(`Unknown command: ${command}\n\n${help()}`);
1028
203
  }
1029
- } catch (e) {
1030
- die(e.message);
204
+ } catch (error) {
205
+ fail(error.message);
1031
206
  }