@haaaiawd/loom 2.1.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli/bin/loom.js CHANGED
@@ -1,280 +1,387 @@
1
- #!/usr/bin/env node
2
-
3
- import { readFileSync } from 'node:fs';
4
- import { dirname, resolve } from 'node:path';
5
- import { fileURLToPath } from 'node:url';
6
- import {
7
- addDeliverable,
8
- blockTask,
9
- checkProject,
10
- checkDeliverableCoverage,
11
- compileContext,
12
- completeTask,
13
- confirmCapability,
14
- configureRuntime,
15
- createCapability,
16
- createDesign,
17
- recordDecision,
18
- getCapability,
19
- getCapabilityStatus,
20
- getDesign,
21
- getKeeperPrompt,
22
- getTask,
23
- importTasks,
24
- initProject,
25
- listCapabilities,
26
- listDeliverables,
27
- listDesigns,
28
- loadProject,
29
- markReady,
30
- recordKeeper,
31
- recordUnderstanding,
32
- reopenTask,
33
- researchCapability,
34
- scaffoldEval,
35
- synthesizeCapability,
36
- skipKeeper,
37
- startTask,
38
- taskSummary,
39
- updateTask,
40
- } from '../src/store.js';
41
- import { promptCatalog } from '../src/protocol.js';
42
-
43
- const argv = process.argv.slice(2);
44
- const [command, subcommand, ...rest] = argv;
45
-
46
- function output(value) {
47
- console.log(typeof value === 'string' ? value : JSON.stringify(value, null, 2));
48
- }
49
-
50
- function fail(message) {
51
- console.error(`Error: ${message}`);
52
- process.exitCode = 1;
53
- }
54
-
55
- function option(name) {
56
- const index = argv.indexOf(name);
57
- return index === -1 ? undefined : argv[index + 1];
58
- }
59
-
60
- function jsonFile() {
61
- const path = option('--json-file');
62
- if (!path) throw new Error('This command requires --json-file <path>');
63
- try {
64
- return JSON.parse(readFileSync(resolve(path), 'utf8'));
65
- } catch (error) {
66
- throw new Error(`Cannot read JSON file ${path}: ${error.message}`);
67
- }
68
- }
69
-
70
- function help() {
71
- return `loom — continuity infrastructure for Agents
72
-
73
- The human talks naturally to the Agent. The Agent uses these commands silently.
74
-
75
- Typical flow (first pass):
76
- 1. loom init — create .loom/ skeleton
77
- 2. loom record — confirm facts, assumptions, open questions
78
- 3. loom design add — write design documents for consequential systems
79
- 4. loom capability add → research → synthesize → confirm
80
- — build professional decision trees for fields that matter
81
- 5. loom deliverable add — decompose the delivery surface
82
- 6. loom task plan — write Tasks with acceptance criteria covering deliverables
83
- 7. loom project ready — freeze a digest for Keeper review
84
- 8. loom keeper prompt → record — fresh Agent verifies build-readiness (one-time)
85
- 9. loom task start → done — execute one Task at a time, fill acceptance evidence
86
- 10. loom check — verify health, coverage, and drift
87
-
88
- When a new idea changes an existing decision:
89
- loom decision --json-file <decision.json> — record what changed and affected tasks
90
- then update the design document directly; loom check warns which done tasks need reopening.
91
-
92
- Start and resume
93
- loom init
94
- loom context [--task TASK-001] [--keeper] [--human-channel available|unavailable]
95
- loom check
96
- loom prompts
97
-
98
- Preserve understanding
99
- loom record --json-file <update.json>
100
- loom decision --json-file <decision.json>
101
- loom project ready
102
- loom design add <slug> --title <text> --kind <product|experience|system|contract|verification|operations|research>
103
- loom design list|get <slug>
104
- loom capability add <slug> --title <professional-field>
105
- loom capability list|get <slug>
106
- loom capability research <slug> --field <text> (creates research/_guide.md — add .md files there)
107
- loom capability synthesize <slug> (builds decision tree from research, validates sources)
108
- loom capability confirm <slug> --scenario <text> (user confirms which expert scenario applies)
109
- loom capability status <slug>
110
-
111
- Map the delivery surface
112
- loom deliverable add <slug> --title <text> --kind <module|feature|behavior|interface|artifact|operational|verification|other>
113
- loom deliverable list
114
- loom deliverable coverage
115
-
116
- Maintain the work map
117
- loom task plan --json-file <tasks.json>
118
- loom task status|next|get <id>
119
- loom task start <id>
120
- loom task update <id> --json-file <patch.json>
121
- loom task block <id> --json-file <block.json>
122
- loom task reopen <id> [--reason <reason>]
123
- loom task done <id> --json-file <evidence.json>
124
-
125
- Each Task should produce one verifiable unit of real work. Use acceptance[] with criterion,
126
- verify_by, and evidence fields. Task completion fills in each acceptance condition's evidence
127
- with the actual result (for acceptance tasks) or quotes each done_when criterion (for legacy tasks).
128
-
129
- One-time independent handoff
130
- loom keeper prompt
131
- loom keeper record --json-file <result.json>
132
- loom keeper skip --reason <reason>
133
-
134
- Evaluate LOOM itself
135
- loom eval scaffold --json-file <scenario.json>
136
-
137
- Use \`--state-dir <outside-workspace-dir>\` on every command to keep LOOM state in an isolated sidecar
138
- (for example, a benchmark runner's per-run state directory). Sidecar initialization never edits AGENTS.md.
139
-
140
- Use JSON files for structured writes so long content and shell quoting remain auditable.
141
- Task completion JSON includes evidence plus either acceptance_results[] (one per acceptance criterion,
142
- each with concrete evidence) or checks[] (one per done_when criterion, for legacy tasks).`;
143
- }
144
-
145
- try {
146
- configureRuntime({ stateDir: option('--state-dir') });
147
- const humanChannel = option('--human-channel');
148
- if (humanChannel && !['available', 'unavailable'].includes(humanChannel)) throw new Error('--human-channel must be available or unavailable');
149
- switch (command) {
150
- case '--version':
151
- case '-v': {
152
- const here = dirname(fileURLToPath(import.meta.url));
153
- const pkg = JSON.parse(readFileSync(resolve(here, '..', '..', 'package.json'), 'utf8'));
154
- output(`loom ${pkg.version}`);
155
- break;
156
- }
157
- case '--help':
158
- case '-h':
159
- case undefined:
160
- output(help());
161
- break;
162
- case 'init':
163
- output(initProject());
164
- break;
165
- case 'context':
166
- case 'resume':
167
- output(compileContext({ taskId: option('--task'), keeper: argv.includes('--keeper'), humanChannel: humanChannel || 'available' }));
168
- break;
169
- case 'prompts':
170
- output(promptCatalog());
171
- break;
172
- case 'record':
173
- output(recordUnderstanding(jsonFile()));
174
- break;
175
- case 'decision':
176
- output(recordDecision(jsonFile()));
177
- break;
178
- case 'check': {
179
- const result = checkProject();
180
- output(result);
181
- if (!result.healthy) process.exitCode = 1;
182
- break;
183
- }
184
- case 'project':
185
- if (subcommand !== 'ready') throw new Error('Usage: loom project ready');
186
- output(markReady());
187
- break;
188
- case 'design': {
189
- if (subcommand === 'add') {
190
- const slug = rest[0];
191
- const title = option('--title');
192
- const kind = option('--kind');
193
- if (!slug) throw new Error('Usage: loom design add <slug> --title <text> --kind <kind>');
194
- output(createDesign(slug, { title, kind }));
195
- } else if (subcommand === 'list') output(listDesigns());
196
- else if (subcommand === 'get') {
197
- if (!rest[0]) throw new Error('Usage: loom design get <slug>');
198
- output(getDesign(rest[0]));
199
- } else throw new Error('Usage: loom design add|list|get');
200
- break;
201
- }
202
- case 'capability': {
203
- if (subcommand === 'add') {
204
- const slug = rest[0];
205
- const title = option('--title');
206
- if (!slug) throw new Error('Usage: loom capability add <slug> --title <text>');
207
- output(createCapability(slug, { title }));
208
- } else if (subcommand === 'list') output(listCapabilities());
209
- else if (subcommand === 'get') {
210
- if (!rest[0]) throw new Error('Usage: loom capability get <slug>');
211
- output(getCapability(rest[0]));
212
- } else if (subcommand === 'research') {
213
- if (!rest[0]) throw new Error('Usage: loom capability research <slug> --field <text>');
214
- output(researchCapability(rest[0], { field: option('--field') }));
215
- } else if (subcommand === 'synthesize') {
216
- if (!rest[0]) throw new Error('Usage: loom capability synthesize <slug>');
217
- output(synthesizeCapability(rest[0]));
218
- } else if (subcommand === 'confirm') {
219
- if (!rest[0]) throw new Error('Usage: loom capability confirm <slug> --scenario <text>');
220
- output(confirmCapability(rest[0], { scenario: option('--scenario') }));
221
- } else if (subcommand === 'status') {
222
- if (!rest[0]) throw new Error('Usage: loom capability status <slug>');
223
- output(getCapabilityStatus(rest[0]));
224
- } else throw new Error('Usage: loom capability add|list|get|research|synthesize|confirm|status');
225
- break;
226
- }
227
- case 'deliverable': {
228
- if (subcommand === 'add') {
229
- const slug = rest[0];
230
- const title = option('--title');
231
- const kind = option('--kind');
232
- if (!slug) throw new Error('Usage: loom deliverable add <slug> --title <text> --kind <module|feature|behavior|interface|artifact|operational|verification|other>');
233
- output(addDeliverable(slug, { title, kind, notes: option('--notes') }));
234
- } else if (subcommand === 'list') output(listDeliverables());
235
- else if (subcommand === 'coverage') output(checkDeliverableCoverage());
236
- else throw new Error('Usage: loom deliverable add|list|coverage');
237
- break;
238
- }
239
- case 'task': {
240
- if (subcommand === 'plan') output(importTasks(jsonFile()));
241
- else if (subcommand === 'status') output(taskSummary(loadProject().taskStore.tasks));
242
- else if (subcommand === 'next') output(getTask());
243
- else if (subcommand === 'get') {
244
- if (!rest[0]) throw new Error('Usage: loom task get <id>');
245
- output(getTask(rest[0]));
246
- } else if (subcommand === 'start') {
247
- if (!rest[0]) throw new Error('Usage: loom task start <id>');
248
- output(startTask(rest[0]));
249
- } else if (subcommand === 'update') {
250
- if (!rest[0]) throw new Error('Usage: loom task update <id> --json-file <patch.json>');
251
- output(updateTask(rest[0], jsonFile()));
252
- } else if (subcommand === 'block') {
253
- if (!rest[0]) throw new Error('Usage: loom task block <id> --json-file <block.json>');
254
- output(blockTask(rest[0], jsonFile()));
255
- } else if (subcommand === 'reopen') {
256
- if (!rest[0]) throw new Error('Usage: loom task reopen <id>');
257
- output(reopenTask(rest[0], { reason: option('--reason') }));
258
- } else if (subcommand === 'done') {
259
- if (!rest[0]) throw new Error('Usage: loom task done <id> --json-file <evidence.json>');
260
- output(completeTask(rest[0], jsonFile()));
261
- } else throw new Error('Usage: loom task plan|status|next|get|start|update|block|reopen|done');
262
- break;
263
- }
264
- case 'keeper': {
265
- if (subcommand === 'prompt') output(getKeeperPrompt());
266
- else if (subcommand === 'record') output(recordKeeper(jsonFile()));
267
- else if (subcommand === 'skip') output(skipKeeper(option('--reason')));
268
- else throw new Error('Usage: loom keeper prompt|record|skip');
269
- break;
270
- }
271
- case 'eval':
272
- if (subcommand !== 'scaffold') throw new Error('Usage: loom eval scaffold --json-file <scenario.json>');
273
- output(scaffoldEval(jsonFile()));
274
- break;
275
- default:
276
- throw new Error(`Unknown command: ${command}\n\n${help()}`);
277
- }
278
- } catch (error) {
279
- fail(error.message);
280
- }
1
+ #!/usr/bin/env node
2
+
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import {
7
+ addDeliverable,
8
+ blockTask,
9
+ checkProject,
10
+ checkDeliverableCoverage,
11
+ compileContext,
12
+ completeTask,
13
+ confirmCapability,
14
+ configureRuntime,
15
+ createCapability,
16
+ createDesign,
17
+ recordDecision,
18
+ getCapability,
19
+ getCapabilityStatus,
20
+ getDesign,
21
+ getKeeperPrompt,
22
+ getTask,
23
+ importTasks,
24
+ initProject,
25
+ listCapabilities,
26
+ listDeliverables,
27
+ listDesigns,
28
+ loadProject,
29
+ markReady,
30
+ recordKeeper,
31
+ recordUnderstanding,
32
+ reopenTask,
33
+ researchCapability,
34
+ scaffoldEval,
35
+ synthesizeCapability,
36
+ skipKeeper,
37
+ startTask,
38
+ taskSummary,
39
+ updateTask,
40
+ } from '../src/store.js';
41
+ import { promptCatalog } from '../src/protocol.js';
42
+
43
+ const argv = process.argv.slice(2);
44
+ const [command, subcommand, ...rest] = argv;
45
+
46
+ function output(value) {
47
+ console.log(typeof value === 'string' ? value : JSON.stringify(value, null, 2));
48
+ }
49
+
50
+ function fail(message) {
51
+ console.error(`Error: ${message}`);
52
+ process.exitCode = 1;
53
+ }
54
+
55
+ function option(name) {
56
+ const index = argv.indexOf(name);
57
+ return index === -1 ? undefined : argv[index + 1];
58
+ }
59
+
60
+ function jsonFile() {
61
+ const path = option('--json-file');
62
+ if (!path) throw new Error('This command requires --json-file <path>');
63
+ try {
64
+ return JSON.parse(readFileSync(resolve(path), 'utf8'));
65
+ } catch (error) {
66
+ throw new Error(`Cannot read JSON file ${path}: ${error.message}`);
67
+ }
68
+ }
69
+
70
+ function help() {
71
+ return `loom — continuity infrastructure for Agents
72
+
73
+ The human talks naturally to the Agent. The Agent uses these commands silently.
74
+
75
+ Typical flow (first pass):
76
+ 1. loom init — create .loom/ skeleton
77
+ 2. loom record — confirm facts, assumptions, open questions
78
+ 3. loom design add — write design documents for consequential systems
79
+ 4. loom capability add → research → synthesize → confirm
80
+ — build professional decision trees for fields that matter
81
+ 5. loom deliverable add — decompose the delivery surface
82
+ 6. loom task plan — write Tasks with acceptance criteria covering deliverables
83
+ 7. loom project ready — freeze a digest for Keeper review
84
+ 8. loom keeper prompt → record — fresh Agent verifies build-readiness (one-time)
85
+ 9. loom task start → done — execute one Task at a time, fill acceptance evidence
86
+ 10. loom check — verify health, coverage, and drift
87
+
88
+ When a new idea changes an existing decision:
89
+ loom decision --json-file <decision.json> — record what changed and affected tasks
90
+ then update the design document directly; loom check warns which done tasks need reopening.
91
+
92
+ Start and resume
93
+ loom init
94
+ loom context [--task TASK-001] [--keeper] [--human-channel available|unavailable]
95
+ loom check
96
+ loom prompts
97
+
98
+ Preserve understanding
99
+ loom record --json-file <update.json>
100
+ loom decision --json-file <decision.json>
101
+ loom project ready
102
+ loom design add <slug> --title <text> --kind <product|experience|system|contract|verification|operations|research>
103
+ loom design list|get <slug>
104
+ loom capability add <slug> --title <professional-field>
105
+ loom capability list|get <slug>
106
+ loom capability research <slug> --field <text> (creates research/_guide.md — add .md files there)
107
+ loom capability synthesize <slug> (builds decision tree from research, validates sources)
108
+ loom capability confirm <slug> --scenario <text> --source human|agent
109
+ — human confirms, or Agent records a provisional selection
110
+ loom capability status <slug>
111
+
112
+ Map the delivery surface
113
+ loom deliverable add <slug> --title <text> --kind <module|feature|behavior|interface|artifact|operational|verification|other>
114
+ loom deliverable list
115
+ loom deliverable coverage
116
+
117
+ Maintain the work map
118
+ loom task plan --json-file <tasks.json>
119
+ loom task status|next|get <id>
120
+ loom task start <id>
121
+ loom task update <id> --json-file <patch.json>
122
+ loom task block <id> --json-file <block.json>
123
+ loom task reopen <id> [--reason <reason>]
124
+ loom task done <id> --json-file <evidence.json>
125
+
126
+ Each Task should produce one verifiable unit of real work. Use acceptance[] with criterion,
127
+ verify_by, and evidence fields. Task completion fills in each acceptance condition's evidence
128
+ with the actual result (for acceptance tasks) or quotes each done_when criterion (for legacy tasks).
129
+
130
+ One-time independent handoff
131
+ loom review [--help] — Keeper handoff and stage-review guide
132
+ loom keeper --help — read-only guide in any project state
133
+ loom keeper prompt
134
+ loom keeper record --json-file <result.json>
135
+ loom keeper skip --reason <reason>
136
+
137
+ Evaluate LOOM itself
138
+ loom eval scaffold --json-file <scenario.json>
139
+
140
+ Use \`--state-dir <outside-workspace-dir>\` on every command to keep LOOM state in an isolated sidecar
141
+ (for example, a benchmark runner's per-run state directory). Sidecar initialization never edits AGENTS.md.
142
+
143
+ Use \`loom <command> --help\` or \`loom help <topic>\` for canonical JSON payloads. Topics:
144
+ record, decision, task-plan, task-update, task-block, task-done, keeper-record, eval-scaffold
145
+
146
+ Use JSON files for structured writes so long content and shell quoting remain auditable.
147
+ Task completion JSON includes evidence plus either acceptance_results[] (one per acceptance criterion,
148
+ each with concrete evidence) or checks[] (one per done_when criterion, for legacy tasks).`;
149
+ }
150
+
151
+ const STRUCTURED_HELP = {
152
+ record: {
153
+ usage: 'loom record --json-file <update.json>',
154
+ example: {
155
+ confirmed: ['A fact confirmed by the human or workspace.'],
156
+ assumptions: [{ text: 'A bounded, reversible Agent assumption.', source: 'agent' }],
157
+ unresolved: [{ question: 'A consequential question still open?', impact: 'high' }],
158
+ resolved: [{ id: 'Q-001', status: 'resolved' }],
159
+ retire_assumptions: ['A-001'],
160
+ decisions: [{ title: 'Decision title', decision: 'Current decision.', rationale: 'Why it follows.', supersedes: [], affects: ['.loom/PROJECT.md'] }],
161
+ },
162
+ },
163
+ decision: {
164
+ usage: 'loom decision --json-file <decision.json>',
165
+ example: { summary: 'What changed and why.', changes: ['.loom/design/system.md', 'src/system.js'], affected_tasks: ['TASK-001'] },
166
+ },
167
+ 'task-plan': {
168
+ usage: 'loom task plan --json-file <tasks.json>',
169
+ example: { tasks: [{ title: 'Create one verifiable result', outcome: 'A concrete artifact behaves as specified.', acceptance: [{ criterion: 'Observable condition', verify_by: 'Exact command or review method', evidence: '' }], boundaries: ['Does not change unrelated behavior'], depends_on: [], reads: ['.loom/PROJECT.md', '.loom/design/system.md'], touches: ['src/result.js'], implements: '.loom/design/system.md#Decision', capability_hooks: [{ node: 'field#C1', at: 'decision point', must_produce: 'project-specific choice' }], covers: ['DLV-001'] }] },
170
+ },
171
+ 'task-update': {
172
+ usage: 'loom task update <id> --json-file <patch.json>',
173
+ example: { progress: { completed: ['Finished checkpoint'], current: 'Verifying behavior', next: 'Run the named acceptance check' } },
174
+ },
175
+ 'task-block': {
176
+ usage: 'loom task block <id> --json-file <block.json>',
177
+ example: { reason: 'A concrete dependency or authority is unavailable.', recovery_conditions: ['Observable condition that permits resuming'], evidence: ['Inspection or command output showing the block'] },
178
+ },
179
+ 'task-done': {
180
+ usage: 'loom task done <id> --json-file <evidence.json>',
181
+ example: { evidence: ['Overall reproducible verification result'], acceptance_results: [{ criterion: 'Exact acceptance criterion from the Task', evidence: 'Concrete command, artifact, or observation' }] },
182
+ },
183
+ 'keeper-record': {
184
+ usage: 'loom keeper record --json-file <result.json>',
185
+ example: { run_id: 'fresh-agent-run-001', prepared_digest: '<digest from loom project ready>', verdict: 'passed', review: { mode: 'independent', reviewer_id: 'fresh-agent-001', evidence: 'Host opened a separate Agent without the shaping conversation.' }, summary: 'Build-readiness judgment.', gaps: [], evidence: ['Files and observations supporting the verdict'] },
186
+ },
187
+ 'eval-scaffold': {
188
+ usage: 'loom eval scaffold --json-file <scenario.json>',
189
+ example: { id: 'EVAL-001', title: 'Ambiguous real project', brief: 'Identical brief for both conditions.', hidden_user_facts: ['Fact revealed by the same answer script'], human_channel: 'unavailable', success_criteria: ['Observable result'], context_reset_points: ['after-shaping', 'mid-task'], repetitions: 3 },
190
+ },
191
+ };
192
+
193
+ function structuredHelp(topic) {
194
+ if (['keeper', 'review', 'keeper-prompt'].includes(topic)) return reviewHelp();
195
+ if (topic === 'keeper-record') return `${STRUCTURED_HELP[topic].usage}\n\nCanonical JSON payload:\n${JSON.stringify(STRUCTURED_HELP[topic].example, null, 2)}\n\nFor a revised pass, add closure_results: [{"gap":"<exact prior gap text>","evidence":"<observed closure proof>"}] for every prior gap. Minor gaps require fresh verification too.`;
196
+ const entry = STRUCTURED_HELP[topic];
197
+ if (!entry) throw new Error(`Unknown help topic: ${topic}. Available topics: ${Object.keys(STRUCTURED_HELP).join(', ')}`);
198
+ return `${entry.usage}\n\nCanonical JSON payload:\n${JSON.stringify(entry.example, null, 2)}`;
199
+ }
200
+
201
+ function reviewHelp() {
202
+ return `LOOM review guide
203
+
204
+ Keeper is the first build-readiness handoff. Stage reviews use ordinary Tasks.
205
+ This guide is read-only and available before initialization or project readiness.
206
+
207
+ Keeper handoff:
208
+ 1. The shaping Agent runs loom project ready after preparing project truth.
209
+ 2. The host opens a fresh Agent without inherited conversation. Give it the workspace
210
+ path, the CLI location (absolute path in source checkouts), and this instruction:
211
+ Run loom keeper prompt, then loom context --keeper, and inspect the referenced files.
212
+ 3. The fresh Agent records its verdict with loom keeper record --json-file <result.json>.
213
+ See loom keeper record --help for the payload and independent review provenance.
214
+ 4. The parent waits for the result, then runs loom context. On needs_revision or blocked,
215
+ repair the named sources, prepare again, and obtain a fresh review, including minor gaps.
216
+ 5. On passed, resume with loom task next and loom task start <id>.
217
+ If isolation is unavailable, explicitly record loom keeper skip --reason <limitation>.
218
+ The CLI prints instructions and records state; it does not launch Agents itself.
219
+
220
+ Stage review:
221
+ Use loom task plan --help to create a review Task naming the implementation, design,
222
+ and verification artifacts in reads. Record each finding as a repair Task with concrete
223
+ acceptance criteria and evidence. Block the review Task with loom task block until repairs
224
+ are ready; reopen it and re-run verification before loom task done. A completed repair
225
+ alone is not a completed review. No findings: close the review Task with inspection evidence.
226
+ Do not rerun first-time Keeper just to review every implementation Task.`;
227
+ }
228
+
229
+ function activeStructuredHelpTopic() {
230
+ if (command === 'record' || command === 'decision') return command;
231
+ if (command === 'task' && ['plan', 'update', 'block', 'done'].includes(subcommand)) return `task-${subcommand}`;
232
+ if (command === 'keeper' && subcommand === 'record') return 'keeper-record';
233
+ if (command === 'eval' && subcommand === 'scaffold') return 'eval-scaffold';
234
+ return '';
235
+ }
236
+
237
+ try {
238
+ configureRuntime({ stateDir: option('--state-dir') });
239
+ const humanChannel = option('--human-channel');
240
+ if (humanChannel && !['available', 'unavailable'].includes(humanChannel)) throw new Error('--human-channel must be available or unavailable');
241
+ switch (command) {
242
+ case '--version':
243
+ case '-v': {
244
+ const here = dirname(fileURLToPath(import.meta.url));
245
+ const pkg = JSON.parse(readFileSync(resolve(here, '..', '..', 'package.json'), 'utf8'));
246
+ output(`loom ${pkg.version}`);
247
+ break;
248
+ }
249
+ case '--help':
250
+ case '-h':
251
+ case undefined:
252
+ output(help());
253
+ break;
254
+ case 'help':
255
+ output(subcommand ? structuredHelp(subcommand) : help());
256
+ break;
257
+ case 'init':
258
+ output(initProject());
259
+ break;
260
+ case 'context':
261
+ case 'resume':
262
+ output(compileContext({ taskId: option('--task'), keeper: argv.includes('--keeper'), humanChannel: humanChannel || 'available' }));
263
+ break;
264
+ case 'prompts':
265
+ output(promptCatalog());
266
+ break;
267
+ case 'record':
268
+ output(subcommand === '--help' ? structuredHelp('record') : recordUnderstanding(jsonFile()));
269
+ break;
270
+ case 'decision':
271
+ output(subcommand === '--help' ? structuredHelp('decision') : recordDecision(jsonFile()));
272
+ break;
273
+ case 'check': {
274
+ const result = checkProject();
275
+ output(result);
276
+ if (!result.healthy) process.exitCode = 1;
277
+ break;
278
+ }
279
+ case 'project':
280
+ if (subcommand !== 'ready') throw new Error('Usage: loom project ready');
281
+ output(markReady());
282
+ break;
283
+ case 'design': {
284
+ if (subcommand === 'add') {
285
+ const slug = rest[0];
286
+ const title = option('--title');
287
+ const kind = option('--kind');
288
+ if (!slug) throw new Error('Usage: loom design add <slug> --title <text> --kind <kind>');
289
+ output(createDesign(slug, { title, kind }));
290
+ } else if (subcommand === 'list') output(listDesigns());
291
+ else if (subcommand === 'get') {
292
+ if (!rest[0]) throw new Error('Usage: loom design get <slug>');
293
+ output(getDesign(rest[0]));
294
+ } else throw new Error('Usage: loom design add|list|get');
295
+ break;
296
+ }
297
+ case 'capability': {
298
+ if (subcommand === 'add') {
299
+ const slug = rest[0];
300
+ const title = option('--title');
301
+ if (!slug) throw new Error('Usage: loom capability add <slug> --title <text>');
302
+ output(createCapability(slug, { title }));
303
+ } else if (subcommand === 'list') output(listCapabilities());
304
+ else if (subcommand === 'get') {
305
+ if (!rest[0]) throw new Error('Usage: loom capability get <slug>');
306
+ output(getCapability(rest[0]));
307
+ } else if (subcommand === 'research') {
308
+ if (!rest[0]) throw new Error('Usage: loom capability research <slug> --field <text>');
309
+ output(researchCapability(rest[0], { field: option('--field') }));
310
+ } else if (subcommand === 'synthesize') {
311
+ if (!rest[0]) throw new Error('Usage: loom capability synthesize <slug>');
312
+ output(synthesizeCapability(rest[0]));
313
+ } else if (subcommand === 'confirm') {
314
+ if (!rest[0]) throw new Error('Usage: loom capability confirm <slug> --scenario <text>');
315
+ output(confirmCapability(rest[0], { scenario: option('--scenario'), source: option('--source') }));
316
+ } else if (subcommand === 'status') {
317
+ if (!rest[0]) throw new Error('Usage: loom capability status <slug>');
318
+ output(getCapabilityStatus(rest[0]));
319
+ } else throw new Error('Usage: loom capability add|list|get|research|synthesize|confirm|status');
320
+ break;
321
+ }
322
+ case 'deliverable': {
323
+ if (subcommand === 'add') {
324
+ const slug = rest[0];
325
+ const title = option('--title');
326
+ const kind = option('--kind');
327
+ if (!slug) throw new Error('Usage: loom deliverable add <slug> --title <text> --kind <module|feature|behavior|interface|artifact|operational|verification|other>');
328
+ output(addDeliverable(slug, { title, kind, notes: option('--notes') }));
329
+ } else if (subcommand === 'list') output(listDeliverables());
330
+ else if (subcommand === 'coverage') output(checkDeliverableCoverage());
331
+ else throw new Error('Usage: loom deliverable add|list|coverage');
332
+ break;
333
+ }
334
+ case 'task': {
335
+ if (argv.includes('--help') && ['plan', 'update', 'block', 'done'].includes(subcommand)) output(structuredHelp(`task-${subcommand}`));
336
+ else if (subcommand === 'plan') output(importTasks(jsonFile()));
337
+ else if (subcommand === 'status') output(taskSummary(loadProject().taskStore.tasks));
338
+ else if (subcommand === 'next') output(getTask());
339
+ else if (subcommand === 'get') {
340
+ if (!rest[0]) throw new Error('Usage: loom task get <id>');
341
+ output(getTask(rest[0]));
342
+ } else if (subcommand === 'start') {
343
+ if (!rest[0]) throw new Error('Usage: loom task start <id>');
344
+ output(startTask(rest[0]));
345
+ } else if (subcommand === 'update') {
346
+ if (!rest[0]) throw new Error('Usage: loom task update <id> --json-file <patch.json>');
347
+ output(updateTask(rest[0], jsonFile()));
348
+ } else if (subcommand === 'block') {
349
+ if (!rest[0]) throw new Error('Usage: loom task block <id> --json-file <block.json>');
350
+ output(blockTask(rest[0], jsonFile()));
351
+ } else if (subcommand === 'reopen') {
352
+ if (!rest[0]) throw new Error('Usage: loom task reopen <id>');
353
+ output(reopenTask(rest[0], { reason: option('--reason') }));
354
+ } else if (subcommand === 'done') {
355
+ if (!rest[0]) throw new Error('Usage: loom task done <id> --json-file <evidence.json>');
356
+ output(completeTask(rest[0], jsonFile()));
357
+ } else throw new Error('Usage: loom task plan|status|next|get|start|update|block|reopen|done');
358
+ break;
359
+ }
360
+ case 'review':
361
+ if (subcommand && subcommand !== '--help') throw new Error('Usage: loom review [--help]');
362
+ output(reviewHelp());
363
+ break;
364
+ case 'keeper': {
365
+ if (argv.includes('--help') && subcommand === 'record') output(structuredHelp('keeper-record'));
366
+ else if (!subcommand || argv.includes('--help')) output(reviewHelp());
367
+ else if (subcommand === 'prompt') output(getKeeperPrompt());
368
+ else if (subcommand === 'record') output(recordKeeper(jsonFile()));
369
+ else if (subcommand === 'skip') output(skipKeeper(option('--reason')));
370
+ else throw new Error('Usage: loom keeper prompt|record|skip');
371
+ break;
372
+ }
373
+ case 'eval':
374
+ if (argv.includes('--help') && subcommand === 'scaffold') output(structuredHelp('eval-scaffold'));
375
+ else {
376
+ if (subcommand !== 'scaffold') throw new Error('Usage: loom eval scaffold --json-file <scenario.json>');
377
+ output(scaffoldEval(jsonFile()));
378
+ }
379
+ break;
380
+ default:
381
+ throw new Error(`Unknown command: ${command}\n\n${help()}`);
382
+ }
383
+ } catch (error) {
384
+ const topic = activeStructuredHelpTopic();
385
+ const pointer = topic && !argv.includes('--help') ? `\nRun ${STRUCTURED_HELP[topic].usage.replace(/ --json-file .+$/, ' --help').replace(/ <id>/, '')} for a canonical payload.` : '';
386
+ fail(`${error.message}${pointer}`);
387
+ }