@cassiomc1/forgeloop 0.1.15 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/DOCS_INDEX.md +60 -0
  2. package/EXECUTION_STATE.md +9 -0
  3. package/LOOP_ENGINEERING.md +15 -0
  4. package/LOOP_SYSTEM_DESIGN.md +8 -0
  5. package/PROTOCOL_INTEGRATION.md +8 -0
  6. package/QUALITY_SCORECARD.md +2 -0
  7. package/README.md +183 -654
  8. package/TERMINOLOGY.md +4 -0
  9. package/THREAT_MODEL.md +9 -0
  10. package/docs/assets/forgeloop-flow.svg +1 -0
  11. package/docs/forgeloop-flow.mmd +51 -0
  12. package/package.json +16 -3
  13. package/schemas/continuity.schema.json +57 -0
  14. package/scripts/CI_VALIDATORS.md +32 -0
  15. package/src/cli.js +307 -204
  16. package/src/commands/clear-continuity.js +9 -0
  17. package/src/commands/continuity.js +25 -0
  18. package/src/commands/doctor.js +17 -2
  19. package/src/commands/reconcile-continuity.js +23 -0
  20. package/src/commands/record-continuity.js +63 -0
  21. package/src/commands/status.js +14 -1
  22. package/src/commands/update.js +12 -13
  23. package/src/commands/validate-protocol.js +19 -0
  24. package/src/core/artifacts.js +1 -0
  25. package/src/core/bundles.js +6 -0
  26. package/src/core/command-resolution.js +295 -0
  27. package/src/core/command-tokenizer.js +122 -0
  28. package/src/core/conformance.js +8 -2
  29. package/src/core/continuity-cli-options.js +58 -0
  30. package/src/core/continuity-conformance.js +46 -0
  31. package/src/core/continuity-observability.js +20 -0
  32. package/src/core/continuity-reconciliation.js +224 -0
  33. package/src/core/continuity.js +245 -0
  34. package/src/core/inspect.js +9 -1
  35. package/src/core/installation-authority.js +178 -0
  36. package/src/core/json-safety.js +17 -13
  37. package/src/core/next-action-artifacts.js +118 -0
  38. package/src/core/next-action-continuity.js +65 -0
  39. package/src/core/next-action-model.js +127 -0
  40. package/src/core/next-action-phases.js +12 -0
  41. package/src/core/next-action.js +22 -249
  42. package/src/core/npm-classifier.js +343 -0
  43. package/src/core/package-manager-classifiers.js +37 -0
  44. package/src/core/preflight-consistency.js +221 -0
  45. package/src/core/preflight-loaders.js +112 -0
  46. package/src/core/preflight-model.js +83 -0
  47. package/src/core/preflight.js +34 -444
  48. package/src/core/protocol.js +7 -0
  49. package/src/core/schema-validation.js +15 -1
  50. package/src/core/templates.js +2 -0
  51. package/src/core/verification-capability.js +31 -1106
  52. package/src/core/verification-constants.js +61 -0
package/src/cli.js CHANGED
@@ -27,12 +27,51 @@ import { formatRecordCheckResult, runRecordCheck } from "./commands/record-check
27
27
  import { formatRunCheckResult, runCheck } from "./commands/run-check.js";
28
28
  import { formatRecordTerminalResult, runRecordTerminalResult } from "./commands/record-terminal-result.js";
29
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";
30
35
  import { resolveTarget } from "./core/filesystem.js";
31
36
  import { getPackageRoot } from "./core/templates.js";
32
37
  import { ARTIFACT_PATHS } from "./core/artifacts.js";
33
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
+
34
73
  function usage(command = null) {
35
- const commands = "init|doctor|update|activate|route|preflight|advance|next|prepare-completion|run-check|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("|");
36
75
  const options = [" --path <directory> target project directory (default: current directory)"];
37
76
  if (!command || command === "init" || command === "update") {
38
77
  options.push(" --dry-run show planned writes without changing files");
@@ -40,6 +79,7 @@ function usage(command = null) {
40
79
  if (!command || command === "doctor") {
41
80
  options.push(" --json emit doctor findings as JSON");
42
81
  options.push(" --strict treat warnings as unhealthy");
82
+ options.push(" --fix restore missing managed template files");
43
83
  options.push(" --adopt <path> preserve an existing adapter in the manifest");
44
84
  }
45
85
  if (!command || command === "route") {
@@ -54,7 +94,16 @@ function usage(command = null) {
54
94
  if (!command || command === "advance") {
55
95
  options.push(" --to <phase> destination workflow phase");
56
96
  }
57
- if (!command || ["activate", "advance", "next", "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"].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)) {
58
107
  options.push(" --json emit structured output as JSON");
59
108
  }
60
109
  if (!command || ["preflight", "complete", "audit", "report"].includes(command)) {
@@ -73,6 +122,7 @@ function usage(command = null) {
73
122
  options.push(" --route-file <path> routing-result JSON relative to target");
74
123
  options.push(" --state-file <path> work-state JSON relative to target");
75
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");
76
126
  options.push(" --task-brief-file <path> task brief JSON (repeatable)");
77
127
  options.push(" --delegated-result-file <path> delegated result JSON (repeatable)");
78
128
  }
@@ -117,6 +167,7 @@ export function parseArgs(argv) {
117
167
  dryRun: false,
118
168
  json: false,
119
169
  strict: false,
170
+ fix: false,
120
171
  adopt: [],
121
172
  work: null,
122
173
  surfaces: [],
@@ -130,6 +181,8 @@ export function parseArgs(argv) {
130
181
  routeFile: null,
131
182
  stateFile: null,
132
183
  receiptFile: null,
184
+ continuityFile: null,
185
+ ...continuityOptionDefaults(),
133
186
  taskBriefFiles: [],
134
187
  delegatedResultFiles: [],
135
188
  checkId: null,
@@ -155,7 +208,7 @@ export function parseArgs(argv) {
155
208
 
156
209
  for (let index = 0; index < argv.length; index += 1) {
157
210
  const argument = argv[index];
158
- if (["init", "doctor", "update", "activate", "route", "preflight", "advance", "next", "prepare-completion", "run-check", "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)) {
159
212
  if (command) throw new Error(`Multiple commands are not supported: ${argument}`);
160
213
  command = argument;
161
214
  } else if (argument === "--help" || argument === "-h") {
@@ -166,6 +219,8 @@ export function parseArgs(argv) {
166
219
  options.json = true;
167
220
  } else if (argument === "--strict") {
168
221
  options.strict = true;
222
+ } else if (argument === "--fix") {
223
+ options.fix = true;
169
224
  } else if (argument === "--adopt") {
170
225
  const relativePath = argv[index + 1];
171
226
  if (!relativePath || relativePath.startsWith("-")) throw new Error("--adopt requires a path");
@@ -215,12 +270,13 @@ export function parseArgs(argv) {
215
270
  if (!contractFile || contractFile.startsWith("-")) throw new Error("--contract-file requires a path");
216
271
  options.contractFile = contractFile;
217
272
  index += 1;
218
- } else if (["--route-file", "--state-file", "--receipt-file"].includes(argument)) {
273
+ } else if (["--route-file", "--state-file", "--receipt-file", "--continuity-file"].includes(argument)) {
219
274
  const file = argv[index + 1];
220
275
  if (!file || file.startsWith("-")) throw new Error(`${argument} requires a path`);
221
276
  if (argument === "--route-file") options.routeFile = file;
222
277
  if (argument === "--state-file") options.stateFile = file;
223
278
  if (argument === "--receipt-file") options.receiptFile = file;
279
+ if (argument === "--continuity-file") options.continuityFile = file;
224
280
  index += 1;
225
281
  } else if (argument === "--task-brief-file") {
226
282
  const file = argv[index + 1];
@@ -328,6 +384,17 @@ export function parseArgs(argv) {
328
384
  } else if (argument === "--" && command === "run-check") {
329
385
  options.commandArgv = argv.slice(index + 1);
330
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;
331
398
  } else if (argument === "--path") {
332
399
  options.path = argv[index + 1];
333
400
  if (!options.path || options.path.startsWith("-")) throw new Error("--path requires a directory");
@@ -348,7 +415,7 @@ export function parseArgs(argv) {
348
415
 
349
416
  if (!command) return { command: null, options };
350
417
 
351
- const jsonCommands = ["doctor", "route", "activate", "advance", "next", "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"];
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"];
352
419
  if (!jsonCommands.includes(command) && options.json) {
353
420
  throw new Error(`Option --json is not valid for ${command}`);
354
421
  }
@@ -364,6 +431,9 @@ export function parseArgs(argv) {
364
431
  if (command !== "doctor" && options.adopt.length > 0) {
365
432
  throw new Error(`Option --adopt is not valid for ${command}`);
366
433
  }
434
+ if (command !== "doctor" && options.fix) {
435
+ throw new Error(`Option --fix is not valid for ${command}`);
436
+ }
367
437
  if (command !== "route" && (options.work || options.surfaces.length || options.risks.length || options.platforms.length || options.behaviorChange || options.executableChange)) {
368
438
  throw new Error(`Route options are not valid for ${command}`);
369
439
  }
@@ -388,7 +458,7 @@ export function parseArgs(argv) {
388
458
  if (!["status", "inspect", "validate-protocol"].includes(command) && options.contractFile) {
389
459
  throw new Error(`Option --contract-file is not valid for ${command}`);
390
460
  }
391
- 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)) {
392
462
  throw new Error(`Protocol artifact options are not valid for ${command}`);
393
463
  }
394
464
  const checkOptions = [
@@ -407,6 +477,7 @@ export function parseArgs(argv) {
407
477
  if (!["record-check", "run-check"].includes(command) && checkOptions.some((value) => value !== null)) {
408
478
  throw new Error(`Check recording options are not valid for ${command}`);
409
479
  }
480
+ validateContinuityOptions(command, options);
410
481
  if (command === "record-check" && !options.help) {
411
482
  if (!options.checkId) throw new Error("record-check requires --id");
412
483
  if (!options.checkRequirement) throw new Error("record-check requires --requirement");
@@ -442,6 +513,227 @@ function printActions(actions) {
442
513
  }
443
514
  }
444
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
+
445
737
  export async function main(argv = process.argv.slice(2)) {
446
738
  try {
447
739
  const { command, options } = parseArgs(argv);
@@ -458,205 +750,16 @@ export async function main(argv = process.argv.slice(2)) {
458
750
  const packageRoot = getPackageRoot();
459
751
  const version = await packageVersion(packageRoot);
460
752
 
461
- if (command === "init") {
462
- const result = await runInit({ target, dryRun: options.dryRun, packageRoot, packageVersion: version });
463
- printActions(result.actions);
464
- return 0;
465
- }
466
-
467
- if (command === "doctor") {
468
- const result = await runDoctor({
469
- target,
470
- packageRoot,
471
- adoptPaths: options.adopt,
472
- strict: options.strict,
473
- });
474
- if (options.json) {
475
- console.log(JSON.stringify(result, null, 2));
476
- } else {
477
- for (const item of result.findings) {
478
- console.log(`${item.severity}: ${item.code}: ${item.path} - ${item.message}`);
479
- }
480
- console.log(result.ok ? "healthy: ForgeLoop target is ready" : "unhealthy: ForgeLoop target needs attention");
481
- }
482
- return result.ok ? 0 : 1;
483
- }
484
-
485
- if (command === "route") {
486
- const result = await runRoute({
487
- target,
488
- packageRoot,
489
- workType: options.work,
490
- surfaces: options.surfaces,
491
- risks: options.risks,
492
- platforms: options.platforms,
493
- behaviorChange: options.behaviorChange,
494
- executableChange: options.executableChange,
495
- });
496
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRouteResult(result));
497
- return 0;
498
- }
499
-
500
- if (command === "activate") {
501
- const result = await runActivate({ target, packageRoot });
502
- console.log(options.json ? JSON.stringify(result, null, 2) : formatActivateResult(result));
503
- return 0;
504
- }
505
-
506
- if (command === "preflight") {
507
- const result = await runPreflight({ target, packageRoot, strict: options.strict });
508
- console.log(options.json ? JSON.stringify(result, null, 2) : formatPreflightResult(result));
509
- return result.status === "READY" ? 0 : 1;
510
- }
511
-
512
- if (command === "advance") {
513
- const result = await runAdvance({ target, packageRoot, to: options.to });
514
- console.log(options.json ? JSON.stringify(result, null, 2) : formatAdvanceResult(result));
515
- return 0;
516
- }
517
-
518
- if (command === "next") {
519
- const result = await runNext({ target, packageRoot });
520
- console.log(options.json ? JSON.stringify(result, null, 2) : formatNextActionResult(result));
521
- return 0;
522
- }
523
-
524
- if (command === "prepare-completion") {
525
- const result = await runPrepareCompletion({ target, packageRoot });
526
- console.log(options.json ? JSON.stringify(result, null, 2) : formatPrepareCompletionResult(result));
527
- return 0;
528
- }
529
-
530
- if (command === "run-check") {
531
- const result = await runCheck({
532
- target,
533
- packageRoot,
534
- id: options.checkId,
535
- requirement: options.checkRequirement,
536
- argv: options.commandArgv,
537
- details: options.checkDetails ?? undefined,
538
- });
539
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRunCheckResult(result));
540
- return result.check.status === "passed" ? 0 : 1;
541
- }
542
-
543
- if (command === "record-check") {
544
- const result = await runRecordCheck({
545
- target,
546
- packageRoot,
547
- id: options.checkId,
548
- kind: options.checkKind ?? "command",
549
- requirement: options.checkRequirement,
550
- status: options.checkStatus,
551
- evidenceKind: options.checkEvidenceKind,
552
- command: options.checkCommand ?? undefined,
553
- result: options.checkResult ?? undefined,
554
- ...(options.checkExitCode === null ? {} : { exitCode: options.checkExitCode }),
555
- details: options.checkDetails ?? undefined,
556
- executionRef: options.checkExecutionRef ?? undefined,
557
- provenance: options.checkProvenance ?? undefined,
558
- });
559
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordCheckResult(result));
560
- return 0;
561
- }
562
-
563
- if (command === "record-terminal-result") {
564
- const result = await runRecordTerminalResult({
565
- target,
566
- packageRoot,
567
- requirement: options.checkRequirement,
568
- type: options.checkType,
569
- status: options.checkStatus,
570
- source: options.checkSource ?? options.checkCommand,
571
- result: options.checkResult,
572
- details: options.checkDetails ?? undefined,
573
- });
574
- console.log(options.json ? JSON.stringify(result, null, 2) : formatRecordTerminalResult(result));
575
- return 0;
576
- }
577
-
578
- if (command === "complete") {
579
- const result = await runComplete({ target, packageRoot, strict: options.strict });
580
- console.log(options.json ? JSON.stringify(result, null, 2) : formatCompleteResult(result));
581
- return result.status === "VALID" ? 0 : 1;
582
- }
583
-
584
- if (command === "audit") {
585
- const result = await runAudit({ target, packageRoot, strict: options.strict });
586
- console.log(options.json ? JSON.stringify(result, null, 2) : formatAuditResult(result));
587
- return result.status === "VALID" ? 0 : 1;
588
- }
589
-
590
- if (command === "report") {
591
- const result = await runReport({ target, packageRoot, strict: options.strict });
592
- console.log(options.json ? JSON.stringify(result, null, 2) : formatReportResult(result));
593
- return result.verdict === "VALID" ? 0 : 1;
594
- }
595
-
596
- if (command === "policy") {
597
- const result = await runPolicy({ target, packageRoot, name: options.policy });
598
- console.log(options.json ? JSON.stringify(result, null, 2) : formatPolicyResult(result));
599
- return 0;
600
- }
601
-
602
- if (command === "bundle") {
603
- const result = await runBundle({ target, packageRoot, taskId: options.task });
604
- console.log(options.json ? JSON.stringify(result, null, 2) : formatBundleResult(result));
605
- return 0;
753
+ const handler = COMMAND_HANDLERS[command];
754
+ if (typeof handler !== "function") {
755
+ throw new Error(`Unsupported command: ${command}`);
606
756
  }
607
-
608
- if (command === "inspect") {
609
- const result = await inspectTarget({ target, packageRoot, contractFile: options.contractFile });
610
- console.log(options.json ? JSON.stringify(result, null, 2) : formatInspectResult(result));
611
- return result.ok ? 0 : 1;
612
- }
613
-
614
- if (command === "validate-receipt") {
615
- const result = await runValidateReceipt({ target, packageRoot, file: options.file });
616
- console.log(options.json ? JSON.stringify(result, null, 2) : "valid: execution receipt");
617
- return 0;
618
- }
619
-
620
- if (command === "validate-protocol") {
621
- const result = await runValidateProtocol({
622
- target,
623
- packageRoot,
624
- stateFile: options.stateFile ?? ARTIFACT_PATHS.state,
625
- receiptFile: options.receiptFile ?? ARTIFACT_PATHS.receipt,
626
- routeFile: options.routeFile ?? ARTIFACT_PATHS.route,
627
- contractFile: options.contractFile,
628
- taskBriefFiles: options.taskBriefFiles,
629
- delegatedResultFiles: options.delegatedResultFiles,
630
- });
631
- console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateProtocolResult(result));
632
- return result.status === "VALID" ? 0 : 1;
633
- }
634
-
635
- if (command === "status") {
636
- const result = await runStatus({ target, packageRoot, contractFile: options.contractFile });
637
- console.log(options.json ? JSON.stringify(result, null, 2) : formatStatusResult(result));
638
- return 0;
639
- }
640
-
641
- if (command === "validate-state") {
642
- const result = await runValidateState({ target, packageRoot });
643
- console.log(options.json ? JSON.stringify(result, null, 2) : formatValidateStateResult(result));
644
- return result.ok ? 0 : 1;
645
- }
646
-
647
- if (command === "clear-state") {
648
- const result = await runClearState({ target });
649
- console.log(options.json ? JSON.stringify(result, null, 2) : formatClearStateResult(result));
650
- return 0;
651
- }
652
-
653
- const result = await runUpdate({ target, dryRun: options.dryRun, packageRoot, packageVersion: version });
654
- printActions(result.actions);
655
- for (const conflict of result.conflicts) {
656
- const code = conflict.code ? `${conflict.code}: ` : "";
657
- console.log(`conflict: ${code}${conflict.path} - ${conflict.message}`);
658
- }
659
- return result.conflicts.length === 0 ? 0 : 1;
757
+ return await handler({
758
+ target,
759
+ packageRoot,
760
+ packageVersion: version,
761
+ options,
762
+ });
660
763
  } catch (error) {
661
764
  console.error(`error: ${error.code ? `${error.code}: ` : ""}${error.message}`);
662
765
  return 1;
@@ -0,0 +1,9 @@
1
+ import { clearContinuity } from "../core/continuity.js";
2
+
3
+ export async function runClearContinuity({ target } = {}) {
4
+ return clearContinuity(target);
5
+ }
6
+
7
+ export function formatClearContinuityResult(result) {
8
+ return `${result.removed ? "cleared" : "absent"}: ${result.path}\n`;
9
+ }
@@ -0,0 +1,25 @@
1
+ import { reconcileContinuity } from "../core/continuity-reconciliation.js";
2
+
3
+ export async function runContinuity({ target, packageRoot } = {}) {
4
+ return reconcileContinuity({ target, packageRoot });
5
+ }
6
+
7
+ export function formatContinuityResult(result) {
8
+ const value = result.continuity;
9
+ const lines = [
10
+ "FORGELOOP EXECUTION CONTINUITY",
11
+ "",
12
+ `classification: ${result.classification}`,
13
+ `task: ${value?.taskId ?? "none"}`,
14
+ `phase: ${value?.phase ?? "none"}`,
15
+ `current focus: ${value?.currentFocus?.id ?? "none"}`,
16
+ `remaining work: ${value?.remainingWork?.length ?? 0}`,
17
+ `known issues: ${value?.knownIssues?.length ?? 0}`,
18
+ `inspect first: ${value?.inspectFirst?.join(", ") || "none"}`,
19
+ "",
20
+ "Authority: OPERATIONAL_CONTEXT_ONLY",
21
+ "Evidence: NONE",
22
+ ];
23
+ if (result.reasons?.length) lines.push(`Reasons: ${result.reasons.join(", ")}`);
24
+ return `${lines.join("\n")}\n`;
25
+ }