ai-foreman 1.7.28 → 1.7.29
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.
- package/dist/adapters/claude.js +1 -1
- package/dist/adapters/types.d.ts +1 -0
- package/dist/buildApproval.d.ts +15 -0
- package/dist/buildApproval.js +43 -0
- package/dist/cli/start.js +13 -1
- package/dist/cli/tickets.d.ts +7 -0
- package/dist/cli/tickets.js +18 -3
- package/dist/foreman.d.ts +11 -2
- package/dist/foreman.js +22 -10
- package/dist/hostGraphApplicability.d.ts +45 -0
- package/dist/hostGraphApplicability.js +99 -0
- package/dist/hostQaAugmentation.d.ts +18 -0
- package/dist/hostQaAugmentation.js +64 -0
- package/dist/hostQaComparison.js +2 -1
- package/dist/qaBuildGate.js +3 -2
- package/dist/qaContractCoverage.js +12 -3
- package/dist/qaEffectiveConfig.d.ts +1 -0
- package/dist/qaEffectiveConfig.js +6 -1
- package/dist/qaPreparation.js +21 -2
- package/dist/qaPreparationStore.js +34 -6
- package/dist/qaReview.js +18 -5
- package/dist/qaToolRecovery.d.ts +12 -0
- package/dist/qaToolRecovery.js +44 -0
- package/dist/qaVerificationContract.d.ts +2 -0
- package/dist/qaVerificationContract.js +37 -2
- package/dist/supervisedStart.d.ts +1 -1
- package/dist/supervisedStart.js +44 -11
- package/dist/supervisor.d.ts +18 -2
- package/dist/supervisor.js +74 -17
- package/dist/workflowDb.js +28 -6
- package/package.json +3 -3
package/dist/supervisor.js
CHANGED
|
@@ -1,9 +1,40 @@
|
|
|
1
1
|
import { processStartIdentity } from "./processIdentity.js";
|
|
2
|
+
import { stopCause } from "rafi-spec";
|
|
2
3
|
import { WorkflowDb } from "./workflowDb.js";
|
|
4
|
+
/**
|
|
5
|
+
* Retry only the short synchronous persistence operation. The caller owns
|
|
6
|
+
* the retry budget and remains responsible for deciding whether ownership is
|
|
7
|
+
* safe after the budget is exhausted.
|
|
8
|
+
*/
|
|
9
|
+
export async function persistWithBoundedRetry(persist, sleep, attempts = 3) {
|
|
10
|
+
let error;
|
|
11
|
+
let completedAttempts = 0;
|
|
12
|
+
for (let attempt = 1; attempt <= Math.max(1, attempts); attempt++) {
|
|
13
|
+
completedAttempts = attempt;
|
|
14
|
+
try {
|
|
15
|
+
persist();
|
|
16
|
+
return { ok: true, attempts: attempt };
|
|
17
|
+
}
|
|
18
|
+
catch (failure) {
|
|
19
|
+
error = failure;
|
|
20
|
+
if (attempt < attempts && retryablePersistenceError(failure))
|
|
21
|
+
await sleep(100 * attempt);
|
|
22
|
+
else
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { ok: false, attempts: completedAttempts, error };
|
|
27
|
+
}
|
|
28
|
+
function retryablePersistenceError(error) {
|
|
29
|
+
const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
|
|
30
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
31
|
+
return code === "SQLITE_BUSY" || code === "SQLITE_LOCKED" || /database is (?:busy|locked)/i.test(message);
|
|
32
|
+
}
|
|
3
33
|
/** Durable worker restart loop. The supervisor never owns the project's mutation lease. */
|
|
4
34
|
export class DurableSupervisor {
|
|
5
35
|
options;
|
|
6
36
|
stopRequested = false;
|
|
37
|
+
requestedStopCause;
|
|
7
38
|
active;
|
|
8
39
|
sleep;
|
|
9
40
|
now;
|
|
@@ -12,8 +43,12 @@ export class DurableSupervisor {
|
|
|
12
43
|
this.sleep = options.sleep ?? ((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
13
44
|
this.now = options.now ?? (() => new Date());
|
|
14
45
|
}
|
|
15
|
-
requestStop() {
|
|
46
|
+
requestStop(cause = "unknown") {
|
|
16
47
|
this.stopRequested = true;
|
|
48
|
+
const source = cause === "user-sigint" || cause === "parent-sigterm" ? "signal" : cause === "heartbeat-timeout" ? "heartbeat" : cause === "heartbeat-persistence-failure" ? "persistence" : cause === "human-decision" ? "human-decision" : "host";
|
|
49
|
+
const actor = cause === "user-sigint" ? "user" : cause === "parent-sigterm" ? "parent" : cause === "heartbeat-persistence-failure" ? "database" : cause === "unknown" ? "unknown" : "supervisor";
|
|
50
|
+
this.requestedStopCause = stopCause({ cause, source, actor, runId: this.options.runId, checkpoint: this.options.checkpoint(), at: this.now().toISOString(), requested: true });
|
|
51
|
+
this.options.onStopRequested?.(this.requestedStopCause);
|
|
17
52
|
void this.active?.stop();
|
|
18
53
|
}
|
|
19
54
|
async run() {
|
|
@@ -64,24 +99,35 @@ export class DurableSupervisor {
|
|
|
64
99
|
db.putSupervisorState(this.options.runId, state);
|
|
65
100
|
});
|
|
66
101
|
let lastCheckpoint = this.options.checkpoint();
|
|
67
|
-
|
|
68
|
-
|
|
102
|
+
let heartbeatInFlight = false;
|
|
103
|
+
const persistHeartbeat = async () => {
|
|
104
|
+
if (heartbeatInFlight || this.stopRequested)
|
|
105
|
+
return;
|
|
106
|
+
heartbeatInFlight = true;
|
|
69
107
|
try {
|
|
70
|
-
|
|
108
|
+
state = { ...state, heartbeatAt: this.now().toISOString() };
|
|
109
|
+
const persisted = await persistWithBoundedRetry(saveState, this.sleep);
|
|
110
|
+
if (persisted.ok)
|
|
111
|
+
return;
|
|
112
|
+
this.requestStop("heartbeat-persistence-failure");
|
|
113
|
+
// Ownership failure remains fail-closed; the explicit cause is
|
|
114
|
+
// delivered to the worker-exit evidence by supervisedStart.
|
|
71
115
|
}
|
|
72
|
-
|
|
73
|
-
|
|
116
|
+
finally {
|
|
117
|
+
heartbeatInFlight = false;
|
|
74
118
|
}
|
|
75
|
-
}
|
|
119
|
+
};
|
|
120
|
+
let heartbeatTask;
|
|
121
|
+
const heartbeat = setInterval(() => { heartbeatTask = persistHeartbeat(); }, this.options.heartbeatIntervalMs ?? 10_000);
|
|
76
122
|
heartbeat.unref();
|
|
77
123
|
try {
|
|
78
124
|
while (!this.stopRequested) {
|
|
79
125
|
const pending = db.pendingHumanDecisions(this.options.runId);
|
|
80
126
|
if (pending.some(decision => !this.options.policy.continueIndependentTickets || !decision.interruptionId.startsWith("ticket:"))) {
|
|
81
|
-
state = { ...state, status: "waiting_for_human", workerPid: undefined, heartbeatAt: this.now().toISOString() };
|
|
127
|
+
state = { ...state, status: "waiting_for_human", workerPid: undefined, heartbeatAt: this.now().toISOString(), stopCause: stopCause({ cause: "human-decision", source: "human-decision", actor: "supervisor", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: "waiting-for-human", at: this.now().toISOString(), requested: true }) };
|
|
82
128
|
saveState();
|
|
83
129
|
if (db.getRun(this.options.runId)?.status === "running")
|
|
84
|
-
db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "waiting-for-human", event: "worker_waiting_for_human" });
|
|
130
|
+
db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "waiting-for-human", event: "worker_waiting_for_human", payload: { cause: state.stopCause } });
|
|
85
131
|
return { kind: "waiting_for_human" };
|
|
86
132
|
}
|
|
87
133
|
const checkpoint = this.options.checkpoint();
|
|
@@ -92,22 +138,27 @@ export class DurableSupervisor {
|
|
|
92
138
|
this.active = await this.options.spawnWorker(state.workerGeneration);
|
|
93
139
|
state = { ...state, workerPid: this.active.pid };
|
|
94
140
|
saveState();
|
|
141
|
+
const workerPid = state.workerPid;
|
|
95
142
|
const outcome = await this.active.result;
|
|
96
143
|
this.active = undefined;
|
|
97
144
|
state = { ...state, workerPid: undefined, heartbeatAt: this.now().toISOString() };
|
|
145
|
+
const cause = this.requestedStopCause
|
|
146
|
+
? stopCause({ ...this.requestedStopCause, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, pid: workerPid, processStart: workerPid ? processStartIdentity(workerPid) : undefined, checkpoint: this.options.checkpoint(), at: this.now().toISOString() })
|
|
147
|
+
: stopCause({ cause: "worker-exit", source: "worker", actor: "worker", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, pid: workerPid, checkpoint: this.options.checkpoint(), at: this.now().toISOString(), requested: false, detail: outcome.kind === "failed" ? outcome.detail : `worker outcome: ${outcome.kind}` });
|
|
148
|
+
state = { ...state, stopCause: cause };
|
|
98
149
|
if (outcome.kind !== "crashed") {
|
|
99
150
|
if (outcome.kind === "failed" && db.getRun(this.options.runId)?.status === "running") {
|
|
100
151
|
const eligibility = db.preparationEligibility(this.options.runId);
|
|
101
152
|
db.transitionSupervisor(this.options.runId, state.workerGeneration, {
|
|
102
153
|
status: eligibility.eligible ? "failed" : "paused",
|
|
103
154
|
checkpoint: eligibility.eligible ? "worker-failed" : "worker-reconciliation-required",
|
|
104
|
-
event: "worker_exit_recorded", payload: { detail: outcome.detail, reasons: eligibility.reasons },
|
|
155
|
+
event: "worker_exit_recorded", payload: { detail: outcome.detail, reasons: eligibility.reasons, cause },
|
|
105
156
|
});
|
|
106
157
|
}
|
|
107
158
|
else if (db.getRun(this.options.runId)?.status === "running")
|
|
108
159
|
db.transitionSupervisor(this.options.runId, state.workerGeneration, {
|
|
109
160
|
status: "paused", checkpoint: outcome.kind === "waiting_for_human" && db.pendingHumanDecisions(this.options.runId).length ? "waiting-for-human" : "worker-exited-before-checkpoint",
|
|
110
|
-
event: "worker_exit_recorded", payload: { outcome: outcome.kind },
|
|
161
|
+
event: "worker_exit_recorded", payload: { outcome: outcome.kind, cause },
|
|
111
162
|
});
|
|
112
163
|
const status = outcome.kind === "completed" ? "stopped" : outcome.kind === "waiting_for_human" ? "waiting_for_human" : outcome.kind === "failed" ? "failed" : "stopped";
|
|
113
164
|
state = { ...state, status };
|
|
@@ -118,7 +169,10 @@ export class DurableSupervisor {
|
|
|
118
169
|
if (!eligibility.eligible) {
|
|
119
170
|
state = { ...state, status: "waiting_for_human" };
|
|
120
171
|
saveState();
|
|
121
|
-
|
|
172
|
+
const cause = stopCause({ ...state.stopCause, cause: "worker-reconciliation-required", source: "reconciliation", actor: "supervisor", checkpoint: "worker-reconciliation-required", at: this.now().toISOString(), requested: false, detail: eligibility.reasons.join("; ") });
|
|
173
|
+
state = { ...state, stopCause: cause };
|
|
174
|
+
saveState();
|
|
175
|
+
db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-reconciliation-required", event: "worker_restart_withheld", payload: { reasons: eligibility.reasons, cause } });
|
|
122
176
|
return { kind: "waiting_for_human" };
|
|
123
177
|
}
|
|
124
178
|
const checkpointLimit = this.options.policy.limits.workerRestartsPerCheckpoint;
|
|
@@ -128,7 +182,7 @@ export class DurableSupervisor {
|
|
|
128
182
|
saveState();
|
|
129
183
|
if (db.getRun(this.options.runId)?.status === "running")
|
|
130
184
|
db.transitionSupervisor(this.options.runId, state.workerGeneration, {
|
|
131
|
-
status: "failed", checkpoint: "worker-restart-budget-exhausted", event: "worker_restart_budget_exhausted", payload: { checkpoint, detail: outcome.detail },
|
|
185
|
+
status: "failed", checkpoint: "worker-restart-budget-exhausted", event: "worker_restart_budget_exhausted", payload: { checkpoint, detail: outcome.detail, cause: state.stopCause },
|
|
132
186
|
});
|
|
133
187
|
return { kind: "failed", detail: `worker restart budget exhausted at ${checkpoint}: ${outcome.detail}` };
|
|
134
188
|
}
|
|
@@ -136,26 +190,29 @@ export class DurableSupervisor {
|
|
|
136
190
|
saveState();
|
|
137
191
|
await this.sleep(Math.min(10_000, 250 * (2 ** Math.min(5, state.checkpointRestarts - 1))));
|
|
138
192
|
}
|
|
139
|
-
state = { ...state, status: "stopped", stopRequestedAt: this.now().toISOString(), workerPid: undefined };
|
|
193
|
+
state = { ...state, status: "stopped", stopRequestedAt: this.now().toISOString(), workerPid: undefined, stopCause: state.stopCause ?? this.requestedStopCause ?? stopCause({ cause: "unknown", source: "host", actor: "unknown", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: "worker-stopped", at: this.now().toISOString(), requested: true, detail: "Supervisor stopped without a more specific durable cause" }) };
|
|
140
194
|
if (db.getRun(this.options.runId)?.status === "running")
|
|
141
|
-
db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-stopped", event: "supervisor_stop_recorded" });
|
|
195
|
+
db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: "paused", checkpoint: "worker-stopped", event: "supervisor_stop_recorded", payload: { cause: state.stopCause } });
|
|
142
196
|
saveState();
|
|
143
197
|
return { kind: "stopped" };
|
|
144
198
|
}
|
|
145
199
|
catch (error) {
|
|
146
|
-
state = { ...state, status: "failed", workerPid: undefined, heartbeatAt: this.now().toISOString() };
|
|
200
|
+
state = { ...state, status: "failed", workerPid: undefined, heartbeatAt: this.now().toISOString(), stopCause: stopCause({ cause: "launch-failure", source: "launch", actor: "supervisor", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: "worker-launch-failed", at: this.now().toISOString(), requested: false, detail: String(error) }) };
|
|
147
201
|
saveState();
|
|
148
202
|
if (db.getRun(this.options.runId)?.status === "running") {
|
|
149
203
|
const eligibility = db.preparationEligibility(this.options.runId);
|
|
204
|
+
const cause = stopCause({ cause: "launch-failure", source: "launch", actor: "supervisor", runId: this.options.runId, supervisorGeneration: state.generation, workerGeneration: state.workerGeneration, checkpoint: eligibility.eligible ? "worker-launch-failed" : "worker-reconciliation-required", at: new Date().toISOString(), requested: false, detail: String(error) });
|
|
150
205
|
db.transitionSupervisor(this.options.runId, state.workerGeneration, { status: eligibility.eligible ? "failed" : "paused",
|
|
151
206
|
checkpoint: eligibility.eligible ? "worker-launch-failed" : "worker-reconciliation-required", event: "worker_launch_failed",
|
|
152
|
-
payload: { detail: String(error), reasons: eligibility.reasons },
|
|
207
|
+
payload: { detail: String(error), reasons: eligibility.reasons, cause },
|
|
153
208
|
});
|
|
154
209
|
}
|
|
155
210
|
throw error;
|
|
156
211
|
}
|
|
157
212
|
finally {
|
|
158
213
|
clearInterval(heartbeat);
|
|
214
|
+
if (heartbeatTask)
|
|
215
|
+
await heartbeatTask;
|
|
159
216
|
try {
|
|
160
217
|
if (ownedAdmission && db.buildAdmission()?.token === ownedAdmission.token)
|
|
161
218
|
db.releaseBuildAdmission(ownedAdmission);
|
package/dist/workflowDb.js
CHANGED
|
@@ -3,7 +3,9 @@ import { canonicalJson } from "./qaProtocolV2.js";
|
|
|
3
3
|
import { resolveEffectiveQaConfiguration } from "./qaEffectiveConfig.js";
|
|
4
4
|
import { registerGraphDerived, graphDerivedAllowed, currentGraphDerivedAccess } from "./graph/derived.js";
|
|
5
5
|
import { bindCoverageHostFields, parseContractCoverage, validateContractCoverage } from "./qaContractCoverage.js";
|
|
6
|
-
import {
|
|
6
|
+
import { findHostQaCheck, hostComparisonDigestFromCoverage, hostQaComparisonV1Schema } from "./hostQaComparison.js";
|
|
7
|
+
import { findGraphQaChecks, hostGraphApplicabilityDigestFromCoverage, hostGraphApplicabilityV1Schema } from "./hostGraphApplicability.js";
|
|
8
|
+
import { applyHostQaAugmentations } from "./hostQaAugmentation.js";
|
|
7
9
|
import { QaPreparationStore, migrateQaPreparation, migrateQaPreparationGuards } from "./qaPreparationStore.js";
|
|
8
10
|
import { GraphStore, migrateGraphStore } from "./graph/storage.js";
|
|
9
11
|
import { reconcileWork } from "./buildWorkReconciliation.js";
|
|
@@ -54,6 +56,7 @@ export function heartbeatCurrentWorkflowLease(projectDir, lease, now = new Date(
|
|
|
54
56
|
throw new Error("workflow recovery database not found");
|
|
55
57
|
const db = new Database(path, { fileMustExist: true });
|
|
56
58
|
db.function("rafi_protocol_v3", () => 1);
|
|
59
|
+
db.pragma("busy_timeout = 5000");
|
|
57
60
|
registerHandbackWriter(db);
|
|
58
61
|
try {
|
|
59
62
|
const at = now.toISOString();
|
|
@@ -144,6 +147,10 @@ export class WorkflowDb {
|
|
|
144
147
|
registerQaGraphJournalWriter(this.db);
|
|
145
148
|
if (!readinessAccess)
|
|
146
149
|
this.db.pragma("journal_mode = WAL");
|
|
150
|
+
// Short-lived coordinator/worker writes can overlap with a heartbeat or
|
|
151
|
+
// another durable transition. Let SQLite wait for the owning transaction
|
|
152
|
+
// instead of immediately surfacing SQLITE_BUSY to the supervisor.
|
|
153
|
+
this.db.pragma("busy_timeout = 5000");
|
|
147
154
|
this.db.pragma("synchronous = FULL");
|
|
148
155
|
this.db.pragma("foreign_keys = ON");
|
|
149
156
|
try {
|
|
@@ -2086,9 +2093,6 @@ export class WorkflowDb {
|
|
|
2086
2093
|
for (const method of check.verification)
|
|
2087
2094
|
if (method.equivalentAuthorityId && !store.equivalentResolver()(method.equivalentAuthorityId, contract, check, method, input.sourceStateDigest))
|
|
2088
2095
|
throw new Error("Equivalent verification authority is stale or foreign");
|
|
2089
|
-
const errors = validateContractCoverage(contract, coverage, { phase: "qa", sourceDigest: input.sourceStateDigest, inputBasisDigest: input.reviewBasisDigest, attemptId: binding.attemptId, sessionId: binding.sessionId });
|
|
2090
|
-
if (errors.length)
|
|
2091
|
-
throw new Error(errors.join("; "));
|
|
2092
2096
|
const initialTurns = this.db.prepare("SELECT intent_json FROM qa_turns WHERE run_id=? AND ticket_id=? AND review_number=? AND source_state_digest=? AND review_basis_digest=?").all(input.runId, input.ticketId, attempt.reviewNumber, input.sourceStateDigest, input.reviewBasisDigest);
|
|
2093
2097
|
const initial = initialTurns.map(row => parseJson(row.intent_json)).find(turn => turn.slot === "initial");
|
|
2094
2098
|
if (!initial || initial.providerSession.sessionId !== binding.sessionId)
|
|
@@ -2110,16 +2114,34 @@ export class WorkflowDb {
|
|
|
2110
2114
|
})
|
|
2111
2115
|
: undefined;
|
|
2112
2116
|
let retainedCoverage = retainedProviderCoverage;
|
|
2117
|
+
let retainedHostComparison;
|
|
2113
2118
|
const hostCheck = findHostQaCheck(contract);
|
|
2114
2119
|
if (hostCheck) {
|
|
2115
2120
|
const hostDigest = hostComparisonDigestFromCoverage(coverage, hostCheck.id);
|
|
2116
2121
|
if (!hostDigest)
|
|
2117
2122
|
throw new Error("Coverage is missing the host-owned comparison artifact reference");
|
|
2118
2123
|
const hostResult = hostQaComparisonV1Schema.parse(store.artifact(hostDigest, "host-qa-comparison"));
|
|
2119
|
-
|
|
2120
|
-
|
|
2124
|
+
retainedHostComparison = { result: hostResult, resultDigest: hostDigest };
|
|
2125
|
+
}
|
|
2126
|
+
const graphCheck = findGraphQaChecks(contract)[0];
|
|
2127
|
+
if (graphCheck) {
|
|
2128
|
+
const graphDigest = hostGraphApplicabilityDigestFromCoverage(coverage, graphCheck.id);
|
|
2129
|
+
if (!graphDigest)
|
|
2130
|
+
throw new Error("Coverage is missing the host-owned Graphify applicability artifact reference");
|
|
2131
|
+
const graphResult = hostGraphApplicabilityV1Schema.parse(store.artifact(graphDigest, "host-graph-applicability"));
|
|
2132
|
+
const retainedHostGraph = { result: graphResult, resultDigest: graphDigest };
|
|
2133
|
+
retainedCoverage = retainedCoverage
|
|
2134
|
+
? applyHostQaAugmentations({ coverage: retainedCoverage, contract, projectDir: this.projectDir, hostComparison: retainedHostComparison, hostGraph: retainedHostGraph })
|
|
2121
2135
|
: undefined;
|
|
2122
2136
|
}
|
|
2137
|
+
else if (retainedCoverage && retainedHostComparison) {
|
|
2138
|
+
retainedCoverage = applyHostQaAugmentations({ coverage: retainedCoverage, contract, projectDir: this.projectDir, hostComparison: retainedHostComparison });
|
|
2139
|
+
}
|
|
2140
|
+
if (!retainedCoverage)
|
|
2141
|
+
throw new Error("Coverage lacks the retained terminal review receipt and authorized host augmentation");
|
|
2142
|
+
const errors = validateContractCoverage(contract, coverage, { phase: "qa", sourceDigest: input.sourceStateDigest, inputBasisDigest: input.reviewBasisDigest, attemptId: binding.attemptId, sessionId: binding.sessionId });
|
|
2143
|
+
if (errors.length)
|
|
2144
|
+
throw new Error(errors.join("; "));
|
|
2123
2145
|
if (!receipt || !retainedCoverage || canonicalJson(retainedCoverage) !== canonicalJson(coverage))
|
|
2124
2146
|
throw new Error("Coverage is not bound to the retained terminal review receipt and authorized host augmentation");
|
|
2125
2147
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-foreman",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.29",
|
|
4
4
|
"description": "An orchestrator that keeps Codex CLI / Claude Code builders moving through their step list unattended. Part of Rafi.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -311,8 +311,8 @@
|
|
|
311
311
|
"luxon": "^3.7.2",
|
|
312
312
|
"parse5": "7.2.1",
|
|
313
313
|
"pdfjs-dist": "4.10.38",
|
|
314
|
-
"rafi-spec": "0.8.
|
|
315
|
-
"special-agents": "0.8.
|
|
314
|
+
"rafi-spec": "0.8.18",
|
|
315
|
+
"special-agents": "0.8.18",
|
|
316
316
|
"yaml": "^2.5.1",
|
|
317
317
|
"zod": "4.4.3"
|
|
318
318
|
},
|