ai-foreman 1.7.20 → 1.7.22
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 +27 -8
- package/dist/adapters/claude.d.ts +19 -0
- package/dist/adapters/claude.js +59 -7
- 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/dirtyBaseline.d.ts +5 -0
- package/dist/branch/dirtyBaseline.js +69 -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/resume.js +2 -2
- 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 +215 -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 +7 -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 +235 -13
- package/dist/cli/recovery.js +3 -2
- package/dist/cli/runtimeAuthPrompt.js +28 -8
- package/dist/cli/start.js +192 -50
- package/dist/cli/status.js +29 -7
- package/dist/cli/tickets.d.ts +4 -0
- package/dist/cli/tickets.js +37 -13
- 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/interviews.d.ts +3 -1
- package/dist/interviews.js +7 -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 +25 -1
- 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/projectDiagnostics.js +32 -8
- 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/recoveryPolicy.js +1 -1
- 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 +19 -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.d.ts +1 -1
- package/dist/tickets/commands.js +13 -11
- package/dist/tickets/details.js +2 -2
- package/dist/tickets/queue.js +1 -1
- package/dist/tickets/stateDb.d.ts +4 -1
- package/dist/tickets/stateDb.js +18 -2
- package/dist/tickets/ticketSchema.d.ts +198 -0
- package/dist/tickets/ticketSchema.js +2 -0
- package/dist/tickets/validate.js +4 -1
- 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 +85 -8
- package/scripts/copy-graph-assets.mjs +3 -0
- package/scripts/graph-benchmark.mjs +20 -0
- package/scripts/graph-evaluation.mjs +85 -0
|
@@ -10,15 +10,21 @@ export class RecoveringAdapter {
|
|
|
10
10
|
eventQueue = new AsyncQueue();
|
|
11
11
|
eventPump;
|
|
12
12
|
closed = false;
|
|
13
|
+
contractEnforcing = false;
|
|
13
14
|
constructor(opts) {
|
|
14
15
|
this.opts = opts;
|
|
15
16
|
this.adapter = opts.initial;
|
|
16
17
|
this.runtime = opts.runtime;
|
|
17
18
|
this.eventPump = this.forwardEvents(this.adapter);
|
|
18
19
|
}
|
|
20
|
+
graphRuntimeSettings() { return this.adapter.graphRuntimeSettings?.(); }
|
|
19
21
|
get agent() {
|
|
20
22
|
return this.adapter.agent;
|
|
21
23
|
}
|
|
24
|
+
contractCapabilities() { return this.adapter.contractCapabilities?.() ?? { sameSessionAcceptance: false, nativeCompactionBarrier: false }; }
|
|
25
|
+
enableContractEnforcement() { this.contractEnforcing = true; this.adapter.enableContractEnforcement?.(); }
|
|
26
|
+
contractCompactionSequence() { return this.adapter.contractCompactionSequence?.() ?? 0; }
|
|
27
|
+
acceptContractDelivery(sequence) { this.adapter.acceptContractDelivery?.(sequence); }
|
|
22
28
|
async sendTurn(text, policy) {
|
|
23
29
|
while (true) {
|
|
24
30
|
const result = await this.adapter.sendTurn(text, policy);
|
|
@@ -37,6 +43,10 @@ export class RecoveringAdapter {
|
|
|
37
43
|
// instruction. Never replay it or silently switch providers.
|
|
38
44
|
if (result.failure.category === "session-unavailable" || result.failure.dispatchState === "unknown")
|
|
39
45
|
return result;
|
|
46
|
+
// A replacement has new host-side barriers, even when it resumes the
|
|
47
|
+
// same native session. Only the shared build gate can renew acceptance.
|
|
48
|
+
if (this.contractEnforcing)
|
|
49
|
+
return { ...result, text: `${result.text}\nContract-enforcing runtime recovery requires an explicit host resume through the shared preparation/delivery gate; this turn was not replayed.` };
|
|
40
50
|
const otherRuntime = this.runtime === "claude" ? "codex" : "claude";
|
|
41
51
|
const choice = this.opts.choose
|
|
42
52
|
? await this.opts.choose(result, this.runtime, otherRuntime)
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProviderTurnPurpose } from "../providerPhase.js";
|
|
1
2
|
/**
|
|
2
3
|
* Agent-agnostic interface over a coding agent ("builder").
|
|
3
4
|
*/
|
|
@@ -233,6 +234,11 @@ export interface BuilderAdapterOptions {
|
|
|
233
234
|
fast?: boolean;
|
|
234
235
|
/** Codex sandbox mode. Defaults to workspace-write for implementation runs. */
|
|
235
236
|
sandboxMode?: "workspace-write" | "read-only";
|
|
237
|
+
/** Explicit opt-in to per-command sandbox escalation. QA remains offline/never. */
|
|
238
|
+
approvalPolicy?: "never" | "on-request";
|
|
239
|
+
approvalTimeoutMs?: number;
|
|
240
|
+
/** Build-scoped explicit grant; ignored for read-only/QA roles. */
|
|
241
|
+
networkAccess?: boolean;
|
|
236
242
|
/** Role system text to append to the harness system prompt (from .rafi/compiled or library). */
|
|
237
243
|
systemPromptAppend?: string;
|
|
238
244
|
/** Skill names to preload for this session (Claude: lazy-loaded; Codex: flattened). */
|
|
@@ -273,12 +279,21 @@ export interface BuilderAdapterOptions {
|
|
|
273
279
|
}) => void;
|
|
274
280
|
}
|
|
275
281
|
export interface BuilderAdapter {
|
|
282
|
+
graphRuntimeSettings?(): Pick<BuilderAdapterOptions, "model" | "effort" | "fast" | "runtimeExecutable"> | undefined;
|
|
276
283
|
readonly agent: "claude" | "codex";
|
|
284
|
+
contractCapabilities?(): {
|
|
285
|
+
sameSessionAcceptance: boolean;
|
|
286
|
+
nativeCompactionBarrier: boolean;
|
|
287
|
+
};
|
|
288
|
+
enableContractEnforcement?(): void;
|
|
289
|
+
contractCompactionSequence?(): number;
|
|
290
|
+
acceptContractDelivery?(sequence: number): void;
|
|
277
291
|
/** Send one instruction; resolves when that turn completes. */
|
|
278
292
|
sendTurn(text: string, policy?: {
|
|
279
293
|
handback?: boolean;
|
|
280
294
|
responseOnly?: boolean;
|
|
281
295
|
logicalActionId?: string;
|
|
296
|
+
purpose?: ProviderTurnPurpose;
|
|
282
297
|
}): Promise<TurnResult>;
|
|
283
298
|
/** Synchronous observation through the event owner. Terminal precedes sendTurn resolution. */
|
|
284
299
|
observeEvents?(listener: (event: BuilderEvent) => void): () => void;
|
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(); }
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { join, resolve } from "node:path";
|
|
5
|
+
/** Capture build input with a private index. Never stash, commit, or modify the user's index. */
|
|
6
|
+
export function captureDirtyBaseline(projectDir, mode) {
|
|
7
|
+
const git = (args, env = process.env) => execFileSync("git", args, { cwd: projectDir, env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
8
|
+
if (git(["diff", "--name-only", "--diff-filter=U"]))
|
|
9
|
+
throw new Error("Resolve Git conflicts before starting an isolated build; Rafi has preserved your files.");
|
|
10
|
+
const head = () => { try {
|
|
11
|
+
return git(["rev-parse", "--verify", "HEAD"]);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return undefined;
|
|
15
|
+
} };
|
|
16
|
+
const parent = head();
|
|
17
|
+
if (!parent && mode === "preserve")
|
|
18
|
+
throw new Error("This repository has no committed baseline. Use --dirty-work include to capture current files privately, or make an initial commit first.");
|
|
19
|
+
const directory = join(projectDir, ".foreman/dirty-baselines");
|
|
20
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
21
|
+
const recordPath = mkdtempSync(join(directory, "baseline-"));
|
|
22
|
+
const indexPath = resolve(projectDir, git(["rev-parse", "--git-path", "index"]));
|
|
23
|
+
const indexDigest = existsSync(indexPath) ? createHash("sha256").update(readFileSync(indexPath)).digest("hex") : null;
|
|
24
|
+
const status = git(["status", "--porcelain", "--untracked-files=all"]);
|
|
25
|
+
const paths = execFileSync("git", ["ls-files", "-z", "--cached", "--others", "--exclude-standard"], { cwd: projectDir }).toString().split("\0").filter(path => path && !path.startsWith(".foreman/") && !/^\.rafi\/(recovery|observability)\.sqlite3/.test(path));
|
|
26
|
+
// HEAD paths are needed too: staged deletions/rename sources no longer exist in the user's index.
|
|
27
|
+
if (parent)
|
|
28
|
+
paths.push(...execFileSync("git", ["ls-tree", "-r", "--name-only", "-z", parent], { cwd: projectDir }).toString().split("\0").filter(path => path && !path.startsWith(".foreman/") && !/^\.rafi\/(recovery|observability)\.sqlite3/.test(path)));
|
|
29
|
+
const files = [...new Set(paths)].map(path => {
|
|
30
|
+
const absolute = join(projectDir, path);
|
|
31
|
+
if (!existsSync(absolute))
|
|
32
|
+
return { path, digest: null };
|
|
33
|
+
const stat = lstatSync(absolute);
|
|
34
|
+
if (stat.isDirectory()) {
|
|
35
|
+
if (mode === "include" && execFileSync("git", ["status", "--porcelain"], { cwd: absolute, encoding: "utf8" }).trim())
|
|
36
|
+
throw new Error(`Dirty submodule ${path} cannot be included automatically. Commit its work or use --dirty-work preserve.`);
|
|
37
|
+
return { path, digest: null, kind: "submodule" };
|
|
38
|
+
}
|
|
39
|
+
return { path, digest: createHash("sha256").update(stat.isSymbolicLink() ? readlinkSync(absolute) : readFileSync(absolute)).digest("hex"), mode: stat.mode, kind: stat.isSymbolicLink() ? "symlink" : "file" };
|
|
40
|
+
});
|
|
41
|
+
const diffBase = parent ?? execFileSync("git", ["hash-object", "-w", "-t", "tree", "--stdin"], { cwd: projectDir, input: "", encoding: "utf8" }).trim();
|
|
42
|
+
const diff = execFileSync("git", ["diff", diffBase, "--binary"], { cwd: projectDir });
|
|
43
|
+
writeFileSync(join(recordPath, "working.patch"), diff, { mode: 0o600 });
|
|
44
|
+
let base = parent;
|
|
45
|
+
if (mode === "include") {
|
|
46
|
+
const env = { ...process.env, GIT_INDEX_FILE: join(recordPath, "index"), GIT_AUTHOR_NAME: "Rafi baseline", GIT_AUTHOR_EMAIL: "baseline@rafi.local", GIT_COMMITTER_NAME: "Rafi baseline", GIT_COMMITTER_EMAIL: "baseline@rafi.local" };
|
|
47
|
+
git(parent ? ["read-tree", parent] : ["read-tree", "--empty"], env);
|
|
48
|
+
if (paths.length)
|
|
49
|
+
execFileSync("git", ["add", "-A", "--pathspec-from-file=-", "--pathspec-file-nul"], { cwd: projectDir, env: { ...env, GIT_LITERAL_PATHSPECS: "1" }, input: `${paths.join("\0")}\0`, stdio: ["pipe", "pipe", "pipe"] });
|
|
50
|
+
const tree = git(["write-tree"], env);
|
|
51
|
+
base = git(["commit-tree", tree, ...(parent ? ["-p", parent] : []), "-m", "Rafi preserved dirty build input"], env);
|
|
52
|
+
}
|
|
53
|
+
const finalIndexDigest = existsSync(indexPath) ? createHash("sha256").update(readFileSync(indexPath)).digest("hex") : null;
|
|
54
|
+
const changed = head() !== parent || finalIndexDigest !== indexDigest || files.some(file => {
|
|
55
|
+
if (file.kind === "submodule")
|
|
56
|
+
return false;
|
|
57
|
+
const absolute = join(projectDir, file.path);
|
|
58
|
+
if (!existsSync(absolute))
|
|
59
|
+
return file.digest !== null;
|
|
60
|
+
if (file.digest === null)
|
|
61
|
+
return true;
|
|
62
|
+
const stat = lstatSync(absolute);
|
|
63
|
+
return createHash("sha256").update(stat.isSymbolicLink() ? readlinkSync(absolute) : readFileSync(absolute)).digest("hex") !== file.digest || stat.mode !== file.mode;
|
|
64
|
+
});
|
|
65
|
+
if (changed)
|
|
66
|
+
throw new Error("Checkout or index changed while capturing the baseline. Your work is preserved; retry after those edits finish.");
|
|
67
|
+
writeFileSync(join(recordPath, "manifest.json"), JSON.stringify({ version: 1, mode, parent, base, indexDigest, status, files, patchDigest: createHash("sha256").update(diff).digest("hex"), createdAt: new Date().toISOString(), delivery: "isolated branches; no automatic merge into dirty checkout" }, null, 2), { mode: 0o600 });
|
|
68
|
+
return { base, recordPath };
|
|
69
|
+
}
|
|
@@ -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/resume.js
CHANGED
|
@@ -73,7 +73,7 @@ export function findResumableBranchSessions(foremanDir) {
|
|
|
73
73
|
}
|
|
74
74
|
export function formatBranchContinueCommand(projectDir, session) {
|
|
75
75
|
const args = [
|
|
76
|
-
"
|
|
76
|
+
"rafi",
|
|
77
77
|
"start",
|
|
78
78
|
shellQuote(projectDir),
|
|
79
79
|
"--steps",
|
|
@@ -91,7 +91,7 @@ export function formatBranchContinueCommand(projectDir, session) {
|
|
|
91
91
|
args.push("--model", shellQuote(session.model));
|
|
92
92
|
if (session.effort)
|
|
93
93
|
args.push("--effort", shellQuote(session.effort));
|
|
94
|
-
if (session.reviewProvider)
|
|
94
|
+
if (session.reviewProvider && session.completionMode !== "none" && (session.completionMode || session.createPr))
|
|
95
95
|
args.push("--provider", shellQuote(session.reviewProvider));
|
|
96
96
|
if (session.fast)
|
|
97
97
|
args.push("--fast");
|
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;
|