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/cli/status.js
CHANGED
|
@@ -6,8 +6,9 @@ import { recoverableBuildRuns } from "../buildRuns.js";
|
|
|
6
6
|
import { WORKFLOW_DB_FILE } from "../workflowDb.js";
|
|
7
7
|
import { WorkflowReader } from "../workflowReader.js";
|
|
8
8
|
import { formatWorkflowIssue } from "../outcomes.js";
|
|
9
|
+
import { accent, error, sanitizeTerminalText, success, warning } from "../terminalStyle.js";
|
|
9
10
|
function fail(message) {
|
|
10
|
-
console.error(
|
|
11
|
+
console.error(`${error("foreman:", { stream: process.stderr })} ${message}`);
|
|
11
12
|
process.exit(1);
|
|
12
13
|
}
|
|
13
14
|
export function buildStatusCommand() {
|
|
@@ -24,6 +25,16 @@ export function runStatus(project) {
|
|
|
24
25
|
const workflowPath = join(projectDir, WORKFLOW_DB_FILE);
|
|
25
26
|
const workflow = existsSync(workflowPath) ? new WorkflowReader(projectDir, workflowPath) : undefined;
|
|
26
27
|
const activeWorkflows = workflow?.activeRuns() ?? [];
|
|
28
|
+
for (const run of activeWorkflows) {
|
|
29
|
+
const preparation = workflow?.qaPreparation(run.runId);
|
|
30
|
+
if (!preparation || preparation.mode === "legacy")
|
|
31
|
+
continue;
|
|
32
|
+
console.log(`foreman: QA preparation ${preparation.mode} — ${run.runId}`);
|
|
33
|
+
const metrics = workflow.qaPreparationMetrics(run.runId);
|
|
34
|
+
console.log(` admitted ${metrics.approved}; started ${metrics.started}; first reviews ${metrics.firstReviews}; passes ${metrics.firstReviewPasses}; unknown finding causes ${metrics.causes.unknown}`);
|
|
35
|
+
for (const work of preparation.works)
|
|
36
|
+
console.log(` ${work.workId}: ${work.state}; depth ${work.depth ?? "assessment pending"}; contract ${work.digest ?? "unpublished"}${work.detail ? `; ${work.detail}` : ""}`);
|
|
37
|
+
}
|
|
27
38
|
if (!existsSync(dir) && activeWorkflows.length === 0) {
|
|
28
39
|
workflow?.close();
|
|
29
40
|
fail(`no foreman runs found under ${dir}`);
|
|
@@ -52,8 +63,8 @@ export function runStatus(project) {
|
|
|
52
63
|
const prEvents = records.filter((r) => ["pr-created", "pr-existing", "pr-failed"].includes(String(r.event)));
|
|
53
64
|
const latestFailure = findLatestGitHubFailure(records);
|
|
54
65
|
const batchEnd = [...records].reverse().find((r) => r.event === "batch-end");
|
|
55
|
-
console.log(
|
|
56
|
-
console.log(
|
|
66
|
+
console.log(`${accent("foreman:")} latest run ${sanitizeTerminalText(logs[logs.length - 1] ?? "")}`);
|
|
67
|
+
console.log(`${accent("foreman:")} ${steps.length} step record(s), ${escalations.length} escalation(s)`);
|
|
57
68
|
if (branchEvents.length || prEvents.length) {
|
|
58
69
|
const completed = branchEvents.filter((r) => r.event === "branch-complete").length;
|
|
59
70
|
const issues = branchEvents.filter((r) => r.event === "branch-issue").length;
|
|
@@ -61,12 +72,16 @@ export function runStatus(project) {
|
|
|
61
72
|
console.log(`foreman: branch mode — ${completed} completed, ${issues} issue(s), ${prs} PR(s)`);
|
|
62
73
|
}
|
|
63
74
|
if (batchEnd) {
|
|
64
|
-
|
|
75
|
+
const outcome = sanitizeTerminalText(String(batchEnd.outcome ?? ""));
|
|
76
|
+
const styledOutcome = /success|complete|pass/i.test(outcome) ? success(outcome)
|
|
77
|
+
: /fail|error/i.test(outcome) ? error(outcome)
|
|
78
|
+
: /warn|partial|blocked/i.test(outcome) ? warning(outcome) : outcome;
|
|
79
|
+
console.log(`${accent("foreman:")} outcome — ${styledOutcome} (${batchEnd.completed}/${batchEnd.requested})`);
|
|
65
80
|
if (batchEnd.detail)
|
|
66
81
|
console.log(`foreman: ${batchEnd.detail}`);
|
|
67
82
|
}
|
|
68
83
|
else {
|
|
69
|
-
console.log("ai-foreman: run is still in progress or did not finish
|
|
84
|
+
console.log(`${accent("ai-foreman:")} run is still in progress or did not finish`);
|
|
70
85
|
}
|
|
71
86
|
if (latestFailure) {
|
|
72
87
|
console.log(`foreman: latest GitHub failure — ${latestFailure.code}: ${latestFailure.message}`);
|
package/dist/cli/tickets.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
+
import { accent, error, sanitizeTerminalText, success, warning } from "../terminalStyle.js";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
4
5
|
import { parse as parseYaml } from "yaml";
|
|
@@ -1240,6 +1241,7 @@ export async function cmdPopulateCli(opts) {
|
|
|
1240
1241
|
builder = roleBuilder.builder;
|
|
1241
1242
|
viewer = printEvents(builder.events());
|
|
1242
1243
|
const foreman = new Foreman(builder, log, { desktop: config.notifications.enabled, terminalBell: config.notifications.terminal_bell }, false, 3, dir);
|
|
1244
|
+
foreman.setGraphPurpose("ticket-population");
|
|
1243
1245
|
console.log(`foreman tickets: populating tickets with ${roleBuilder.runtime}`);
|
|
1244
1246
|
console.log(`foreman tickets: project ${dir}`);
|
|
1245
1247
|
console.log(`foreman tickets: role ${TICKET_POPULATE_ROLE}`);
|
|
@@ -1927,7 +1929,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1927
1929
|
evidence: opts.evidence,
|
|
1928
1930
|
lastError: opts.lastError,
|
|
1929
1931
|
});
|
|
1930
|
-
console.log(
|
|
1932
|
+
console.log(`${accent("foreman tickets:")} updated ${sanitizeTerminalText(ticketId)}`);
|
|
1931
1933
|
}
|
|
1932
1934
|
catch (err) {
|
|
1933
1935
|
fail(String(err instanceof Error ? err.message : err));
|
|
@@ -1954,7 +1956,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1954
1956
|
evidence: opts.evidence,
|
|
1955
1957
|
validationNotes: opts.validationNotes,
|
|
1956
1958
|
});
|
|
1957
|
-
console.log(
|
|
1959
|
+
console.log(`${accent("foreman tickets:")} ${success("completed")} ${sanitizeTerminalText(ticketId)}`);
|
|
1958
1960
|
}
|
|
1959
1961
|
catch (err) {
|
|
1960
1962
|
fail(String(err instanceof Error ? err.message : err));
|
|
@@ -1979,7 +1981,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1979
1981
|
actor: opts.actor,
|
|
1980
1982
|
unblockCriteria: opts.unblockCriteria,
|
|
1981
1983
|
});
|
|
1982
|
-
console.log(
|
|
1984
|
+
console.log(`${accent("foreman tickets:")} ${warning("blocked")} ${sanitizeTerminalText(ticketId)}`);
|
|
1983
1985
|
}
|
|
1984
1986
|
catch (err) {
|
|
1985
1987
|
fail(String(err instanceof Error ? err.message : err));
|
|
@@ -1998,7 +2000,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
1998
2000
|
summary: opts.summary,
|
|
1999
2001
|
actor: opts.actor,
|
|
2000
2002
|
});
|
|
2001
|
-
console.log(
|
|
2003
|
+
console.log(`${accent("foreman tickets:")} ${success("unblocked")} ${sanitizeTerminalText(ticketId)}`);
|
|
2002
2004
|
}
|
|
2003
2005
|
catch (err) {
|
|
2004
2006
|
fail(String(err instanceof Error ? err.message : err));
|
|
@@ -2188,10 +2190,10 @@ export function buildTicketsCommand(options = {}) {
|
|
|
2188
2190
|
try {
|
|
2189
2191
|
const result = cmdValidate(cwd(opts));
|
|
2190
2192
|
if (result.issues.length === 0) {
|
|
2191
|
-
console.log("foreman tickets: validation passed — all 4 passes clean
|
|
2193
|
+
console.log(`${accent("foreman tickets:")} ${success("validation passed")} — all 4 passes clean`);
|
|
2192
2194
|
}
|
|
2193
2195
|
else {
|
|
2194
|
-
console.log(
|
|
2196
|
+
console.log(`${accent("foreman tickets:")} ${error(`${result.issues.length} issue(s) found:`)}`);
|
|
2195
2197
|
console.log(formatValidationIssues(result.issues));
|
|
2196
2198
|
if (!result.clean)
|
|
2197
2199
|
process.exit(1);
|
|
@@ -2221,7 +2223,7 @@ export function buildTicketsCommand(options = {}) {
|
|
|
2221
2223
|
console.log(line);
|
|
2222
2224
|
return;
|
|
2223
2225
|
}
|
|
2224
|
-
console.log("Batches: none configured. Queue is flat
|
|
2226
|
+
console.log(`${accent("Batches:")} none configured. Queue is flat.`);
|
|
2225
2227
|
const rows = cmdQueue(dir, opts.limit !== undefined ? Number(opts.limit) : undefined);
|
|
2226
2228
|
if (rows.length === 0) {
|
|
2227
2229
|
console.log("No remaining tickets.");
|
package/dist/continuity.d.ts
CHANGED
|
@@ -30,7 +30,7 @@ export declare function parseContinuityDelta(text: string): {
|
|
|
30
30
|
};
|
|
31
31
|
export declare function mergeContinuityDeltas(deltas: readonly ContinuityDelta[]): ContinuityDelta;
|
|
32
32
|
export declare function baselineContinuityDelta(nextAction?: string): ContinuityDelta;
|
|
33
|
-
export declare function continuityInstruction(): string;
|
|
33
|
+
export declare function continuityInstruction(role?: "builder" | "qa"): string;
|
|
34
34
|
export interface ContinuityAdapterOptions {
|
|
35
35
|
adapter: BuilderAdapter;
|
|
36
36
|
projectDir: string;
|
|
@@ -61,16 +61,30 @@ export interface ContinuityAdapterOptions {
|
|
|
61
61
|
export declare class ContinuityAdapter implements BuilderAdapter {
|
|
62
62
|
private readonly options;
|
|
63
63
|
private adapter;
|
|
64
|
+
contractCapabilities(): {
|
|
65
|
+
sameSessionAcceptance: boolean;
|
|
66
|
+
nativeCompactionBarrier: boolean;
|
|
67
|
+
};
|
|
68
|
+
private contractEnforcing;
|
|
69
|
+
enableContractEnforcement(): void;
|
|
70
|
+
contractCompactionSequence(): number;
|
|
71
|
+
acceptContractDelivery(sequence: number): void;
|
|
64
72
|
private readonly queue;
|
|
65
73
|
private sourcePump?;
|
|
66
74
|
private recoveryLeasePending;
|
|
75
|
+
private validatingFreshRecovery;
|
|
76
|
+
private readonly buildAuthority?;
|
|
67
77
|
constructor(options: ContinuityAdapterOptions);
|
|
78
|
+
/** Establish an explicitly requested fresh owner before context probes or work. */
|
|
79
|
+
validateFreshRecovery(): Promise<void>;
|
|
68
80
|
get agent(): "claude" | "codex";
|
|
69
81
|
sendTurn(instruction: string, policy?: {
|
|
70
82
|
handback?: boolean;
|
|
71
83
|
responseOnly?: boolean;
|
|
72
84
|
logicalActionId?: string;
|
|
85
|
+
purpose?: import("./providerPhase.js").ProviderTurnPurpose;
|
|
73
86
|
}): Promise<TurnResult>;
|
|
87
|
+
private sendTurnOwned;
|
|
74
88
|
observeEvents(listener: (event: import("./adapters/types.js").BuilderEvent) => void): () => void;
|
|
75
89
|
acceptHandbackTurn(turn: TurnResult): void;
|
|
76
90
|
sessionId(): string | undefined;
|
|
@@ -86,6 +100,7 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
86
100
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
87
101
|
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
88
102
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
103
|
+
graphRuntimeSettings(): Pick<import("./adapters/types.js").BuilderAdapterOptions, "model" | "effort" | "fast" | "runtimeExecutable"> | undefined;
|
|
89
104
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
90
105
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
91
106
|
events(): AsyncIterable<import("./adapters/types.js").BuilderEvent>;
|
package/dist/continuity.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { dispatchWithGraphAccess, withGraphSessionAccess } from "./graph/session.js";
|
|
2
|
+
import { localBuildAuthority } from "./buildAdmission.js";
|
|
1
3
|
import { randomUUID, createHash } from "node:crypto";
|
|
2
4
|
import { BuilderEventQueue } from "./activity.js";
|
|
3
5
|
import { providerSessionKey } from "./sessionIdentity.js";
|
|
@@ -96,10 +98,11 @@ export function mergeContinuityDeltas(deltas) {
|
|
|
96
98
|
export function baselineContinuityDelta(nextAction = "Await the first role action.") {
|
|
97
99
|
return { version: 1, decisions: [], constraints: [], discoveries: [], completedActions: [], evidence: [], failures: [], blockers: [], openWork: [], nextAction };
|
|
98
100
|
}
|
|
99
|
-
export function continuityInstruction() {
|
|
101
|
+
export function continuityInstruction(role) {
|
|
100
102
|
return [
|
|
101
103
|
"Before your final STEP_STATUS line (or as the final line when STEP_STATUS is not requested), emit exactly one single-line continuity record:",
|
|
102
104
|
`${CONTINUITY_MARKER} {"version":1,"decisions":[],"constraints":[],"discoveries":[],"completedActions":[],"evidence":[],"failures":[],"blockers":[],"openWork":[],"nextAction":"specific next action"}`,
|
|
105
|
+
...(role === "qa" ? ["QA protocol: this continuity record is required even when the review requests only a report or status. Put it before RAFI_QA_FAILURE_REPORT_START when returning a failure envelope, outside the report JSON. Rafi strips the record before validating the report, so keep the report envelope immediately followed by STEP_STATUS."] : []),
|
|
103
106
|
"Keep it concise and cumulative for facts learned this turn. Do not include credentials, hidden reasoning, or raw transcripts.",
|
|
104
107
|
].join("\n");
|
|
105
108
|
}
|
|
@@ -107,12 +110,20 @@ export function continuityInstruction() {
|
|
|
107
110
|
export class ContinuityAdapter {
|
|
108
111
|
options;
|
|
109
112
|
adapter;
|
|
113
|
+
contractCapabilities() { return this.adapter.contractCapabilities?.() ?? { sameSessionAcceptance: false, nativeCompactionBarrier: false }; }
|
|
114
|
+
contractEnforcing = false;
|
|
115
|
+
enableContractEnforcement() { this.contractEnforcing = true; this.adapter.enableContractEnforcement?.(); }
|
|
116
|
+
contractCompactionSequence() { return this.adapter.contractCompactionSequence?.() ?? 0; }
|
|
117
|
+
acceptContractDelivery(sequence) { this.adapter.acceptContractDelivery?.(sequence); }
|
|
110
118
|
queue = new BuilderEventQueue();
|
|
111
119
|
sourcePump;
|
|
112
120
|
recoveryLeasePending;
|
|
121
|
+
validatingFreshRecovery = false;
|
|
122
|
+
buildAuthority;
|
|
113
123
|
constructor(options) {
|
|
114
124
|
this.options = options;
|
|
115
125
|
this.adapter = options.adapter;
|
|
126
|
+
this.buildAuthority = localBuildAuthority(options.projectDir);
|
|
116
127
|
this.recoveryLeasePending = Boolean(options.replaceRecoveryLeaseAfterCheckpoint);
|
|
117
128
|
const db = new WorkflowDb(options.projectDir);
|
|
118
129
|
try {
|
|
@@ -130,8 +141,42 @@ export class ContinuityAdapter {
|
|
|
130
141
|
}
|
|
131
142
|
this.pumpEvents();
|
|
132
143
|
}
|
|
144
|
+
/** Establish an explicitly requested fresh owner before context probes or work. */
|
|
145
|
+
async validateFreshRecovery() {
|
|
146
|
+
if (!this.recoveryLeasePending)
|
|
147
|
+
return;
|
|
148
|
+
const db = new WorkflowDb(this.options.projectDir);
|
|
149
|
+
let owner;
|
|
150
|
+
try {
|
|
151
|
+
owner = db.roleMutationLease(this.options.runId, this.options.role);
|
|
152
|
+
}
|
|
153
|
+
finally {
|
|
154
|
+
db.close();
|
|
155
|
+
}
|
|
156
|
+
const current = this.adapter.sessionRef?.();
|
|
157
|
+
if (!owner || (current && owner.sessionKey === providerSessionKey(current))) {
|
|
158
|
+
this.recoveryLeasePending = false;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
this.validatingFreshRecovery = true;
|
|
162
|
+
try {
|
|
163
|
+
const result = await this.sendTurn("Fresh recovery validation only. Do not use tools, change files, implement work, or request a handoff. Acknowledge this new session and emit the required continuity record using only the supplied instructions.", { responseOnly: true });
|
|
164
|
+
if (result.isError || result.failure || this.recoveryLeasePending)
|
|
165
|
+
throw new ContinuityRecoveryRequiredError(this.options.runId, this.options.role, "fresh recovery did not establish a valid continuity checkpoint");
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
await this.close().catch(() => { });
|
|
169
|
+
throw error;
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
this.validatingFreshRecovery = false;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
133
175
|
get agent() { return this.adapter.agent; }
|
|
134
176
|
async sendTurn(instruction, policy) {
|
|
177
|
+
return withGraphSessionAccess(this.options.projectDir, this.adapter, () => this.sendTurnOwned(instruction, policy));
|
|
178
|
+
}
|
|
179
|
+
async sendTurnOwned(instruction, policy) {
|
|
135
180
|
const db = new WorkflowDb(this.options.projectDir);
|
|
136
181
|
try {
|
|
137
182
|
db.appendContinuityEvent({ runId: this.options.runId, role: "host", kind: "turn_started", payload: { role: this.options.role, instructionDigest: sha(instruction), instructionBytes: Buffer.byteLength(instruction) }, authoritativeStateRevision: this.revision() });
|
|
@@ -139,12 +184,16 @@ export class ContinuityAdapter {
|
|
|
139
184
|
finally {
|
|
140
185
|
db.close();
|
|
141
186
|
}
|
|
142
|
-
const providerInstruction = `${instruction}\n\n${continuityInstruction()}`;
|
|
187
|
+
const providerInstruction = `${instruction}\n\n${continuityInstruction(this.options.role)}`;
|
|
143
188
|
const journal = new WorkflowDb(this.options.projectDir);
|
|
144
189
|
const dispatchId = `dispatch:${this.options.runId}:${this.options.role}:${randomUUID()}`;
|
|
145
190
|
let original;
|
|
146
191
|
try {
|
|
147
192
|
journal.atomic(() => {
|
|
193
|
+
if (this.buildAuthority)
|
|
194
|
+
journal.assertBuildAdmission(this.buildAuthority);
|
|
195
|
+
else if (journal.buildAdmission())
|
|
196
|
+
throw new ContinuityRecoveryRequiredError(this.options.runId, this.options.role, "dispatch requires original build admission authority");
|
|
148
197
|
if (process.send && process.env.RAFI_BUILD_WORKER_RUN === this.options.runId) {
|
|
149
198
|
const supervisor = journal.supervisorState(this.options.runId);
|
|
150
199
|
if (!process.connected || supervisor?.status !== "running" || supervisor.workerGeneration !== Number(process.env.RAFI_BUILD_WORKER_GENERATION))
|
|
@@ -153,7 +202,7 @@ export class ContinuityAdapter {
|
|
|
153
202
|
const owner = journal.roleMutationLease(this.options.runId, this.options.role);
|
|
154
203
|
const ref = this.adapter.sessionRef?.();
|
|
155
204
|
const session = ref?.sessionId ?? this.adapter.sessionId();
|
|
156
|
-
if (owner && session && (owner.providerSessionId !== session || (owner.sessionKey && ref && owner.sessionKey !== providerSessionKey(ref)))) {
|
|
205
|
+
if (!(this.validatingFreshRecovery && this.recoveryLeasePending && policy?.responseOnly) && owner && session && (owner.providerSessionId !== session || (owner.sessionKey && ref && owner.sessionKey !== providerSessionKey(ref)))) {
|
|
157
206
|
throw new ContinuityRecoveryRequiredError(this.options.runId, this.options.role, "provider session no longer owns role dispatch authority");
|
|
158
207
|
}
|
|
159
208
|
if (journal.unresolvedRoleDispatches(this.options.runId, this.options.role).length)
|
|
@@ -162,7 +211,7 @@ export class ContinuityAdapter {
|
|
|
162
211
|
journal.updateOperation(dispatchId, "in_progress");
|
|
163
212
|
});
|
|
164
213
|
try {
|
|
165
|
-
original = await this.adapter.sendTurn(
|
|
214
|
+
original = await dispatchWithGraphAccess(this.options.projectDir, this.adapter, providerInstruction, policy, (text, p) => this.adapter.sendTurn(text, p));
|
|
166
215
|
}
|
|
167
216
|
catch (error) {
|
|
168
217
|
journal.updateOperation(dispatchId, "uncertain", { error: String(error) });
|
|
@@ -203,7 +252,7 @@ export class ContinuityAdapter {
|
|
|
203
252
|
if (parsed.delta && !original.isError && !original.failure) {
|
|
204
253
|
this.publish(parsed.delta, "turn_completed", original);
|
|
205
254
|
this.moveRecoveryLeaseAfterCheckpoint();
|
|
206
|
-
if (this.options.durableSingleTurn) {
|
|
255
|
+
if (this.options.durableSingleTurn || this.validatingFreshRecovery) {
|
|
207
256
|
return { ...original, text: parsed.cleanText, hostInstruction: instruction, providerInstruction: original.providerInstruction ?? providerInstruction,
|
|
208
257
|
rawResponse: original.rawResponse ?? original.text, cleanedResponse: parsed.cleanText };
|
|
209
258
|
}
|
|
@@ -213,12 +262,12 @@ export class ContinuityAdapter {
|
|
|
213
262
|
return this.sendTurn([
|
|
214
263
|
"Continue the frozen action after the accepted Rafi handoff. Do not repeat completed side effects.",
|
|
215
264
|
instruction,
|
|
216
|
-
].join("\n\n"));
|
|
265
|
+
].join("\n\n"), policy);
|
|
217
266
|
}
|
|
218
267
|
return { ...original, text: parsed.cleanText, hostInstruction: instruction, providerInstruction: original.providerInstruction ?? providerInstruction,
|
|
219
268
|
rawResponse: original.rawResponse ?? original.text, cleanedResponse: parsed.cleanText };
|
|
220
269
|
}
|
|
221
|
-
if (this.options.durableSingleTurn) {
|
|
270
|
+
if (this.options.durableSingleTurn || (this.validatingFreshRecovery && this.recoveryLeasePending && policy?.responseOnly)) {
|
|
222
271
|
const invalidDb = new WorkflowDb(this.options.projectDir);
|
|
223
272
|
try {
|
|
224
273
|
invalidDb.appendContinuityEvent({ runId: this.options.runId, role: "host", kind: "continuity_invalid", payload: { problems: parsed.error?.problems }, authoritativeStateRevision: this.revision() });
|
|
@@ -226,7 +275,7 @@ export class ContinuityAdapter {
|
|
|
226
275
|
finally {
|
|
227
276
|
invalidDb.close();
|
|
228
277
|
}
|
|
229
|
-
return { ...original, isError: true, text: `QA continuity record was invalid: ${parsed.error?.problems.join("; ") ?? "unknown error"}`,
|
|
278
|
+
return { ...original, isError: true, continuityErrors: parsed.error?.problems ?? ["unknown continuity error"], text: `QA continuity record was invalid: ${parsed.error?.problems.join("; ") ?? "unknown error"}`,
|
|
230
279
|
hostInstruction: instruction, providerInstruction: original.providerInstruction ?? providerInstruction,
|
|
231
280
|
rawResponse: original.rawResponse ?? original.text, cleanedResponse: parsed.cleanText };
|
|
232
281
|
}
|
|
@@ -240,12 +289,12 @@ export class ContinuityAdapter {
|
|
|
240
289
|
finally {
|
|
241
290
|
budget.close();
|
|
242
291
|
}
|
|
243
|
-
const repair = await this.adapter
|
|
292
|
+
const repair = await dispatchWithGraphAccess(this.options.projectDir, this.adapter, [
|
|
244
293
|
"Continuity protocol repair only. Do not run tools, repeat work, or change files.",
|
|
245
294
|
`Your prior turn's continuity record was invalid: ${parsed.error?.problems.join("; ")}.`,
|
|
246
295
|
continuityInstruction(),
|
|
247
296
|
"Return only the continuity record.",
|
|
248
|
-
].join("\n"), { ...policy, responseOnly: true });
|
|
297
|
+
].join("\n"), { ...policy, purpose: "response-repair", responseOnly: true }, (text, p) => this.adapter.sendTurn(text, p));
|
|
249
298
|
const repaired = parseContinuityDelta(repair.text);
|
|
250
299
|
if (repaired.delta && !repair.isError && !repair.failure) {
|
|
251
300
|
this.publish(repaired.delta, "turn_completed_after_repair", original);
|
|
@@ -253,7 +302,7 @@ export class ContinuityAdapter {
|
|
|
253
302
|
const successor = await this.options.handleHandoffRequest?.(original.text, this.adapter, instruction);
|
|
254
303
|
if (successor && successor !== this.adapter) {
|
|
255
304
|
await this.adoptValidatedSuccessor(successor);
|
|
256
|
-
return this.sendTurn(["Continue the frozen action after the accepted Rafi handoff. Do not repeat completed side effects.", instruction].join("\n\n"));
|
|
305
|
+
return this.sendTurn(["Continue the frozen action after the accepted Rafi handoff. Do not repeat completed side effects.", instruction].join("\n\n"), policy);
|
|
257
306
|
}
|
|
258
307
|
return { ...original, text: parsed.cleanText, hostInstruction: instruction, providerInstruction: original.providerInstruction ?? providerInstruction,
|
|
259
308
|
rawResponse: original.rawResponse ?? original.text, cleanedResponse: parsed.cleanText };
|
|
@@ -290,7 +339,7 @@ export class ContinuityAdapter {
|
|
|
290
339
|
if (!this.options.createSuccessor)
|
|
291
340
|
throw new ContinuityRecoveryRequiredError(this.options.runId, this.options.role, "continuity repair failed in the original session and no validated successor is configured");
|
|
292
341
|
const successor = await this.options.createSuccessor(handoff);
|
|
293
|
-
const accepted = await successor
|
|
342
|
+
const accepted = await dispatchWithGraphAccess(this.options.projectDir, successor, `${handoff}\n\nReply with HANDOFF_ACCEPTED on the first line, then ${continuityInstruction()}`, { purpose: "contract-acceptance", responseOnly: true }, (text, p) => successor.sendTurn(text, p));
|
|
294
343
|
const successorDelta = parseContinuityDelta(accepted.text);
|
|
295
344
|
const firstAcceptanceLine = accepted.text.split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
296
345
|
if (firstAcceptanceLine !== "HANDOFF_ACCEPTED" || !successorDelta.delta || !successor.sessionId()) {
|
|
@@ -338,12 +387,19 @@ export class ContinuityAdapter {
|
|
|
338
387
|
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
|
339
388
|
contextUsageAfterNativeCompaction(compaction) { return this.adapter.contextUsageAfterNativeCompaction?.(compaction) ?? Promise.resolve(undefined); }
|
|
340
389
|
contextUsage() { return this.adapter.contextUsage?.() ?? Promise.resolve(undefined); }
|
|
390
|
+
graphRuntimeSettings() { return this.adapter.graphRuntimeSettings?.(); }
|
|
341
391
|
sessionUsage() { return this.adapter.sessionUsage?.() ?? Promise.resolve(undefined); }
|
|
342
392
|
switchSettings(settings) { return this.adapter.switchSettings ? this.adapter.switchSettings(settings) : Promise.resolve({ ok: false, error: "provider adapter does not support settings changes" }); }
|
|
343
393
|
events() { return this.queue; }
|
|
344
394
|
async close() { await this.adapter.close(); await this.sourcePump?.catch(() => { }); this.queue.close(); }
|
|
345
395
|
/** Keep the observable wrapper and event stream stable while moving to an accepted successor. */
|
|
346
396
|
async adoptValidatedSuccessor(successor) {
|
|
397
|
+
if (this.contractEnforcing) {
|
|
398
|
+
successor.enableContractEnforcement?.();
|
|
399
|
+
if (successor !== this.adapter)
|
|
400
|
+
await successor.close().catch(() => { });
|
|
401
|
+
throw new ContinuityRecoveryRequiredError(this.options.runId, this.options.role, "Replacement Builder requires host contract delivery before any continuation; completed turn must be reconciled without automatic replay");
|
|
402
|
+
}
|
|
347
403
|
if (successor === this.adapter)
|
|
348
404
|
return;
|
|
349
405
|
const prior = this.adapter;
|
package/dist/foreman.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { GraphPurpose } from "rafi-spec";
|
|
1
2
|
import type { BuilderAdapter, PermissionHandler, PermissionRequest, TurnResult } from "./adapters/types.js";
|
|
2
3
|
import type { PermissionPolicy } from "./permissions/policy.js";
|
|
3
4
|
import { type AnsweredProviderQuestion } from "./providerQuestions.js";
|
|
@@ -85,19 +86,25 @@ export declare class Foreman {
|
|
|
85
86
|
/** Exact durable build-run scope. Observability is never an identity authority. */
|
|
86
87
|
private readonly qaRunId;
|
|
87
88
|
private readonly continueIndependentTickets;
|
|
89
|
+
private readonly builderWorktree?;
|
|
90
|
+
private readonly decisionPrompt?;
|
|
91
|
+
private graphPurpose;
|
|
92
|
+
setGraphPurpose(purpose: GraphPurpose): void;
|
|
88
93
|
private readonly ticketsEnabled;
|
|
89
94
|
private readonly notificationsEnabled;
|
|
90
95
|
private readonly terminalBellEnabled;
|
|
91
96
|
private readonly qaStream;
|
|
92
97
|
private builderWorkSessions;
|
|
93
98
|
private currentTicketId?;
|
|
99
|
+
private preflightPlan;
|
|
100
|
+
approvedPlanText(): string;
|
|
94
101
|
constructor(builder: BuilderAdapter, log: Log, notifications?: boolean | ForemanNotificationOptions, qaEnabled?: boolean, qaMaxCycles?: number, projectDir?: string | undefined,
|
|
95
102
|
/** @deprecated QA must be created by qaFactory in a disposable snapshot. */
|
|
96
103
|
_deprecatedSameSessionReviewer?: BuilderAdapter, qaFactory?: ((cwd: string, sessionId?: string) => Promise<QaSessionHandle>) | undefined, qaSessionStrategy?: SessionStrategy, builderFactory?: ((cwd: string, sessionId?: string) => Promise<BuilderAdapter>) | undefined, builderSessionStrategy?: SessionStrategy, qaNonconvergence?: ((context: QaNonconvergenceContext) => Promise<QaNonconvergenceDecision>) | undefined, beforeBuilderTurn?: ((adapter: BuilderAdapter, frozenAction: string) => Promise<BuilderAdapter>) | undefined, builderSessionBoundary?: ((adapter: BuilderAdapter, frozenAction: string, strategy: SessionStrategy) => Promise<BuilderAdapter>) | undefined, qaSessionBoundary?: ((handle: QaSessionHandle, frozenAction: string, strategy: SessionStrategy, cwd: string, recovery?: QaSessionBoundaryRecovery) => Promise<QaSessionBoundaryResult>) | undefined, observeQaNativeCompactions?: ((adapter: BuilderAdapter) => Promise<void>) | undefined,
|
|
97
104
|
/** Persist a Builder's provider-native event immediately after every turn. */
|
|
98
105
|
observeBuilderNativeCompactions?: ((adapter: BuilderAdapter) => Promise<void>) | undefined, observer?: RunObserver | undefined, qaRuntimeContext?: unknown | undefined, qaContinuityManaged?: boolean, qaReportRecovery?: QaReportRecoveryHandler | undefined, qaResumedRecovery?: QaRecoveryPacket | undefined,
|
|
99
106
|
/** Exact durable build-run scope. Observability is never an identity authority. */
|
|
100
|
-
qaRunId?: string, continueIndependentTickets?: boolean, initialTicketId?: string);
|
|
107
|
+
qaRunId?: string, continueIndependentTickets?: boolean, initialTicketId?: string, builderWorktree?: string | undefined, decisionPrompt?: ((decision: import("rafi-spec").PendingHumanDecision) => Promise<string | undefined>) | undefined);
|
|
101
108
|
private waitForUserInput;
|
|
102
109
|
/**
|
|
103
110
|
* Send one instruction and resolve any needs_input exchanges before returning.
|
|
@@ -106,8 +113,9 @@ export declare class Foreman {
|
|
|
106
113
|
private doTurn;
|
|
107
114
|
private prepareBuilderBoundary;
|
|
108
115
|
private doTurnWith;
|
|
116
|
+
private doTurnWithOwned;
|
|
109
117
|
/** Send a planning turn and return the builder's response text. Does not count toward steps. */
|
|
110
|
-
runPreflight(n: number, ticketsContent?: string, preferredTicketId?: string): Promise<string>;
|
|
118
|
+
runPreflight(n: number, ticketsContent?: string, preferredTicketId?: string, executionTickets?: readonly string[]): Promise<string>;
|
|
111
119
|
/** Send user feedback on the plan; builder responds with a revised list. Does not count toward steps. */
|
|
112
120
|
sendPreflightFeedback(feedback: string): Promise<void>;
|
|
113
121
|
/** Send one custom instruction through the same needs_input loop as a batch turn. */
|
|
@@ -140,7 +148,7 @@ export declare class Foreman {
|
|
|
140
148
|
passCertificateId?: string;
|
|
141
149
|
sourceStateDigest?: string;
|
|
142
150
|
}>;
|
|
143
|
-
/**
|
|
151
|
+
/** Resume the pending boundary, including authorized waiting guidance, then complete its tracker transition. */
|
|
144
152
|
completePendingQaRecovery(ticketId: string): Promise<{
|
|
145
153
|
outcome: "passed" | "blocked" | "needs-human" | "waived";
|
|
146
154
|
detail?: string;
|
|
@@ -153,5 +161,5 @@ export declare class Foreman {
|
|
|
153
161
|
builderAdapter(): BuilderAdapter;
|
|
154
162
|
close(): Promise<void>;
|
|
155
163
|
private ticketForQa;
|
|
156
|
-
runBatch(n: number, trackerPath?: string, onTicketStart?: (ticketId: string) => void | Promise<void>, preferredTicketId?: string): Promise<BatchResult>;
|
|
164
|
+
runBatch(n: number, trackerPath?: string, onTicketStart?: (ticketId: string) => void | Promise<void>, preferredTicketId?: string, recoveryTickets?: readonly string[]): Promise<BatchResult>;
|
|
157
165
|
}
|