cool-workflow 0.1.80 → 0.1.81

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 (110) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/.codex-plugin/plugin.json +1 -1
  3. package/README.md +42 -2
  4. package/apps/architecture-review/app.json +1 -1
  5. package/apps/architecture-review-fast/app.json +1 -1
  6. package/apps/end-to-end-golden-path/app.json +1 -1
  7. package/apps/pr-review-fix-ci/app.json +1 -1
  8. package/apps/release-cut/app.json +1 -1
  9. package/apps/research-synthesis/app.json +1 -1
  10. package/dist/agent-config.js +21 -7
  11. package/dist/candidate-scoring.js +42 -22
  12. package/dist/capability-core.js +94 -17
  13. package/dist/capability-registry.js +138 -171
  14. package/dist/cli.js +90 -100
  15. package/dist/collaboration.js +5 -6
  16. package/dist/commit.js +20 -6
  17. package/dist/compare.js +18 -0
  18. package/dist/coordinator/classify.js +45 -0
  19. package/dist/coordinator/paths.js +42 -0
  20. package/dist/coordinator/util.js +129 -0
  21. package/dist/coordinator.js +127 -300
  22. package/dist/dispatch.js +35 -0
  23. package/dist/drive.js +7 -7
  24. package/dist/error-feedback.js +8 -4
  25. package/dist/evidence-reasoning.js +1 -1
  26. package/dist/execution-backend/agent.js +331 -0
  27. package/dist/execution-backend/probes.js +96 -0
  28. package/dist/execution-backend/util.js +47 -0
  29. package/dist/execution-backend.js +67 -420
  30. package/dist/mcp-server.js +34 -173
  31. package/dist/multi-agent/graph.js +84 -0
  32. package/dist/multi-agent/helpers.js +145 -0
  33. package/dist/multi-agent/paths.js +22 -0
  34. package/dist/multi-agent-eval/format.js +194 -0
  35. package/dist/multi-agent-eval/normalize.js +51 -0
  36. package/dist/multi-agent-eval.js +39 -244
  37. package/dist/multi-agent-host.js +0 -19
  38. package/dist/multi-agent.js +125 -314
  39. package/dist/node-snapshot.js +3 -3
  40. package/dist/observability/format.js +61 -0
  41. package/dist/observability/intake.js +98 -0
  42. package/dist/observability.js +14 -160
  43. package/dist/operator-ux/format.js +364 -0
  44. package/dist/operator-ux.js +22 -363
  45. package/dist/orchestrator/report.js +8 -0
  46. package/dist/orchestrator.js +25 -8
  47. package/dist/reclamation.js +26 -21
  48. package/dist/run-export.js +138 -14
  49. package/dist/run-registry/derive.js +172 -0
  50. package/dist/run-registry/format.js +124 -0
  51. package/dist/run-registry/gc.js +251 -0
  52. package/dist/run-registry/policy.js +16 -0
  53. package/dist/run-registry/queue.js +116 -0
  54. package/dist/run-registry.js +78 -593
  55. package/dist/run-state-schema.js +1 -0
  56. package/dist/sandbox-profile.js +43 -2
  57. package/dist/state-explosion/format.js +159 -0
  58. package/dist/state-explosion/helpers.js +82 -0
  59. package/dist/state-explosion.js +65 -283
  60. package/dist/state-node.js +19 -4
  61. package/dist/telemetry-attestation.js +55 -0
  62. package/dist/telemetry-demo.js +15 -3
  63. package/dist/telemetry-ledger.js +60 -15
  64. package/dist/topology.js +25 -8
  65. package/dist/triggers.js +33 -14
  66. package/dist/trust-audit.js +145 -33
  67. package/dist/version.js +1 -1
  68. package/dist/worker-isolation/helpers.js +51 -0
  69. package/dist/worker-isolation/paths.js +46 -0
  70. package/dist/worker-isolation.js +39 -115
  71. package/docs/agent-delegation-drive.7.md +13 -0
  72. package/docs/cli-mcp-parity.7.md +4 -0
  73. package/docs/contract-migration-tooling.7.md +2 -0
  74. package/docs/control-plane-scheduling.7.md +2 -0
  75. package/docs/dogfood/resume-drive-real-agent-2026-06-14.md +40 -0
  76. package/docs/durable-state-and-locking.7.md +4 -0
  77. package/docs/evidence-adoption-reasoning-chain.7.md +2 -0
  78. package/docs/execution-backends.7.md +2 -0
  79. package/docs/index.md +1 -0
  80. package/docs/launch/launch-kit.md +46 -23
  81. package/docs/launch/pre-launch-checklist.md +14 -14
  82. package/docs/multi-agent-cli-mcp-surface.7.md +4 -0
  83. package/docs/multi-agent-eval-replay-harness.7.md +2 -0
  84. package/docs/multi-agent-operator-ux.7.md +2 -0
  85. package/docs/multi-agent-trust-policy-audit.7.md +27 -0
  86. package/docs/node-snapshot-diff-replay.7.md +2 -0
  87. package/docs/observability-cost-accounting.7.md +2 -0
  88. package/docs/project-index.md +18 -5
  89. package/docs/real-execution-backends.7.md +2 -0
  90. package/docs/release-and-migration.7.md +4 -0
  91. package/docs/release-tooling.7.md +2 -0
  92. package/docs/run-registry-control-plane.7.md +54 -8
  93. package/docs/run-retention-reclamation.7.md +4 -0
  94. package/docs/state-explosion-management.7.md +2 -0
  95. package/docs/team-collaboration.7.md +2 -0
  96. package/docs/trust-model.md +267 -0
  97. package/docs/vendor-manifest-loadability.7.md +43 -0
  98. package/docs/web-desktop-workbench.7.md +2 -0
  99. package/manifest/plugin.manifest.json +1 -1
  100. package/package.json +4 -2
  101. package/scripts/agents/builtin-templates.json +7 -0
  102. package/scripts/bump-version.js +5 -11
  103. package/scripts/canonical-apps-list.js +64 -0
  104. package/scripts/canonical-apps.js +19 -4
  105. package/scripts/dogfood-release.js +1 -1
  106. package/scripts/golden-path.js +4 -4
  107. package/scripts/parity-check.js +5 -0
  108. package/scripts/release-check.js +5 -1
  109. package/scripts/version-sync-check.js +5 -8
  110. package/dist/capability-dispatcher.js +0 -86
@@ -34,51 +34,25 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
34
34
  return (mod && mod.__esModule) ? mod : { "default": mod };
35
35
  };
36
36
  Object.defineProperty(exports, "__esModule", { value: true });
37
- exports.RunRegistry = exports.DEFAULT_RUN_REGISTRY_POLICY = exports.RUN_REGISTRY_SCHEMA_VERSION = void 0;
37
+ exports.formatQueueList = exports.formatHistory = exports.formatResume = exports.formatGcVerify = exports.formatGcRun = exports.formatGcPlan = exports.formatRunShow = exports.formatRunSearch = exports.formatRegistryReport = exports.RunRegistry = exports.RUN_REGISTRY_SCHEMA_VERSION = exports.DEFAULT_RUN_REGISTRY_POLICY = exports.isRunLifecycleState = exports.compareQueue = void 0;
38
38
  exports.resolveCwHome = resolveCwHome;
39
39
  exports.deriveLifecycle = deriveLifecycle;
40
- exports.compareQueue = compareQueue;
41
- exports.isRunLifecycleState = isRunLifecycleState;
42
- exports.formatRegistryReport = formatRegistryReport;
43
- exports.formatRunSearch = formatRunSearch;
44
- exports.formatRunShow = formatRunShow;
45
- exports.formatGcPlan = formatGcPlan;
46
- exports.formatGcRun = formatGcRun;
47
- exports.formatGcVerify = formatGcVerify;
48
- exports.formatResume = formatResume;
49
- exports.formatHistory = formatHistory;
50
- exports.formatQueueList = formatQueueList;
51
40
  const node_crypto_1 = __importDefault(require("node:crypto"));
52
41
  const node_fs_1 = __importDefault(require("node:fs"));
53
42
  const node_os_1 = __importDefault(require("node:os"));
54
43
  const node_path_1 = __importDefault(require("node:path"));
55
44
  const state_1 = require("./state");
56
- const reclamation_1 = require("./reclamation");
57
- exports.RUN_REGISTRY_SCHEMA_VERSION = 1;
58
- const LIFECYCLE_STATES = [
59
- "queued",
60
- "running",
61
- "blocked",
62
- "completed",
63
- "failed",
64
- "archived",
65
- "reclaimed"
66
- ];
67
- // POLICY defaults. Configurable; never baked into the index. archiveOlderThanDays
68
- // = 0 disables retention archiving (explicit selection still works). The v0.1.39
69
- // reclamation knobs all default to RECLAIM NOTHING (back-compatible, opt-in).
70
- exports.DEFAULT_RUN_REGISTRY_POLICY = {
71
- schemaVersion: 1,
72
- archiveOlderThanDays: 0,
73
- archiveStates: ["completed", "failed"],
74
- defaultQueuePriority: 100,
75
- reclaimAfterArchiveDays: 0,
76
- reclaimStates: ["completed", "failed"],
77
- keepSnapshots: false,
78
- keepScratch: false,
79
- maxReclaimRuns: 0,
80
- maxReclaimBytes: 0
81
- };
45
+ // planReclamation/runReclamation/verifyReclamation/ReclamationError moved with the
46
+ // GC cluster into ./run-registry/gc (FreeBSD-audit R2 deep).
47
+ const compare_1 = require("./compare");
48
+ const derive_1 = require("./run-registry/derive");
49
+ Object.defineProperty(exports, "compareQueue", { enumerable: true, get: function () { return derive_1.compareQueue; } });
50
+ Object.defineProperty(exports, "isRunLifecycleState", { enumerable: true, get: function () { return derive_1.isRunLifecycleState; } });
51
+ const gc_1 = require("./run-registry/gc");
52
+ const queue_1 = require("./run-registry/queue");
53
+ const policy_1 = require("./run-registry/policy");
54
+ Object.defineProperty(exports, "DEFAULT_RUN_REGISTRY_POLICY", { enumerable: true, get: function () { return policy_1.DEFAULT_RUN_REGISTRY_POLICY; } });
55
+ Object.defineProperty(exports, "RUN_REGISTRY_SCHEMA_VERSION", { enumerable: true, get: function () { return policy_1.RUN_REGISTRY_SCHEMA_VERSION; } });
82
56
  // ---------------------------------------------------------------------------
83
57
  // Home registry location (EXPLICIT, INSPECTABLE STATE)
84
58
  // ---------------------------------------------------------------------------
@@ -109,16 +83,16 @@ function fingerprintRun(run) {
109
83
  `loopStage:${run.loopStage}`,
110
84
  `schema:${run.schemaVersion}`
111
85
  ];
112
- for (const task of [...run.tasks].sort((a, b) => a.id.localeCompare(b.id))) {
86
+ for (const task of [...run.tasks].sort((a, b) => (0, compare_1.compareBytes)(a.id, b.id))) {
113
87
  parts.push(`task:${task.id}:${task.status}`);
114
88
  }
115
- for (const commit of [...run.commits].sort((a, b) => a.id.localeCompare(b.id))) {
89
+ for (const commit of [...run.commits].sort((a, b) => (0, compare_1.compareBytes)(a.id, b.id))) {
116
90
  parts.push(`commit:${commit.id}:${commit.verifierGated ? "gated" : "checkpoint"}`);
117
91
  }
118
- for (const phase of [...run.phases].sort((a, b) => a.id.localeCompare(b.id))) {
92
+ for (const phase of [...run.phases].sort((a, b) => (0, compare_1.compareBytes)(a.id, b.id))) {
119
93
  parts.push(`phase:${phase.id}:${phase.status}`);
120
94
  }
121
- for (const fb of [...(run.feedback || [])].sort((a, b) => a.id.localeCompare(b.id))) {
95
+ for (const fb of [...(run.feedback || [])].sort((a, b) => (0, compare_1.compareBytes)(a.id, b.id))) {
122
96
  parts.push(`feedback:${fb.id}:${fb.status}`);
123
97
  }
124
98
  return fingerprintStrings(parts);
@@ -182,6 +156,8 @@ class RunRegistry {
182
156
  repoRegistryDir(repo) {
183
157
  return node_path_1.default.join(repo, ".cw", "registry");
184
158
  }
159
+ // Public so the carved queue cluster (run-registry/queue.ts) can resolve the
160
+ // home-registry dir without reaching into private state (QueueHost).
185
161
  homeRegistryDir() {
186
162
  return node_path_1.default.join(this.homeRoot, "registry");
187
163
  }
@@ -216,6 +192,11 @@ class RunRegistry {
216
192
  provenance: this.loadProvenanceOverlay(repo)
217
193
  };
218
194
  }
195
+ /** Default queue priority from POLICY (QueueHost). Exposed so the carved queue
196
+ * cluster never re-derives policy. */
197
+ get defaultQueuePriority() {
198
+ return policy_1.DEFAULT_RUN_REGISTRY_POLICY.defaultQueuePriority;
199
+ }
219
200
  // ---- home registry files ------------------------------------------------
220
201
  reposFilePath() {
221
202
  return node_path_1.default.join(this.homeRegistryDir(), "repos.json");
@@ -252,28 +233,17 @@ class RunRegistry {
252
233
  const already = current.repos.some((entry) => node_path_1.default.resolve(entry.root) === resolved);
253
234
  if (!already)
254
235
  current.repos.push({ root: resolved, addedAt: new Date().toISOString() });
255
- current.repos.sort((a, b) => a.root.localeCompare(b.root));
236
+ current.repos.sort((a, b) => (0, compare_1.compareBytes)(a.root, b.root));
256
237
  (0, state_1.writeJson)(file, current, { durable: true });
257
238
  return { registered: !already, repos: current.repos.map((entry) => entry.root) };
258
239
  });
259
240
  }
260
- queueFilePath() {
261
- return node_path_1.default.join(this.homeRegistryDir(), "queue.json");
262
- }
241
+ // Queue file helpers + queueAdd/List/Show/Drain now live in ./run-registry/queue
242
+ // (FreeBSD-audit R2 deep). These remain as thin delegators; `this` satisfies the
243
+ // QueueHost contract structurally (repoRoot, defaultQueuePriority,
244
+ // homeRegistryDir, registerRepo).
263
245
  loadQueue() {
264
- const file = this.queueFilePath();
265
- if (!node_fs_1.default.existsSync(file))
266
- return [];
267
- try {
268
- const parsed = (0, state_1.readJson)(file);
269
- return Array.isArray(parsed.entries) ? parsed.entries : [];
270
- }
271
- catch {
272
- return [];
273
- }
274
- }
275
- saveQueue(entries) {
276
- (0, state_1.writeJson)(this.queueFilePath(), { schemaVersion: 1, entries }, { durable: true });
246
+ return (0, queue_1.loadQueue)(this);
277
247
  }
278
248
  // Public queue accessors for the v0.1.37 control-plane scheduler (it operates ON
279
249
  // this queue store via pure functions in scheduling.ts; the queue file is never
@@ -283,7 +253,7 @@ class RunRegistry {
283
253
  return this.loadQueue();
284
254
  }
285
255
  saveQueueEntries(entries) {
286
- this.saveQueue(entries);
256
+ (0, queue_1.saveQueue)(this, entries);
287
257
  }
288
258
  schedulingPolicyPath() {
289
259
  return node_path_1.default.join(this.homeRegistryDir(), "scheduling-policy.json");
@@ -313,7 +283,7 @@ class RunRegistry {
313
283
  // Run Retention & Provable Reclamation (v0.1.39): the per-run reclaimed.json
314
284
  // overlay (if any) raises the disk-tier above `archived` and downgrades the
315
285
  // capability. Derived from source, never invented.
316
- const reclaim = loadReclaimedFromDir(runDir);
286
+ const reclaim = (0, derive_1.loadReclaimedFromDir)(runDir);
317
287
  const lastTombstone = reclaim.tombstones[reclaim.tombstones.length - 1];
318
288
  const tier = lastTombstone ? "reclaimed" : archive ? "archived" : "live";
319
289
  const capability = lastTombstone ? lastTombstone.capability : "re-runnable";
@@ -356,8 +326,8 @@ class RunRegistry {
356
326
  commitCount: (run.commits || []).length,
357
327
  verifierGatedCommitCount: li.verifierGatedCommits,
358
328
  openFeedbackCount: li.openFeedback,
359
- backends: distinctBackends(run),
360
- inputsDigest: digestInputs(run.inputs),
329
+ backends: (0, derive_1.distinctBackends)(run),
330
+ inputsDigest: (0, derive_1.digestInputs)(run.inputs),
361
331
  sourceFingerprint: fingerprintRun(run),
362
332
  freshness: "valid",
363
333
  provenance
@@ -379,7 +349,7 @@ class RunRegistry {
379
349
  if (record)
380
350
  records.push(record);
381
351
  }
382
- return records.sort(compareRecords);
352
+ return records.sort(derive_1.compareRecords);
383
353
  }
384
354
  // ---- index construction (current truth) ---------------------------------
385
355
  /** Build the CURRENT index fresh from source for the requested scope. This is
@@ -389,7 +359,7 @@ class RunRegistry {
389
359
  const records = [];
390
360
  for (const repo of repos)
391
361
  records.push(...this.scanRepo(repo));
392
- records.sort(compareRecords);
362
+ records.sort(derive_1.compareRecords);
393
363
  const queue = scope === "home" ? this.loadQueue() : this.loadQueue().filter((q) => node_path_1.default.resolve(q.repo) === this.repoRoot);
394
364
  const sourceFingerprint = fingerprintStrings([
395
365
  ...repos.map((r) => `repo:${r}`),
@@ -404,7 +374,7 @@ class RunRegistry {
404
374
  repos,
405
375
  records,
406
376
  queue,
407
- counts: countRecords(records)
377
+ counts: (0, derive_1.countRecords)(records)
408
378
  };
409
379
  }
410
380
  persistedIndexPath(scope) {
@@ -496,20 +466,20 @@ class RunRegistry {
496
466
  const index = this.buildIndex(scope);
497
467
  const report = this.report(scope, index);
498
468
  const query = {
499
- text: optionalLower(raw.text),
500
- app: optionalLower(raw.app),
469
+ text: (0, derive_1.optionalLower)(raw.text),
470
+ app: (0, derive_1.optionalLower)(raw.app),
501
471
  status: raw.status,
502
472
  repo: raw.repo ? node_path_1.default.resolve(raw.repo) : undefined,
503
473
  since: raw.since,
504
474
  until: raw.until,
505
475
  includeArchived: raw.includeArchived ?? true,
506
- offset: clampInt(raw.offset, 0, 0),
507
- limit: clampInt(raw.limit, 50, 1)
476
+ offset: (0, derive_1.clampInt)(raw.offset, 0, 0),
477
+ limit: (0, derive_1.clampInt)(raw.limit, 50, 1)
508
478
  };
509
- let records = index.records.filter((record) => matchesQuery(record, query));
479
+ let records = index.records.filter((record) => (0, derive_1.matchesQuery)(record, query));
510
480
  if (!query.includeArchived)
511
481
  records = records.filter((record) => !record.archived);
512
- records.sort(compareRecords);
482
+ records.sort(derive_1.compareRecords);
513
483
  const total = records.length;
514
484
  const page = records.slice(query.offset, query.offset + query.limit);
515
485
  return {
@@ -568,6 +538,8 @@ class RunRegistry {
568
538
  nextAction: "node scripts/cw.js registry refresh" + (scope === "home" ? " --scope home" : "")
569
539
  };
570
540
  }
541
+ // Public so the carved gc cluster (run-registry/gc.ts) can resolve a run
542
+ // repo-first without reaching into private state (GcHost).
571
543
  locate(runId, scope) {
572
544
  // Current repo first (least astonishment: cwd wins).
573
545
  const here = this.deriveRecordForRun(this.repoRoot, runId);
@@ -599,6 +571,8 @@ class RunRegistry {
599
571
  }
600
572
  return undefined;
601
573
  }
574
+ // Public so the carved gc cluster (run-registry/gc.ts) can load source state
575
+ // for a resolved run without reaching into private state (GcHost).
602
576
  loadRun(repo, runId) {
603
577
  const statePath = node_path_1.default.join(this.repoRunsDir(repo), runId, "state.json");
604
578
  if (!node_fs_1.default.existsSync(statePath))
@@ -618,7 +592,7 @@ class RunRegistry {
618
592
  }
619
593
  const record = located.record;
620
594
  const run = this.loadRun(record.repo, runId);
621
- const limit = clampInt(options.limit, 5, 1);
595
+ const limit = (0, derive_1.clampInt)(options.limit, 5, 1);
622
596
  const nextTasks = (run.tasks || [])
623
597
  .filter((t) => t.status === "pending" || t.status === "running")
624
598
  .slice(0, limit)
@@ -699,7 +673,7 @@ class RunRegistry {
699
673
  /** Apply a retention POLICY: archive eligible runs older than the window. The
700
674
  * window/states are policy inputs, never baked into the index. Returns the set
701
675
  * archived; archives are overlay marks, so nothing is destroyed. */
702
- archiveByPolicy(policy = exports.DEFAULT_RUN_REGISTRY_POLICY, options = {}) {
676
+ archiveByPolicy(policy = policy_1.DEFAULT_RUN_REGISTRY_POLICY, options = {}) {
703
677
  const scope = options.scope || "home";
704
678
  if (!policy.archiveOlderThanDays || policy.archiveOlderThanDays <= 0) {
705
679
  return { policy, archived: [], eligible: 0 };
@@ -720,227 +694,29 @@ class RunRegistry {
720
694
  // dry-run (frees nothing); `gc run` executes the write-ahead reclamation
721
695
  // transaction (skeleton → tombstone → fsync → free); `gc verify` re-proves a
722
696
  // reclaimed run independently. Eligibility is explicit and fail-closed.
697
+ // Implementations live in ./run-registry/gc (FreeBSD-audit R2 deep); these are
698
+ // thin delegators preserving the public surface. `this` satisfies GcHost
699
+ // (buildIndex, locate, loadRun).
723
700
  /** Resolve the effective reclamation policy (defaults reclaim NOTHING). */
724
701
  reclamationPolicy(overrides = {}) {
725
- return { ...exports.DEFAULT_RUN_REGISTRY_POLICY, ...overrides };
726
- }
727
- /** Fail-closed eligibility: terminal AND archived AND no open feedback AND past
728
- * retention. Returns the matching refusal code, or null when eligible. Reads
729
- * the live-source-derived record; order yields distinct, stable codes. */
730
- reclaimEligibility(record, policy, nowMs) {
731
- if (record.tier === "reclaimed")
732
- return "already-reclaimed";
733
- const terminalStates = policy.reclaimStates && policy.reclaimStates.length ? policy.reclaimStates : ["completed", "failed"];
734
- if (record.derivedLifecycle !== "completed" && record.derivedLifecycle !== "failed")
735
- return "non-terminal";
736
- if (!terminalStates.includes(record.derivedLifecycle))
737
- return "non-terminal";
738
- if (record.openFeedbackCount > 0)
739
- return "open-feedback";
740
- if (!record.archived)
741
- return "not-archived";
742
- const days = policy.reclaimAfterArchiveDays ?? 0;
743
- if (days > 0) {
744
- const archivedAtMs = record.archivedAt ? Date.parse(record.archivedAt) : NaN;
745
- if (!Number.isFinite(archivedAtMs))
746
- return "within-retention";
747
- if (archivedAtMs > nowMs - days * 24 * 60 * 60 * 1000)
748
- return "within-retention";
749
- }
750
- return null;
751
- }
752
- /** Resolve a single run to a one-element record list via locate() (repo-first),
753
- * avoiding a full-registry scan for single-run gc plan/run. */
754
- recordsForRunId(runId, scope) {
755
- const located = this.locate(runId, scope);
756
- return located ? [located.record] : [];
702
+ return (0, gc_1.reclamationPolicy)(overrides);
757
703
  }
758
704
  /** Dry-run: compute eligible runs, per-kind bytes that WOULD be freed, and the
759
705
  * capability downgrade. Frees NOTHING. */
760
706
  gcPlan(options = {}) {
761
- const scope = options.scope || "home";
762
- const policy = this.reclamationPolicy(options.policy);
763
- const nowIso = options.now || new Date().toISOString();
764
- const nowMs = Date.parse(nowIso);
765
- // Fast, deterministic single-run path: resolve just that run via locate()
766
- // (repo-first) so a home-scope plan never re-scans the whole registry.
767
- const records = options.runId ? this.recordsForRunId(options.runId, scope) : this.buildIndex(scope).records;
768
- const entries = [];
769
- let bytesToFree = 0;
770
- let eligibleCount = 0;
771
- for (const record of records) {
772
- const refusal = this.reclaimEligibility(record, policy, nowMs);
773
- let plan;
774
- try {
775
- const run = this.loadRun(record.repo, record.runId);
776
- plan = (0, reclamation_1.planReclamation)(run, { keepScratch: policy.keepScratch, keepSnapshots: policy.keepSnapshots });
777
- }
778
- catch {
779
- entries.push({
780
- runId: record.runId,
781
- repo: record.repo,
782
- eligible: false,
783
- reason: "unreadable",
784
- tier: record.tier || "live",
785
- capability: record.capability || "re-runnable",
786
- capabilityReason: record.capabilityReason || "live-full",
787
- bytesToFree: 0,
788
- byKind: {},
789
- freeable: []
790
- });
791
- continue;
792
- }
793
- const eligible = refusal === null;
794
- const entry = {
795
- runId: record.runId,
796
- repo: record.repo,
797
- eligible,
798
- reason: eligible ? "eligible" : refusal,
799
- tier: record.tier || "live",
800
- capability: plan.capability,
801
- capabilityReason: plan.capabilityReason,
802
- bytesToFree: eligible ? plan.bytesToFree : 0,
803
- byKind: eligible ? plan.byKind : {},
804
- freeable: eligible ? plan.freeable.map((f) => ({ path: f.path, kind: f.kind, bytes: f.bytes })) : []
805
- };
806
- entries.push(entry);
807
- if (eligible) {
808
- eligibleCount += 1;
809
- bytesToFree += plan.bytesToFree;
810
- }
811
- }
812
- return {
813
- schemaVersion: 1,
814
- scope,
815
- generatedAt: nowIso,
816
- policy: {
817
- reclaimAfterArchiveDays: policy.reclaimAfterArchiveDays ?? 0,
818
- keepSnapshots: Boolean(policy.keepSnapshots),
819
- keepScratch: Boolean(policy.keepScratch),
820
- reclaimStates: policy.reclaimStates && policy.reclaimStates.length ? policy.reclaimStates : ["completed", "failed"]
821
- },
822
- total: entries.length,
823
- eligibleCount,
824
- bytesToFree,
825
- entries,
826
- nextAction: eligibleCount ? "node scripts/cw.js gc run" : "node scripts/cw.js run search"
827
- };
707
+ return (0, gc_1.gcPlan)(this, options);
828
708
  }
829
709
  /** Execute the write-ahead reclamation transaction for eligible runs. Bounded
830
710
  * (`maxReclaimRuns` / `maxReclaimBytes`), fail-closed on any incomplete
831
711
  * skeleton. Produces a tombstone and frees the bulk. */
832
712
  gcRun(options = {}) {
833
- const scope = options.scope || "home";
834
- const policy = this.reclamationPolicy(options.policy);
835
- const nowIso = options.now || new Date().toISOString();
836
- const nowMs = Date.parse(nowIso);
837
- const records = options.runId ? this.recordsForRunId(options.runId, scope) : this.buildIndex(scope).records;
838
- const maxRuns = options.limit ?? (policy.maxReclaimRuns || 0);
839
- const maxBytes = policy.maxReclaimBytes || 0;
840
- const reclaimed = [];
841
- const refused = [];
842
- let totalBytesFreed = 0;
843
- for (const record of records) {
844
- const refusal = this.reclaimEligibility(record, policy, nowMs);
845
- if (refusal) {
846
- refused.push({ runId: record.runId, code: refusal });
847
- continue;
848
- }
849
- if (maxRuns > 0 && reclaimed.length >= maxRuns)
850
- break;
851
- let run;
852
- try {
853
- run = this.loadRun(record.repo, record.runId);
854
- }
855
- catch {
856
- refused.push({ runId: record.runId, code: "unreadable" });
857
- continue;
858
- }
859
- try {
860
- const result = (0, reclamation_1.runReclamation)(run, {
861
- now: nowIso,
862
- actor: options.actor,
863
- policy: { reclaimAfterArchiveDays: policy.reclaimAfterArchiveDays, keepScratch: policy.keepScratch, keepSnapshots: policy.keepSnapshots },
864
- reclaimPolicy: { keepScratch: policy.keepScratch, keepSnapshots: policy.keepSnapshots }
865
- });
866
- // No post-free saveCheckpoint: runReclamation now DURABLY persists the
867
- // result-node re-point inside the transaction (before any byte is freed),
868
- // so state.json can never reference a freed path even on a crash here.
869
- reclaimed.push({
870
- runId: record.runId,
871
- bytesFreed: result.bytesFreed,
872
- tombstoneHash: result.tombstone.tombstoneHash,
873
- capability: result.tombstone.capability,
874
- capabilityReason: result.tombstone.capabilityReason
875
- });
876
- totalBytesFreed += result.bytesFreed;
877
- if (maxBytes > 0 && totalBytesFreed >= maxBytes)
878
- break;
879
- }
880
- catch (error) {
881
- if (error instanceof reclamation_1.ReclamationError)
882
- refused.push({ runId: record.runId, code: error.code });
883
- else
884
- throw error;
885
- }
886
- }
887
- return {
888
- schemaVersion: 1,
889
- scope,
890
- generatedAt: nowIso,
891
- dryRun: false,
892
- reclaimed,
893
- refused,
894
- totalBytesFreed,
895
- nextAction: reclaimed.length ? "node scripts/cw.js gc verify <run-id>" : "node scripts/cw.js gc plan"
896
- };
713
+ return (0, gc_1.gcRun)(this, options);
897
714
  }
898
715
  /** Re-prove a reclaimed run: skeleton schema-complete, tombstone chain
899
716
  * recomputed-and-untampered, each reconstructable artifact re-derived from its
900
717
  * RETAINED inputs to its expectDigest, and eligible-when-reclaimed. */
901
718
  gcVerify(runId, options = {}) {
902
- const scope = options.scope || "home";
903
- const located = this.locate(runId, scope);
904
- if (!located) {
905
- return {
906
- schemaVersion: 1,
907
- runId,
908
- reclaimed: false,
909
- verified: false,
910
- tier: "live",
911
- capability: "re-runnable",
912
- chainLength: 0,
913
- checks: [{ name: "located", pass: false, code: "not-reclaimed", detail: "run source not found" }],
914
- nextAction: "node scripts/cw.js registry refresh" + (scope === "home" ? " --scope home" : "")
915
- };
916
- }
917
- const run = this.loadRun(located.record.repo, runId);
918
- const result = (0, reclamation_1.verifyReclamation)(run);
919
- const checks = result.checks.map((c) => ({ name: c.name, pass: c.pass, code: c.code, detail: c.detail }));
920
- // Eligible-when-reclaimed: each tombstone must have sealed a terminal verdict.
921
- let eligibleWhenReclaimed = result.reclaimed;
922
- for (const tombstone of result.tombstones) {
923
- const terminal = tombstone.skeleton.finalVerdict?.terminal === true;
924
- if (!terminal) {
925
- eligibleWhenReclaimed = false;
926
- checks.push({ name: `eligible-when-reclaimed:${tombstone.tombstoneId}`, pass: false, code: "ineligible-when-reclaimed", detail: "non-terminal verdict sealed" });
927
- }
928
- }
929
- const last = result.tombstones[result.tombstones.length - 1];
930
- const verified = result.verified && eligibleWhenReclaimed;
931
- return {
932
- schemaVersion: 1,
933
- runId,
934
- reclaimed: result.reclaimed,
935
- verified,
936
- tier: located.record.tier || (result.reclaimed ? "reclaimed" : "live"),
937
- capability: located.record.capability || "re-runnable",
938
- capabilityReason: located.record.capabilityReason,
939
- tombstoneHash: last?.tombstoneHash,
940
- chainLength: result.tombstones.length,
941
- checks,
942
- nextAction: verified ? "node scripts/cw.js run show " + runId : "node scripts/cw.js gc plan"
943
- };
719
+ return (0, gc_1.gcVerify)(this, runId, options);
944
720
  }
945
721
  // ---- rerun (NEW run linked to the original; original preserved) ---------
946
722
  rerun(runId, options = {}) {
@@ -992,88 +768,34 @@ class RunRegistry {
992
768
  };
993
769
  }
994
770
  // ---- queue (durable, ordered; drained by the host) ----------------------
771
+ // Implementations live in ./run-registry/queue (FreeBSD-audit R2 deep); these
772
+ // are thin delegators preserving the public surface. `this` satisfies QueueHost.
995
773
  queueAdd(options = {}) {
996
- const repo = options.repo ? node_path_1.default.resolve(options.repo) : this.repoRoot;
997
- // Cross-process read-modify-write on the home queue: lock so a concurrently
998
- // added task can never vanish (v0.1.40, P1-D).
999
- return (0, state_1.withFileLock)(this.queueFilePath(), () => {
1000
- const entries = this.loadQueue();
1001
- const entry = {
1002
- schemaVersion: 1,
1003
- id: options.id || queueId(),
1004
- runId: options.runId,
1005
- appId: options.appId,
1006
- workflowId: options.workflowId,
1007
- repo,
1008
- priority: Number.isFinite(options.priority) ? Number(options.priority) : exports.DEFAULT_RUN_REGISTRY_POLICY.defaultQueuePriority,
1009
- enqueuedAt: new Date().toISOString(),
1010
- status: "pending",
1011
- inputs: options.inputs,
1012
- note: options.note
1013
- };
1014
- entries.push(entry);
1015
- this.registerRepo(repo);
1016
- this.saveQueue(entries);
1017
- return entry;
1018
- });
774
+ return (0, queue_1.queueAdd)(this, options);
1019
775
  }
1020
776
  queueList(options = {}) {
1021
- let entries = this.loadQueue();
1022
- if (options.status)
1023
- entries = entries.filter((e) => e.status === options.status);
1024
- if (options.repo) {
1025
- const repo = node_path_1.default.resolve(options.repo);
1026
- entries = entries.filter((e) => node_path_1.default.resolve(e.repo) === repo);
1027
- }
1028
- entries = [...entries].sort(compareQueue);
1029
- return { schemaVersion: 1, total: entries.length, entries };
777
+ return (0, queue_1.queueList)(this, options);
1030
778
  }
1031
779
  queueShow(id) {
1032
- const entry = this.loadQueue().find((e) => e.id === id);
1033
- if (!entry)
1034
- throw new Error(`Queue entry not found: ${id}`);
1035
- return entry;
780
+ return (0, queue_1.queueShow)(this, id);
1036
781
  }
1037
- /** Drain the next N ready/pending entries in policy order, marking them drained.
1038
- * CW records readiness/order; the HOST still executes the workers. */
1039
782
  queueDrain(options = {}) {
1040
- const limit = clampInt(options.limit, 1, 1);
1041
- const repoFilter = options.repo ? node_path_1.default.resolve(options.repo) : undefined;
1042
- // Lock the drain RMW so two hosts can never double-drain the same entry
1043
- // (v0.1.40, P1-D — the scheduling kernel's concurrency ceiling now holds
1044
- // across processes, not just within one).
1045
- return (0, state_1.withFileLock)(this.queueFilePath(), () => {
1046
- const entries = this.loadQueue();
1047
- const drainable = entries
1048
- .filter((e) => e.status === "pending" || e.status === "ready")
1049
- .filter((e) => !repoFilter || node_path_1.default.resolve(e.repo) === repoFilter)
1050
- .sort(compareQueue);
1051
- const drained = [];
1052
- const drainedAt = new Date().toISOString();
1053
- for (const entry of drainable.slice(0, limit)) {
1054
- entry.status = "drained";
1055
- entry.drainedAt = drainedAt;
1056
- drained.push(entry);
1057
- }
1058
- this.saveQueue(entries);
1059
- const remaining = entries.filter((e) => e.status === "pending" || e.status === "ready").length;
1060
- return { schemaVersion: 1, drained, remaining };
1061
- });
783
+ return (0, queue_1.queueDrain)(this, options);
1062
784
  }
1063
785
  // ---- cross-repo history (unified timeline) ------------------------------
1064
786
  history(options = {}) {
1065
787
  const scope = options.scope || "home";
1066
788
  const index = this.buildIndex(scope);
1067
789
  const report = this.report(scope, index);
1068
- const app = optionalLower(options.app);
1069
- const limit = clampInt(options.limit, 50, 1);
1070
- const offset = clampInt(options.offset, 0, 0);
790
+ const app = (0, derive_1.optionalLower)(options.app);
791
+ const limit = (0, derive_1.clampInt)(options.limit, 50, 1);
792
+ const offset = (0, derive_1.clampInt)(options.offset, 0, 0);
1071
793
  let records = index.records;
1072
794
  if (app)
1073
795
  records = records.filter((r) => (r.appId || r.workflowId || "").toLowerCase().includes(app));
1074
796
  if (options.status)
1075
797
  records = records.filter((r) => r.lifecycle === options.status || r.derivedLifecycle === options.status);
1076
- const ordered = [...records].sort(compareHistory);
798
+ const ordered = [...records].sort(derive_1.compareHistory);
1077
799
  const total = ordered.length;
1078
800
  const page = ordered.slice(offset, offset + limit);
1079
801
  const entries = page.map((r) => ({
@@ -1102,253 +824,16 @@ class RunRegistry {
1102
824
  }
1103
825
  }
1104
826
  exports.RunRegistry = RunRegistry;
1105
- // ---------------------------------------------------------------------------
1106
- // pure helpers
1107
- // ---------------------------------------------------------------------------
1108
- function compareRecords(a, b) {
1109
- if (a.createdAt !== b.createdAt)
1110
- return a.createdAt < b.createdAt ? -1 : 1;
1111
- return a.runId.localeCompare(b.runId);
1112
- }
1113
- function compareHistory(a, b) {
1114
- // Newest first.
1115
- if (a.createdAt !== b.createdAt)
1116
- return a.createdAt < b.createdAt ? 1 : -1;
1117
- return a.runId.localeCompare(b.runId);
1118
- }
1119
- function compareQueue(a, b) {
1120
- if (a.priority !== b.priority)
1121
- return a.priority - b.priority;
1122
- if (a.enqueuedAt !== b.enqueuedAt)
1123
- return a.enqueuedAt < b.enqueuedAt ? -1 : 1;
1124
- return a.id.localeCompare(b.id);
1125
- }
1126
- function matchesQuery(record, query) {
1127
- if (query.app && !(record.appId || record.workflowId || "").toLowerCase().includes(query.app))
1128
- return false;
1129
- if (query.status && record.lifecycle !== query.status && record.derivedLifecycle !== query.status)
1130
- return false;
1131
- if (query.repo && node_path_1.default.resolve(record.repo) !== query.repo)
1132
- return false;
1133
- if (query.since && record.createdAt < query.since)
1134
- return false;
1135
- if (query.until && record.createdAt > query.until)
1136
- return false;
1137
- if (query.text) {
1138
- const haystack = [
1139
- record.runId,
1140
- record.appId,
1141
- record.workflowId,
1142
- record.title,
1143
- record.repo,
1144
- record.lifecycle,
1145
- record.loopStage,
1146
- record.inputsDigest
1147
- ]
1148
- .filter(Boolean)
1149
- .join(" ")
1150
- .toLowerCase();
1151
- if (!haystack.includes(query.text))
1152
- return false;
1153
- }
1154
- return true;
1155
- }
1156
- /** Bounded, deterministic stringification of run inputs for free-text search.
1157
- * Descriptive intent keys (question, prompt, ...) come first so they survive
1158
- * truncation; the rest follow alphabetically. Deterministic and compact. */
1159
- const DIGEST_PRIORITY_KEYS = ["question", "prompt", "task", "summary", "title", "objective", "focus", "topic"];
1160
- /** Distinct execution backends used by a run's dispatches/tasks, recomputed from
1161
- * source state. Sorted; empty for pre-v0.1.29 / default-only runs that never
1162
- * recorded a backend. The registry stays backend-agnostic — this is metadata. */
1163
- function distinctBackends(run) {
1164
- const backends = new Set();
1165
- for (const dispatch of run.dispatches || []) {
1166
- if (dispatch.backendId)
1167
- backends.add(dispatch.backendId);
1168
- }
1169
- for (const task of run.tasks || []) {
1170
- if (task.backendId)
1171
- backends.add(task.backendId);
1172
- }
1173
- return [...backends].sort();
1174
- }
1175
- function digestInputs(inputs) {
1176
- if (!inputs || typeof inputs !== "object")
1177
- return undefined;
1178
- const keys = Object.keys(inputs);
1179
- const ordered = [
1180
- ...DIGEST_PRIORITY_KEYS.filter((k) => keys.includes(k)),
1181
- ...keys.filter((k) => !DIGEST_PRIORITY_KEYS.includes(k)).sort()
1182
- ];
1183
- const parts = [];
1184
- for (const key of ordered) {
1185
- const value = inputs[key];
1186
- if (value === undefined || value === null)
1187
- continue;
1188
- const rendered = Array.isArray(value) ? value.join(",") : typeof value === "object" ? JSON.stringify(value) : String(value);
1189
- parts.push(`${key}=${rendered}`);
1190
- }
1191
- const joined = parts.join(" ").replace(/\s+/g, " ").trim();
1192
- return joined.length > 360 ? `${joined.slice(0, 357)}...` : joined;
1193
- }
1194
- function countRecords(records) {
1195
- const counts = {
1196
- total: records.length,
1197
- queued: 0,
1198
- running: 0,
1199
- blocked: 0,
1200
- completed: 0,
1201
- failed: 0,
1202
- archived: 0,
1203
- reclaimed: 0
1204
- };
1205
- for (const record of records) {
1206
- counts[record.lifecycle] = (counts[record.lifecycle] || 0) + 1;
1207
- }
1208
- return counts;
1209
- }
1210
- function optionalLower(value) {
1211
- if (value === undefined || value === null || value === "")
1212
- return undefined;
1213
- return String(value).toLowerCase();
1214
- }
1215
- function clampInt(value, fallback, min) {
1216
- const n = Number(value);
1217
- if (!Number.isFinite(n))
1218
- return fallback;
1219
- return Math.max(min, Math.floor(n));
1220
- }
1221
- let queueCounter = 0;
1222
- function queueId() {
1223
- queueCounter += 1;
1224
- const stamp = new Date().toISOString().replace(/[-:.TZ]/g, "").slice(0, 14);
1225
- return `q-${stamp}-${String(queueCounter).padStart(3, "0")}`;
1226
- }
1227
- function isRunLifecycleState(value) {
1228
- return typeof value === "string" && LIFECYCLE_STATES.includes(value);
1229
- }
1230
- /** Read a run dir's `reclaimed.json` overlay (v0.1.39). Fail-closed to an empty
1231
- * chain on absence/corruption — a malformed overlay must never brick the run. */
1232
- function loadReclaimedFromDir(runDir) {
1233
- const file = node_path_1.default.join(runDir, "reclaimed.json");
1234
- if (!node_fs_1.default.existsSync(file))
1235
- return { schemaVersion: 1, runId: "", tombstones: [] };
1236
- try {
1237
- const parsed = JSON.parse(node_fs_1.default.readFileSync(file, "utf8"));
1238
- return { schemaVersion: 1, runId: parsed.runId || "", tombstones: Array.isArray(parsed.tombstones) ? parsed.tombstones : [] };
1239
- }
1240
- catch {
1241
- return { schemaVersion: 1, runId: "", tombstones: [] };
1242
- }
1243
- }
1244
- // ---------------------------------------------------------------------------
1245
- // Human formatting (CLI-only; never affects --json / MCP payloads)
1246
- // ---------------------------------------------------------------------------
1247
- function countsLine(counts) {
1248
- return `total=${counts.total} queued=${counts.queued} running=${counts.running} blocked=${counts.blocked} completed=${counts.completed} failed=${counts.failed} archived=${counts.archived} reclaimed=${counts.reclaimed}`;
1249
- }
1250
- function recordLine(record) {
1251
- const flags = [record.archived ? "archived" : "", record.provenance?.rerunOf ? `rerunOf=${record.provenance.rerunOf}` : ""].filter(Boolean).join(" ");
1252
- return ` [${record.lifecycle}] ${record.runId} (${record.appId || record.workflowId}) ${record.loopStage}${flags ? ` {${flags}}` : ""}`;
1253
- }
1254
- function formatRegistryReport(report) {
1255
- const lines = [];
1256
- lines.push(`Run Registry (${report.scope}): ${report.root}`);
1257
- lines.push(`Freshness: ${report.freshness.status}${report.freshness.staleRuns.length ? ` (stale: ${report.freshness.staleRuns.join(", ")})` : ""}${report.freshness.missingRuns.length ? ` (missing: ${report.freshness.missingRuns.join(", ")})` : ""}`);
1258
- lines.push(`Repos: ${report.index.repos.length}`);
1259
- lines.push(countsLine(report.counts));
1260
- if (report.freshness.status !== "valid")
1261
- lines.push(`Next Action: ${report.nextAction}`);
1262
- return lines.join("\n");
1263
- }
1264
- function formatRunSearch(result) {
1265
- const lines = [];
1266
- lines.push(`Run Search (${result.scope}): ${result.total} match(es), showing ${result.records.length} [offset ${result.offset}] freshness=${result.freshness}`);
1267
- for (const record of result.records)
1268
- lines.push(recordLine(record));
1269
- if (!result.records.length)
1270
- lines.push(" (no matching runs)");
1271
- return lines.join("\n");
1272
- }
1273
- function formatRunShow(result) {
1274
- if (!result.found) {
1275
- return `Run ${result.runId}: MISSING (source state.json absent — fail closed). Next: ${result.nextAction}`;
1276
- }
1277
- const r = result.record;
1278
- const lines = [
1279
- `Run ${r.runId} [${r.lifecycle}] (derived: ${r.derivedLifecycle})`,
1280
- ` app=${r.appId || r.workflowId} loopStage=${r.loopStage} repo=${r.repo}`,
1281
- ` tasks: total=${r.tasks.total} pending=${r.tasks.pending} running=${r.tasks.running} failed=${r.tasks.failed} completed=${r.tasks.completed}`,
1282
- ` commits=${r.commitCount} (verifier-gated=${r.verifierGatedCommitCount}) openFeedback=${r.openFeedbackCount}`
1283
- ];
1284
- if (r.provenance?.rerunOf)
1285
- lines.push(` provenance: rerunOf=${r.provenance.rerunOf} gen=${r.provenance.generation} origin=${r.provenance.originRunId}`);
1286
- if (r.tier && r.tier !== "live") {
1287
- lines.push(` tier=${r.tier} capability=${r.capability} reason=${r.capabilityReason}${r.reclaimedBytes ? ` bytesFreed=${r.reclaimedBytes}` : ""}${r.tombstoneHash ? ` tombstone=${r.tombstoneHash.slice(0, 19)}` : ""}`);
1288
- }
1289
- return lines.join("\n");
1290
- }
1291
- function formatGcPlan(result) {
1292
- const lines = [
1293
- `GC Plan (${result.scope}): ${result.eligibleCount}/${result.total} eligible, ${result.bytesToFree} byte(s) would be freed [DRY-RUN, frees nothing]`,
1294
- ` policy: reclaimAfterArchiveDays=${result.policy.reclaimAfterArchiveDays} keepScratch=${result.policy.keepScratch} keepSnapshots=${result.policy.keepSnapshots}`
1295
- ];
1296
- for (const entry of result.entries) {
1297
- if (entry.eligible) {
1298
- const kinds = Object.entries(entry.byKind).map(([k, v]) => `${k}=${v}`).join(" ");
1299
- lines.push(` [eligible] ${entry.runId} -> ${entry.capability} (${entry.capabilityReason}) ${entry.bytesToFree}B {${kinds}}`);
1300
- }
1301
- else {
1302
- lines.push(` [skip:${entry.reason}] ${entry.runId} (tier=${entry.tier})`);
1303
- }
1304
- }
1305
- if (!result.entries.length)
1306
- lines.push(" (no runs in scope)");
1307
- return lines.join("\n");
1308
- }
1309
- function formatGcRun(result) {
1310
- const lines = [`GC Run (${result.scope}): reclaimed ${result.reclaimed.length} run(s), freed ${result.totalBytesFreed} byte(s)`];
1311
- for (const r of result.reclaimed)
1312
- lines.push(` [reclaimed] ${r.runId} -> ${r.capability} (${r.capabilityReason}) ${r.bytesFreed}B tombstone=${r.tombstoneHash.slice(0, 19)}`);
1313
- for (const r of result.refused)
1314
- lines.push(` [refused:${r.code}] ${r.runId}`);
1315
- if (!result.reclaimed.length && !result.refused.length)
1316
- lines.push(" (nothing eligible)");
1317
- return lines.join("\n");
1318
- }
1319
- function formatGcVerify(result) {
1320
- const lines = [
1321
- `GC Verify ${result.runId}: reclaimed=${result.reclaimed} verified=${result.verified} tier=${result.tier} capability=${result.capability}${result.tombstoneHash ? ` tombstone=${result.tombstoneHash.slice(0, 19)}` : ""}`
1322
- ];
1323
- for (const check of result.checks)
1324
- lines.push(` ${check.pass ? "PASS" : "FAIL"} ${check.name}${check.code ? ` [${check.code}]` : ""}${check.detail ? ` (${check.detail})` : ""}`);
1325
- return lines.join("\n");
1326
- }
1327
- function formatResume(result) {
1328
- const lines = [
1329
- `Resume ${result.runId} [${result.lifecycle}] loopStage=${result.loopStage} (resolved from ${result.resolvedFrom}, ${result.freshness})`,
1330
- ` resumable=${result.resumable} nextTasks=${result.nextTasks.length}`
1331
- ];
1332
- for (const action of result.nextActions)
1333
- lines.push(` -> ${action.command}\n ${action.reason}`);
1334
- return lines.join("\n");
1335
- }
1336
- function formatHistory(result) {
1337
- const lines = [];
1338
- lines.push(`Run History (${result.scope}): ${result.total} run(s) across ${result.repos.length} repo(s), freshness=${result.freshness}`);
1339
- for (const entry of result.entries) {
1340
- lines.push(` ${entry.createdAt} [${entry.lifecycle}] ${entry.runId} (${entry.appId || entry.workflowId})${entry.provenance?.rerunOf ? ` rerunOf=${entry.provenance.rerunOf}` : ""}`);
1341
- }
1342
- if (!result.entries.length)
1343
- lines.push(" (no runs)");
1344
- return lines.join("\n");
1345
- }
1346
- function formatQueueList(result) {
1347
- const lines = [`Run Queue: ${result.total} entry(ies) [priority asc]`];
1348
- for (const entry of result.entries) {
1349
- lines.push(` #${entry.priority} ${entry.id} [${entry.status}] ${entry.appId || entry.workflowId || entry.runId || "?"} repo=${entry.repo}${entry.note ? ` note=${entry.note}` : ""}`);
1350
- }
1351
- if (!result.entries.length)
1352
- lines.push(" (queue empty)");
1353
- return lines.join("\n");
1354
- }
827
+ // Human formatting (CLI-only) now lives in ./run-registry/format.ts (FreeBSD-
828
+ // audit R2: rendering carved out of the registry class). Re-exported so that
829
+ // importers of "./run-registry" see an unchanged surface.
830
+ var format_1 = require("./run-registry/format");
831
+ Object.defineProperty(exports, "formatRegistryReport", { enumerable: true, get: function () { return format_1.formatRegistryReport; } });
832
+ Object.defineProperty(exports, "formatRunSearch", { enumerable: true, get: function () { return format_1.formatRunSearch; } });
833
+ Object.defineProperty(exports, "formatRunShow", { enumerable: true, get: function () { return format_1.formatRunShow; } });
834
+ Object.defineProperty(exports, "formatGcPlan", { enumerable: true, get: function () { return format_1.formatGcPlan; } });
835
+ Object.defineProperty(exports, "formatGcRun", { enumerable: true, get: function () { return format_1.formatGcRun; } });
836
+ Object.defineProperty(exports, "formatGcVerify", { enumerable: true, get: function () { return format_1.formatGcVerify; } });
837
+ Object.defineProperty(exports, "formatResume", { enumerable: true, get: function () { return format_1.formatResume; } });
838
+ Object.defineProperty(exports, "formatHistory", { enumerable: true, get: function () { return format_1.formatHistory; } });
839
+ Object.defineProperty(exports, "formatQueueList", { enumerable: true, get: function () { return format_1.formatQueueList; } });