ai-foreman 1.7.19 → 1.7.21
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/README.md +29 -5
- package/dist/activity.d.ts +3 -0
- package/dist/activity.js +18 -6
- package/dist/adapters/claude.d.ts +16 -0
- package/dist/adapters/claude.js +28 -3
- package/dist/adapters/codex.d.ts +20 -0
- package/dist/adapters/codex.js +99 -5
- package/dist/adapters/recovering.d.ts +9 -0
- package/dist/adapters/recovering.js +10 -0
- package/dist/adapters/types.d.ts +15 -0
- package/dist/agentRun.d.ts +7 -0
- package/dist/agentRun.js +24 -13
- package/dist/branch/currentGuard.d.ts +1 -0
- package/dist/branch/currentGuard.js +1 -0
- package/dist/branch/finalization.js +6 -1
- package/dist/branch/git.d.ts +1 -0
- package/dist/branch/git.js +15 -1
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +92 -5
- package/dist/buildAdmission.d.ts +61 -0
- package/dist/buildAdmission.js +286 -0
- package/dist/buildApproval.d.ts +2 -0
- package/dist/buildApproval.js +17 -0
- package/dist/buildAssignment.d.ts +33 -0
- package/dist/buildAssignment.js +215 -0
- package/dist/buildInterventions.d.ts +90 -0
- package/dist/buildInterventions.js +334 -0
- package/dist/buildLaunchGate.d.ts +1 -0
- package/dist/buildLaunchGate.js +36 -0
- package/dist/buildOwnershipReconciliation.d.ts +19 -0
- package/dist/buildOwnershipReconciliation.js +41 -0
- package/dist/buildOwnershipRepair.d.ts +33 -0
- package/dist/buildOwnershipRepair.js +111 -0
- package/dist/buildRuns.d.ts +24 -1
- package/dist/buildRuns.js +216 -70
- package/dist/buildSyntheticWork.d.ts +2 -0
- package/dist/buildSyntheticWork.js +30 -0
- package/dist/buildWorkAdmission.d.ts +17 -0
- package/dist/buildWorkAdmission.js +57 -0
- package/dist/buildWorkAuthorization.d.ts +2 -0
- package/dist/buildWorkAuthorization.js +23 -0
- package/dist/buildWorkContext.d.ts +3 -0
- package/dist/buildWorkContext.js +6 -0
- package/dist/buildWorkMigration.d.ts +9 -0
- package/dist/buildWorkMigration.js +206 -0
- package/dist/buildWorkReconciliation.d.ts +12 -0
- package/dist/buildWorkReconciliation.js +70 -0
- package/dist/builderGuidanceFollowup.d.ts +15 -0
- package/dist/builderGuidanceFollowup.js +34 -0
- package/dist/cli/doctor.js +3 -2
- package/dist/cli/graph.d.ts +2 -0
- package/dist/cli/graph.js +75 -0
- package/dist/cli/manager.d.ts +8 -0
- package/dist/cli/manager.js +234 -12
- package/dist/cli/recovery.js +3 -2
- package/dist/cli/runtimeAuthPrompt.d.ts +5 -0
- package/dist/cli/runtimeAuthPrompt.js +41 -7
- package/dist/cli/start.d.ts +8 -0
- package/dist/cli/start.js +2091 -1849
- package/dist/cli/status.js +20 -5
- package/dist/cli/tickets.js +9 -7
- package/dist/continuity.d.ts +16 -1
- package/dist/continuity.js +68 -12
- package/dist/foreman.d.ts +12 -4
- package/dist/foreman.js +397 -60
- package/dist/graph/boundary.d.ts +15 -0
- package/dist/graph/boundary.js +50 -0
- package/dist/graph/bridge.d.ts +9 -0
- package/dist/graph/bridge.js +146 -0
- package/dist/graph/bridge.py +205 -0
- package/dist/graph/config.d.ts +11 -0
- package/dist/graph/config.js +40 -0
- package/dist/graph/corpus.d.ts +16 -0
- package/dist/graph/corpus.js +188 -0
- package/dist/graph/derived.d.ts +17 -0
- package/dist/graph/derived.js +73 -0
- package/dist/graph/host.d.ts +4 -0
- package/dist/graph/host.js +160 -0
- package/dist/graph/install.d.ts +3 -0
- package/dist/graph/install.js +50 -0
- package/dist/graph/lifecycle.d.ts +11 -0
- package/dist/graph/lifecycle.js +124 -0
- package/dist/graph/maintenance.d.ts +43 -0
- package/dist/graph/maintenance.js +301 -0
- package/dist/graph/manager.d.ts +9 -0
- package/dist/graph/manager.js +92 -0
- package/dist/graph/preparation.d.ts +41 -0
- package/dist/graph/preparation.js +43 -0
- package/dist/graph/read.d.ts +27 -0
- package/dist/graph/read.js +152 -0
- package/dist/graph/semantic.d.ts +17 -0
- package/dist/graph/semantic.js +27 -0
- package/dist/graph/session.d.ts +8 -0
- package/dist/graph/session.js +92 -0
- package/dist/graph/sources.d.ts +4 -0
- package/dist/graph/sources.js +11 -0
- package/dist/graph/storage.d.ts +38 -0
- package/dist/graph/storage.js +159 -0
- package/dist/graph/turn.d.ts +17 -0
- package/dist/graph/turn.js +360 -0
- package/dist/graph/util.d.ts +5 -0
- package/dist/graph/util.js +51 -0
- package/dist/handoffs.d.ts +1 -0
- package/dist/handoffs.js +5 -1
- package/dist/humanDecision.d.ts +21 -2
- package/dist/humanDecision.js +169 -10
- package/dist/index.js +4 -1
- package/dist/installOwnership.d.ts +46 -0
- package/dist/installOwnership.js +440 -0
- package/dist/linuxProbeInventory.d.ts +12 -0
- package/dist/linuxProbeInventory.js +48 -0
- package/dist/log.d.ts +1 -1
- package/dist/log.js +7 -1
- package/dist/managerEvidence.d.ts +25 -0
- package/dist/managerEvidence.js +326 -0
- package/dist/managerEvidenceArtifacts.d.ts +38 -0
- package/dist/managerEvidenceArtifacts.js +118 -0
- package/dist/managerPacket.d.ts +2 -0
- package/dist/managerPacket.js +23 -0
- package/dist/markers.d.ts +1 -1
- package/dist/markers.js +2 -2
- package/dist/observability.js +1 -1
- package/dist/processIdentity.d.ts +21 -1
- package/dist/processIdentity.js +87 -5
- package/dist/providerPhase.d.ts +13 -0
- package/dist/providerPhase.js +26 -0
- package/dist/qaAuthorizedDisposition.d.ts +24 -0
- package/dist/qaAuthorizedDisposition.js +26 -0
- package/dist/qaBuildGate.d.ts +4 -0
- package/dist/qaBuildGate.js +216 -0
- package/dist/qaBuilderCoverage.d.ts +5 -0
- package/dist/qaBuilderCoverage.js +78 -0
- package/dist/qaContractCoverage.d.ts +10 -0
- package/dist/qaContractCoverage.js +55 -0
- package/dist/qaContractDelivery.d.ts +23 -0
- package/dist/qaContractDelivery.js +145 -0
- package/dist/qaContractFreshness.d.ts +15 -0
- package/dist/qaContractFreshness.js +36 -0
- package/dist/qaDeliveryJournal.d.ts +10 -0
- package/dist/qaDeliveryJournal.js +16 -1
- package/dist/qaEffectiveConfig.d.ts +20 -0
- package/dist/qaEffectiveConfig.js +40 -0
- package/dist/qaEvidenceReader.d.ts +14 -0
- package/dist/qaEvidenceReader.js +176 -0
- package/dist/qaFailureDelivery.d.ts +2 -0
- package/dist/qaFailureDelivery.js +107 -10
- package/dist/qaHandbackMigration.d.ts +1 -1
- package/dist/qaHandbackMigration.js +3 -3
- package/dist/qaPreparation.d.ts +26 -0
- package/dist/qaPreparation.js +150 -0
- package/dist/qaPreparationInputs.d.ts +8 -0
- package/dist/qaPreparationInputs.js +47 -0
- package/dist/qaPreparationMetrics.d.ts +145 -0
- package/dist/qaPreparationMetrics.js +120 -0
- package/dist/qaPreparationPolicy.d.ts +5 -0
- package/dist/qaPreparationPolicy.js +51 -0
- package/dist/qaPreparationStore.d.ts +157 -0
- package/dist/qaPreparationStore.js +412 -0
- package/dist/qaProtocolV2.d.ts +28 -0
- package/dist/qaProtocolV2.js +12 -0
- package/dist/qaReview.d.ts +6 -0
- package/dist/qaReview.js +293 -41
- package/dist/qaTimeline.d.ts +18 -0
- package/dist/qaTimeline.js +123 -0
- package/dist/qaVerificationContract.d.ts +17 -0
- package/dist/qaVerificationContract.js +236 -0
- package/dist/readinessCleanup.d.ts +19 -0
- package/dist/readinessCleanup.js +122 -0
- package/dist/readinessGate.d.ts +1 -0
- package/dist/readinessGate.js +82 -0
- package/dist/recoveryGuidance.d.ts +17 -0
- package/dist/recoveryGuidance.js +38 -0
- package/dist/runtimeAuth.d.ts +1 -0
- package/dist/runtimeAuth.js +5 -1
- package/dist/runtimeReadiness.d.ts +13 -0
- package/dist/runtimeReadiness.js +206 -10
- package/dist/sessionIdentity.d.ts +2 -0
- package/dist/sessionIdentity.js +14 -0
- package/dist/sessionLifecycle.js +3 -3
- package/dist/sources/sourceRegistry.js +7 -0
- package/dist/stateTransfer.js +85 -8
- package/dist/statusReporter.d.ts +1 -0
- package/dist/statusReporter.js +1 -0
- package/dist/supervisedStart.d.ts +6 -0
- package/dist/supervisedStart.js +178 -62
- package/dist/supervisor.d.ts +2 -0
- package/dist/supervisor.js +26 -2
- package/dist/terminalStyle.d.ts +21 -0
- package/dist/terminalStyle.js +50 -0
- package/dist/ticketPopulation.d.ts +1 -0
- package/dist/ticketPopulation.js +5 -1
- package/dist/tickets/commands.js +2 -2
- package/dist/tickets/queue.js +1 -1
- package/dist/tickets/ticketSchema.d.ts +198 -0
- package/dist/tickets/ticketSchema.js +2 -0
- package/dist/windowsProbeJob.d.ts +12 -0
- package/dist/windowsProbeJob.js +144 -0
- package/dist/workflowDb.d.ts +121 -6
- package/dist/workflowDb.js +874 -30
- package/dist/workflowReader.d.ts +116 -2
- package/dist/workflowReader.js +131 -4
- package/package.json +89 -8
- package/scripts/copy-graph-assets.mjs +3 -0
- package/scripts/graph-benchmark.mjs +20 -0
- package/scripts/graph-evaluation.mjs +85 -0
package/dist/humanDecision.js
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
|
+
import { formatDecisionCommands, formatExactRunRecovery } from "./recoveryGuidance.js";
|
|
1
2
|
import { createHash } from "node:crypto";
|
|
2
3
|
import { isCancel } from "@clack/prompts";
|
|
3
4
|
import { WorkflowDb } from "./workflowDb.js";
|
|
4
5
|
import { pauseActivityForInput } from "./activity.js";
|
|
6
|
+
export class HumanDecisionCancelled extends Error {
|
|
7
|
+
constructor() { super("Build cancelled at an input prompt"); this.name = "HumanDecisionCancelled"; }
|
|
8
|
+
}
|
|
5
9
|
export class HumanDecisionRequired extends Error {
|
|
6
10
|
decisionId;
|
|
7
11
|
runId;
|
|
8
|
-
constructor(decisionId, runId, prompt) {
|
|
9
|
-
super(`${prompt}\nRafi is waiting for input (${decisionId})
|
|
12
|
+
constructor(decisionId, runId, prompt, projectDir = process.cwd(), choices = []) {
|
|
13
|
+
super(`${prompt}\nRafi is waiting for input (${decisionId}).\n${formatDecisionCommands(projectDir, runId, { decisionId, choices }).join("\n")}\nResume with ${formatExactRunRecovery(projectDir, runId)}`);
|
|
10
14
|
this.decisionId = decisionId;
|
|
11
15
|
this.runId = runId;
|
|
12
16
|
this.name = "HumanDecisionRequired";
|
|
@@ -16,24 +20,70 @@ export async function durableHumanDecision(input) {
|
|
|
16
20
|
const db = new WorkflowDb(input.projectDir);
|
|
17
21
|
try {
|
|
18
22
|
const key = `${input.runId}:${input.key}:${createHash("sha256").update(JSON.stringify([input.prompt, input.choices])).digest("hex")}`;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const legacy = db.pendingHumanDecisions(input.runId).find(item => item.interruptionId === (input.ticketId ? `ticket:${input.ticketId}` : input.key) && item.prompt === input.prompt && JSON.stringify(item.choices.map(choice => choice.label)) === JSON.stringify(input.choices.map(choice => choice.label)) && db.humanDecisionKey(item.decisionId)?.startsWith(`${input.runId}:${input.key}:`));
|
|
24
|
+
const decision = legacy ?? db.ensureHumanDecision({ decisionKey: key, runId: input.runId, interruptionId: input.ticketId ? `ticket:${input.ticketId}` : input.key, prompt: input.prompt, choices: input.choices });
|
|
25
|
+
if (decision.status === "answered") {
|
|
26
|
+
if (input.ticketId && !db.decisionContinuationAvailable(input.runId, decision.decisionId))
|
|
27
|
+
throw new Error("Answered ticket decision has already been dispatched or is uncertain; reconcile before reusing its answer");
|
|
28
|
+
input.onAnswered?.(decision);
|
|
29
|
+
return decisionResponse(decision);
|
|
30
|
+
}
|
|
22
31
|
console.error(`rafi: input required: ${input.prompt} [decision ${decision.decisionId}]`);
|
|
23
32
|
const previous = db.getRun(input.runId);
|
|
24
33
|
if (!input.defer)
|
|
25
34
|
db.transition(input.runId, { status: "paused", checkpoint: "waiting-for-human", state: { ...previous.state, status: "recoverable", checkpoint: "waiting-for-human", phase: "waiting-for-human", pendingDecisionId: decision.decisionId } });
|
|
26
35
|
if (input.defer || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
27
|
-
throw new HumanDecisionRequired(decision.decisionId, input.runId, input.prompt);
|
|
28
|
-
const
|
|
29
|
-
|
|
36
|
+
throw new HumanDecisionRequired(decision.decisionId, input.runId, input.prompt, input.projectDir, decision.choices);
|
|
37
|
+
const controller = new AbortController();
|
|
38
|
+
let poll;
|
|
39
|
+
let remoteAnswered = false;
|
|
40
|
+
const remote = new Promise((resolve, reject) => {
|
|
41
|
+
poll = setInterval(() => {
|
|
42
|
+
try {
|
|
43
|
+
if (input.ticketId)
|
|
44
|
+
db.consumeInstructionControls(input.runId, input.ticketId);
|
|
45
|
+
if (input.ticketId && input.key.startsWith("qa-nonconvergence:"))
|
|
46
|
+
db.consumeInstructionControls(input.runId, input.ticketId, "attempts");
|
|
47
|
+
const current = db.humanDecision(decision.decisionId);
|
|
48
|
+
if (current?.status === "answered") {
|
|
49
|
+
remoteAnswered = true;
|
|
50
|
+
resolve(decisionResponse(current));
|
|
51
|
+
controller.abort();
|
|
52
|
+
}
|
|
53
|
+
else if (!current || current.status !== "pending") {
|
|
54
|
+
controller.abort();
|
|
55
|
+
reject(new Error("Pending decision was superseded; resume the durable scoped recovery before continuing"));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
reject(error);
|
|
60
|
+
}
|
|
61
|
+
}, 250);
|
|
62
|
+
});
|
|
63
|
+
const wait = () => pauseActivityForInput(() => Promise.race([input.operation(controller.signal), remote]));
|
|
64
|
+
let answer;
|
|
65
|
+
try {
|
|
66
|
+
answer = await (input.observer ? input.observer.span("user_wait", input.prompt, wait) : wait());
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
if (poll)
|
|
70
|
+
clearInterval(poll);
|
|
71
|
+
controller.abort();
|
|
72
|
+
}
|
|
73
|
+
if (remoteAnswered) {
|
|
74
|
+
input.onAnswered?.(db.humanDecision(decision.decisionId));
|
|
75
|
+
const current = db.getRun(input.runId);
|
|
76
|
+
db.transition(input.runId, { status: previous.status, checkpoint: "decision-received", state: { ...current.state, pendingDecisionId: undefined } });
|
|
77
|
+
return answer;
|
|
78
|
+
}
|
|
30
79
|
if (isCancel(answer) || answer === undefined)
|
|
31
80
|
return answer;
|
|
32
81
|
const selected = String(answer);
|
|
33
|
-
const choice =
|
|
82
|
+
const choice = decision.choices.find(item => item.id === selected || item.label === selected);
|
|
34
83
|
if (!choice && !input.choices.some(item => item.id === "custom"))
|
|
35
84
|
throw new Error("answer does not match the durable decision choices");
|
|
36
|
-
db.answerHumanDecision(input.runId, decision.decisionId, choice?.id ?? "custom", undefined, choice ? undefined : selected);
|
|
85
|
+
const answered = db.answerHumanDecision(input.runId, decision.decisionId, choice?.id ?? "custom", undefined, choice ? undefined : selected);
|
|
86
|
+
input.onAnswered?.(answered);
|
|
37
87
|
db.transition(input.runId, { status: previous.status, checkpoint: "decision-received", state: { ...previous.state, pendingDecisionId: undefined } });
|
|
38
88
|
return answer;
|
|
39
89
|
}
|
|
@@ -41,3 +91,112 @@ export async function durableHumanDecision(input) {
|
|
|
41
91
|
db.close();
|
|
42
92
|
}
|
|
43
93
|
}
|
|
94
|
+
/** Fixed choices deliver their meaning, rather than a generated menu identifier. */
|
|
95
|
+
export function decisionResponse(decision) {
|
|
96
|
+
return decision.answer ?? (decision.selectedChoiceId?.startsWith("option-") ? decision.choices.find(choice => choice.id === decision.selectedChoiceId)?.label : decision.selectedChoiceId);
|
|
97
|
+
}
|
|
98
|
+
/** Returns false on safe pause/EOF. Never launches a provider or grants capabilities. */
|
|
99
|
+
export async function servicePendingHumanDecisions(input) {
|
|
100
|
+
const db = new WorkflowDb(input.projectDir);
|
|
101
|
+
try {
|
|
102
|
+
if (input.scopeRevision)
|
|
103
|
+
db.refreshStaleTicketDecisions(input.runId, input.scopeRevision, input.tickets);
|
|
104
|
+
for (const work of db.workDefinitions(input.runId))
|
|
105
|
+
if (!input.tickets || input.tickets.includes(work.workId)) {
|
|
106
|
+
db.consumeInstructionControls(input.runId, work.workId);
|
|
107
|
+
db.consumeInstructionControls(input.runId, work.workId, "attempts");
|
|
108
|
+
}
|
|
109
|
+
const applicable = () => db.pendingHumanDecisions(input.runId).filter(decision => !input.tickets || !db.decisionWorkId(decision) || input.tickets.includes(db.decisionWorkId(decision)));
|
|
110
|
+
for (const decision of applicable()) {
|
|
111
|
+
if (db.humanDecision(decision.decisionId)?.status !== "pending")
|
|
112
|
+
continue;
|
|
113
|
+
const run = db.getRun(input.runId);
|
|
114
|
+
db.transition(input.runId, { status: "paused", checkpoint: "waiting-for-human", state: { ...run.state, status: "recoverable", phase: "waiting-for-human", checkpoint: "waiting-for-human", pendingDecisionId: decision.decisionId } });
|
|
115
|
+
if (!input.prompt && (!process.stdin.isTTY || !process.stdout.isTTY))
|
|
116
|
+
throw new HumanDecisionRequired(decision.decisionId, input.runId, decision.prompt, input.projectDir, decision.choices);
|
|
117
|
+
const controller = new AbortController();
|
|
118
|
+
const ask = async () => {
|
|
119
|
+
if (input.prompt)
|
|
120
|
+
return input.prompt(decision, controller.signal);
|
|
121
|
+
const { select, text } = await import("@clack/prompts");
|
|
122
|
+
const choice = await select({ message: decision.prompt, signal: controller.signal, options: [...decision.choices.map(item => ({ value: item.id, label: item.label })), { value: "__rafi_pause__", label: "Pause safely" }, { value: "__rafi_cancel__", label: "Cancel build" }] });
|
|
123
|
+
if (isCancel(choice) || choice === "__rafi_pause__")
|
|
124
|
+
return undefined;
|
|
125
|
+
if (choice !== "custom")
|
|
126
|
+
return choice;
|
|
127
|
+
const answer = await text({ message: "Custom response:", signal: controller.signal, validate: value => value?.trim() ? undefined : "Enter a response" });
|
|
128
|
+
return isCancel(answer) ? undefined : String(answer);
|
|
129
|
+
};
|
|
130
|
+
let remoteAnswered = false;
|
|
131
|
+
let poll;
|
|
132
|
+
const remote = new Promise((resolve, reject) => {
|
|
133
|
+
poll = setInterval(() => {
|
|
134
|
+
try {
|
|
135
|
+
const workId = db.decisionWorkId(decision);
|
|
136
|
+
if (workId)
|
|
137
|
+
db.consumeInstructionControls(input.runId, workId);
|
|
138
|
+
if (workId)
|
|
139
|
+
db.consumeInstructionControls(input.runId, workId, "attempts");
|
|
140
|
+
const current = db.humanDecision(decision.decisionId);
|
|
141
|
+
if (current?.status === "answered") {
|
|
142
|
+
remoteAnswered = true;
|
|
143
|
+
resolve(decisionResponse(current));
|
|
144
|
+
controller.abort();
|
|
145
|
+
}
|
|
146
|
+
else if (current?.status === "cancelled" && db.humanDecisionKey(decision.decisionId)?.startsWith(`${input.runId}:qa-nonconvergence:`)) {
|
|
147
|
+
remoteAnswered = true;
|
|
148
|
+
resolve(undefined);
|
|
149
|
+
controller.abort();
|
|
150
|
+
}
|
|
151
|
+
else if (!current || current.status !== "pending")
|
|
152
|
+
reject(new Error("Pending decision changed; refresh before continuing"));
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
reject(error);
|
|
156
|
+
}
|
|
157
|
+
}, 250);
|
|
158
|
+
});
|
|
159
|
+
const wait = () => pauseActivityForInput(() => Promise.race([ask(), remote]));
|
|
160
|
+
const lease = db.currentLease();
|
|
161
|
+
const heartbeat = lease?.runId === input.runId ? setInterval(() => { try {
|
|
162
|
+
db.heartbeatLease(lease);
|
|
163
|
+
}
|
|
164
|
+
catch { /* fenced mutation rechecks authority after input */ } }, 10_000) : undefined;
|
|
165
|
+
heartbeat?.unref();
|
|
166
|
+
let answer;
|
|
167
|
+
try {
|
|
168
|
+
answer = await (input.observer ? input.observer.span("user_wait", decision.prompt, wait) : wait());
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
if (heartbeat)
|
|
172
|
+
clearInterval(heartbeat);
|
|
173
|
+
if (poll)
|
|
174
|
+
clearInterval(poll);
|
|
175
|
+
controller.abort();
|
|
176
|
+
}
|
|
177
|
+
if (remoteAnswered) {
|
|
178
|
+
const current = db.getRun(input.runId);
|
|
179
|
+
db.transition(input.runId, { status: run.status, checkpoint: "decision-received", state: { ...current.state, pendingDecisionId: undefined } });
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
if (answer === "__rafi_cancel__") {
|
|
183
|
+
db.atomic(() => {
|
|
184
|
+
db.cancelPendingHumanDecisions(input.runId, "User cancelled build; pending questions superseded");
|
|
185
|
+
const current = db.getRun(input.runId);
|
|
186
|
+
db.transition(input.runId, { status: "cancelled", checkpoint: "cancelled", state: { ...current.state, status: "cancelled", checkpoint: "cancelled", pendingDecisionId: undefined }, event: "user_cancelled", payload: { source: "question-menu" } });
|
|
187
|
+
});
|
|
188
|
+
throw new HumanDecisionCancelled();
|
|
189
|
+
}
|
|
190
|
+
if (answer === undefined)
|
|
191
|
+
return false;
|
|
192
|
+
const choice = decision.choices.find(item => item.id === answer);
|
|
193
|
+
db.answerHumanDecision(input.runId, decision.decisionId, choice?.id ?? "custom", undefined, choice ? undefined : answer);
|
|
194
|
+
const current = db.getRun(input.runId);
|
|
195
|
+
db.transition(input.runId, { status: run.status, checkpoint: "decision-received", state: { ...current.state, pendingDecisionId: undefined } });
|
|
196
|
+
}
|
|
197
|
+
return applicable().length === 0;
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
db.close();
|
|
201
|
+
}
|
|
202
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { withRecoveryCommandFamily } from "./recoveryGuidance.js";
|
|
2
3
|
import { HumanDecisionRequired } from "./humanDecision.js";
|
|
3
4
|
import { Command } from "commander";
|
|
5
|
+
import { buildGraphCommand } from "./cli/graph.js";
|
|
4
6
|
import { readFileSync } from "node:fs";
|
|
5
7
|
import { buildTicketsCommand } from "./cli/tickets.js";
|
|
6
8
|
import { buildStartCommand } from "./cli/start.js";
|
|
@@ -17,6 +19,7 @@ program
|
|
|
17
19
|
.description("Keep Codex / Claude Code builders moving through their step list.")
|
|
18
20
|
.version(PACKAGE_VERSION);
|
|
19
21
|
program.addCommand(buildTicketsCommand());
|
|
22
|
+
program.addCommand(buildGraphCommand());
|
|
20
23
|
program.addCommand(buildStartCommand());
|
|
21
24
|
program.addCommand(buildStatusCommand());
|
|
22
25
|
program.addCommand(buildDoctorCommand());
|
|
@@ -31,7 +34,7 @@ const argv = [...process.argv];
|
|
|
31
34
|
if (argv[2] === "--")
|
|
32
35
|
argv.splice(2, 1);
|
|
33
36
|
const command = argv.slice(2).filter((arg) => !arg.startsWith("-")).slice(0, 3).join(" ") || "ai-foreman";
|
|
34
|
-
withActivityContext(command, () => program.parseAsync(argv)).catch((err) => {
|
|
37
|
+
withActivityContext(command, () => withRecoveryCommandFamily("ai-foreman", () => program.parseAsync(argv))).catch((err) => {
|
|
35
38
|
console.error(`foreman: ${String(err)}`);
|
|
36
39
|
process.exit(err instanceof HumanDecisionRequired ? 2 : 1);
|
|
37
40
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { InstallDependencyV1, InstallManifest, InstallManifestEntryV1, InstallManifestEntryV2, InstallManifestV2, InstallOwnershipCategory } from "rafi-spec";
|
|
2
|
+
export declare const INSTALL_MANIFEST = ".rafi/install-manifest.json";
|
|
3
|
+
export declare const OWNERSHIP_BACKUPS = ".rafi/ownership-backups";
|
|
4
|
+
export declare function initializeInstallManifest(projectDir: string, dirtyChoice: InstallManifestV2["repository"]["dirtyChoice"]): InstallManifestV2;
|
|
5
|
+
export declare function readInstallManifest(projectDir: string): InstallManifestV2 | undefined;
|
|
6
|
+
export declare function registerOwnedFile(projectDir: string, relativePath: string, input: Omit<InstallManifestEntryV1, "path" | "sha256"> & {
|
|
7
|
+
sha256?: string | null;
|
|
8
|
+
category?: InstallOwnershipCategory;
|
|
9
|
+
}): InstallManifestV2;
|
|
10
|
+
export declare function prepareOwnedWrite(projectDir: string, relativePath: string, origin: string, category: InstallOwnershipCategory): InstallManifestV2;
|
|
11
|
+
export declare function finalizePreparedOwnedWrite(projectDir: string, relativePath: string, origin: string, category: InstallOwnershipCategory): InstallManifestV2;
|
|
12
|
+
/** Capture an exact pre-Rafi file before a managed write. */
|
|
13
|
+
export declare function capturePreimage(projectDir: string, relativePath: string, origin: string, category?: InstallOwnershipCategory): InstallManifestEntryV2;
|
|
14
|
+
export declare function finalizeOwnedWrite(projectDir: string, entry: InstallManifestEntryV2): InstallManifestV2;
|
|
15
|
+
export declare function registerDependency(projectDir: string, dependency: InstallDependencyV1): InstallManifestV2;
|
|
16
|
+
export declare function validateInstallManifest(projectDir: string, manifest: InstallManifest): void;
|
|
17
|
+
export declare function validateOwnedPath(projectDir: string, value: string): string;
|
|
18
|
+
export declare function listOwnedTree(projectDir: string, relativeDirectory: string): string[];
|
|
19
|
+
export declare function removeOwnedPathsTransaction(projectDir: string, paths: string[]): {
|
|
20
|
+
runId: string;
|
|
21
|
+
recoveryId: string;
|
|
22
|
+
removed: string[];
|
|
23
|
+
};
|
|
24
|
+
export declare function restoreOwnedPreimagesTransaction(projectDir: string, entries: InstallManifestEntryV2[]): {
|
|
25
|
+
runId: string;
|
|
26
|
+
recoveryId: string;
|
|
27
|
+
restored: string[];
|
|
28
|
+
};
|
|
29
|
+
/** Preserve recorded pre-Rafi bytes for a file the user chose to keep today. */
|
|
30
|
+
export declare function preservePreimagesForLaterRestore(projectDir: string, entries: InstallManifestEntryV2[]): {
|
|
31
|
+
recoveryId: string;
|
|
32
|
+
preserved: string[];
|
|
33
|
+
} | undefined;
|
|
34
|
+
export declare function removeManagedBlocksTransaction(projectDir: string, entries: InstallManifestEntryV2[]): {
|
|
35
|
+
runId: string;
|
|
36
|
+
recoveryId: string;
|
|
37
|
+
edited: string[];
|
|
38
|
+
};
|
|
39
|
+
export declare function restoreUninstallRecovery(projectDir: string, recoveryId: string, selectedPaths?: string[], overwrite?: boolean): {
|
|
40
|
+
restored: string[];
|
|
41
|
+
collisions: string[];
|
|
42
|
+
backupId?: string;
|
|
43
|
+
};
|
|
44
|
+
export declare function cleanupUninstallRecovery(projectDir: string, recoveryIds: string[]): string[];
|
|
45
|
+
export declare function listUninstallRecoveries(projectDir: string): string[];
|
|
46
|
+
export declare function categorizeOwnedPath(path: string, mode?: string): InstallOwnershipCategory;
|