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