@cassiomc1/forgeloop 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/.cursor/rules/project-loop.mdc +3 -2
  2. package/.github/copilot-instructions.md +1 -0
  3. package/AGENTS.md +2 -1
  4. package/CLAUDE.md +1 -0
  5. package/DOCS_INDEX.md +36 -0
  6. package/ENG/design-code-eng.md +31 -0
  7. package/ENG/documentation-quality-eng.md +400 -0
  8. package/EXECUTION_STATE.md +23 -13
  9. package/GUIDE_ROUTER.md +23 -8
  10. package/LOOP_ENGINEERING.md +77 -12
  11. package/PROTOCOL_INTEGRATION.md +9 -6
  12. package/README.md +82 -39
  13. package/docs/ARTIFACT_REFERENCE.md +442 -0
  14. package/docs/CLI_REFERENCE.md +892 -0
  15. package/docs/CROSS_HARNESS_CONTINUITY.md +198 -0
  16. package/docs/DOCUMENTATION_GUIDE.md +161 -0
  17. package/docs/GETTING_STARTED.md +348 -0
  18. package/docs/RECIPES.md +250 -0
  19. package/docs/TROUBLESHOOTING.md +345 -0
  20. package/docs/assets/forgeloop-flow.svg +1 -1
  21. package/docs/forgeloop-flow.mmd +1 -1
  22. package/package.json +13 -2
  23. package/schemas/task-descriptor.schema.json +56 -0
  24. package/schemas/work-state.schema.json +18 -1
  25. package/scripts/CI_VALIDATORS.md +7 -0
  26. package/src/cli.js +280 -388
  27. package/src/commands/advance.js +5 -2
  28. package/src/commands/audit.js +11 -1
  29. package/src/commands/clear-continuity.js +5 -2
  30. package/src/commands/clear-state.js +5 -2
  31. package/src/commands/complete.js +9 -1
  32. package/src/commands/continuity.js +5 -2
  33. package/src/commands/inspect.js +10 -2
  34. package/src/commands/next.js +5 -2
  35. package/src/commands/preflight.js +9 -1
  36. package/src/commands/prepare-completion.js +5 -2
  37. package/src/commands/reconcile-continuity.js +5 -2
  38. package/src/commands/record-check.js +7 -1
  39. package/src/commands/record-continuity.js +21 -14
  40. package/src/commands/record-terminal-result.js +7 -1
  41. package/src/commands/route.js +22 -18
  42. package/src/commands/run-check.js +52 -44
  43. package/src/commands/status.js +18 -12
  44. package/src/commands/task-create.js +94 -0
  45. package/src/commands/task-list.js +48 -0
  46. package/src/commands/task-migrate.js +34 -0
  47. package/src/commands/task-scope.js +75 -0
  48. package/src/commands/task-show.js +81 -0
  49. package/src/commands/task-unlock.js +35 -0
  50. package/src/commands/validate-protocol.js +37 -20
  51. package/src/commands/validate-state.js +24 -18
  52. package/src/config/guides.json +42 -0
  53. package/src/core/activation.js +8 -4
  54. package/src/core/artifact-registry.js +166 -0
  55. package/src/core/audit.js +65 -12
  56. package/src/core/bundles.js +76 -50
  57. package/src/core/cli-command-definitions.js +611 -0
  58. package/src/core/cli-metadata.js +23 -0
  59. package/src/core/completion-artifacts.js +161 -74
  60. package/src/core/completion.js +134 -76
  61. package/src/core/continuity.js +20 -13
  62. package/src/core/contract.js +6 -3
  63. package/src/core/error-codes.js +197 -0
  64. package/src/core/events.js +19 -14
  65. package/src/core/execution.js +38 -6
  66. package/src/core/gate-artifact.js +12 -9
  67. package/src/core/gates.js +4 -2
  68. package/src/core/guide-metadata.js +7 -11
  69. package/src/core/guide-registry.js +29 -0
  70. package/src/core/inspect.js +7 -4
  71. package/src/core/native-adapters.js +6 -0
  72. package/src/core/phase.js +85 -33
  73. package/src/core/preflight-consistency.js +24 -14
  74. package/src/core/preflight-loaders.js +16 -11
  75. package/src/core/preflight.js +44 -25
  76. package/src/core/protocol.js +2 -11
  77. package/src/core/receipt.js +1 -1
  78. package/src/core/report.js +2 -2
  79. package/src/core/repository.js +46 -12
  80. package/src/core/resumability.js +6 -4
  81. package/src/core/route-artifact.js +9 -5
  82. package/src/core/router.js +11 -7
  83. package/src/core/schema-validation.js +1 -0
  84. package/src/core/task-command.js +41 -0
  85. package/src/core/task-context.js +126 -0
  86. package/src/core/task-descriptor.js +81 -0
  87. package/src/core/task-discovery.js +116 -0
  88. package/src/core/task-identity.js +76 -0
  89. package/src/core/task-lock.js +209 -0
  90. package/src/core/task-migration-validation.js +140 -0
  91. package/src/core/task-migration.js +361 -0
  92. package/src/core/task-paths.js +96 -0
  93. package/src/core/task-scope.js +179 -0
  94. package/src/core/templates.js +3 -9
  95. package/src/core/work-state.js +24 -13
package/src/cli.js CHANGED
@@ -31,137 +31,158 @@ import { formatContinuityResult, runContinuity } from "./commands/continuity.js"
31
31
  import { formatRecordContinuityResult, runRecordContinuity } from "./commands/record-continuity.js";
32
32
  import { formatReconcileContinuityResult, runReconcileContinuity } from "./commands/reconcile-continuity.js";
33
33
  import { formatClearContinuityResult, runClearContinuity } from "./commands/clear-continuity.js";
34
- import { continuityOptionDefaults, consumeContinuityOption, validateContinuityOptions } from "./core/continuity-cli-options.js";
34
+ import { formatTaskCreateResult, runTaskCreate } from "./commands/task-create.js";
35
+ import { formatTaskListResult, runTaskList } from "./commands/task-list.js";
36
+ import { formatTaskShowResult, runTaskShow } from "./commands/task-show.js";
37
+ import { formatTaskScopeResult, runTaskScope } from "./commands/task-scope.js";
38
+ import { formatTaskMigrateResult, runTaskMigrate } from "./commands/task-migrate.js";
39
+ import { formatTaskUnlockResult, runTaskUnlock } from "./commands/task-unlock.js";
40
+ import { continuityOptionDefaults, validateContinuityOptions } from "./core/continuity-cli-options.js";
35
41
  import { resolveTarget } from "./core/filesystem.js";
36
42
  import { getPackageRoot } from "./core/templates.js";
37
- import { ARTIFACT_PATHS } from "./core/artifacts.js";
38
-
39
- export const COMMANDS = Object.freeze([
40
- "init",
41
- "doctor",
42
- "update",
43
- "activate",
44
- "route",
45
- "preflight",
46
- "advance",
47
- "next",
48
- "continuity",
49
- "record-continuity",
50
- "reconcile-continuity",
51
- "clear-continuity",
52
- "prepare-completion",
53
- "run-check",
54
- "record-check",
55
- "record-terminal-result",
56
- "complete",
57
- "audit",
58
- "report",
59
- "policy",
60
- "bundle",
61
- "inspect",
62
- "status",
63
- "validate-state",
64
- "clear-state",
65
- "validate-receipt",
66
- "validate-protocol",
67
- ]);
68
-
69
- // Commands whose usage blocks already describe --json (doctor, route) or that
70
- // accept no --json at all (init, update).
71
- const GENERIC_JSON_EXCLUDED_COMMANDS = new Set(["init", "doctor", "update", "route"]);
72
-
73
- function usage(command = null) {
74
- const commands = COMMANDS.join("|");
75
- const options = [" --path <directory> target project directory (default: current directory)"];
76
- if (!command || command === "init" || command === "update") {
77
- options.push(" --dry-run show planned writes without changing files");
78
- }
79
- if (!command || command === "doctor") {
80
- options.push(" --json emit doctor findings as JSON");
81
- options.push(" --strict treat warnings as unhealthy");
82
- options.push(" --fix restore missing managed template files");
83
- options.push(" --adopt <path> preserve an existing adapter in the manifest");
84
- }
85
- if (!command || command === "route") {
86
- options.push(" --work <type> declared work type");
87
- options.push(" --surface <value> affected surface (repeatable)");
88
- options.push(" --risk <value> task risk (repeatable)");
89
- options.push(" --platform <value> affected platform (repeatable)");
90
- options.push(" --behavior-change declare behavior change");
91
- options.push(" --executable-change declare executable/configuration change");
92
- options.push(" --json emit route result as JSON");
93
- }
94
- if (!command || command === "advance") {
95
- options.push(" --to <phase> destination workflow phase");
96
- }
97
- if (!command || command === "record-continuity") {
98
- options.push(" --focus-id <id> current implementation focus ID");
99
- options.push(" --focus-summary <text> current implementation focus summary");
100
- options.push(" --remaining <id:summary> remaining implementation item (repeatable)");
101
- options.push(" --known-issue <id:summary> known implementation issue (repeatable)");
102
- options.push(" --changed-area <path> changed project area (repeatable)");
103
- options.push(" --inspect-first <path> suggested inspection path (repeatable)");
104
- options.push(" --resume-note <text> bounded operational resume note");
105
- }
106
- if (!command || !GENERIC_JSON_EXCLUDED_COMMANDS.has(command)) {
107
- options.push(" --json emit structured output as JSON");
108
- }
109
- if (!command || ["preflight", "complete", "audit", "report"].includes(command)) {
110
- options.push(" --strict require strict protocol compliance");
111
- }
112
- if (!command || command === "policy") {
113
- options.push(" <name> policy pack name");
114
- }
115
- if (!command || command === "bundle") {
116
- options.push(" --task <id> task ID to export as a portable bundle");
43
+ import { CLI_COMMAND_DEFINITIONS, buildOptionLookup, getPositionalDefinitions } from "./core/cli-command-definitions.js";
44
+
45
+ export const COMMANDS = Object.freeze(Object.keys(CLI_COMMAND_DEFINITIONS));
46
+
47
+ function formatOptionUsage(optKey, optDef) {
48
+ let label = optKey;
49
+ if (optDef.valueName) {
50
+ label = optKey === "--" ? `-- <${optDef.valueName}>` : `${optKey} <${optDef.valueName}>`;
117
51
  }
118
- if (!command || ["status", "inspect", "validate-protocol"].includes(command)) {
119
- options.push(" --contract-file <path> current JSON contract used for freshness comparison");
52
+ return ` ${label.padEnd(20)} ${optDef.description}`;
53
+ }
54
+
55
+ export function usage(command = null) {
56
+ if (command && CLI_COMMAND_DEFINITIONS[command]) {
57
+ const def = CLI_COMMAND_DEFINITIONS[command];
58
+ const lines = Object.entries(def.options).map(([optKey, optDef]) => formatOptionUsage(optKey, optDef));
59
+ return `Usage: forgeloop <${command}> [options]\n\nOptions:\n${lines.join("\n")}\n`;
120
60
  }
121
- if (!command || command === "validate-protocol") {
122
- options.push(" --route-file <path> routing-result JSON relative to target");
123
- options.push(" --state-file <path> work-state JSON relative to target");
124
- options.push(" --receipt-file <path> execution-receipt JSON relative to target");
125
- options.push(" --continuity-file <path> optional execution-continuity JSON relative to target");
126
- options.push(" --task-brief-file <path> task brief JSON (repeatable)");
127
- options.push(" --delegated-result-file <path> delegated result JSON (repeatable)");
61
+
62
+ const allOptions = new Map();
63
+ for (const def of Object.values(CLI_COMMAND_DEFINITIONS)) {
64
+ for (const [optKey, optDef] of Object.entries(def.options)) {
65
+ if (!allOptions.has(optKey)) {
66
+ allOptions.set(optKey, optDef);
67
+ }
68
+ }
128
69
  }
129
- if (!command || command === "validate-receipt") {
130
- options.push(" --file <path> receipt file relative to target");
70
+
71
+ const lines = [...allOptions.entries()].map(([optKey, optDef]) => formatOptionUsage(optKey, optDef));
72
+ const commands = COMMANDS.join("|");
73
+ return `Usage: forgeloop <${commands}> [options]\n\nOptions:\n${lines.join("\n")}\n`;
74
+ }
75
+
76
+ export function splitLongOption(argument) {
77
+ if (!argument.startsWith("--")) {
78
+ return { name: argument, inlineValue: undefined };
131
79
  }
132
- if (!command || command === "record-check" || command === "run-check") {
133
- options.push(" --id <id> stable check identifier");
134
- options.push(" --requirement <id> completion requirement covered by the check");
135
- options.push(" --details <json> additional structured check details");
80
+ const index = argument.indexOf("=");
81
+ if (index === -1) {
82
+ return { name: argument, inlineValue: undefined };
136
83
  }
137
- if (!command || command === "record-check") {
138
- options.push(" --kind <kind> check kind (default: command; use manual-review for manual evidence)");
139
- options.push(" --status <status> passed, failed, blocked, or not-run");
140
- options.push(" --evidence-kind <kind> OBSERVED, INFERRED, NOT_VERIFIED, or BLOCKED");
141
- options.push(" --command <text> recorded only as metadata; it is never executed");
142
- options.push(" --result <text> observed result supplied by the actor");
143
- options.push(" --exit-code <number> observed process exit code");
144
- options.push(" --execution-ref <id> ForgeLoop execution artifact reference");
145
- options.push(" --provenance <value> FORGELOOP_EXECUTED, ACTOR_REPORTED, or MANUAL_OBSERVATION");
84
+ return {
85
+ name: argument.slice(0, index),
86
+ inlineValue: argument.slice(index + 1),
87
+ };
88
+ }
89
+
90
+ function applyOption({ canonicalName, optionDef, inlineValue, argv, index, options, suppliedFlags }) {
91
+ const key = optionDef.targetKey;
92
+ suppliedFlags.add(canonicalName);
93
+
94
+ if (inlineValue !== undefined && !optionDef.takesValue) {
95
+ throw new Error(`${canonicalName} does not accept a value`);
146
96
  }
147
- if (!command || command === "run-check") {
148
- options.push(" -- <argv> exact command argv to classify, execute, and attest");
97
+
98
+ switch (optionDef.parseType) {
99
+ case "boolean": {
100
+ options[key] = true;
101
+ return { index };
102
+ }
103
+ case "string": {
104
+ const value = inlineValue ?? argv[index + 1];
105
+ if (
106
+ value === undefined ||
107
+ (value.length === 0 && !optionDef.allowEmpty) ||
108
+ (value.startsWith("-") && inlineValue === undefined && !optionDef.allowLeadingHyphen)
109
+ ) {
110
+ throw new Error(optionDef.missingValueMessage ?? `${canonicalName} requires a value`);
111
+ }
112
+ if (optionDef.repeatable) {
113
+ if (!Array.isArray(options[key])) options[key] = [];
114
+ options[key].push(value);
115
+ } else {
116
+ options[key] = value;
117
+ }
118
+ return { index: inlineValue === undefined ? index + 1 : index };
119
+ }
120
+ case "non-negative-integer": {
121
+ const value = inlineValue ?? argv[index + 1];
122
+ if (value === undefined || !/^\d+$/.test(value)) {
123
+ throw new Error(optionDef.missingValueMessage ?? `${canonicalName} requires a non-negative integer`);
124
+ }
125
+ options[key] = Number(value);
126
+ return { index: inlineValue === undefined ? index + 1 : index };
127
+ }
128
+ case "json-object": {
129
+ const raw = inlineValue ?? argv[index + 1];
130
+ if (!raw || (raw.startsWith("-") && inlineValue === undefined)) {
131
+ throw new Error(optionDef.missingValueMessage ?? `${canonicalName} requires a JSON object`);
132
+ }
133
+ let parsed;
134
+ try {
135
+ parsed = JSON.parse(raw);
136
+ } catch {
137
+ throw new Error(`${canonicalName} must be valid JSON`);
138
+ }
139
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
140
+ throw new Error(`${canonicalName} must be a JSON object`);
141
+ }
142
+ options[key] = parsed;
143
+ return { index: inlineValue === undefined ? index + 1 : index };
144
+ }
145
+ case "argv": {
146
+ const remaining = argv.slice(index + 1);
147
+ options[key] = remaining;
148
+ return { index: argv.length, stop: true };
149
+ }
150
+ default:
151
+ throw new Error(`Unsupported option type: ${optionDef.parseType}`);
149
152
  }
150
- if (!command || command === "record-terminal-result") {
151
- options.push(" --requirement <id> terminal requirement covered by the result");
152
- options.push(" --type <type> PUBLICATION or PRODUCTION_READINESS");
153
- options.push(" --status <status> observed terminal status");
154
- options.push(" --source <text> external action source (e.g. npm publish, git push)");
155
- options.push(" --result <text> observed external result description");
156
- options.push(" --details <json> additional structured result details");
153
+ }
154
+
155
+ const ALL_VALUE_TAKING_FLAGS = new Set();
156
+ for (const def of Object.values(CLI_COMMAND_DEFINITIONS)) {
157
+ for (const [optName, optDef] of Object.entries(def.options)) {
158
+ if (optDef.takesValue && optName.startsWith("-")) {
159
+ ALL_VALUE_TAKING_FLAGS.add(optName);
160
+ }
157
161
  }
158
- options.push(" --version show the installed package version");
159
- options.push(" --help show this help");
162
+ }
160
163
 
161
- return `Usage: forgeloop <${command ?? commands}> [options]\n\nOptions:\n${options.join("\n")}\n`;
164
+ export function discoverCommand(argv) {
165
+ for (let i = 0; i < argv.length; i += 1) {
166
+ const arg = argv[i];
167
+ if (arg === "--") break;
168
+
169
+ if (arg.startsWith("-")) {
170
+ const eqIdx = arg.indexOf("=");
171
+ const optName = eqIdx === -1 ? arg : arg.slice(0, eqIdx);
172
+ if (eqIdx === -1 && ALL_VALUE_TAKING_FLAGS.has(optName)) {
173
+ i += 1; // Skip the option's value so it is never scanned as a candidate command
174
+ }
175
+ continue;
176
+ }
177
+
178
+ if (COMMANDS.includes(arg)) {
179
+ return arg;
180
+ }
181
+ }
182
+ return null;
162
183
  }
163
184
 
164
- export function parseArgs(argv) {
185
+ export function parseCliSyntax(argv) {
165
186
  const options = {
166
187
  path: ".",
167
188
  dryRun: false,
@@ -204,242 +225,84 @@ export function parseArgs(argv) {
204
225
  help: false,
205
226
  version: false,
206
227
  };
207
- let command = null;
228
+
229
+ const command = discoverCommand(argv);
230
+ const bootstrapLookup = buildOptionLookup(null);
231
+ const commandLookup = buildOptionLookup(command);
232
+ const positionalDefs = getPositionalDefinitions(command);
233
+ let positionalCursor = 0;
234
+ const suppliedFlags = new Set();
235
+ let commandSeen = false;
208
236
 
209
237
  for (let index = 0; index < argv.length; index += 1) {
210
238
  const argument = argv[index];
211
- if (COMMANDS.includes(argument)) {
212
- if (command) throw new Error(`Multiple commands are not supported: ${argument}`);
213
- command = argument;
214
- } else if (argument === "--help" || argument === "-h") {
215
- options.help = true;
216
- } else if (argument === "--dry-run") {
217
- options.dryRun = true;
218
- } else if (argument === "--json") {
219
- options.json = true;
220
- } else if (argument === "--strict") {
221
- options.strict = true;
222
- } else if (argument === "--fix") {
223
- options.fix = true;
224
- } else if (argument === "--adopt") {
225
- const relativePath = argv[index + 1];
226
- if (!relativePath || relativePath.startsWith("-")) throw new Error("--adopt requires a path");
227
- options.adopt.push(relativePath);
228
- index += 1;
229
- } else if (argument === "--work") {
230
- const workType = argv[index + 1];
231
- if (!workType || workType.startsWith("-")) throw new Error("--work requires a type");
232
- options.work = workType;
233
- index += 1;
234
- } else if (argument === "--surface") {
235
- const surface = argv[index + 1];
236
- if (!surface || surface.startsWith("-")) throw new Error("--surface requires a value");
237
- options.surfaces.push(surface);
238
- index += 1;
239
- } else if (argument === "--risk") {
240
- const risk = argv[index + 1];
241
- if (!risk || risk.startsWith("-")) throw new Error("--risk requires a value");
242
- options.risks.push(risk);
243
- index += 1;
244
- } else if (argument === "--platform") {
245
- const platform = argv[index + 1];
246
- if (!platform || platform.startsWith("-")) throw new Error("--platform requires a value");
247
- options.platforms.push(platform);
248
- index += 1;
249
- } else if (argument === "--behavior-change") {
250
- options.behaviorChange = true;
251
- } else if (argument === "--executable-change") {
252
- options.executableChange = true;
253
- } else if (argument === "--to") {
254
- const phase = argv[index + 1];
255
- if (!phase || phase.startsWith("-")) throw new Error("--to requires a phase");
256
- options.to = phase;
257
- index += 1;
258
- } else if (argument === "--task") {
259
- const task = argv[index + 1];
260
- if (!task || task.startsWith("-")) throw new Error("--task requires an ID");
261
- options.task = task;
262
- index += 1;
263
- } else if (argument === "--file") {
264
- const file = argv[index + 1];
265
- if (!file || file.startsWith("-")) throw new Error("--file requires a path");
266
- options.file = file;
267
- index += 1;
268
- } else if (argument === "--contract-file") {
269
- const contractFile = argv[index + 1];
270
- if (!contractFile || contractFile.startsWith("-")) throw new Error("--contract-file requires a path");
271
- options.contractFile = contractFile;
272
- index += 1;
273
- } else if (["--route-file", "--state-file", "--receipt-file", "--continuity-file"].includes(argument)) {
274
- const file = argv[index + 1];
275
- if (!file || file.startsWith("-")) throw new Error(`${argument} requires a path`);
276
- if (argument === "--route-file") options.routeFile = file;
277
- if (argument === "--state-file") options.stateFile = file;
278
- if (argument === "--receipt-file") options.receiptFile = file;
279
- if (argument === "--continuity-file") options.continuityFile = file;
280
- index += 1;
281
- } else if (argument === "--task-brief-file") {
282
- const file = argv[index + 1];
283
- if (!file || file.startsWith("-")) throw new Error("--task-brief-file requires a path");
284
- options.taskBriefFiles.push(file);
285
- index += 1;
286
- } else if (argument === "--delegated-result-file") {
287
- const file = argv[index + 1];
288
- if (!file || file.startsWith("-")) throw new Error("--delegated-result-file requires a path");
289
- options.delegatedResultFiles.push(file);
290
- index += 1;
291
- } else if (argument === "--id") {
292
- const id = argv[index + 1];
293
- if (!id || id.startsWith("-")) throw new Error("--id requires a check ID");
294
- options.checkId = id;
295
- index += 1;
296
- } else if (argument.startsWith("--id=")) {
297
- const id = argument.slice("--id=".length);
298
- if (!id || id.startsWith("-")) throw new Error("--id requires a check ID");
299
- options.checkId = id;
300
- } else if (argument === "--kind") {
301
- const kind = argv[index + 1];
302
- if (!kind || kind.startsWith("-")) throw new Error("--kind requires a check kind");
303
- options.checkKind = kind;
304
- index += 1;
305
- } else if (argument === "--requirement") {
306
- const requirement = argv[index + 1];
307
- if (!requirement || requirement.startsWith("-")) throw new Error("--requirement requires an evidence target");
308
- options.checkRequirement = requirement;
309
- index += 1;
310
- } else if (argument.startsWith("--requirement=")) {
311
- const requirement = argument.slice("--requirement=".length);
312
- if (!requirement) throw new Error("--requirement requires an evidence target");
313
- options.checkRequirement = requirement;
314
- } else if (argument === "--status") {
315
- const status = argv[index + 1];
316
- if (!status || status.startsWith("-")) throw new Error("--status requires a check status");
317
- options.checkStatus = status;
318
- index += 1;
319
- } else if (argument === "--evidence-kind") {
320
- const evidenceKind = argv[index + 1];
321
- if (!evidenceKind || evidenceKind.startsWith("-")) throw new Error("--evidence-kind requires an evidence kind");
322
- options.checkEvidenceKind = evidenceKind;
323
- index += 1;
324
- } else if (argument === "--command") {
325
- const commandText = argv[index + 1];
326
- if (!commandText || commandText.startsWith("-")) throw new Error("--command requires recorded text");
327
- options.checkCommand = commandText;
328
- index += 1;
329
- } else if (argument === "--source") {
330
- const sourceText = argv[index + 1];
331
- if (!sourceText || sourceText.startsWith("-")) throw new Error("--source requires recorded text");
332
- options.checkSource = sourceText;
333
- index += 1;
334
- } else if (argument.startsWith("--source=")) {
335
- const sourceText = argument.slice("--source=".length);
336
- if (!sourceText) throw new Error("--source requires recorded text");
337
- options.checkSource = sourceText;
338
- } else if (argument === "--type") {
339
- const typeValue = argv[index + 1];
340
- if (!typeValue || typeValue.startsWith("-")) throw new Error("--type requires a terminal type");
341
- options.checkType = typeValue;
342
- index += 1;
343
- } else if (argument.startsWith("--type=")) {
344
- const typeValue = argument.slice("--type=".length);
345
- if (!typeValue) throw new Error("--type requires a terminal type");
346
- options.checkType = typeValue;
347
- } else if (argument === "--result") {
348
- const resultText = argv[index + 1];
349
- if (!resultText || resultText.startsWith("-")) throw new Error("--result requires recorded text");
350
- options.checkResult = resultText;
351
- index += 1;
352
- } else if (argument.startsWith("--result=")) {
353
- const resultText = argument.slice("--result=".length);
354
- if (!resultText) throw new Error("--result requires recorded text");
355
- options.checkResult = resultText;
356
- } else if (argument === "--exit-code") {
357
- const exitCode = argv[index + 1];
358
- if (!exitCode || exitCode.startsWith("-")) throw new Error("--exit-code requires a non-negative integer");
359
- if (!/^\d+$/.test(exitCode)) throw new Error("--exit-code requires a non-negative integer");
360
- options.checkExitCode = Number(exitCode);
361
- index += 1;
362
- } else if (argument === "--details") {
363
- const details = argv[index + 1];
364
- if (!details || details.startsWith("-")) throw new Error("--details requires a JSON object");
365
- try {
366
- options.checkDetails = JSON.parse(details);
367
- } catch {
368
- throw new Error("--details must be valid JSON");
239
+
240
+ if (argument === command && !commandSeen) {
241
+ commandSeen = true;
242
+ continue;
243
+ }
244
+
245
+ if (argument === "--") {
246
+ if (!commandSeen) {
247
+ throw new Error("-- is not valid before a command");
369
248
  }
370
- if (!options.checkDetails || typeof options.checkDetails !== "object" || Array.isArray(options.checkDetails)) {
371
- throw new Error("--details must be a JSON object");
249
+ const passthrough = commandLookup.get("--");
250
+ if (!passthrough) {
251
+ throw new Error(`Unknown option: --`);
372
252
  }
373
- index += 1;
374
- } else if (argument === "--execution-ref") {
375
- const executionRef = argv[index + 1];
376
- if (!executionRef || executionRef.startsWith("-")) throw new Error("--execution-ref requires an execution ID");
377
- options.checkExecutionRef = executionRef;
378
- index += 1;
379
- } else if (argument === "--provenance") {
380
- const provenance = argv[index + 1];
381
- if (!provenance || provenance.startsWith("-")) throw new Error("--provenance requires a provenance value");
382
- options.checkProvenance = provenance;
383
- index += 1;
384
- } else if (argument === "--" && command === "run-check") {
385
- options.commandArgv = argv.slice(index + 1);
253
+ options[passthrough.optionDef.targetKey] = argv.slice(index + 1);
254
+ suppliedFlags.add("--");
386
255
  break;
387
- } else if ([
388
- "--focus-id",
389
- "--focus-summary",
390
- "--remaining",
391
- "--known-issue",
392
- "--changed-area",
393
- "--inspect-first",
394
- "--resume-note",
395
- ].includes(argument)) {
396
- const consumed = consumeContinuityOption({ argument, argv, index, options });
397
- index = consumed.index;
398
- } else if (argument === "--path") {
399
- options.path = argv[index + 1];
400
- if (!options.path || options.path.startsWith("-")) throw new Error("--path requires a directory");
401
- index += 1;
402
- } else if (argument.startsWith("--path=")) {
403
- options.path = argument.slice("--path=".length);
404
- if (!options.path || options.path.startsWith("-")) throw new Error("--path requires a directory");
405
- } else if (argument === "--version" || argument === "-v") {
406
- options.version = true;
407
- } else if (!argument.startsWith("-") && command === "policy" && !options.policy) {
408
- options.policy = argument;
409
- } else if (!argument.startsWith("-") && !command) {
410
- command = argument;
256
+ }
257
+
258
+ const { name: optName, inlineValue } = splitLongOption(argument);
259
+ const activeLookup = commandSeen ? commandLookup : bootstrapLookup;
260
+ const matched = activeLookup.get(optName);
261
+
262
+ if (matched) {
263
+ const res = applyOption({
264
+ canonicalName: matched.canonicalName,
265
+ optionDef: matched.optionDef,
266
+ inlineValue,
267
+ argv,
268
+ index,
269
+ options,
270
+ suppliedFlags,
271
+ });
272
+ index = res.index;
273
+ if (res.stop) break;
274
+ continue;
275
+ }
276
+
277
+ if (commandSeen && command && !argument.startsWith("-")) {
278
+ const positional = positionalDefs[positionalCursor];
279
+ if (positional) {
280
+ options[positional.targetKey] = argument;
281
+ positionalCursor += 1;
282
+ continue;
283
+ }
284
+ }
285
+
286
+ if (!commandSeen) {
287
+ if (!command) {
288
+ throw new Error(`Unknown option: ${argument}`);
289
+ }
290
+ throw new Error(`Option ${argument} is not valid before a command`);
291
+ }
292
+
293
+ if (command) {
294
+ throw new Error(`Option ${argument} is not valid for ${command}`);
411
295
  } else {
412
296
  throw new Error(`Unknown option: ${argument}`);
413
297
  }
414
298
  }
415
299
 
416
- if (!command) return { command: null, options };
300
+ return { command, options };
301
+ }
302
+
303
+ export function validateCliSemantics({ command, options } = {}) {
304
+ if (!command) return;
417
305
 
418
- const jsonCommands = ["doctor", "route", "activate", "advance", "next", "continuity", "record-continuity", "reconcile-continuity", "clear-continuity", "prepare-completion", "run-check", "record-check", "record-terminal-result", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"];
419
- if (!jsonCommands.includes(command) && options.json) {
420
- throw new Error(`Option --json is not valid for ${command}`);
421
- }
422
- if (![
423
- "init",
424
- "update",
425
- ].includes(command) && options.dryRun) {
426
- throw new Error(`Option --dry-run is not valid for ${command}`);
427
- }
428
- if (!["doctor", "preflight", "complete", "audit", "report"].includes(command) && options.strict) {
429
- throw new Error(`Option --strict is not valid for ${command}`);
430
- }
431
- if (command !== "doctor" && options.adopt.length > 0) {
432
- throw new Error(`Option --adopt is not valid for ${command}`);
433
- }
434
- if (command !== "doctor" && options.fix) {
435
- throw new Error(`Option --fix is not valid for ${command}`);
436
- }
437
- if (command !== "route" && (options.work || options.surfaces.length || options.risks.length || options.platforms.length || options.behaviorChange || options.executableChange)) {
438
- throw new Error(`Route options are not valid for ${command}`);
439
- }
440
- if (command !== "advance" && options.to) {
441
- throw new Error(`Option --to is not valid for ${command}`);
442
- }
443
306
  if (command === "policy" && !options.policy) {
444
307
  throw new Error("policy requires a name");
445
308
  }
@@ -449,35 +312,12 @@ export function parseArgs(argv) {
449
312
  if (command === "bundle" && !options.task) {
450
313
  throw new Error("bundle requires --task");
451
314
  }
452
- if (command !== "bundle" && options.task) {
453
- throw new Error(`--task is not valid for ${command}`);
454
- }
455
- if (command !== "validate-receipt" && options.file) {
456
- throw new Error(`Option --file is not valid for ${command}`);
457
- }
458
- if (!["status", "inspect", "validate-protocol"].includes(command) && options.contractFile) {
459
- throw new Error(`Option --contract-file is not valid for ${command}`);
460
- }
461
- if (command !== "validate-protocol" && (options.routeFile || options.stateFile || options.receiptFile || options.continuityFile || options.taskBriefFiles.length > 0 || options.delegatedResultFiles.length > 0)) {
462
- throw new Error(`Protocol artifact options are not valid for ${command}`);
463
- }
464
- const checkOptions = [
465
- options.checkId,
466
- options.checkKind,
467
- options.checkRequirement,
468
- options.checkStatus,
469
- options.checkEvidenceKind,
470
- options.checkCommand,
471
- options.checkResult,
472
- options.checkExitCode,
473
- options.checkDetails,
474
- options.checkExecutionRef,
475
- options.checkProvenance,
476
- ];
477
- if (!["record-check", "run-check"].includes(command) && checkOptions.some((value) => value !== null)) {
478
- throw new Error(`Check recording options are not valid for ${command}`);
315
+ if (command === "task-create" && !options.task) {
316
+ throw new Error("task-create requires --task");
479
317
  }
318
+
480
319
  validateContinuityOptions(command, options);
320
+
481
321
  if (command === "record-check" && !options.help) {
482
322
  if (!options.checkId) throw new Error("record-check requires --id");
483
323
  if (!options.checkRequirement) throw new Error("record-check requires --requirement");
@@ -496,7 +336,12 @@ export function parseArgs(argv) {
496
336
  throw new Error("run-check requires -- followed by an exact command argv");
497
337
  }
498
338
  }
499
- return { command, options };
339
+ }
340
+
341
+ export function parseArgs(argv) {
342
+ const parsed = parseCliSyntax(argv);
343
+ validateCliSemantics(parsed);
344
+ return parsed;
500
345
  }
501
346
 
502
347
  async function packageVersion(packageRoot) {
@@ -547,6 +392,7 @@ export const COMMAND_HANDLERS = Object.freeze({
547
392
  platforms: options.platforms,
548
393
  behaviorChange: options.behaviorChange,
549
394
  executableChange: options.executableChange,
395
+ taskId: options.task,
550
396
  });
551
397
  console.log(options.json ? JSON.stringify(result, null, 2) : formatRouteResult(result));
552
398
  return 0;
@@ -557,22 +403,22 @@ export const COMMAND_HANDLERS = Object.freeze({
557
403
  return 0;
558
404
  },
559
405
  preflight: async ({ target, packageRoot, options }) => {
560
- const result = await runPreflight({ target, packageRoot, strict: options.strict });
406
+ const result = await runPreflight({ target, packageRoot, strict: options.strict, taskId: options.task });
561
407
  console.log(options.json ? JSON.stringify(result, null, 2) : formatPreflightResult(result));
562
408
  return result.status === "READY" ? 0 : 1;
563
409
  },
564
410
  advance: async ({ target, packageRoot, options }) => {
565
- const result = await runAdvance({ target, packageRoot, to: options.to });
411
+ const result = await runAdvance({ target, packageRoot, to: options.to, taskId: options.task });
566
412
  console.log(options.json ? JSON.stringify(result, null, 2) : formatAdvanceResult(result));
567
413
  return 0;
568
414
  },
569
415
  next: async ({ target, packageRoot, options }) => {
570
- const result = await runNext({ target, packageRoot });
416
+ const result = await runNext({ target, packageRoot, taskId: options.task });
571
417
  console.log(options.json ? JSON.stringify(result, null, 2) : formatNextActionResult(result));
572
418
  return 0;
573
419
  },
574
420
  continuity: async ({ target, packageRoot, options }) => {
575
- const result = await runContinuity({ target, packageRoot });
421
+ const result = await runContinuity({ target, packageRoot, taskId: options.task });
576
422
  console.log(options.json ? JSON.stringify(result, null, 2) : formatContinuityResult(result));
577
423
  return 0;
578
424
  },
@@ -586,22 +432,23 @@ export const COMMAND_HANDLERS = Object.freeze({
586
432
  changedAreas: options.continuityChangedAreas,
587
433
  inspectFirst: options.continuityInspectFirst,
588
434
  resumeNote: options.continuityResumeNote,
435
+ taskId: options.task,
589
436
  });
590
437
  console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordContinuityResult(result));
591
438
  return 0;
592
439
  },
593
440
  "reconcile-continuity": async ({ target, packageRoot, options }) => {
594
- const result = await runReconcileContinuity({ target, packageRoot });
441
+ const result = await runReconcileContinuity({ target, packageRoot, taskId: options.task });
595
442
  console.log(options.json ? JSON.stringify(result, null, 2) : formatReconcileContinuityResult(result));
596
443
  return 0;
597
444
  },
598
445
  "clear-continuity": async ({ target, options }) => {
599
- const result = await runClearContinuity({ target });
446
+ const result = await runClearContinuity({ target, taskId: options.task });
600
447
  console.log(options.json ? JSON.stringify(result, null, 2) : formatClearContinuityResult(result));
601
448
  return 0;
602
449
  },
603
450
  "prepare-completion": async ({ target, packageRoot, options }) => {
604
- const result = await runPrepareCompletion({ target, packageRoot });
451
+ const result = await runPrepareCompletion({ target, packageRoot, taskId: options.task });
605
452
  console.log(options.json ? JSON.stringify(result, null, 2) : formatPrepareCompletionResult(result));
606
453
  return 0;
607
454
  },
@@ -613,6 +460,7 @@ export const COMMAND_HANDLERS = Object.freeze({
613
460
  requirement: options.checkRequirement,
614
461
  argv: options.commandArgv,
615
462
  details: options.checkDetails ?? undefined,
463
+ taskId: options.task,
616
464
  });
617
465
  console.log(options.json ? JSON.stringify(result, null, 2) : formatRunCheckResult(result));
618
466
  return result.check.status === "passed" ? 0 : 1;
@@ -632,6 +480,7 @@ export const COMMAND_HANDLERS = Object.freeze({
632
480
  details: options.checkDetails ?? undefined,
633
481
  executionRef: options.checkExecutionRef ?? undefined,
634
482
  provenance: options.checkProvenance ?? undefined,
483
+ taskId: options.task,
635
484
  });
636
485
  console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordCheckResult(result));
637
486
  return 0;
@@ -646,27 +495,28 @@ export const COMMAND_HANDLERS = Object.freeze({
646
495
  source: options.checkSource ?? options.checkCommand,
647
496
  result: options.checkResult,
648
497
  details: options.checkDetails ?? undefined,
498
+ taskId: options.task,
649
499
  });
650
500
  console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordTerminalResult(result));
651
501
  return 0;
652
502
  },
653
503
  complete: async ({ target, packageRoot, options }) => {
654
- const result = await runComplete({ target, packageRoot, strict: options.strict });
504
+ const result = await runComplete({ target, packageRoot, strict: options.strict, taskId: options.task });
655
505
  console.log(options.json ? JSON.stringify(result, null, 2) : formatCompleteResult(result));
656
506
  return result.status === "VALID" ? 0 : 1;
657
507
  },
658
508
  audit: async ({ target, packageRoot, options }) => {
659
- const result = await runAudit({ target, packageRoot, strict: options.strict });
509
+ const result = await runAudit({ target, packageRoot, strict: options.strict, taskId: options.task });
660
510
  console.log(options.json ? JSON.stringify(result, null, 2) : formatAuditResult(result));
661
511
  return result.status === "VALID" ? 0 : 1;
662
512
  },
663
513
  report: async ({ target, packageRoot, options }) => {
664
- const result = await runReport({ target, packageRoot, strict: options.strict });
514
+ const result = await runReport({ target, packageRoot, strict: options.strict, taskId: options.task });
665
515
  console.log(options.json ? JSON.stringify(result, null, 2) : formatReportResult(result));
666
516
  return result.verdict === "VALID" ? 0 : 1;
667
517
  },
668
518
  policy: async ({ target, packageRoot, options }) => {
669
- const result = await runPolicy({ target, packageRoot, name: options.policy });
519
+ const result = await runPolicy({ target, packageRoot, name: options.policy, taskId: options.task });
670
520
  console.log(options.json ? JSON.stringify(result, null, 2) : formatPolicyResult(result));
671
521
  return 0;
672
522
  },
@@ -676,7 +526,7 @@ export const COMMAND_HANDLERS = Object.freeze({
676
526
  return 0;
677
527
  },
678
528
  inspect: async ({ target, packageRoot, options }) => {
679
- const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile });
529
+ const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile, taskId: options.task });
680
530
  console.log(options.json ? JSON.stringify(result, null, 2) : formatInspectResult(result));
681
531
  return result.ok ? 0 : 1;
682
532
  },
@@ -689,32 +539,74 @@ export const COMMAND_HANDLERS = Object.freeze({
689
539
  const result = await runValidateProtocol({
690
540
  target,
691
541
  packageRoot,
692
- stateFile: options.stateFile ?? ARTIFACT_PATHS.state,
693
- receiptFile: options.receiptFile ?? ARTIFACT_PATHS.receipt,
694
- routeFile: options.routeFile ?? ARTIFACT_PATHS.route,
542
+ stateFile: options.stateFile,
543
+ receiptFile: options.receiptFile,
544
+ routeFile: options.routeFile,
695
545
  contractFile: options.contractFile,
696
546
  continuityFile: options.continuityFile,
697
547
  taskBriefFiles: options.taskBriefFiles,
698
548
  delegatedResultFiles: options.delegatedResultFiles,
549
+ taskId: options.task,
699
550
  });
700
551
  console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateProtocolResult(result));
701
552
  return result.status === "VALID" ? 0 : 1;
702
553
  },
703
554
  status: async ({ target, packageRoot, options }) => {
704
- const result = await runStatus({ target, packageRoot, contractFile: options.contractFile });
555
+ const result = await runStatus({ target, packageRoot, contractFile: options.contractFile, taskId: options.task });
705
556
  console.log(options.json ? JSON.stringify(result, null, 2) : formatStatusResult(result));
706
557
  return 0;
707
558
  },
708
559
  "validate-state": async ({ target, packageRoot, options }) => {
709
- const result = await runValidateState({ target, packageRoot });
560
+ const result = await runValidateState({ target, packageRoot, taskId: options.task });
710
561
  console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateStateResult(result));
711
562
  return result.ok ? 0 : 1;
712
563
  },
713
564
  "clear-state": async ({ target, options }) => {
714
- const result = await runClearState({ target });
565
+ const result = await runClearState({ target, taskId: options.task });
715
566
  console.log(options.json ? JSON.stringify(result, null, 2) : formatClearStateResult(result));
716
567
  return 0;
717
568
  },
569
+ "task-create": async ({ target, packageRoot, options }) => {
570
+ const result = await runTaskCreate({
571
+ target,
572
+ packageRoot,
573
+ taskId: options.task,
574
+ claims: options.claims,
575
+ contractFile: options.contractFile,
576
+ });
577
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatTaskCreateResult(result));
578
+ return 0;
579
+ },
580
+ "task-list": async ({ target, packageRoot, options }) => {
581
+ const result = await runTaskList({ target, packageRoot });
582
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatTaskListResult(result));
583
+ return 0;
584
+ },
585
+ "task-show": async ({ target, packageRoot, options }) => {
586
+ const result = await runTaskShow({ target, packageRoot, taskId: options.task });
587
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatTaskShowResult(result));
588
+ return 0;
589
+ },
590
+ "task-scope": async ({ target, packageRoot, options }) => {
591
+ const result = await runTaskScope({
592
+ target,
593
+ packageRoot,
594
+ taskId: options.task,
595
+ claims: options.claims,
596
+ });
597
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatTaskScopeResult(result));
598
+ return 0;
599
+ },
600
+ "task-migrate": async ({ target, packageRoot, options }) => {
601
+ const result = await runTaskMigrate({ target, packageRoot, dryRun: options.dryRun });
602
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatTaskMigrateResult(result));
603
+ return 0;
604
+ },
605
+ "task-unlock": async ({ target, packageRoot, options }) => {
606
+ const result = await runTaskUnlock({ target, packageRoot, taskId: options.task, force: options.force });
607
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatTaskUnlockResult(result));
608
+ return 0;
609
+ },
718
610
  update: async ({ target, packageRoot, packageVersion, options }) => {
719
611
  const result = await runUpdate({ target, dryRun: options.dryRun, packageRoot, packageVersion });
720
612
  printActions(result.actions);