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
@@ -0,0 +1,251 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.reclamationPolicy = reclamationPolicy;
4
+ exports.reclaimEligibility = reclaimEligibility;
5
+ exports.gcPlan = gcPlan;
6
+ exports.gcRun = gcRun;
7
+ exports.gcVerify = gcVerify;
8
+ const reclamation_1 = require("../reclamation");
9
+ const trust_audit_1 = require("../trust-audit");
10
+ const policy_1 = require("./policy");
11
+ /** Resolve the effective reclamation policy (defaults reclaim NOTHING). */
12
+ function reclamationPolicy(overrides = {}) {
13
+ return { ...policy_1.DEFAULT_RUN_REGISTRY_POLICY, ...overrides };
14
+ }
15
+ /** Fail-closed eligibility: terminal AND archived AND no open feedback AND past
16
+ * retention. Returns the matching refusal code, or null when eligible. Reads
17
+ * the live-source-derived record; order yields distinct, stable codes. */
18
+ function reclaimEligibility(record, policy, nowMs) {
19
+ if (record.tier === "reclaimed")
20
+ return "already-reclaimed";
21
+ const terminalStates = policy.reclaimStates && policy.reclaimStates.length ? policy.reclaimStates : ["completed", "failed"];
22
+ if (record.derivedLifecycle !== "completed" && record.derivedLifecycle !== "failed")
23
+ return "non-terminal";
24
+ if (!terminalStates.includes(record.derivedLifecycle))
25
+ return "non-terminal";
26
+ if (record.openFeedbackCount > 0)
27
+ return "open-feedback";
28
+ if (!record.archived)
29
+ return "not-archived";
30
+ const days = policy.reclaimAfterArchiveDays ?? 0;
31
+ if (days > 0) {
32
+ const archivedAtMs = record.archivedAt ? Date.parse(record.archivedAt) : NaN;
33
+ if (!Number.isFinite(archivedAtMs))
34
+ return "within-retention";
35
+ if (archivedAtMs > nowMs - days * 24 * 60 * 60 * 1000)
36
+ return "within-retention";
37
+ }
38
+ return null;
39
+ }
40
+ /** Resolve a single run to a one-element record list via locate() (repo-first),
41
+ * avoiding a full-registry scan for single-run gc plan/run. */
42
+ function recordsForRunId(host, runId, scope) {
43
+ const located = host.locate(runId, scope);
44
+ return located ? [located.record] : [];
45
+ }
46
+ /** Dry-run: compute eligible runs, per-kind bytes that WOULD be freed, and the
47
+ * capability downgrade. Frees NOTHING. */
48
+ function gcPlan(host, options = {}) {
49
+ const scope = options.scope || "home";
50
+ const policy = reclamationPolicy(options.policy);
51
+ const nowIso = options.now || new Date().toISOString();
52
+ const nowMs = Date.parse(nowIso);
53
+ // Fast, deterministic single-run path: resolve just that run via locate()
54
+ // (repo-first) so a home-scope plan never re-scans the whole registry.
55
+ const records = options.runId ? recordsForRunId(host, options.runId, scope) : host.buildIndex(scope).records;
56
+ const entries = [];
57
+ let bytesToFree = 0;
58
+ let eligibleCount = 0;
59
+ for (const record of records) {
60
+ const refusal = reclaimEligibility(record, policy, nowMs);
61
+ let plan;
62
+ try {
63
+ const run = host.loadRun(record.repo, record.runId);
64
+ plan = (0, reclamation_1.planReclamation)(run, { keepScratch: policy.keepScratch, keepSnapshots: policy.keepSnapshots });
65
+ }
66
+ catch {
67
+ entries.push({
68
+ runId: record.runId,
69
+ repo: record.repo,
70
+ eligible: false,
71
+ reason: "unreadable",
72
+ tier: record.tier || "live",
73
+ capability: record.capability || "re-runnable",
74
+ capabilityReason: record.capabilityReason || "live-full",
75
+ bytesToFree: 0,
76
+ byKind: {},
77
+ freeable: []
78
+ });
79
+ continue;
80
+ }
81
+ const eligible = refusal === null;
82
+ const entry = {
83
+ runId: record.runId,
84
+ repo: record.repo,
85
+ eligible,
86
+ reason: eligible ? "eligible" : refusal,
87
+ tier: record.tier || "live",
88
+ capability: plan.capability,
89
+ capabilityReason: plan.capabilityReason,
90
+ bytesToFree: eligible ? plan.bytesToFree : 0,
91
+ byKind: eligible ? plan.byKind : {},
92
+ freeable: eligible ? plan.freeable.map((f) => ({ path: f.path, kind: f.kind, bytes: f.bytes })) : []
93
+ };
94
+ entries.push(entry);
95
+ if (eligible) {
96
+ eligibleCount += 1;
97
+ bytesToFree += plan.bytesToFree;
98
+ }
99
+ }
100
+ return {
101
+ schemaVersion: 1,
102
+ scope,
103
+ generatedAt: nowIso,
104
+ policy: {
105
+ reclaimAfterArchiveDays: policy.reclaimAfterArchiveDays ?? 0,
106
+ keepSnapshots: Boolean(policy.keepSnapshots),
107
+ keepScratch: Boolean(policy.keepScratch),
108
+ reclaimStates: policy.reclaimStates && policy.reclaimStates.length ? policy.reclaimStates : ["completed", "failed"]
109
+ },
110
+ total: entries.length,
111
+ eligibleCount,
112
+ bytesToFree,
113
+ entries,
114
+ nextAction: eligibleCount ? "node scripts/cw.js gc run" : "node scripts/cw.js run search"
115
+ };
116
+ }
117
+ /** Execute the write-ahead reclamation transaction for eligible runs. Bounded
118
+ * (`maxReclaimRuns` / `maxReclaimBytes`), fail-closed on any incomplete
119
+ * skeleton. Produces a tombstone and frees the bulk. */
120
+ function gcRun(host, options = {}) {
121
+ const scope = options.scope || "home";
122
+ const policy = reclamationPolicy(options.policy);
123
+ const nowIso = options.now || new Date().toISOString();
124
+ const nowMs = Date.parse(nowIso);
125
+ const records = options.runId ? recordsForRunId(host, options.runId, scope) : host.buildIndex(scope).records;
126
+ const maxRuns = options.limit ?? (policy.maxReclaimRuns || 0);
127
+ const maxBytes = policy.maxReclaimBytes || 0;
128
+ const reclaimed = [];
129
+ const refused = [];
130
+ let totalBytesFreed = 0;
131
+ for (const record of records) {
132
+ const refusal = reclaimEligibility(record, policy, nowMs);
133
+ if (refusal) {
134
+ refused.push({ runId: record.runId, code: refusal });
135
+ continue;
136
+ }
137
+ if (maxRuns > 0 && reclaimed.length >= maxRuns)
138
+ break;
139
+ let run;
140
+ try {
141
+ run = host.loadRun(record.repo, record.runId);
142
+ }
143
+ catch {
144
+ refused.push({ runId: record.runId, code: "unreadable" });
145
+ continue;
146
+ }
147
+ try {
148
+ const result = (0, reclamation_1.runReclamation)(run, {
149
+ now: nowIso,
150
+ actor: options.actor,
151
+ policy: { reclaimAfterArchiveDays: policy.reclaimAfterArchiveDays, keepScratch: policy.keepScratch, keepSnapshots: policy.keepSnapshots },
152
+ reclaimPolicy: { keepScratch: policy.keepScratch, keepSnapshots: policy.keepSnapshots }
153
+ });
154
+ // No post-free saveCheckpoint: runReclamation now DURABLY persists the
155
+ // result-node re-point inside the transaction (before any byte is freed),
156
+ // so state.json can never reference a freed path even on a crash here.
157
+ reclaimed.push({
158
+ runId: record.runId,
159
+ bytesFreed: result.bytesFreed,
160
+ tombstoneHash: result.tombstone.tombstoneHash,
161
+ capability: result.tombstone.capability,
162
+ capabilityReason: result.tombstone.capabilityReason
163
+ });
164
+ // Independent reclamation WITNESS in the tamper-evident trust-audit chain:
165
+ // proves this run WAS reclaimed even if reclaimed.json is later deleted — so
166
+ // `gc verify` can tell proof-deletion apart from never-reclaimed.
167
+ (0, trust_audit_1.recordTrustAuditEvent)(run, {
168
+ kind: "run.reclaimed",
169
+ decision: "recorded",
170
+ source: "cw-validated",
171
+ metadata: { tombstoneHash: result.tombstone.tombstoneHash, bytesFreed: result.bytesFreed, capability: result.tombstone.capability }
172
+ });
173
+ totalBytesFreed += result.bytesFreed;
174
+ if (maxBytes > 0 && totalBytesFreed >= maxBytes)
175
+ break;
176
+ }
177
+ catch (error) {
178
+ if (error instanceof reclamation_1.ReclamationError)
179
+ refused.push({ runId: record.runId, code: error.code });
180
+ else
181
+ throw error;
182
+ }
183
+ }
184
+ return {
185
+ schemaVersion: 1,
186
+ scope,
187
+ generatedAt: nowIso,
188
+ dryRun: false,
189
+ reclaimed,
190
+ refused,
191
+ totalBytesFreed,
192
+ nextAction: reclaimed.length ? "node scripts/cw.js gc verify <run-id>" : "node scripts/cw.js gc plan"
193
+ };
194
+ }
195
+ /** Re-prove a reclaimed run: skeleton schema-complete, tombstone chain
196
+ * recomputed-and-untampered, each reconstructable artifact re-derived from its
197
+ * RETAINED inputs to its expectDigest, and eligible-when-reclaimed. */
198
+ function gcVerify(host, runId, options = {}) {
199
+ const scope = options.scope || "home";
200
+ const located = host.locate(runId, scope);
201
+ if (!located) {
202
+ return {
203
+ schemaVersion: 1,
204
+ runId,
205
+ reclaimed: false,
206
+ verified: false,
207
+ tier: "live",
208
+ capability: "re-runnable",
209
+ chainLength: 0,
210
+ checks: [{ name: "located", pass: false, code: "not-reclaimed", detail: "run source not found" }],
211
+ nextAction: "node scripts/cw.js registry refresh" + (scope === "home" ? " --scope home" : "")
212
+ };
213
+ }
214
+ const run = host.loadRun(located.record.repo, runId);
215
+ const result = (0, reclamation_1.verifyReclamation)(run);
216
+ const checks = result.checks.map((c) => ({ name: c.name, pass: c.pass, code: c.code, detail: c.detail }));
217
+ // Eligible-when-reclaimed: each tombstone must have sealed a terminal verdict.
218
+ let eligibleWhenReclaimed = result.reclaimed;
219
+ for (const tombstone of result.tombstones) {
220
+ const terminal = tombstone.skeleton.finalVerdict?.terminal === true;
221
+ if (!terminal) {
222
+ eligibleWhenReclaimed = false;
223
+ checks.push({ name: `eligible-when-reclaimed:${tombstone.tombstoneId}`, pass: false, code: "ineligible-when-reclaimed", detail: "non-terminal verdict sealed" });
224
+ }
225
+ }
226
+ const last = result.tombstones[result.tombstones.length - 1];
227
+ // Independent witness: a trust-audit "run.reclaimed" event proves this run was
228
+ // reclaimed even if reclaimed.json was deleted. A present witness + missing proof
229
+ // = the proof was deleted/tampered (NOT "never reclaimed") — fail closed so
230
+ // `gc verify <run> && deploy` cannot pass on a wiped reclamation record.
231
+ const witnessed = (0, trust_audit_1.listTrustAuditEvents)(run).some((event) => event.kind === "run.reclaimed");
232
+ const proofDeleted = witnessed && !result.reclaimed;
233
+ if (proofDeleted) {
234
+ checks.push({ name: "reclaim-witness", pass: false, code: "reclaim-proof-deleted", detail: "trust-audit attests reclamation but reclaimed.json is missing/empty" });
235
+ }
236
+ const reclaimed = result.reclaimed || proofDeleted;
237
+ const verified = result.verified && eligibleWhenReclaimed && !proofDeleted;
238
+ return {
239
+ schemaVersion: 1,
240
+ runId,
241
+ reclaimed,
242
+ verified,
243
+ tier: located.record.tier || (reclaimed ? "reclaimed" : "live"),
244
+ capability: located.record.capability || "re-runnable",
245
+ capabilityReason: located.record.capabilityReason,
246
+ tombstoneHash: last?.tombstoneHash,
247
+ chainLength: result.tombstones.length,
248
+ checks,
249
+ nextAction: verified ? "node scripts/cw.js run show " + runId : "node scripts/cw.js gc plan"
250
+ };
251
+ }
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_RUN_REGISTRY_POLICY = exports.RUN_REGISTRY_SCHEMA_VERSION = void 0;
4
+ exports.RUN_REGISTRY_SCHEMA_VERSION = 1;
5
+ exports.DEFAULT_RUN_REGISTRY_POLICY = {
6
+ schemaVersion: 1,
7
+ archiveOlderThanDays: 0,
8
+ archiveStates: ["completed", "failed"],
9
+ defaultQueuePriority: 100,
10
+ reclaimAfterArchiveDays: 0,
11
+ reclaimStates: ["completed", "failed"],
12
+ keepSnapshots: false,
13
+ keepScratch: false,
14
+ maxReclaimRuns: 0,
15
+ maxReclaimBytes: 0
16
+ };
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.queueFilePath = queueFilePath;
7
+ exports.loadQueue = loadQueue;
8
+ exports.saveQueue = saveQueue;
9
+ exports.queueAdd = queueAdd;
10
+ exports.queueList = queueList;
11
+ exports.queueShow = queueShow;
12
+ exports.queueDrain = queueDrain;
13
+ // Durable run-queue operations for the run registry (FreeBSD-audit R2 deep).
14
+ // Carved out of run-registry.ts so the RunRegistry class no longer bundles the
15
+ // stateful queue cluster; the class keeps the public methods as thin delegators.
16
+ //
17
+ // BEHAVIOR-PRESERVING — pure code movement, zero logic change. Each function
18
+ // takes a `QueueHost` (the registry, narrowed to exactly the file-access +
19
+ // repo-registration helpers the queue needs) so it stays a function of its
20
+ // inputs, matching the existing router pattern (orchestrator/*-operations.ts,
21
+ // run-registry/derive.ts + format.ts).
22
+ //
23
+ // The queue file lives beside the other home-registry plain files (EXPLICIT,
24
+ // INSPECTABLE STATE): readable, diffable, no hidden database. Cross-process
25
+ // read-modify-write is locked (v0.1.40, P1-D) so a concurrent add/drain can
26
+ // never drop or double-drain an entry.
27
+ const node_path_1 = __importDefault(require("node:path"));
28
+ const node_fs_1 = __importDefault(require("node:fs"));
29
+ const state_1 = require("../state");
30
+ const derive_1 = require("./derive");
31
+ function queueFilePath(host) {
32
+ return node_path_1.default.join(host.homeRegistryDir(), "queue.json");
33
+ }
34
+ function loadQueue(host) {
35
+ const file = queueFilePath(host);
36
+ if (!node_fs_1.default.existsSync(file))
37
+ return [];
38
+ try {
39
+ const parsed = (0, state_1.readJson)(file);
40
+ return Array.isArray(parsed.entries) ? parsed.entries : [];
41
+ }
42
+ catch {
43
+ return [];
44
+ }
45
+ }
46
+ function saveQueue(host, entries) {
47
+ (0, state_1.writeJson)(queueFilePath(host), { schemaVersion: 1, entries }, { durable: true });
48
+ }
49
+ function queueAdd(host, options = {}) {
50
+ const repo = options.repo ? node_path_1.default.resolve(options.repo) : host.repoRoot;
51
+ // Cross-process read-modify-write on the home queue: lock so a concurrently
52
+ // added task can never vanish (v0.1.40, P1-D).
53
+ return (0, state_1.withFileLock)(queueFilePath(host), () => {
54
+ const entries = loadQueue(host);
55
+ const entry = {
56
+ schemaVersion: 1,
57
+ id: options.id || (0, derive_1.queueId)(),
58
+ runId: options.runId,
59
+ appId: options.appId,
60
+ workflowId: options.workflowId,
61
+ repo,
62
+ priority: Number.isFinite(options.priority) ? Number(options.priority) : host.defaultQueuePriority,
63
+ enqueuedAt: new Date().toISOString(),
64
+ status: "pending",
65
+ inputs: options.inputs,
66
+ note: options.note
67
+ };
68
+ entries.push(entry);
69
+ host.registerRepo(repo);
70
+ saveQueue(host, entries);
71
+ return entry;
72
+ });
73
+ }
74
+ function queueList(host, options = {}) {
75
+ let entries = loadQueue(host);
76
+ if (options.status)
77
+ entries = entries.filter((e) => e.status === options.status);
78
+ if (options.repo) {
79
+ const repo = node_path_1.default.resolve(options.repo);
80
+ entries = entries.filter((e) => node_path_1.default.resolve(e.repo) === repo);
81
+ }
82
+ entries = [...entries].sort(derive_1.compareQueue);
83
+ return { schemaVersion: 1, total: entries.length, entries };
84
+ }
85
+ function queueShow(host, id) {
86
+ const entry = loadQueue(host).find((e) => e.id === id);
87
+ if (!entry)
88
+ throw new Error(`Queue entry not found: ${id}`);
89
+ return entry;
90
+ }
91
+ /** Drain the next N ready/pending entries in policy order, marking them drained.
92
+ * CW records readiness/order; the HOST still executes the workers. */
93
+ function queueDrain(host, options = {}) {
94
+ const limit = (0, derive_1.clampInt)(options.limit, 1, 1);
95
+ const repoFilter = options.repo ? node_path_1.default.resolve(options.repo) : undefined;
96
+ // Lock the drain RMW so two hosts can never double-drain the same entry
97
+ // (v0.1.40, P1-D — the scheduling kernel's concurrency ceiling now holds
98
+ // across processes, not just within one).
99
+ return (0, state_1.withFileLock)(queueFilePath(host), () => {
100
+ const entries = loadQueue(host);
101
+ const drainable = entries
102
+ .filter((e) => e.status === "pending" || e.status === "ready")
103
+ .filter((e) => !repoFilter || node_path_1.default.resolve(e.repo) === repoFilter)
104
+ .sort(derive_1.compareQueue);
105
+ const drained = [];
106
+ const drainedAt = new Date().toISOString();
107
+ for (const entry of drainable.slice(0, limit)) {
108
+ entry.status = "drained";
109
+ entry.drainedAt = drainedAt;
110
+ drained.push(entry);
111
+ }
112
+ saveQueue(host, entries);
113
+ const remaining = entries.filter((e) => e.status === "pending" || e.status === "ready").length;
114
+ return { schemaVersion: 1, drained, remaining };
115
+ });
116
+ }