@cassiomc1/forgeloop 0.1.14 → 0.1.16

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 (61) hide show
  1. package/.forgeloop/forgeloop.gitignore +1 -0
  2. package/DOCS_INDEX.md +60 -0
  3. package/EXECUTION_STATE.md +9 -0
  4. package/LOOP_ENGINEERING.md +44 -6
  5. package/LOOP_SYSTEM_DESIGN.md +8 -0
  6. package/PROTOCOL_INTEGRATION.md +41 -0
  7. package/QUALITY_SCORECARD.md +2 -0
  8. package/README.md +183 -643
  9. package/TERMINOLOGY.md +4 -0
  10. package/THREAT_MODEL.md +14 -0
  11. package/docs/assets/forgeloop-flow.svg +1 -0
  12. package/docs/forgeloop-flow.mmd +51 -0
  13. package/package.json +16 -3
  14. package/schemas/check.schema.json +2 -0
  15. package/schemas/continuity.schema.json +57 -0
  16. package/schemas/execution.schema.json +63 -0
  17. package/scripts/CI_VALIDATORS.md +32 -0
  18. package/src/cli.js +351 -196
  19. package/src/commands/clear-continuity.js +9 -0
  20. package/src/commands/continuity.js +25 -0
  21. package/src/commands/doctor.js +17 -2
  22. package/src/commands/reconcile-continuity.js +23 -0
  23. package/src/commands/record-continuity.js +63 -0
  24. package/src/commands/run-check.js +83 -0
  25. package/src/commands/status.js +14 -1
  26. package/src/commands/update.js +12 -13
  27. package/src/commands/validate-protocol.js +37 -0
  28. package/src/core/artifacts.js +9 -0
  29. package/src/core/bundles.js +78 -0
  30. package/src/core/checks.js +16 -0
  31. package/src/core/command-resolution.js +295 -0
  32. package/src/core/command-tokenizer.js +122 -0
  33. package/src/core/completion-artifacts.js +229 -47
  34. package/src/core/completion.js +19 -1
  35. package/src/core/conformance.js +8 -2
  36. package/src/core/continuity-cli-options.js +58 -0
  37. package/src/core/continuity-conformance.js +46 -0
  38. package/src/core/continuity-observability.js +20 -0
  39. package/src/core/continuity-reconciliation.js +224 -0
  40. package/src/core/continuity.js +245 -0
  41. package/src/core/evidence-readiness.js +26 -1
  42. package/src/core/execution.js +185 -0
  43. package/src/core/inspect.js +9 -1
  44. package/src/core/installation-authority.js +178 -0
  45. package/src/core/json-safety.js +17 -13
  46. package/src/core/next-action-artifacts.js +118 -0
  47. package/src/core/next-action-continuity.js +65 -0
  48. package/src/core/next-action-model.js +127 -0
  49. package/src/core/next-action-phases.js +12 -0
  50. package/src/core/next-action.js +22 -249
  51. package/src/core/npm-classifier.js +343 -0
  52. package/src/core/package-manager-classifiers.js +37 -0
  53. package/src/core/preflight-consistency.js +221 -0
  54. package/src/core/preflight-loaders.js +112 -0
  55. package/src/core/preflight-model.js +83 -0
  56. package/src/core/preflight.js +34 -444
  57. package/src/core/protocol.js +7 -0
  58. package/src/core/schema-validation.js +16 -1
  59. package/src/core/templates.js +3 -0
  60. package/src/core/verification-capability.js +31 -495
  61. package/src/core/verification-constants.js +61 -0
package/src/cli.js CHANGED
@@ -24,14 +24,54 @@ import { formatPolicyResult, runPolicy } from "./commands/policy.js";
24
24
  import { formatBundleResult, runBundle } from "./commands/bundle.js";
25
25
  import { formatPrepareCompletionResult, runPrepareCompletion } from "./commands/prepare-completion.js";
26
26
  import { formatRecordCheckResult, runRecordCheck } from "./commands/record-check.js";
27
+ import { formatRunCheckResult, runCheck } from "./commands/run-check.js";
27
28
  import { formatRecordTerminalResult, runRecordTerminalResult } from "./commands/record-terminal-result.js";
28
29
  import { formatNextActionResult, runNext } from "./commands/next.js";
30
+ import { formatContinuityResult, runContinuity } from "./commands/continuity.js";
31
+ import { formatRecordContinuityResult, runRecordContinuity } from "./commands/record-continuity.js";
32
+ import { formatReconcileContinuityResult, runReconcileContinuity } from "./commands/reconcile-continuity.js";
33
+ import { formatClearContinuityResult, runClearContinuity } from "./commands/clear-continuity.js";
34
+ import { continuityOptionDefaults, consumeContinuityOption, validateContinuityOptions } from "./core/continuity-cli-options.js";
29
35
  import { resolveTarget } from "./core/filesystem.js";
30
36
  import { getPackageRoot } from "./core/templates.js";
31
37
  import { ARTIFACT_PATHS } from "./core/artifacts.js";
32
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
+
33
73
  function usage(command = null) {
34
- const commands = "init|doctor|update|activate|route|preflight|advance|next|prepare-completion|record-check|record-terminal-result|complete|audit|report|policy|bundle|inspect|status|validate-state|clear-state|validate-receipt|validate-protocol";
74
+ const commands = COMMANDS.join("|");
35
75
  const options = [" --path <directory> target project directory (default: current directory)"];
36
76
  if (!command || command === "init" || command === "update") {
37
77
  options.push(" --dry-run show planned writes without changing files");
@@ -39,6 +79,7 @@ function usage(command = null) {
39
79
  if (!command || command === "doctor") {
40
80
  options.push(" --json emit doctor findings as JSON");
41
81
  options.push(" --strict treat warnings as unhealthy");
82
+ options.push(" --fix restore missing managed template files");
42
83
  options.push(" --adopt <path> preserve an existing adapter in the manifest");
43
84
  }
44
85
  if (!command || command === "route") {
@@ -53,7 +94,16 @@ function usage(command = null) {
53
94
  if (!command || command === "advance") {
54
95
  options.push(" --to <phase> destination workflow phase");
55
96
  }
56
- if (!command || ["activate", "advance", "next", "prepare-completion", "record-check", "record-terminal-result", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(command)) {
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)) {
57
107
  options.push(" --json emit structured output as JSON");
58
108
  }
59
109
  if (!command || ["preflight", "complete", "audit", "report"].includes(command)) {
@@ -72,22 +122,30 @@ function usage(command = null) {
72
122
  options.push(" --route-file <path> routing-result JSON relative to target");
73
123
  options.push(" --state-file <path> work-state JSON relative to target");
74
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");
75
126
  options.push(" --task-brief-file <path> task brief JSON (repeatable)");
76
127
  options.push(" --delegated-result-file <path> delegated result JSON (repeatable)");
77
128
  }
78
129
  if (!command || command === "validate-receipt") {
79
130
  options.push(" --file <path> receipt file relative to target");
80
131
  }
81
- if (!command || command === "record-check") {
132
+ if (!command || command === "record-check" || command === "run-check") {
82
133
  options.push(" --id <id> stable check identifier");
83
- options.push(" --kind <kind> check kind (default: command)");
84
134
  options.push(" --requirement <id> completion requirement covered by the check");
135
+ options.push(" --details <json> additional structured check details");
136
+ }
137
+ if (!command || command === "record-check") {
138
+ options.push(" --kind <kind> check kind (default: command; use manual-review for manual evidence)");
85
139
  options.push(" --status <status> passed, failed, blocked, or not-run");
86
140
  options.push(" --evidence-kind <kind> OBSERVED, INFERRED, NOT_VERIFIED, or BLOCKED");
87
- options.push(" --command <text> command already run by the agent (recorded only)");
88
- options.push(" --result <text> observed result supplied by the agent");
141
+ options.push(" --command <text> recorded only as metadata; it is never executed");
142
+ options.push(" --result <text> observed result supplied by the actor");
89
143
  options.push(" --exit-code <number> observed process exit code");
90
- options.push(" --details <json> additional structured check details");
144
+ options.push(" --execution-ref <id> ForgeLoop execution artifact reference");
145
+ options.push(" --provenance <value> FORGELOOP_EXECUTED, ACTOR_REPORTED, or MANUAL_OBSERVATION");
146
+ }
147
+ if (!command || command === "run-check") {
148
+ options.push(" -- <argv> exact command argv to classify, execute, and attest");
91
149
  }
92
150
  if (!command || command === "record-terminal-result") {
93
151
  options.push(" --requirement <id> terminal requirement covered by the result");
@@ -109,6 +167,7 @@ export function parseArgs(argv) {
109
167
  dryRun: false,
110
168
  json: false,
111
169
  strict: false,
170
+ fix: false,
112
171
  adopt: [],
113
172
  work: null,
114
173
  surfaces: [],
@@ -122,6 +181,8 @@ export function parseArgs(argv) {
122
181
  routeFile: null,
123
182
  stateFile: null,
124
183
  receiptFile: null,
184
+ continuityFile: null,
185
+ ...continuityOptionDefaults(),
125
186
  taskBriefFiles: [],
126
187
  delegatedResultFiles: [],
127
188
  checkId: null,
@@ -133,6 +194,9 @@ export function parseArgs(argv) {
133
194
  checkResult: null,
134
195
  checkExitCode: null,
135
196
  checkDetails: null,
197
+ checkExecutionRef: null,
198
+ checkProvenance: null,
199
+ commandArgv: [],
136
200
  checkType: null,
137
201
  checkSource: null,
138
202
  policy: null,
@@ -144,7 +208,7 @@ export function parseArgs(argv) {
144
208
 
145
209
  for (let index = 0; index < argv.length; index += 1) {
146
210
  const argument = argv[index];
147
- if (["init", "doctor", "update", "activate", "route", "preflight", "advance", "next", "prepare-completion", "record-check", "record-terminal-result", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"].includes(argument)) {
211
+ if (COMMANDS.includes(argument)) {
148
212
  if (command) throw new Error(`Multiple commands are not supported: ${argument}`);
149
213
  command = argument;
150
214
  } else if (argument === "--help" || argument === "-h") {
@@ -155,6 +219,8 @@ export function parseArgs(argv) {
155
219
  options.json = true;
156
220
  } else if (argument === "--strict") {
157
221
  options.strict = true;
222
+ } else if (argument === "--fix") {
223
+ options.fix = true;
158
224
  } else if (argument === "--adopt") {
159
225
  const relativePath = argv[index + 1];
160
226
  if (!relativePath || relativePath.startsWith("-")) throw new Error("--adopt requires a path");
@@ -204,12 +270,13 @@ export function parseArgs(argv) {
204
270
  if (!contractFile || contractFile.startsWith("-")) throw new Error("--contract-file requires a path");
205
271
  options.contractFile = contractFile;
206
272
  index += 1;
207
- } else if (["--route-file", "--state-file", "--receipt-file"].includes(argument)) {
273
+ } else if (["--route-file", "--state-file", "--receipt-file", "--continuity-file"].includes(argument)) {
208
274
  const file = argv[index + 1];
209
275
  if (!file || file.startsWith("-")) throw new Error(`${argument} requires a path`);
210
276
  if (argument === "--route-file") options.routeFile = file;
211
277
  if (argument === "--state-file") options.stateFile = file;
212
278
  if (argument === "--receipt-file") options.receiptFile = file;
279
+ if (argument === "--continuity-file") options.continuityFile = file;
213
280
  index += 1;
214
281
  } else if (argument === "--task-brief-file") {
215
282
  const file = argv[index + 1];
@@ -304,6 +371,30 @@ export function parseArgs(argv) {
304
371
  throw new Error("--details must be a JSON object");
305
372
  }
306
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);
386
+ 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;
307
398
  } else if (argument === "--path") {
308
399
  options.path = argv[index + 1];
309
400
  if (!options.path || options.path.startsWith("-")) throw new Error("--path requires a directory");
@@ -324,7 +415,7 @@ export function parseArgs(argv) {
324
415
 
325
416
  if (!command) return { command: null, options };
326
417
 
327
- const jsonCommands = ["doctor", "route", "activate", "advance", "next", "prepare-completion", "record-check", "record-terminal-result", "preflight", "complete", "audit", "report", "policy", "bundle", "inspect", "status", "validate-state", "clear-state", "validate-receipt", "validate-protocol"];
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"];
328
419
  if (!jsonCommands.includes(command) && options.json) {
329
420
  throw new Error(`Option --json is not valid for ${command}`);
330
421
  }
@@ -340,6 +431,9 @@ export function parseArgs(argv) {
340
431
  if (command !== "doctor" && options.adopt.length > 0) {
341
432
  throw new Error(`Option --adopt is not valid for ${command}`);
342
433
  }
434
+ if (command !== "doctor" && options.fix) {
435
+ throw new Error(`Option --fix is not valid for ${command}`);
436
+ }
343
437
  if (command !== "route" && (options.work || options.surfaces.length || options.risks.length || options.platforms.length || options.behaviorChange || options.executableChange)) {
344
438
  throw new Error(`Route options are not valid for ${command}`);
345
439
  }
@@ -364,7 +458,7 @@ export function parseArgs(argv) {
364
458
  if (!["status", "inspect", "validate-protocol"].includes(command) && options.contractFile) {
365
459
  throw new Error(`Option --contract-file is not valid for ${command}`);
366
460
  }
367
- if (command !== "validate-protocol" && (options.routeFile || options.stateFile || options.receiptFile || options.taskBriefFiles.length > 0 || options.delegatedResultFiles.length > 0)) {
461
+ if (command !== "validate-protocol" && (options.routeFile || options.stateFile || options.receiptFile || options.continuityFile || options.taskBriefFiles.length > 0 || options.delegatedResultFiles.length > 0)) {
368
462
  throw new Error(`Protocol artifact options are not valid for ${command}`);
369
463
  }
370
464
  const checkOptions = [
@@ -377,10 +471,13 @@ export function parseArgs(argv) {
377
471
  options.checkResult,
378
472
  options.checkExitCode,
379
473
  options.checkDetails,
474
+ options.checkExecutionRef,
475
+ options.checkProvenance,
380
476
  ];
381
- if (command !== "record-check" && checkOptions.some((value) => value !== null)) {
477
+ if (!["record-check", "run-check"].includes(command) && checkOptions.some((value) => value !== null)) {
382
478
  throw new Error(`Check recording options are not valid for ${command}`);
383
479
  }
480
+ validateContinuityOptions(command, options);
384
481
  if (command === "record-check" && !options.help) {
385
482
  if (!options.checkId) throw new Error("record-check requires --id");
386
483
  if (!options.checkRequirement) throw new Error("record-check requires --requirement");
@@ -388,6 +485,17 @@ export function parseArgs(argv) {
388
485
  if (!options.checkEvidenceKind) throw new Error("record-check requires --evidence-kind");
389
486
  if (!options.checkCommand && !options.checkResult) throw new Error("record-check requires --command or --result");
390
487
  }
488
+ if (command === "run-check" && !options.help) {
489
+ if (!options.checkId) throw new Error("run-check requires --id");
490
+ if (!options.checkRequirement) throw new Error("run-check requires --requirement");
491
+ if (options.checkKind || options.checkStatus || options.checkEvidenceKind || options.checkCommand
492
+ || options.checkResult || options.checkExitCode !== null || options.checkExecutionRef || options.checkProvenance) {
493
+ throw new Error("run-check accepts only --id, --requirement, --details, and -- <argv>");
494
+ }
495
+ if (!Array.isArray(options.commandArgv) || options.commandArgv.length === 0) {
496
+ throw new Error("run-check requires -- followed by an exact command argv");
497
+ }
498
+ }
391
499
  return { command, options };
392
500
  }
393
501
 
@@ -405,6 +513,227 @@ function printActions(actions) {
405
513
  }
406
514
  }
407
515
 
516
+ export const COMMAND_HANDLERS = Object.freeze({
517
+ init: async ({ target, packageRoot, packageVersion, options }) => {
518
+ const result = await runInit({ target, dryRun: options.dryRun, packageRoot, packageVersion });
519
+ printActions(result.actions);
520
+ return 0;
521
+ },
522
+ doctor: async ({ target, packageRoot, options }) => {
523
+ const result = await runDoctor({
524
+ target,
525
+ packageRoot,
526
+ adoptPaths: options.adopt,
527
+ strict: options.strict,
528
+ fix: options.fix,
529
+ });
530
+ if (options.json) {
531
+ console.log(JSON.stringify(result, null, 2));
532
+ } else {
533
+ for (const item of result.findings) {
534
+ console.log(`${item.severity}: ${item.code}: ${item.path} - ${item.message}`);
535
+ }
536
+ console.log(result.ok ? "healthy: ForgeLoop target is ready" : "unhealthy: ForgeLoop target needs attention");
537
+ }
538
+ return result.ok ? 0 : 1;
539
+ },
540
+ route: async ({ target, packageRoot, options }) => {
541
+ const result = await runRoute({
542
+ target,
543
+ packageRoot,
544
+ workType: options.work,
545
+ surfaces: options.surfaces,
546
+ risks: options.risks,
547
+ platforms: options.platforms,
548
+ behaviorChange: options.behaviorChange,
549
+ executableChange: options.executableChange,
550
+ });
551
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatRouteResult(result));
552
+ return 0;
553
+ },
554
+ activate: async ({ target, packageRoot, options }) => {
555
+ const result = await runActivate({ target, packageRoot });
556
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatActivateResult(result));
557
+ return 0;
558
+ },
559
+ preflight: async ({ target, packageRoot, options }) => {
560
+ const result = await runPreflight({ target, packageRoot, strict: options.strict });
561
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatPreflightResult(result));
562
+ return result.status === "READY" ? 0 : 1;
563
+ },
564
+ advance: async ({ target, packageRoot, options }) => {
565
+ const result = await runAdvance({ target, packageRoot, to: options.to });
566
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatAdvanceResult(result));
567
+ return 0;
568
+ },
569
+ next: async ({ target, packageRoot, options }) => {
570
+ const result = await runNext({ target, packageRoot });
571
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatNextActionResult(result));
572
+ return 0;
573
+ },
574
+ continuity: async ({ target, packageRoot, options }) => {
575
+ const result = await runContinuity({ target, packageRoot });
576
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatContinuityResult(result));
577
+ return 0;
578
+ },
579
+ "record-continuity": async ({ target, packageRoot, options }) => {
580
+ const result = await runRecordContinuity({
581
+ target, packageRoot,
582
+ focusId: options.continuityFocusId,
583
+ focusSummary: options.continuityFocusSummary,
584
+ remaining: options.continuityRemaining,
585
+ knownIssues: options.continuityKnownIssues,
586
+ changedAreas: options.continuityChangedAreas,
587
+ inspectFirst: options.continuityInspectFirst,
588
+ resumeNote: options.continuityResumeNote,
589
+ });
590
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordContinuityResult(result));
591
+ return 0;
592
+ },
593
+ "reconcile-continuity": async ({ target, packageRoot, options }) => {
594
+ const result = await runReconcileContinuity({ target, packageRoot });
595
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatReconcileContinuityResult(result));
596
+ return 0;
597
+ },
598
+ "clear-continuity": async ({ target, options }) => {
599
+ const result = await runClearContinuity({ target });
600
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatClearContinuityResult(result));
601
+ return 0;
602
+ },
603
+ "prepare-completion": async ({ target, packageRoot, options }) => {
604
+ const result = await runPrepareCompletion({ target, packageRoot });
605
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatPrepareCompletionResult(result));
606
+ return 0;
607
+ },
608
+ "run-check": async ({ target, packageRoot, options }) => {
609
+ const result = await runCheck({
610
+ target,
611
+ packageRoot,
612
+ id: options.checkId,
613
+ requirement: options.checkRequirement,
614
+ argv: options.commandArgv,
615
+ details: options.checkDetails ?? undefined,
616
+ });
617
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatRunCheckResult(result));
618
+ return result.check.status === "passed" ? 0 : 1;
619
+ },
620
+ "record-check": async ({ target, packageRoot, options }) => {
621
+ const result = await runRecordCheck({
622
+ target,
623
+ packageRoot,
624
+ id: options.checkId,
625
+ kind: options.checkKind ?? "command",
626
+ requirement: options.checkRequirement,
627
+ status: options.checkStatus,
628
+ evidenceKind: options.checkEvidenceKind,
629
+ command: options.checkCommand ?? undefined,
630
+ result: options.checkResult ?? undefined,
631
+ ...(options.checkExitCode === null ? {} : { exitCode: options.checkExitCode }),
632
+ details: options.checkDetails ?? undefined,
633
+ executionRef: options.checkExecutionRef ?? undefined,
634
+ provenance: options.checkProvenance ?? undefined,
635
+ });
636
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordCheckResult(result));
637
+ return 0;
638
+ },
639
+ "record-terminal-result": async ({ target, packageRoot, options }) => {
640
+ const result = await runRecordTerminalResult({
641
+ target,
642
+ packageRoot,
643
+ requirement: options.checkRequirement,
644
+ type: options.checkType,
645
+ status: options.checkStatus,
646
+ source: options.checkSource ?? options.checkCommand,
647
+ result: options.checkResult,
648
+ details: options.checkDetails ?? undefined,
649
+ });
650
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordTerminalResult(result));
651
+ return 0;
652
+ },
653
+ complete: async ({ target, packageRoot, options }) => {
654
+ const result = await runComplete({ target, packageRoot, strict: options.strict });
655
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatCompleteResult(result));
656
+ return result.status === "VALID" ? 0 : 1;
657
+ },
658
+ audit: async ({ target, packageRoot, options }) => {
659
+ const result = await runAudit({ target, packageRoot, strict: options.strict });
660
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatAuditResult(result));
661
+ return result.status === "VALID" ? 0 : 1;
662
+ },
663
+ report: async ({ target, packageRoot, options }) => {
664
+ const result = await runReport({ target, packageRoot, strict: options.strict });
665
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatReportResult(result));
666
+ return result.verdict === "VALID" ? 0 : 1;
667
+ },
668
+ policy: async ({ target, packageRoot, options }) => {
669
+ const result = await runPolicy({ target, packageRoot, name: options.policy });
670
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatPolicyResult(result));
671
+ return 0;
672
+ },
673
+ bundle: async ({ target, packageRoot, options }) => {
674
+ const result = await runBundle({ target, packageRoot, taskId: options.task });
675
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatBundleResult(result));
676
+ return 0;
677
+ },
678
+ inspect: async ({ target, packageRoot, options }) => {
679
+ const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile });
680
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatInspectResult(result));
681
+ return result.ok ? 0 : 1;
682
+ },
683
+ "validate-receipt": async ({ target, packageRoot, options }) => {
684
+ const result = await runValidateReceipt({ target, packageRoot, file: options.file });
685
+ console.log(options.json ? JSON.stringify(result, null, 2) : "valid: execution receipt");
686
+ return 0;
687
+ },
688
+ "validate-protocol": async ({ target, packageRoot, options }) => {
689
+ const result = await runValidateProtocol({
690
+ target,
691
+ packageRoot,
692
+ stateFile: options.stateFile ?? ARTIFACT_PATHS.state,
693
+ receiptFile: options.receiptFile ?? ARTIFACT_PATHS.receipt,
694
+ routeFile: options.routeFile ?? ARTIFACT_PATHS.route,
695
+ contractFile: options.contractFile,
696
+ continuityFile: options.continuityFile,
697
+ taskBriefFiles: options.taskBriefFiles,
698
+ delegatedResultFiles: options.delegatedResultFiles,
699
+ });
700
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateProtocolResult(result));
701
+ return result.status === "VALID" ? 0 : 1;
702
+ },
703
+ status: async ({ target, packageRoot, options }) => {
704
+ const result = await runStatus({ target, packageRoot, contractFile: options.contractFile });
705
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatStatusResult(result));
706
+ return 0;
707
+ },
708
+ "validate-state": async ({ target, packageRoot, options }) => {
709
+ const result = await runValidateState({ target, packageRoot });
710
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateStateResult(result));
711
+ return result.ok ? 0 : 1;
712
+ },
713
+ "clear-state": async ({ target, options }) => {
714
+ const result = await runClearState({ target });
715
+ console.log(options.json ? JSON.stringify(result, null, 2) : formatClearStateResult(result));
716
+ return 0;
717
+ },
718
+ update: async ({ target, packageRoot, packageVersion, options }) => {
719
+ const result = await runUpdate({ target, dryRun: options.dryRun, packageRoot, packageVersion });
720
+ printActions(result.actions);
721
+ for (const conflict of result.conflicts) {
722
+ const code = conflict.code ? `${conflict.code}: ` : "";
723
+ console.log(`conflict: ${code}${conflict.path} - ${conflict.message}`);
724
+ }
725
+ return result.conflicts.length === 0 ? 0 : 1;
726
+ },
727
+ });
728
+
729
+ export const COMMAND_TABLE = Object.freeze(
730
+ COMMANDS.map((name) => Object.freeze({
731
+ name,
732
+ handler: COMMAND_HANDLERS[name],
733
+ usage: usage(name),
734
+ })),
735
+ );
736
+
408
737
  export async function main(argv = process.argv.slice(2)) {
409
738
  try {
410
739
  const { command, options } = parseArgs(argv);
@@ -421,192 +750,18 @@ export async function main(argv = process.argv.slice(2)) {
421
750
  const packageRoot = getPackageRoot();
422
751
  const version = await packageVersion(packageRoot);
423
752
 
424
- if (command === "init") {
425
- const result = await runInit({ target, dryRun: options.dryRun, packageRoot, packageVersion: version });
426
- printActions(result.actions);
427
- return 0;
428
- }
429
-
430
- if (command === "doctor") {
431
- const result = await runDoctor({
432
- target,
433
- packageRoot,
434
- adoptPaths: options.adopt,
435
- strict: options.strict,
436
- });
437
- if (options.json) {
438
- console.log(JSON.stringify(result, null, 2));
439
- } else {
440
- for (const item of result.findings) {
441
- console.log(`${item.severity}: ${item.code}: ${item.path} - ${item.message}`);
442
- }
443
- console.log(result.ok ? "healthy: ForgeLoop target is ready" : "unhealthy: ForgeLoop target needs attention");
444
- }
445
- return result.ok ? 0 : 1;
446
- }
447
-
448
- if (command === "route") {
449
- const result = await runRoute({
450
- target,
451
- packageRoot,
452
- workType: options.work,
453
- surfaces: options.surfaces,
454
- risks: options.risks,
455
- platforms: options.platforms,
456
- behaviorChange: options.behaviorChange,
457
- executableChange: options.executableChange,
458
- });
459
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRouteResult(result));
460
- return 0;
461
- }
462
-
463
- if (command === "activate") {
464
- const result = await runActivate({ target, packageRoot });
465
- console.log(options.json ? JSON.stringify(result, null, 2) : formatActivateResult(result));
466
- return 0;
467
- }
468
-
469
- if (command === "preflight") {
470
- const result = await runPreflight({ target, packageRoot, strict: options.strict });
471
- console.log(options.json ? JSON.stringify(result, null, 2) : formatPreflightResult(result));
472
- return result.status === "READY" ? 0 : 1;
473
- }
474
-
475
- if (command === "advance") {
476
- const result = await runAdvance({ target, packageRoot, to: options.to });
477
- console.log(options.json ? JSON.stringify(result, null, 2) : formatAdvanceResult(result));
478
- return 0;
479
- }
480
-
481
- if (command === "next") {
482
- const result = await runNext({ target, packageRoot });
483
- console.log(options.json ? JSON.stringify(result, null, 2) : formatNextActionResult(result));
484
- return 0;
753
+ const handler = COMMAND_HANDLERS[command];
754
+ if (typeof handler !== "function") {
755
+ throw new Error(`Unsupported command: ${command}`);
485
756
  }
486
-
487
- if (command === "prepare-completion") {
488
- const result = await runPrepareCompletion({ target, packageRoot });
489
- console.log(options.json ? JSON.stringify(result, null, 2) : formatPrepareCompletionResult(result));
490
- return 0;
491
- }
492
-
493
- if (command === "record-check") {
494
- const result = await runRecordCheck({
495
- target,
496
- packageRoot,
497
- id: options.checkId,
498
- kind: options.checkKind ?? "command",
499
- requirement: options.checkRequirement,
500
- status: options.checkStatus,
501
- evidenceKind: options.checkEvidenceKind,
502
- command: options.checkCommand ?? undefined,
503
- result: options.checkResult ?? undefined,
504
- exitCode: options.checkExitCode,
505
- details: options.checkDetails ?? undefined,
506
- });
507
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordCheckResult(result));
508
- return 0;
509
- }
510
-
511
- if (command === "record-terminal-result") {
512
- const result = await runRecordTerminalResult({
513
- target,
514
- packageRoot,
515
- requirement: options.checkRequirement,
516
- type: options.checkType,
517
- status: options.checkStatus,
518
- source: options.checkSource ?? options.checkCommand,
519
- result: options.checkResult,
520
- details: options.checkDetails ?? undefined,
521
- });
522
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordTerminalResult(result));
523
- return 0;
524
- }
525
-
526
- if (command === "complete") {
527
- const result = await runComplete({ target, packageRoot, strict: options.strict });
528
- console.log(options.json ? JSON.stringify(result, null, 2) : formatCompleteResult(result));
529
- return result.status === "VALID" ? 0 : 1;
530
- }
531
-
532
- if (command === "audit") {
533
- const result = await runAudit({ target, packageRoot, strict: options.strict });
534
- console.log(options.json ? JSON.stringify(result, null, 2) : formatAuditResult(result));
535
- return result.status === "VALID" ? 0 : 1;
536
- }
537
-
538
- if (command === "report") {
539
- const result = await runReport({ target, packageRoot, strict: options.strict });
540
- console.log(options.json ? JSON.stringify(result, null, 2) : formatReportResult(result));
541
- return result.verdict === "VALID" ? 0 : 1;
542
- }
543
-
544
- if (command === "policy") {
545
- const result = await runPolicy({ target, packageRoot, name: options.policy });
546
- console.log(options.json ? JSON.stringify(result, null, 2) : formatPolicyResult(result));
547
- return 0;
548
- }
549
-
550
- if (command === "bundle") {
551
- const result = await runBundle({ target, packageRoot, taskId: options.task });
552
- console.log(options.json ? JSON.stringify(result, null, 2) : formatBundleResult(result));
553
- return 0;
554
- }
555
-
556
- if (command === "inspect") {
557
- const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile });
558
- console.log(options.json ? JSON.stringify(result, null, 2) : formatInspectResult(result));
559
- return result.ok ? 0 : 1;
560
- }
561
-
562
- if (command === "validate-receipt") {
563
- const result = await runValidateReceipt({ target, packageRoot, file: options.file });
564
- console.log(options.json ? JSON.stringify(result, null, 2) : "valid: execution receipt");
565
- return 0;
566
- }
567
-
568
- if (command === "validate-protocol") {
569
- const result = await runValidateProtocol({
570
- target,
571
- packageRoot,
572
- stateFile: options.stateFile ?? ARTIFACT_PATHS.state,
573
- receiptFile: options.receiptFile ?? ARTIFACT_PATHS.receipt,
574
- routeFile: options.routeFile ?? ARTIFACT_PATHS.route,
575
- contractFile: options.contractFile,
576
- taskBriefFiles: options.taskBriefFiles,
577
- delegatedResultFiles: options.delegatedResultFiles,
578
- });
579
- console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateProtocolResult(result));
580
- return result.status === "VALID" ? 0 : 1;
581
- }
582
-
583
- if (command === "status") {
584
- const result = await runStatus({ target, packageRoot, contractFile: options.contractFile });
585
- console.log(options.json ? JSON.stringify(result, null, 2) : formatStatusResult(result));
586
- return 0;
587
- }
588
-
589
- if (command === "validate-state") {
590
- const result = await runValidateState({ target, packageRoot });
591
- console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateStateResult(result));
592
- return result.ok ? 0 : 1;
593
- }
594
-
595
- if (command === "clear-state") {
596
- const result = await runClearState({ target });
597
- console.log(options.json ? JSON.stringify(result, null, 2) : formatClearStateResult(result));
598
- return 0;
599
- }
600
-
601
- const result = await runUpdate({ target, dryRun: options.dryRun, packageRoot, packageVersion: version });
602
- printActions(result.actions);
603
- for (const conflict of result.conflicts) {
604
- const code = conflict.code ? `${conflict.code}: ` : "";
605
- console.log(`conflict: ${code}${conflict.path} - ${conflict.message}`);
606
- }
607
- return result.conflicts.length === 0 ? 0 : 1;
757
+ return await handler({
758
+ target,
759
+ packageRoot,
760
+ packageVersion: version,
761
+ options,
762
+ });
608
763
  } catch (error) {
609
- console.error(`error: ${error.message}`);
764
+ console.error(`error: ${error.code ? `${error.code}: ` : ""}${error.message}`);
610
765
  return 1;
611
766
  }
612
767
  }