@haaaiawd/loom 2.1.2 → 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/CHANGELOG.md +50 -40
- package/README.md +223 -224
- package/README.zh-CN.md +172 -172
- package/cli/bin/loom.js +293 -254
- package/cli/src/protocol.js +697 -691
- package/cli/src/store.js +1017 -946
- package/docs/PROMPT_CATALOG.md +139 -116
- package/docs/UX_FLOW.md +188 -184
- package/package.json +56 -56
package/cli/bin/loom.js
CHANGED
|
@@ -1,143 +1,145 @@
|
|
|
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
|
-
|
|
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
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)
|
|
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
108
|
loom capability confirm <slug> --scenario <text> --source human|agent
|
|
109
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
|
|
132
|
-
loom keeper
|
|
133
|
-
loom keeper
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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
|
+
|
|
141
143
|
Use \`loom <command> --help\` or \`loom help <topic>\` for canonical JSON payloads. Topics:
|
|
142
144
|
record, decision, task-plan, task-update, task-block, task-done, keeper-record, eval-scaffold
|
|
143
145
|
|
|
@@ -153,6 +155,8 @@ const STRUCTURED_HELP = {
|
|
|
153
155
|
confirmed: ['A fact confirmed by the human or workspace.'],
|
|
154
156
|
assumptions: [{ text: 'A bounded, reversible Agent assumption.', source: 'agent' }],
|
|
155
157
|
unresolved: [{ question: 'A consequential question still open?', impact: 'high' }],
|
|
158
|
+
resolved: [{ id: 'Q-001', status: 'resolved' }],
|
|
159
|
+
retire_assumptions: ['A-001'],
|
|
156
160
|
decisions: [{ title: 'Decision title', decision: 'Current decision.', rationale: 'Why it follows.', supersedes: [], affects: ['.loom/PROJECT.md'] }],
|
|
157
161
|
},
|
|
158
162
|
},
|
|
@@ -187,11 +191,41 @@ const STRUCTURED_HELP = {
|
|
|
187
191
|
};
|
|
188
192
|
|
|
189
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.`;
|
|
190
196
|
const entry = STRUCTURED_HELP[topic];
|
|
191
197
|
if (!entry) throw new Error(`Unknown help topic: ${topic}. Available topics: ${Object.keys(STRUCTURED_HELP).join(', ')}`);
|
|
192
198
|
return `${entry.usage}\n\nCanonical JSON payload:\n${JSON.stringify(entry.example, null, 2)}`;
|
|
193
199
|
}
|
|
194
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
|
+
|
|
195
229
|
function activeStructuredHelpTopic() {
|
|
196
230
|
if (command === 'record' || command === 'decision') return command;
|
|
197
231
|
if (command === 'task' && ['plan', 'update', 'block', 'done'].includes(subcommand)) return `task-${subcommand}`;
|
|
@@ -199,148 +233,153 @@ function activeStructuredHelpTopic() {
|
|
|
199
233
|
if (command === 'eval' && subcommand === 'scaffold') return 'eval-scaffold';
|
|
200
234
|
return '';
|
|
201
235
|
}
|
|
202
|
-
|
|
203
|
-
try {
|
|
204
|
-
configureRuntime({ stateDir: option('--state-dir') });
|
|
205
|
-
const humanChannel = option('--human-channel');
|
|
206
|
-
if (humanChannel && !['available', 'unavailable'].includes(humanChannel)) throw new Error('--human-channel must be available or unavailable');
|
|
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');
|
|
207
241
|
switch (command) {
|
|
208
|
-
case '--version':
|
|
209
|
-
case '-v': {
|
|
210
|
-
const here = dirname(fileURLToPath(import.meta.url));
|
|
211
|
-
const pkg = JSON.parse(readFileSync(resolve(here, '..', '..', 'package.json'), 'utf8'));
|
|
212
|
-
output(`loom ${pkg.version}`);
|
|
213
|
-
break;
|
|
214
|
-
}
|
|
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
|
+
}
|
|
215
249
|
case '--help':
|
|
216
|
-
case '-h':
|
|
217
|
-
case undefined:
|
|
250
|
+
case '-h':
|
|
251
|
+
case undefined:
|
|
218
252
|
output(help());
|
|
219
253
|
break;
|
|
220
254
|
case 'help':
|
|
221
255
|
output(subcommand ? structuredHelp(subcommand) : help());
|
|
222
256
|
break;
|
|
223
|
-
case 'init':
|
|
224
|
-
output(initProject());
|
|
225
|
-
break;
|
|
226
|
-
case 'context':
|
|
227
|
-
case 'resume':
|
|
228
|
-
output(compileContext({ taskId: option('--task'), keeper: argv.includes('--keeper'), humanChannel: humanChannel || 'available' }));
|
|
229
|
-
break;
|
|
230
|
-
case 'prompts':
|
|
231
|
-
output(promptCatalog());
|
|
232
|
-
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;
|
|
233
267
|
case 'record':
|
|
234
268
|
output(subcommand === '--help' ? structuredHelp('record') : recordUnderstanding(jsonFile()));
|
|
235
269
|
break;
|
|
236
270
|
case 'decision':
|
|
237
271
|
output(subcommand === '--help' ? structuredHelp('decision') : recordDecision(jsonFile()));
|
|
238
|
-
break;
|
|
239
|
-
case 'check': {
|
|
240
|
-
const result = checkProject();
|
|
241
|
-
output(result);
|
|
242
|
-
if (!result.healthy) process.exitCode = 1;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
case 'project':
|
|
246
|
-
if (subcommand !== 'ready') throw new Error('Usage: loom project ready');
|
|
247
|
-
output(markReady());
|
|
248
|
-
break;
|
|
249
|
-
case 'design': {
|
|
250
|
-
if (subcommand === 'add') {
|
|
251
|
-
const slug = rest[0];
|
|
252
|
-
const title = option('--title');
|
|
253
|
-
const kind = option('--kind');
|
|
254
|
-
if (!slug) throw new Error('Usage: loom design add <slug> --title <text> --kind <kind>');
|
|
255
|
-
output(createDesign(slug, { title, kind }));
|
|
256
|
-
} else if (subcommand === 'list') output(listDesigns());
|
|
257
|
-
else if (subcommand === 'get') {
|
|
258
|
-
if (!rest[0]) throw new Error('Usage: loom design get <slug>');
|
|
259
|
-
output(getDesign(rest[0]));
|
|
260
|
-
} else throw new Error('Usage: loom design add|list|get');
|
|
261
|
-
break;
|
|
262
|
-
}
|
|
263
|
-
case 'capability': {
|
|
264
|
-
if (subcommand === 'add') {
|
|
265
|
-
const slug = rest[0];
|
|
266
|
-
const title = option('--title');
|
|
267
|
-
if (!slug) throw new Error('Usage: loom capability add <slug> --title <text>');
|
|
268
|
-
output(createCapability(slug, { title }));
|
|
269
|
-
} else if (subcommand === 'list') output(listCapabilities());
|
|
270
|
-
else if (subcommand === 'get') {
|
|
271
|
-
if (!rest[0]) throw new Error('Usage: loom capability get <slug>');
|
|
272
|
-
output(getCapability(rest[0]));
|
|
273
|
-
} else if (subcommand === 'research') {
|
|
274
|
-
if (!rest[0]) throw new Error('Usage: loom capability research <slug> --field <text>');
|
|
275
|
-
output(researchCapability(rest[0], { field: option('--field') }));
|
|
276
|
-
} else if (subcommand === 'synthesize') {
|
|
277
|
-
if (!rest[0]) throw new Error('Usage: loom capability synthesize <slug>');
|
|
278
|
-
output(synthesizeCapability(rest[0]));
|
|
279
|
-
} else if (subcommand === 'confirm') {
|
|
280
|
-
if (!rest[0]) throw new Error('Usage: loom capability confirm <slug> --scenario <text>');
|
|
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>');
|
|
281
315
|
output(confirmCapability(rest[0], { scenario: option('--scenario'), source: option('--source') }));
|
|
282
|
-
} else if (subcommand === 'status') {
|
|
283
|
-
if (!rest[0]) throw new Error('Usage: loom capability status <slug>');
|
|
284
|
-
output(getCapabilityStatus(rest[0]));
|
|
285
|
-
} else throw new Error('Usage: loom capability add|list|get|research|synthesize|confirm|status');
|
|
286
|
-
break;
|
|
287
|
-
}
|
|
288
|
-
case 'deliverable': {
|
|
289
|
-
if (subcommand === 'add') {
|
|
290
|
-
const slug = rest[0];
|
|
291
|
-
const title = option('--title');
|
|
292
|
-
const kind = option('--kind');
|
|
293
|
-
if (!slug) throw new Error('Usage: loom deliverable add <slug> --title <text> --kind <module|feature|behavior|interface|artifact|operational|verification|other>');
|
|
294
|
-
output(addDeliverable(slug, { title, kind, notes: option('--notes') }));
|
|
295
|
-
} else if (subcommand === 'list') output(listDeliverables());
|
|
296
|
-
else if (subcommand === 'coverage') output(checkDeliverableCoverage());
|
|
297
|
-
else throw new Error('Usage: loom deliverable add|list|coverage');
|
|
298
|
-
break;
|
|
299
|
-
}
|
|
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
|
+
}
|
|
300
334
|
case 'task': {
|
|
301
335
|
if (argv.includes('--help') && ['plan', 'update', 'block', 'done'].includes(subcommand)) output(structuredHelp(`task-${subcommand}`));
|
|
302
336
|
else if (subcommand === 'plan') output(importTasks(jsonFile()));
|
|
303
|
-
else if (subcommand === 'status') output(taskSummary(loadProject().taskStore.tasks));
|
|
304
|
-
else if (subcommand === 'next') output(getTask());
|
|
305
|
-
else if (subcommand === 'get') {
|
|
306
|
-
if (!rest[0]) throw new Error('Usage: loom task get <id>');
|
|
307
|
-
output(getTask(rest[0]));
|
|
308
|
-
} else if (subcommand === 'start') {
|
|
309
|
-
if (!rest[0]) throw new Error('Usage: loom task start <id>');
|
|
310
|
-
output(startTask(rest[0]));
|
|
311
|
-
} else if (subcommand === 'update') {
|
|
312
|
-
if (!rest[0]) throw new Error('Usage: loom task update <id> --json-file <patch.json>');
|
|
313
|
-
output(updateTask(rest[0], jsonFile()));
|
|
314
|
-
} else if (subcommand === 'block') {
|
|
315
|
-
if (!rest[0]) throw new Error('Usage: loom task block <id> --json-file <block.json>');
|
|
316
|
-
output(blockTask(rest[0], jsonFile()));
|
|
317
|
-
} else if (subcommand === 'reopen') {
|
|
318
|
-
if (!rest[0]) throw new Error('Usage: loom task reopen <id>');
|
|
319
|
-
output(reopenTask(rest[0], { reason: option('--reason') }));
|
|
320
|
-
} else if (subcommand === 'done') {
|
|
321
|
-
if (!rest[0]) throw new Error('Usage: loom task done <id> --json-file <evidence.json>');
|
|
322
|
-
output(completeTask(rest[0], jsonFile()));
|
|
323
|
-
} else throw new Error('Usage: loom task plan|status|next|get|start|update|block|reopen|done');
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
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;
|
|
326
364
|
case 'keeper': {
|
|
327
365
|
if (argv.includes('--help') && subcommand === 'record') output(structuredHelp('keeper-record'));
|
|
366
|
+
else if (!subcommand || argv.includes('--help')) output(reviewHelp());
|
|
328
367
|
else if (subcommand === 'prompt') output(getKeeperPrompt());
|
|
329
|
-
else if (subcommand === 'record') output(recordKeeper(jsonFile()));
|
|
330
|
-
else if (subcommand === 'skip') output(skipKeeper(option('--reason')));
|
|
331
|
-
else throw new Error('Usage: loom keeper prompt|record|skip');
|
|
332
|
-
break;
|
|
333
|
-
}
|
|
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
|
+
}
|
|
334
373
|
case 'eval':
|
|
335
374
|
if (argv.includes('--help') && subcommand === 'scaffold') output(structuredHelp('eval-scaffold'));
|
|
336
375
|
else {
|
|
337
376
|
if (subcommand !== 'scaffold') throw new Error('Usage: loom eval scaffold --json-file <scenario.json>');
|
|
338
377
|
output(scaffoldEval(jsonFile()));
|
|
339
378
|
}
|
|
340
|
-
break;
|
|
341
|
-
default:
|
|
342
|
-
throw new Error(`Unknown command: ${command}\n\n${help()}`);
|
|
343
|
-
}
|
|
379
|
+
break;
|
|
380
|
+
default:
|
|
381
|
+
throw new Error(`Unknown command: ${command}\n\n${help()}`);
|
|
382
|
+
}
|
|
344
383
|
} catch (error) {
|
|
345
384
|
const topic = activeStructuredHelpTopic();
|
|
346
385
|
const pointer = topic && !argv.includes('--help') ? `\nRun ${STRUCTURED_HELP[topic].usage.replace(/ --json-file .+$/, ' --help').replace(/ <id>/, '')} for a canonical payload.` : '';
|