ai-foreman 1.7.20 → 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 +12 -2
- 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 +90 -4
- package/dist/buildAdmission.js +2 -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/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 +6 -0
- package/dist/buildRuns.js +23 -17
- 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.js +28 -8
- package/dist/cli/start.js +152 -48
- package/dist/cli/status.js +20 -5
- package/dist/cli/tickets.js +9 -7
- package/dist/continuity.d.ts +11 -0
- package/dist/continuity.js +22 -6
- package/dist/foreman.d.ts +10 -2
- package/dist/foreman.js +330 -39
- 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 -11
- package/dist/index.js +2 -0
- 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.js +7 -0
- 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 +234 -24
- 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.js +12 -5
- package/dist/recoveryGuidance.d.ts +9 -0
- package/dist/recoveryGuidance.js +13 -0
- package/dist/runtimeAuth.d.ts +1 -0
- package/dist/runtimeAuth.js +5 -1
- package/dist/runtimeReadiness.d.ts +1 -0
- package/dist/runtimeReadiness.js +13 -1
- 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 +50 -1
- package/dist/statusReporter.d.ts +1 -0
- package/dist/statusReporter.js +1 -0
- package/dist/supervisedStart.js +18 -8
- 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/workflowDb.d.ts +51 -5
- package/dist/workflowDb.js +338 -7
- package/dist/workflowReader.d.ts +109 -2
- package/dist/workflowReader.js +48 -4
- package/package.json +84 -7
- package/scripts/copy-graph-assets.mjs +3 -0
- package/scripts/graph-benchmark.mjs +20 -0
- package/scripts/graph-evaluation.mjs +85 -0
package/dist/agentRun.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { withGraphDerivedAccess } from "./graph/derived.js";
|
|
1
2
|
import { type PermissionConfig } from "./config.js";
|
|
2
3
|
import { Log } from "./log.js";
|
|
3
4
|
import { type StepStatus } from "./foreman.js";
|
|
@@ -6,10 +7,13 @@ import type { AnsweredProviderQuestion } from "./providerQuestions.js";
|
|
|
6
7
|
import { type RoleBundle } from "./roles.js";
|
|
7
8
|
import type { AgentRuntime } from "./runtimeAuth.js";
|
|
8
9
|
import type { AgentRoleDefaultsV1, ProviderSessionRefV1 } from "rafi-spec";
|
|
10
|
+
import type { GraphPurpose } from "rafi-spec";
|
|
9
11
|
export type { EffortLevel } from "./adapters/types.js";
|
|
10
12
|
export declare const VALID_EFFORT: readonly EffortLevel[];
|
|
11
13
|
export interface RoleBuilderOptions {
|
|
14
|
+
graphPurpose?: GraphPurpose;
|
|
12
15
|
projectDir: string;
|
|
16
|
+
configRoot?: string;
|
|
13
17
|
role: string;
|
|
14
18
|
agent?: string;
|
|
15
19
|
model?: string;
|
|
@@ -21,6 +25,7 @@ export interface RoleBuilderOptions {
|
|
|
21
25
|
log?: Log;
|
|
22
26
|
permissionConfig?: PermissionConfig;
|
|
23
27
|
extraSkills?: string[];
|
|
28
|
+
preloadedSkillContent?: BuilderAdapterOptions["preloadedSkillContent"];
|
|
24
29
|
sandboxMode?: BuilderAdapterOptions["sandboxMode"];
|
|
25
30
|
/** Disable recovery-session persistence for isolated, non-recoverable roles such as Manager. */
|
|
26
31
|
persistSessionBindings?: boolean;
|
|
@@ -33,6 +38,7 @@ export interface RoleBuilderOptions {
|
|
|
33
38
|
onProviderQuestion?: (request: import("./adapters/types.js").PermissionRequest) => void;
|
|
34
39
|
}
|
|
35
40
|
export interface RoleBuilder {
|
|
41
|
+
graphRoot?: string;
|
|
36
42
|
builder: BuilderAdapter;
|
|
37
43
|
runtime: AgentRuntime;
|
|
38
44
|
model?: string;
|
|
@@ -65,5 +71,6 @@ export declare function makeLogPath(projectDir: string, label: string): string;
|
|
|
65
71
|
export declare function mergeSkills(roleSkills: readonly string[], extraSkills?: readonly string[]): string[];
|
|
66
72
|
export declare function readOnlyPermissionConfig(): PermissionConfig;
|
|
67
73
|
export declare function createRoleBuilder(opts: RoleBuilderOptions): Promise<RoleBuilder>;
|
|
74
|
+
export declare function sendRoleGraphTurn(role: RoleBuilder, instruction: string, purpose: GraphPurpose, policy?: Parameters<BuilderAdapter["sendTurn"]>[1]): Promise<TurnResult>;
|
|
68
75
|
export declare function readRoleDefaultsForExecution(projectDir: string, role: string): AgentRoleDefaultsV1 | undefined;
|
|
69
76
|
export declare function runRoleInstruction(opts: RoleInstructionRunOptions): Promise<RoleInstructionRunResult>;
|
package/dist/agentRun.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export { withGraphDerivedAccess } from "./graph/derived.js";
|
|
1
2
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { loadConfig, DEFAULT_CONFIG } from "./config.js";
|
|
@@ -15,6 +16,7 @@ import { resolveAgentForProject } from "./cli/runtimeSelection.js";
|
|
|
15
16
|
import { parse as parseYaml } from "yaml";
|
|
16
17
|
import { captureWorkspaceIdentity, createProviderSessionRef, resolveUniqueSessionBinding } from "./sessionIdentity.js";
|
|
17
18
|
import { WorkflowDb } from "./workflowDb.js";
|
|
19
|
+
import { graphContext, sendGraphTurn } from "./graph/turn.js";
|
|
18
20
|
export const VALID_EFFORT = ["low", "medium", "high", "xhigh"];
|
|
19
21
|
export function assertEffortLevel(effort) {
|
|
20
22
|
if (effort && !VALID_EFFORT.includes(effort)) {
|
|
@@ -107,13 +109,14 @@ export async function createRoleBuilder(opts) {
|
|
|
107
109
|
if (!existsSync(opts.projectDir)) {
|
|
108
110
|
throw new Error(`project directory not found: ${opts.projectDir}`);
|
|
109
111
|
}
|
|
110
|
-
const
|
|
112
|
+
const executionRoot = opts.configRoot ?? opts.projectDir;
|
|
113
|
+
const saved = readRoleDefaultsForExecution(executionRoot, opts.role);
|
|
111
114
|
if (opts.resumeSessionRef && opts.resumeSessionId && opts.resumeSessionRef.sessionId !== opts.resumeSessionId) {
|
|
112
115
|
throw new Error("resume session ID does not match the supplied location-scoped reference");
|
|
113
116
|
}
|
|
114
117
|
let requestedResumeRef = opts.resumeSessionRef;
|
|
115
118
|
if (!requestedResumeRef && opts.resumeSessionId) {
|
|
116
|
-
const bindingDb = new WorkflowDb(
|
|
119
|
+
const bindingDb = new WorkflowDb(executionRoot);
|
|
117
120
|
try {
|
|
118
121
|
requestedResumeRef = resolveUniqueSessionBinding(bindingDb.providerSessionBindings(opts.resumeSessionId), opts.resumeSessionId);
|
|
119
122
|
}
|
|
@@ -124,19 +127,19 @@ export async function createRoleBuilder(opts) {
|
|
|
124
127
|
const configuredEffort = saved?.reasoning && saved.reasoning !== "default" ? saved.reasoning : undefined;
|
|
125
128
|
const effectiveEffort = opts.effort ?? (VALID_EFFORT.includes(configuredEffort) ? configuredEffort : undefined);
|
|
126
129
|
assertEffortLevel(effectiveEffort);
|
|
127
|
-
let runtime = resolveAgentForProject(
|
|
130
|
+
let runtime = resolveAgentForProject(executionRoot, opts.agent ?? requestedResumeRef?.provider ?? saved?.make);
|
|
128
131
|
if (requestedResumeRef && requestedResumeRef.provider !== runtime) {
|
|
129
132
|
throw new Error(`session ${requestedResumeRef.sessionId} belongs to ${requestedResumeRef.provider}, but ${runtime} was selected`);
|
|
130
133
|
}
|
|
131
134
|
if (requestedResumeRef && requestedResumeRef.role !== configurableRole(opts.role)) {
|
|
132
135
|
throw new Error(`session ${requestedResumeRef.sessionId} belongs to role ${requestedResumeRef.role}, not ${configurableRole(opts.role)}`);
|
|
133
136
|
}
|
|
134
|
-
const config = loadConfig(join(
|
|
135
|
-
const log = opts.log ?? new Log(makeLogPath(
|
|
136
|
-
const roleBundle = loadRoleBundle(opts.role, { projectDir: opts.projectDir });
|
|
137
|
+
const config = loadConfig(join(executionRoot, "foreman.yaml"));
|
|
138
|
+
const log = opts.log ?? new Log(makeLogPath(executionRoot, opts.label.replace(/\s+/g, "-")));
|
|
139
|
+
const roleBundle = loadRoleBundle(opts.role, { projectDir: opts.configRoot ?? opts.projectDir });
|
|
137
140
|
const initialModel = opts.model ?? (saved?.model !== "default" ? saved?.model : undefined) ?? roleBundle.model ?? undefined;
|
|
138
141
|
const effort = effectiveEffort ?? roleBundle.effort ?? undefined;
|
|
139
|
-
const ready = await ensureRuntimeReadyForCommand(
|
|
142
|
+
const ready = await ensureRuntimeReadyForCommand(executionRoot, runtime, {
|
|
140
143
|
label: opts.label,
|
|
141
144
|
yes: Boolean(opts.yes),
|
|
142
145
|
allowSwitch: requestedResumeRef || opts.resumeSessionId ? false : opts.allowSwitch,
|
|
@@ -151,7 +154,7 @@ export async function createRoleBuilder(opts) {
|
|
|
151
154
|
const makeAdapter = async (nextRuntime, resumeSessionId, resumeSessionRef) => {
|
|
152
155
|
let scopedRef = resumeSessionRef;
|
|
153
156
|
if (!scopedRef && resumeSessionId) {
|
|
154
|
-
const bindingDb = new WorkflowDb(
|
|
157
|
+
const bindingDb = new WorkflowDb(executionRoot);
|
|
155
158
|
try {
|
|
156
159
|
scopedRef = resolveUniqueSessionBinding(bindingDb.providerSessionBindings(resumeSessionId), resumeSessionId);
|
|
157
160
|
}
|
|
@@ -165,7 +168,7 @@ export async function createRoleBuilder(opts) {
|
|
|
165
168
|
stream: opts.role,
|
|
166
169
|
generation: 0,
|
|
167
170
|
cwd: opts.projectDir,
|
|
168
|
-
configRoot: opts.projectDir,
|
|
171
|
+
configRoot: opts.configRoot ?? opts.projectDir,
|
|
169
172
|
workspaceIdentity: captureWorkspaceIdentity(opts.projectDir),
|
|
170
173
|
source: "legacy-inferred",
|
|
171
174
|
});
|
|
@@ -175,7 +178,7 @@ export async function createRoleBuilder(opts) {
|
|
|
175
178
|
}
|
|
176
179
|
const adapterOpts = {
|
|
177
180
|
cwd: opts.projectDir,
|
|
178
|
-
configRoot: opts.projectDir,
|
|
181
|
+
configRoot: opts.configRoot ?? opts.projectDir,
|
|
179
182
|
runtimeExecutable,
|
|
180
183
|
runtimePhase: phaseForRole(opts.role),
|
|
181
184
|
onQuestionTrace: (trace) => log.write("question-round-trip", { ...trace }),
|
|
@@ -196,6 +199,7 @@ export async function createRoleBuilder(opts) {
|
|
|
196
199
|
sandboxMode: opts.sandboxMode,
|
|
197
200
|
systemPromptAppend: roleBundle.system || undefined,
|
|
198
201
|
skills: skills.length > 0 ? skills : undefined,
|
|
202
|
+
preloadedSkillContent: opts.preloadedSkillContent,
|
|
199
203
|
};
|
|
200
204
|
const adapter = nextRuntime === "codex"
|
|
201
205
|
? new CodexAdapter(adapterOpts)
|
|
@@ -221,7 +225,7 @@ export async function createRoleBuilder(opts) {
|
|
|
221
225
|
const persistSessionRef = (ref) => {
|
|
222
226
|
if (opts.persistSessionBindings === false)
|
|
223
227
|
return;
|
|
224
|
-
const bindingDb = new WorkflowDb(
|
|
228
|
+
const bindingDb = new WorkflowDb(executionRoot);
|
|
225
229
|
try {
|
|
226
230
|
bindingDb.recordProviderSessionBinding(ref);
|
|
227
231
|
}
|
|
@@ -239,7 +243,7 @@ export async function createRoleBuilder(opts) {
|
|
|
239
243
|
enabled: interactive,
|
|
240
244
|
allowSwitch: opts.allowSwitch !== false && !opts.resumeSessionId && !requestedResumeRef,
|
|
241
245
|
recreate: async (nextRuntime, resumeSessionId, resumeSessionRef) => {
|
|
242
|
-
const nextReady = await ensureRuntimeReadyForCommand(
|
|
246
|
+
const nextReady = await ensureRuntimeReadyForCommand(executionRoot, nextRuntime, {
|
|
243
247
|
label: opts.label,
|
|
244
248
|
allowSwitch: false,
|
|
245
249
|
model: nextRuntime === runtime ? model : undefined,
|
|
@@ -251,7 +255,13 @@ export async function createRoleBuilder(opts) {
|
|
|
251
255
|
},
|
|
252
256
|
onSessionRef: persistSessionRef,
|
|
253
257
|
});
|
|
254
|
-
return { builder, runtime, model, effort, roleBundle, skills, log };
|
|
258
|
+
return { builder, runtime, model, effort, roleBundle, skills, log, graphRoot: opts.configRoot ?? opts.projectDir };
|
|
259
|
+
}
|
|
260
|
+
export function sendRoleGraphTurn(role, instruction, purpose, policy) {
|
|
261
|
+
const root = role.graphRoot;
|
|
262
|
+
if (!root)
|
|
263
|
+
return role.builder.sendTurn(instruction, policy);
|
|
264
|
+
return sendGraphTurn(role.builder, instruction, graphContext(root, role.builder.sessionRef?.()?.cwd ?? root, purpose, instruction), policy);
|
|
255
265
|
}
|
|
256
266
|
function phaseForRole(role) {
|
|
257
267
|
if (role === "planner")
|
|
@@ -322,6 +332,7 @@ export async function runRoleInstruction(opts) {
|
|
|
322
332
|
const viewer = printEvents(builder.events());
|
|
323
333
|
const config = loadConfig(join(opts.projectDir, "foreman.yaml"));
|
|
324
334
|
const foreman = new Foreman(builder, log, { desktop: config.notifications.enabled, terminalBell: config.notifications.terminal_bell }, false, 3, opts.projectDir);
|
|
335
|
+
foreman.setGraphPurpose(opts.graphPurpose ?? { planner: "planning", discovery: "discovery", "ticket-maker": "ticket-population", uninstaller: "uninstall-analysis" }[opts.role] ?? "implementation");
|
|
325
336
|
try {
|
|
326
337
|
const turn = await foreman.runInstruction(opts.instruction);
|
|
327
338
|
await builder.close();
|
|
@@ -34,6 +34,7 @@ export declare class CurrentWorkflowGuardAdapter implements BuilderAdapter {
|
|
|
34
34
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
35
35
|
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
36
36
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
37
|
+
graphRuntimeSettings(): Pick<import("../adapters/types.js").BuilderAdapterOptions, "model" | "effort" | "fast" | "runtimeExecutable"> | undefined;
|
|
37
38
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
38
39
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
39
40
|
events(): AsyncIterable<BuilderEvent>;
|
|
@@ -63,6 +63,7 @@ export class CurrentWorkflowGuardAdapter {
|
|
|
63
63
|
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
|
64
64
|
contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
|
|
65
65
|
contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
|
|
66
|
+
graphRuntimeSettings() { return this.adapter.graphRuntimeSettings?.(); }
|
|
66
67
|
sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
|
|
67
68
|
switchSettings(settings) { return this.adapter.switchSettings?.(settings) ?? Promise.resolve({ ok: false, error: "settings switch unavailable" }); }
|
|
68
69
|
events() { return this.adapter.events(); }
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { graphCheckoutBoundary } from "./git.js";
|
|
1
2
|
import { currentWorktreeBranch, findWorktreeForBranch, hasWorktreeChanges, mergeBranchToLocalBase, runGit } from "./git.js";
|
|
2
3
|
import { captureProspectiveGitTree } from "../qaSnapshot.js";
|
|
3
4
|
export class DirectMergeSourceChangedError extends Error {
|
|
@@ -75,6 +76,7 @@ export function removeDirectMergeWorktree(projectDir, intent, worktree) {
|
|
|
75
76
|
verifyDirectMergeWorktree(projectDir, intent);
|
|
76
77
|
// Let Git reject edits made even after the preceding verification. Never
|
|
77
78
|
// force-remove or recursively delete a finalization worktree on failure.
|
|
79
|
+
graphCheckoutBoundary(projectDir, worktree, "worktree-removed");
|
|
78
80
|
runGit(projectDir, ["worktree", "remove", worktree]);
|
|
79
81
|
}
|
|
80
82
|
export function hasExactStagedDirectMerge(projectDir, intent) {
|
|
@@ -95,8 +97,10 @@ export function hasExactStagedDirectMerge(projectDir, intent) {
|
|
|
95
97
|
}
|
|
96
98
|
export function executeDirectMerge(projectDir, intent, message) {
|
|
97
99
|
const completed = reconcileDirectMerge(projectDir, intent);
|
|
98
|
-
if (completed)
|
|
100
|
+
if (completed) {
|
|
101
|
+
graphCheckoutBoundary(projectDir, projectDir, "merge-or-rebase");
|
|
99
102
|
return completed;
|
|
103
|
+
}
|
|
100
104
|
verifyDirectMergeWorktree(projectDir, intent);
|
|
101
105
|
const indexTree = runGit(projectDir, ["write-tree"]).stdout;
|
|
102
106
|
const baseTree = runGit(projectDir, ["rev-parse", `${intent.baseCommit}^{tree}`]).stdout;
|
|
@@ -107,6 +111,7 @@ export function executeDirectMerge(projectDir, intent, message) {
|
|
|
107
111
|
// Git prepared the merge before the host crashed, but commit did not.
|
|
108
112
|
// Only commit the exact tree frozen in the durable intent.
|
|
109
113
|
runGit(projectDir, ["commit", "-m", message]);
|
|
114
|
+
graphCheckoutBoundary(projectDir, projectDir, "merge-or-rebase");
|
|
110
115
|
}
|
|
111
116
|
else {
|
|
112
117
|
// Squash/merge publish the immutable source commit, even if an external
|
package/dist/branch/git.d.ts
CHANGED
|
@@ -16,6 +16,7 @@ export declare function ensureCleanBaseWorktree(cwd: string, opts?: BaseWorktree
|
|
|
16
16
|
export declare function ensureForemanExcluded(cwd: string): void;
|
|
17
17
|
export declare function createTicketWorktree(projectDir: string, runId: string, branch: string, base: string): string;
|
|
18
18
|
export declare function findWorktreeForBranch(projectDir: string, branch: string): string | undefined;
|
|
19
|
+
export declare function graphCheckoutBoundary(projectDir: string, workspace: string, reason: "merge-or-rebase" | "worktree-created" | "worktree-removed"): void;
|
|
19
20
|
export declare function removeTicketWorktree(projectDir: string, worktreePath: string): void;
|
|
20
21
|
export declare function currentWorktreeBranch(worktreePath: string): string;
|
|
21
22
|
export declare function hasTrackerChanges(worktreePath: string, paths?: {
|
package/dist/branch/git.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { invalidateGraphWorkspace } from "../graph/lifecycle.js";
|
|
1
2
|
import { execFileSync } from "node:child_process";
|
|
2
3
|
import { appendFileSync, existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
|
|
3
4
|
import { dirname, join } from "node:path";
|
|
@@ -52,6 +53,7 @@ export function createTicketWorktree(projectDir, runId, branch, base) {
|
|
|
52
53
|
const worktreePath = join(projectDir, ".foreman", "worktrees", runId, branch.replace(/\//g, "__"));
|
|
53
54
|
mkdirSync(dirname(worktreePath), { recursive: true });
|
|
54
55
|
runGit(projectDir, ["worktree", "add", "-b", branch, worktreePath, base]);
|
|
56
|
+
graphCheckoutBoundary(projectDir, worktreePath, "worktree-created");
|
|
55
57
|
return worktreePath;
|
|
56
58
|
}
|
|
57
59
|
export function findWorktreeForBranch(projectDir, branch) {
|
|
@@ -67,7 +69,16 @@ export function findWorktreeForBranch(projectDir, branch) {
|
|
|
67
69
|
}
|
|
68
70
|
return undefined;
|
|
69
71
|
}
|
|
72
|
+
export function graphCheckoutBoundary(projectDir, workspace, reason) {
|
|
73
|
+
try {
|
|
74
|
+
invalidateGraphWorkspace(projectDir, workspace, reason);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
console.error(`rafi graph: checkout metadata invalidation failed: ${String(error)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
70
80
|
export function removeTicketWorktree(projectDir, worktreePath) {
|
|
81
|
+
graphCheckoutBoundary(projectDir, worktreePath, "worktree-removed");
|
|
71
82
|
try {
|
|
72
83
|
runGit(projectDir, ["worktree", "remove", "--force", worktreePath]);
|
|
73
84
|
}
|
|
@@ -120,8 +131,10 @@ export function pushBranch(worktreePath, branch) {
|
|
|
120
131
|
export function mergeBranchToLocalBase(projectDir, branch, baseBranch, message, method = "squash") {
|
|
121
132
|
if (method === "rebase") {
|
|
122
133
|
const branchWorktree = findWorktreeForBranch(projectDir, branch);
|
|
123
|
-
if (branchWorktree)
|
|
134
|
+
if (branchWorktree) {
|
|
124
135
|
runGit(branchWorktree, ["rebase", baseBranch]);
|
|
136
|
+
graphCheckoutBoundary(projectDir, branchWorktree, "merge-or-rebase");
|
|
137
|
+
}
|
|
125
138
|
else
|
|
126
139
|
runGit(projectDir, ["rebase", baseBranch, branch]);
|
|
127
140
|
}
|
|
@@ -140,6 +153,7 @@ export function mergeBranchToLocalBase(projectDir, branch, baseBranch, message,
|
|
|
140
153
|
else {
|
|
141
154
|
runGit(projectDir, ["merge", "--ff-only", branch]);
|
|
142
155
|
}
|
|
156
|
+
graphCheckoutBoundary(projectDir, projectDir, "merge-or-rebase");
|
|
143
157
|
return runGit(projectDir, ["rev-parse", "--short", "HEAD"]).stdout;
|
|
144
158
|
}
|
|
145
159
|
/** Compatibility export for callers compiled against the older helper. */
|
package/dist/branch/runner.d.ts
CHANGED
|
@@ -79,8 +79,10 @@ export declare function runBranchPlan(opts: BranchRunnerOptions): Promise<Branch
|
|
|
79
79
|
export declare function buildBranchTicketInstruction(node: BranchPlanNode, trackerPaths?: {
|
|
80
80
|
progressDoc?: string;
|
|
81
81
|
archiveDoc?: string;
|
|
82
|
+
validationChecklist?: string[];
|
|
82
83
|
}): string;
|
|
83
84
|
export declare function buildBranchTicketResumeInstruction(node: BranchPlanNode, trackerPaths?: {
|
|
84
85
|
progressDoc?: string;
|
|
85
86
|
archiveDoc?: string;
|
|
87
|
+
validationChecklist?: string[];
|
|
86
88
|
}): string;
|
package/dist/branch/runner.js
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
import { renderBuildWorkContext } from "../buildWorkContext.js";
|
|
2
|
+
import { loadTicketSetupConfigWithDefaults } from "../tickets/setupConfig.js";
|
|
3
|
+
import { deliverBuilderGuidanceFollowup } from "../builderGuidanceFollowup.js";
|
|
4
|
+
import { qaDigest } from "../qaProtocolV2.js";
|
|
5
|
+
import { loadTickets } from "../tickets/ticketLoader.js";
|
|
1
6
|
import { formatRecoveryCommand } from "../recoveryGuidance.js";
|
|
2
7
|
import { WorkflowReader } from "../workflowReader.js";
|
|
3
8
|
import { buildScopeRevision } from "../buildApproval.js";
|
|
@@ -15,6 +20,7 @@ import { checkGitLabMrMerged, createOrReuseMr, enableGitLabAutoMerge, pushBranch
|
|
|
15
20
|
import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
16
21
|
import { join, resolve } from "node:path";
|
|
17
22
|
import { WorkflowDb } from "../workflowDb.js";
|
|
23
|
+
import { assertBuildAssignmentReconciled, BuildAssignmentRejected } from "../buildAssignment.js";
|
|
18
24
|
import { currentActivity, withActivityPhase } from "../activity.js";
|
|
19
25
|
import { SessionUnavailableError } from "../adapters/sessionFailure.js";
|
|
20
26
|
import { SessionUnavailableContinuityError } from "../continuity.js";
|
|
@@ -46,6 +52,7 @@ function deliverySessionPath(projectDir, unitId) {
|
|
|
46
52
|
return join(projectDir, ".foreman", "delivery-sessions", `${unitId}.json`);
|
|
47
53
|
}
|
|
48
54
|
export async function runBranchPlan(opts) {
|
|
55
|
+
assertBuildAssignmentReconciled(opts.projectDir, opts.runId);
|
|
49
56
|
let qaResumedRecovery = opts.qaResumedRecovery;
|
|
50
57
|
if (qaResumedRecovery) {
|
|
51
58
|
const matches = opts.plan.nodes.filter((node) => node.ticket.id === qaResumedRecovery.manifest.ticketId);
|
|
@@ -165,6 +172,15 @@ export async function runBranchPlan(opts) {
|
|
|
165
172
|
const commitOperation = finalizationOperationKey(protocolDb, opts.runId, node.ticket.id, "commit");
|
|
166
173
|
const completionOperation = finalizationOperationKey(protocolDb, opts.runId, node.ticket.id, "ticket-complete");
|
|
167
174
|
let completedDirectMerge = finalizationRecovery ? protocolDb.operation(directMergeOperation) : undefined;
|
|
175
|
+
try {
|
|
176
|
+
if (finalizationRecovery)
|
|
177
|
+
protocolDb.assertFinalizationControls(opts.runId, node.ticket.id);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
protocolDb.close();
|
|
181
|
+
summaries.push(summaryFor(node, "needs-human", error instanceof Error ? error.message : String(error)));
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
168
184
|
if (completedDirectMerge?.status === "in_progress") {
|
|
169
185
|
try {
|
|
170
186
|
const intent = readDirectMergeIntent(completedDirectMerge.intent);
|
|
@@ -271,6 +287,13 @@ export async function runBranchPlan(opts) {
|
|
|
271
287
|
continue;
|
|
272
288
|
}
|
|
273
289
|
}
|
|
290
|
+
const admissionDb = new WorkflowDb(opts.projectDir);
|
|
291
|
+
try {
|
|
292
|
+
admissionDb.assertAdmittedWork(opts.runId, node.ticket.id);
|
|
293
|
+
}
|
|
294
|
+
finally {
|
|
295
|
+
admissionDb.close();
|
|
296
|
+
}
|
|
274
297
|
opts.log.write("branch-start", {
|
|
275
298
|
ticket: node.ticket.id,
|
|
276
299
|
branch: node.branch,
|
|
@@ -308,7 +331,7 @@ export async function runBranchPlan(opts) {
|
|
|
308
331
|
summary: resumeSession ? `Resuming branch ${node.branch}` : `Starting branch ${node.branch}`,
|
|
309
332
|
});
|
|
310
333
|
});
|
|
311
|
-
let ticketInstruction = resumeSession ? buildBranchTicketResumeInstruction(node, opts.trackerPaths) : buildBranchTicketInstruction(node, opts.trackerPaths);
|
|
334
|
+
let ticketInstruction = resumeSession ? buildBranchTicketResumeInstruction(node, { ...opts.trackerPaths, validationChecklist: loadTicketSetupConfigWithDefaults(opts.projectDir).build.validation_checklist }) : buildBranchTicketInstruction(node, { ...opts.trackerPaths, validationChecklist: loadTicketSetupConfigWithDefaults(opts.projectDir).build.validation_checklist });
|
|
312
335
|
if (continuations.length)
|
|
313
336
|
ticketInstruction += "\n\nScoped answers authorizing this ticket continuation:\n" + continuations.map(decision => `${decision.prompt}\nAnswer: ${decision.answer ?? decision.selectedChoiceId}`).join("\n");
|
|
314
337
|
// Reattach the predecessor even for a `fresh` strategy so the host can
|
|
@@ -337,7 +360,7 @@ export async function runBranchPlan(opts) {
|
|
|
337
360
|
if (!qaOnlyRecovery)
|
|
338
361
|
builderWorkSessions += 1;
|
|
339
362
|
viewer = opts.observeBuilder?.(builder);
|
|
340
|
-
const foreman = new Foreman(builder, opts.log, { desktop: opts.notificationsEnabled, terminalBell: opts.terminalBellEnabled ?? true }, opts.qaEnabled, 3, opts.projectDir, undefined,
|
|
363
|
+
const foreman = new Foreman(builder, opts.log, { desktop: opts.notificationsEnabled, terminalBell: opts.terminalBellEnabled ?? true }, opts.qaEnabled, 3, opts.projectDir, undefined, opts.createQa, opts.qaSessionStrategy ?? "compact", undefined, opts.builderSessionStrategy ?? "compact", undefined, opts.beforeBuilderTurn ? (adapter, action) => opts.beforeBuilderTurn(adapter, action, worktreePath) : undefined, undefined, undefined, undefined, async (adapter) => opts.observeBuilderNativeCompactions?.(adapter, worktreePath), opts.observer, undefined, false, undefined, undefined, opts.runId, opts.continueIndependentTickets ?? true, node.ticket.id, worktreePath);
|
|
341
364
|
const turn = async () => {
|
|
342
365
|
const db = new WorkflowDb(opts.projectDir);
|
|
343
366
|
try {
|
|
@@ -450,6 +473,54 @@ export async function runBranchPlan(opts) {
|
|
|
450
473
|
observeNativeCompactions: opts.observeQaNativeCompactions,
|
|
451
474
|
resolveBlocked: (adapter, reason) => foreman.resolveBlocker(adapter, reason, "qa"),
|
|
452
475
|
evidence: (entry) => opts.log.write("qa-evidence", { ticket: node.ticket.id, ...entry }),
|
|
476
|
+
resumeBuilderGuidance: qaOnlyRecovery,
|
|
477
|
+
deliverBuilderFollowup: (instruction, followup) => deliverBuilderGuidanceFollowup({ projectDir: opts.projectDir, runId: opts.runId, ticketId: node.ticket.id, worktree: worktreePath }, instruction, followup, {
|
|
478
|
+
prepare: async (instruction) => {
|
|
479
|
+
if (!builder)
|
|
480
|
+
throw new Error("Builder session unavailable");
|
|
481
|
+
const strategy = opts.builderSessionStrategy ?? "compact";
|
|
482
|
+
if (opts.builderSessionBoundary)
|
|
483
|
+
builder = await opts.builderSessionBoundary(builder, instruction, strategy, worktreePath);
|
|
484
|
+
else if (strategy === "fresh") {
|
|
485
|
+
await builder.close();
|
|
486
|
+
builder = await opts.createBuilder(worktreePath);
|
|
487
|
+
}
|
|
488
|
+
else if (builder.sessionId()) {
|
|
489
|
+
const compacted = await compactWithRetry(builder);
|
|
490
|
+
if (!compacted.ok) {
|
|
491
|
+
await builder.close();
|
|
492
|
+
builder = await opts.createBuilder(worktreePath);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
if (opts.beforeBuilderTurn)
|
|
496
|
+
builder = await opts.beforeBuilderTurn(builder, instruction, worktreePath);
|
|
497
|
+
return builder;
|
|
498
|
+
},
|
|
499
|
+
validateRequirements: () => {
|
|
500
|
+
const current = loadTickets(resolveTicketPaths(loadTicketsConfig(opts.projectDir), opts.projectDir).tickets).find(ticket => ticket.id === node.ticket.id);
|
|
501
|
+
if (!current || qaDigest("admitted-requirements", current) !== followup.requirementsDigest)
|
|
502
|
+
throw new BuildAssignmentRejected("Ticket requirements changed during session preparation; renewed scope approval is required");
|
|
503
|
+
},
|
|
504
|
+
recordSession: adapter => {
|
|
505
|
+
const sessionId = adapter.sessionId(), sessionRef = adapter.sessionRef?.();
|
|
506
|
+
if (!sessionId)
|
|
507
|
+
return;
|
|
508
|
+
builderStream = { sessionId, ...(sessionRef ? { sessionRef } : {}), worktreePath };
|
|
509
|
+
const db = new WorkflowDb(opts.projectDir);
|
|
510
|
+
try {
|
|
511
|
+
db.recordBranchResumeSession(opts.runId, { ticket: node.ticket.id, branch: node.branch, base: node.baseBranch, worktreePath, sessionId, sessionRef, logPath: "structured-recovery", deliveryUnitId: node.deliveryUnitId });
|
|
512
|
+
}
|
|
513
|
+
finally {
|
|
514
|
+
db.close();
|
|
515
|
+
}
|
|
516
|
+
opts.recordBuilderSession?.(sessionRef ?? sessionId, node.ticket.id, worktreePath);
|
|
517
|
+
},
|
|
518
|
+
completed: async (operationId, adapter) => {
|
|
519
|
+
builderWorkSessions += 1;
|
|
520
|
+
await opts.observeBuilderNativeCompactions?.(adapter, worktreePath);
|
|
521
|
+
opts.log.write("qa-fix", { kind: "builder-guidance-followup", ticket: node.ticket.id, operationId });
|
|
522
|
+
},
|
|
523
|
+
}),
|
|
453
524
|
deliverFailure: async (request) => {
|
|
454
525
|
if (!builder)
|
|
455
526
|
return { ok: false, detail: "Builder session unavailable" };
|
|
@@ -530,6 +601,13 @@ export async function runBranchPlan(opts) {
|
|
|
530
601
|
isError: false,
|
|
531
602
|
});
|
|
532
603
|
}
|
|
604
|
+
const finalControlsDb = new WorkflowDb(opts.projectDir);
|
|
605
|
+
try {
|
|
606
|
+
finalControlsDb.assertFinalizationControls(opts.runId, node.ticket.id);
|
|
607
|
+
}
|
|
608
|
+
finally {
|
|
609
|
+
finalControlsDb.close();
|
|
610
|
+
}
|
|
533
611
|
if (opts.qaEnabled && !qaWaived && !finalizationRecovery) {
|
|
534
612
|
if (!qaPassCertificateId || !qaSourceStateDigest)
|
|
535
613
|
throw new Error("QA passed without a durable pass certificate");
|
|
@@ -760,6 +838,10 @@ export async function runBranchPlan(opts) {
|
|
|
760
838
|
await observeNode(opts, node, "cleanup", "removing ticket worktree", () => removeTicketWorktree(opts.projectDir, worktreePath));
|
|
761
839
|
}
|
|
762
840
|
catch (err) {
|
|
841
|
+
if (err instanceof BuildAssignmentRejected) {
|
|
842
|
+
summaries.push(summaryFor(node, "needs-human", err.message));
|
|
843
|
+
return summaries;
|
|
844
|
+
}
|
|
763
845
|
if (err instanceof SessionUnavailableError || err instanceof SessionUnavailableContinuityError) {
|
|
764
846
|
const message = err.message;
|
|
765
847
|
opts.log.write("branch-issue", { ticket: node.ticket.id, code: "session_unavailable", message, blocking: true });
|
|
@@ -1027,6 +1109,8 @@ export function buildBranchTicketInstruction(node, trackerPaths = {}) {
|
|
|
1027
1109
|
|
|
1028
1110
|
${node.ticket.id}: ${node.ticket.title}
|
|
1029
1111
|
|
|
1112
|
+
${renderBuildWorkContext(node.ticket, trackerPaths.validationChecklist ?? [])}
|
|
1113
|
+
|
|
1030
1114
|
Summary:
|
|
1031
1115
|
${node.ticket.summary}
|
|
1032
1116
|
|
|
@@ -1047,7 +1131,7 @@ Branch-mode rules:
|
|
|
1047
1131
|
- If another selected ticket is required before this one can be completed, stop and end with STEP_STATUS: blocked | ticket="${node.ticket.id}" branch_dependency="<ticket-id>" reason="<why>".
|
|
1048
1132
|
- End with STEP_STATUS: done | ticket="${node.ticket.id}" summary="<what changed>" when the ticket is implemented.
|
|
1049
1133
|
|
|
1050
|
-
QA
|
|
1134
|
+
Final QA runs independently in a separate disposable snapshot and fresh reviewer conversation.
|
|
1051
1135
|
|
|
1052
1136
|
${MARKER_SPEC}`;
|
|
1053
1137
|
}
|
|
@@ -1057,6 +1141,8 @@ export function buildBranchTicketResumeInstruction(node, trackerPaths = {}) {
|
|
|
1057
1141
|
|
|
1058
1142
|
${node.ticket.id}: ${node.ticket.title}
|
|
1059
1143
|
|
|
1144
|
+
${renderBuildWorkContext(node.ticket, trackerPaths.validationChecklist ?? [])}
|
|
1145
|
+
|
|
1060
1146
|
Resume from the current repository state. Inspect the worktree if needed, then finish only this ticket.
|
|
1061
1147
|
|
|
1062
1148
|
Branch-mode rules:
|
|
@@ -1067,7 +1153,7 @@ Branch-mode rules:
|
|
|
1067
1153
|
- If another selected ticket is required before this one can be completed, stop and end with STEP_STATUS: blocked | ticket="${node.ticket.id}" branch_dependency="<ticket-id>" reason="<why>".
|
|
1068
1154
|
- End with STEP_STATUS: done | ticket="${node.ticket.id}" summary="<what changed>" when the ticket is implemented.
|
|
1069
1155
|
|
|
1070
|
-
QA
|
|
1156
|
+
Final QA runs independently in a separate disposable snapshot and fresh reviewer conversation.
|
|
1071
1157
|
|
|
1072
1158
|
${MARKER_SPEC}`;
|
|
1073
1159
|
}
|
package/dist/buildAdmission.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { registerHandbackWriter } from "./qaHandbackMigration.js";
|
|
1
2
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
4
|
import { realpathSync } from "node:fs";
|
|
@@ -56,6 +57,7 @@ export function maintainBuildAdmission(authority) {
|
|
|
56
57
|
try {
|
|
57
58
|
db = new Database(join(authority.project, ".rafi/recovery.sqlite3"), { fileMustExist: true });
|
|
58
59
|
db.function("rafi_protocol_v3", () => 1);
|
|
60
|
+
registerHandbackWriter(db);
|
|
59
61
|
const changed = db.prepare("UPDATE build_admission SET record_json=json_set(record_json,'$.heartbeatAt',?) WHERE singleton=1 AND json_extract(record_json,'$.token')=?").run(new Date().toISOString(), authority.token);
|
|
60
62
|
if (changed.changes !== 1)
|
|
61
63
|
stopBuildAdmissionHeartbeat(authority);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { BuilderAdapter } from "./adapters/types.js";
|
|
2
|
+
import type { StepStatus } from "./foreman.js";
|
|
3
|
+
import type { TurnResult } from "./adapters/types.js";
|
|
4
|
+
import { type FrozenQaSourceState } from "./qaSnapshot.js";
|
|
5
|
+
/** Work responses confirm an assignment; they never select the next ticket. */
|
|
6
|
+
export declare function validateBuildAssignment(ticketId: string, status: StepStatus, responseText?: string): string | undefined;
|
|
7
|
+
export interface BuildAssignmentCapture {
|
|
8
|
+
operationId: string;
|
|
9
|
+
runId: string;
|
|
10
|
+
ticketId: string;
|
|
11
|
+
worktree: string;
|
|
12
|
+
before: BuildSourceState;
|
|
13
|
+
instruction: string;
|
|
14
|
+
guidanceIds: string[];
|
|
15
|
+
}
|
|
16
|
+
type BuildSourceState = Pick<FrozenQaSourceState, "digest" | "head" | "contentDigest" | "capturedAt" | "pathInventory" | "stagedDiff" | "unstagedDiff" | "untracked"> & {
|
|
17
|
+
captureMode: "git" | "filesystem";
|
|
18
|
+
omissions?: string[];
|
|
19
|
+
};
|
|
20
|
+
export declare class BuildAssignmentRejected extends Error {
|
|
21
|
+
}
|
|
22
|
+
export interface BuilderGuidanceFollowup {
|
|
23
|
+
reviewAttemptId: string;
|
|
24
|
+
requirementsDigest: string;
|
|
25
|
+
maximum: number;
|
|
26
|
+
authorizationId?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function assertBuildAssignmentReconciled(projectDir: string, runId: string, worktree?: string): void;
|
|
29
|
+
/** Commit response/source bytes before interpreting any scoped outcome. */
|
|
30
|
+
export declare function beginBuildAssignment(projectDir: string, runId: string, ticketId: string, worktree: string, instruction: string, followup?: BuilderGuidanceFollowup, adapter?: BuilderAdapter): BuildAssignmentCapture;
|
|
31
|
+
export declare function finishBuildAssignment(projectDir: string, assignment: BuildAssignmentCapture, result: TurnResult, status: StepStatus): string | undefined;
|
|
32
|
+
export declare function captureBuildSource(worktree: string): BuildSourceState;
|
|
33
|
+
export {};
|