ai-foreman 1.7.28 → 2.0.0
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 +1 -1
- package/dist/adapters/claude.d.ts +23 -12
- package/dist/adapters/claude.js +228 -24
- package/dist/adapters/codex.d.ts +17 -7
- package/dist/adapters/codex.js +195 -17
- package/dist/adapters/recovering.d.ts +2 -0
- package/dist/adapters/recovering.js +5 -1
- package/dist/adapters/structured.d.ts +71 -0
- package/dist/adapters/structured.js +213 -0
- package/dist/adapters/types.d.ts +86 -9
- package/dist/agentRun.d.ts +2 -0
- package/dist/agentRun.js +2 -1
- package/dist/boundedTurnEvidence.d.ts +18 -0
- package/dist/boundedTurnEvidence.js +76 -0
- package/dist/branch/currentGuard.d.ts +2 -0
- package/dist/branch/currentGuard.js +2 -0
- package/dist/branch/planner.js +1 -4
- package/dist/branch/runner.d.ts +2 -1
- package/dist/branch/runner.js +10 -7
- package/dist/buildAdmission.js +4 -0
- package/dist/buildApproval.d.ts +30 -0
- package/dist/buildApproval.js +68 -0
- package/dist/buildAssignment.d.ts +1 -0
- package/dist/buildAssignment.js +82 -23
- package/dist/buildInterventions.d.ts +2 -2
- package/dist/buildInterventions.js +11 -4
- package/dist/buildLaunchGate.js +3 -2
- package/dist/builderGuidanceFollowup.js +17 -6
- package/dist/cli/manager.js +22 -20
- package/dist/cli/start.js +32 -24
- package/dist/cli/tickets.js +40 -24
- package/dist/continuity.d.ts +10 -14
- package/dist/continuity.js +198 -179
- package/dist/continuityOrigin.d.ts +13 -0
- package/dist/continuityOrigin.js +91 -0
- package/dist/foreman.d.ts +20 -5
- package/dist/foreman.js +57 -187
- package/dist/graph/boundary.d.ts +1 -1
- package/dist/graph/boundary.js +1 -1
- package/dist/graph/host.js +8 -19
- package/dist/graph/manager.d.ts +1 -1
- package/dist/graph/manager.js +1 -1
- package/dist/graph/preparation.d.ts +38 -0
- package/dist/graph/preparation.js +7 -6
- package/dist/graph/semantic.d.ts +1 -0
- package/dist/graph/semantic.js +4 -1
- package/dist/graph/session.js +7 -2
- package/dist/graph/turn.d.ts +2 -3
- package/dist/graph/turn.js +28 -40
- package/dist/handoffProtocol.js +2 -2
- package/dist/handoffs.d.ts +3 -1
- package/dist/handoffs.js +90 -59
- package/dist/hostGraphApplicability.d.ts +46 -0
- package/dist/hostGraphApplicability.js +97 -0
- package/dist/hostQaAugmentation.d.ts +18 -0
- package/dist/hostQaAugmentation.js +81 -0
- package/dist/hostQaComparison.d.ts +21 -2
- package/dist/hostQaComparison.js +43 -28
- package/dist/hostSourcePreservation.d.ts +38 -0
- package/dist/hostSourcePreservation.js +72 -0
- package/dist/interviews.d.ts +7 -0
- package/dist/interviews.js +18 -1
- package/dist/legacyCommunicationDisplay.d.ts +2 -0
- package/dist/legacyCommunicationDisplay.js +2 -0
- package/dist/managerPacket.js +3 -3
- package/dist/markers.d.ts +4 -3
- package/dist/markers.js +4 -20
- package/dist/probeIpc.d.ts +14 -0
- package/dist/probeIpc.js +29 -0
- package/dist/qaBuildGate.d.ts +21 -0
- package/dist/qaBuildGate.js +48 -12
- package/dist/qaBuilderCoverage.js +22 -13
- package/dist/qaBuilderEvidence.d.ts +16 -1
- package/dist/qaBuilderEvidence.js +48 -8
- package/dist/qaContractCoverage.d.ts +2 -2
- package/dist/qaContractCoverage.js +11 -7
- package/dist/qaContractDelivery.js +34 -20
- package/dist/qaCoverageAssembly.d.ts +22 -0
- package/dist/qaCoverageAssembly.js +16 -0
- package/dist/qaDeliveryJournal.d.ts +2 -0
- package/dist/qaDeliveryJournal.js +19 -0
- package/dist/qaEffectiveConfig.d.ts +1 -0
- package/dist/qaEffectiveConfig.js +6 -1
- package/dist/qaFailureDelivery.js +97 -85
- package/dist/qaHostTransformation.d.ts +26 -0
- package/dist/qaHostTransformation.js +100 -0
- package/dist/qaPreparation.d.ts +8 -20
- package/dist/qaPreparation.js +133 -229
- package/dist/qaPreparationInputs.js +19 -0
- package/dist/qaPreparationStore.js +34 -6
- package/dist/qaPrerequisites.d.ts +2 -1
- package/dist/qaPrerequisites.js +21 -1
- package/dist/qaProtocolV2.d.ts +15 -0
- package/dist/qaRecovery.js +21 -4
- package/dist/qaReview.js +232 -394
- package/dist/qaReviewEvidence.d.ts +7 -3
- package/dist/qaReviewEvidence.js +15 -4
- package/dist/qaRuntime.js +1 -1
- package/dist/qaSnapshot.js +71 -21
- package/dist/qaStructuredResponse.d.ts +10 -0
- package/dist/qaStructuredResponse.js +57 -0
- package/dist/qaTimeline.js +5 -3
- package/dist/qaToolRecovery.d.ts +12 -0
- package/dist/qaToolRecovery.js +44 -0
- package/dist/qaVerificationContract.d.ts +3 -2
- package/dist/qaVerificationContract.js +58 -34
- package/dist/readinessGate.js +5 -4
- package/dist/runtimeReadiness.js +23 -15
- package/dist/statusReporter.d.ts +2 -0
- package/dist/statusReporter.js +2 -0
- package/dist/structuredCutover.d.ts +31 -0
- package/dist/structuredCutover.js +510 -0
- package/dist/structuredCutoverFiles.d.ts +10 -0
- package/dist/structuredCutoverFiles.js +181 -0
- package/dist/structuredDispatchAuthority.d.ts +22 -0
- package/dist/structuredDispatchAuthority.js +110 -0
- package/dist/structuredDispatchRecord.d.ts +29 -0
- package/dist/structuredDispatchRecord.js +36 -0
- package/dist/structuredLiveBudget.d.ts +2 -0
- package/dist/structuredLiveBudget.js +23 -0
- package/dist/structuredOperation.d.ts +18 -0
- package/dist/structuredOperation.js +257 -0
- package/dist/supervisedStart.d.ts +1 -1
- package/dist/supervisedStart.js +78 -31
- package/dist/supervisor.d.ts +18 -2
- package/dist/supervisor.js +74 -17
- package/dist/ticketPlanning.d.ts +1 -373
- package/dist/ticketPlanning.js +2 -26
- package/dist/ticketPopulation.d.ts +0 -3
- package/dist/ticketPopulation.js +4 -21
- package/dist/ticketPopulationPageAuthority.d.ts +13 -0
- package/dist/ticketPopulationPageAuthority.js +30 -0
- package/dist/ticketPopulationPaging.d.ts +27 -0
- package/dist/ticketPopulationPaging.js +105 -0
- package/dist/tickets/renderMarkdown.d.ts +14 -0
- package/dist/tickets/renderMarkdown.js +39 -5
- package/dist/tickets/ticketLoader.js +10 -0
- package/dist/tickets/ticketSchema.d.ts +107 -0
- package/dist/tickets/ticketSchema.js +4 -1
- package/dist/workflowDb.d.ts +100 -2
- package/dist/workflowDb.js +781 -35
- package/dist/workflowReader.d.ts +1 -0
- package/dist/workflowReader.js +9 -0
- package/package.json +15 -7
package/dist/cli/start.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nativePolicy, operationInstruction, operationPayload, structuredTurnErrorMessage } from "../structuredOperation.js";
|
|
1
2
|
import { finalQaRoleInstructions, resolveEffectiveQaConfiguration } from "../qaEffectiveConfig.js";
|
|
2
3
|
import { activateQaRecoveryBuilder } from "../qaBuilderActivation.js";
|
|
3
4
|
import { graphContext, sendGraphTurn } from "../graph/turn.js";
|
|
@@ -11,7 +12,7 @@ import { captureFrozenQaSource } from "../qaSnapshot.js";
|
|
|
11
12
|
import { finishStartAdmission, superviseStart, supervisedRunId } from "../supervisedStart.js";
|
|
12
13
|
import { assertBuildAssignmentReconciled, isInterruptedBuilderRecovery, assertInterruptedSourceSnapshot } from "../buildAssignment.js";
|
|
13
14
|
import { durableHumanDecision, HumanDecisionRequired, HumanDecisionCancelled, servicePendingHumanDecisions } from "../humanDecision.js";
|
|
14
|
-
import { requiresBuildApproval, buildScopeRevision, createBuildApprovalGate } from "../buildApproval.js";
|
|
15
|
+
import { requiresBuildApproval, buildScopeRevision, createBuildApprovalGate, validatePlanApprovalBinding, validateFrozenWorkApprovalBinding } from "../buildApproval.js";
|
|
15
16
|
import { Command, Option } from "commander";
|
|
16
17
|
import { accent, error, sanitizeTerminalText, success, warning } from "../terminalStyle.js";
|
|
17
18
|
import { resolve, join, relative } from "node:path";
|
|
@@ -34,7 +35,7 @@ import { isTicketsInitialized, loadTicketsConfig, resolveTicketPaths } from "../
|
|
|
34
35
|
import { hasRollbackOrMitigationPlan, loadTickets } from "../tickets/ticketLoader.js";
|
|
35
36
|
import { StateDb } from "../tickets/stateDb.js";
|
|
36
37
|
import { ticketDefinitionDigest } from "../tickets/riskWaivers.js";
|
|
37
|
-
import { applySharedDeliveryBranch, applySizeBranchPolicy, buildBranchAuditInstruction, buildBranchPlan
|
|
38
|
+
import { applySharedDeliveryBranch, applySizeBranchPolicy, buildBranchAuditInstruction, buildBranchPlan } from "../branch/planner.js";
|
|
38
39
|
import { currentGitRef, ensureCleanBaseWorktree, generatedTrackerDirtyPaths } from "../branch/git.js";
|
|
39
40
|
import { captureDirtyBaseline } from "../branch/dirtyBaseline.js";
|
|
40
41
|
import { formatGitHubFailure, preflightGh } from "../branch/github.js";
|
|
@@ -42,7 +43,7 @@ import { preflightGlab } from "../branch/gitlab.js";
|
|
|
42
43
|
import { readDeliveryUnitSession, runBranchPlan } from "../branch/runner.js";
|
|
43
44
|
import { branchPlanLogMetadata, presentBranchPlan } from "../branch/presentation.js";
|
|
44
45
|
import { branchTicketScopeProgress, buildRunSessionBinding, checkpointBuildRun, completeBuildRun, createBuildRun, currentBranchRecoveryTicket, currentBranchTicketProgress, recoveryExecutionTickets, finishBranchTicketScope, finishRecoveredTicketScope, heartbeatBuildRun, persistBuildSession, persistCurrentBuildSession, projectBuildRecovery, readBuildRuns, releaseBuildLease, resumeBuildRun } from "../buildRuns.js";
|
|
45
|
-
import {
|
|
46
|
+
import { branchAuditOutput, plannerRemediationOutput } from "rafi-spec";
|
|
46
47
|
import { parse as parseYaml } from "yaml";
|
|
47
48
|
import { findResumableBranchSessions, formatBranchContinueCommand, formatBranchSummaryFollowupCommands, } from "../branch/resume.js";
|
|
48
49
|
import { detectGitProvider, loadTicketSetupConfig } from "../tickets/setupConfig.js";
|
|
@@ -693,6 +694,20 @@ export function buildStartCommand(options = {}) {
|
|
|
693
694
|
? recoveryRecord.recoveryDecision?.planUpdateApproval === "auto"
|
|
694
695
|
&& (recoveryRecord.recoveryDecision.planUpdateApprovalSource === "explicit-yes" || recoveryRecord.recoveryDecision.planUpdateApprovalSource === "explicit-choice")
|
|
695
696
|
: Boolean(opts.yes);
|
|
697
|
+
const recoveryTickets = recoveryRecord?.recoveryDecision?.executionTickets ?? recoveryRecord?.tickets ?? [];
|
|
698
|
+
// Empty-scope recovery is used by established/supervisor lifecycle
|
|
699
|
+
// recovery fixtures and has no ticket plan to bind. Ticket-bearing
|
|
700
|
+
// recovery remains fail-closed on an exact current-plan approval.
|
|
701
|
+
if (recoveryRecord && autoApprovePlanUpdates && recoveryTickets.length > 0) {
|
|
702
|
+
const decision = recoveryRecord.recoveryDecision;
|
|
703
|
+
if (!decision?.planApproval && !decision?.frozenWorkApproval)
|
|
704
|
+
fail("saved --yes approval has no exact plan binding; rerun rafi build:resume --yes to approve the current plan");
|
|
705
|
+
const approvalCheck = decision.frozenWorkApproval
|
|
706
|
+
? validateFrozenWorkApprovalBinding(cwd, recoveryRecord.runId, recoveryTickets, decision.frozenWorkApproval)
|
|
707
|
+
: validatePlanApprovalBinding(cwd, recoveryTickets, decision.planApproval);
|
|
708
|
+
if (!approvalCheck.valid)
|
|
709
|
+
fail(`saved --yes approval is stale (${approvalCheck.reason}); rerun rafi build:resume --yes before provider work`);
|
|
710
|
+
}
|
|
696
711
|
if (opts.recoveryMode) {
|
|
697
712
|
const allowed = ["exact-session", "fresh-with-handoff", "fresh-recovery-only", "guided-recovery"];
|
|
698
713
|
if (!allowed.includes(String(opts.recoveryMode)))
|
|
@@ -1293,7 +1308,6 @@ export function buildStartCommand(options = {}) {
|
|
|
1293
1308
|
try {
|
|
1294
1309
|
const revision = countDb.continuityHead(runId, "builder")?.authoritativeStateRevision ?? settings.settings_revision;
|
|
1295
1310
|
countDb.appendContinuityEvent({ runId, role: "builder", kind: "builder_requested_handoff_delta", payload: request.delta, authoritativeStateRevision: revision });
|
|
1296
|
-
countDb.publishContinuityCheckpoint({ runId, role: "builder", delta: request.delta, authoritativeStateRevision: revision });
|
|
1297
1311
|
compactionCount = countDb.successfulCompactionCount(runId, "builder", predecessor.sessionRef?.() ?? sessionId);
|
|
1298
1312
|
}
|
|
1299
1313
|
finally {
|
|
@@ -1306,6 +1320,7 @@ export function buildStartCommand(options = {}) {
|
|
|
1306
1320
|
predecessorSessionId: sessionId,
|
|
1307
1321
|
predecessorSessionRef: predecessor.sessionRef?.(),
|
|
1308
1322
|
requestedByBuilder: true,
|
|
1323
|
+
predecessorDispatchId: request.predecessorDispatchId,
|
|
1309
1324
|
roleState: {
|
|
1310
1325
|
...request.roleState,
|
|
1311
1326
|
frozenActionDigest: createHash("sha256").update(frozenAction).digest("hex"),
|
|
@@ -1322,7 +1337,8 @@ export function buildStartCommand(options = {}) {
|
|
|
1322
1337
|
resources: [{ label: "frozen-action", content: frozenAction, authoritative: true }],
|
|
1323
1338
|
}, (_handoff, runtime) => createBuilderForSettings(builderCwd, settingsForRequestedRuntime(effectiveSettings, runtime)), handoffRecoveryOptions("builder"));
|
|
1324
1339
|
log.write("handoff-transfer", {
|
|
1325
|
-
requestedByBuilder: true,
|
|
1340
|
+
requestedByBuilder: true,
|
|
1341
|
+
predecessorDispatchId: request.predecessorDispatchId, reason: request.reason, generation: transfer.manifest.generation,
|
|
1326
1342
|
predecessorSessionId: transfer.manifest.predecessorSessionId, successorSessionId: transfer.successorSessionId,
|
|
1327
1343
|
acceptanceCheckpointDigest: transfer.acceptanceCheckpointDigest,
|
|
1328
1344
|
});
|
|
@@ -2015,10 +2031,13 @@ export function buildStartCommand(options = {}) {
|
|
|
2015
2031
|
try {
|
|
2016
2032
|
auditBuilder = await createBuilder(cwd);
|
|
2017
2033
|
auditViewer = printEvents(auditBuilder.events());
|
|
2018
|
-
const audit = await sendGraphTurn(auditBuilder, buildBranchAuditInstruction(plan.nodes.map((node) => node.ticket))
|
|
2034
|
+
const audit = await sendGraphTurn(auditBuilder, `${buildBranchAuditInstruction(plan.nodes.map((node) => node.ticket))}\n${operationInstruction(branchAuditOutput)}`, graphContext(cwd, auditBuilder.sessionRef?.()?.cwd ?? cwd, "branch-dependency-audit", plan.nodes.map(node => node.ticket.id).join(" ")), nativePolicy(branchAuditOutput, { purpose: "planning", journalRunId: auditRunId }));
|
|
2019
2035
|
if (audit.isError)
|
|
2020
|
-
throw new Error(audit
|
|
2021
|
-
const auditDependencies =
|
|
2036
|
+
throw new Error(structuredTurnErrorMessage(audit, "Branch dependency audit"));
|
|
2037
|
+
const auditDependencies = operationPayload(audit, branchAuditOutput).dependencies;
|
|
2038
|
+
const selectedIds = new Set(plan.nodes.map(node => node.ticket.id));
|
|
2039
|
+
if (auditDependencies.some(dep => dep.ticket === dep.dependsOn || !selectedIds.has(dep.ticket) || !selectedIds.has(dep.dependsOn)))
|
|
2040
|
+
throw new Error("Branch audit returned a foreign or self dependency");
|
|
2022
2041
|
plan = buildBranchPlan(tickets, states, {
|
|
2023
2042
|
steps,
|
|
2024
2043
|
baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch ?? currentGitRef(cwd),
|
|
@@ -3134,8 +3153,7 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
3134
3153
|
`User discussion: ${String(discussion)}`,
|
|
3135
3154
|
`Complete QA history:\n${JSON.stringify(context.history, null, 2)}`,
|
|
3136
3155
|
`${diffTruncated ? "Builder diff (TRUNCATED at 128 KiB; inspect the actual worktree before relying on it)" : "Full Builder diff"}:\n${diff || "(no tracked diff)"}`,
|
|
3137
|
-
"
|
|
3138
|
-
"End with STEP_STATUS: plan_complete | summary=\"QA remediation proposed\"",
|
|
3156
|
+
"Submit the native planner-remediation payload with summary and fix_instructions.",
|
|
3139
3157
|
].join("\n\n");
|
|
3140
3158
|
let resumeSessionId;
|
|
3141
3159
|
let revision = 0;
|
|
@@ -3143,24 +3161,14 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
3143
3161
|
revision++;
|
|
3144
3162
|
const proposalDigest = createHash("sha256").update(JSON.stringify([scope, round, revision, instruction, planner, captureFrozenQaSource(context.builderWorktree).digest])).digest("hex");
|
|
3145
3163
|
const run = await durableReadOnlyProposal({ projectDir, runId, ticketId: context.ticket.id, digest: proposalDigest, operation: () => runRoleInstruction({
|
|
3146
|
-
projectDir, role: "planner", instruction, label: "QA Planner remediation", agent: planner?.make,
|
|
3164
|
+
projectDir, role: "planner", instruction, output: plannerRemediationOutput, label: "QA Planner remediation", agent: planner?.make,
|
|
3147
3165
|
model: explicitDefaultValue(planner?.model), effort: explicitEffort(planner?.reasoning), fast: planner?.fast,
|
|
3148
3166
|
resumeSessionId, permissionConfig: readOnlyPermissionConfig(), sandboxMode: "read-only",
|
|
3149
3167
|
logPath: makeRoleLogPath(projectDir, "qa-remediation"), logEvent: "rafi-plan",
|
|
3150
3168
|
}) });
|
|
3151
|
-
if (run.turn.result.isError || run.turn.status.kind !== "
|
|
3169
|
+
if (run.turn.result.isError || run.turn.status.kind !== "done")
|
|
3152
3170
|
throw new Error(`Planner remediation failed: ${run.turn.status.error ?? run.turn.result.text.slice(0, 500)}`);
|
|
3153
|
-
const
|
|
3154
|
-
if (!match)
|
|
3155
|
-
throw new Error("Planner remediation did not return the required structured proposal");
|
|
3156
|
-
let parsed;
|
|
3157
|
-
try {
|
|
3158
|
-
parsed = JSON.parse(match[1].replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/, ""));
|
|
3159
|
-
}
|
|
3160
|
-
catch (error) {
|
|
3161
|
-
throw new Error(`Planner remediation proposal is invalid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
3162
|
-
}
|
|
3163
|
-
const proposal = parseProviderContract(qaRemediationProposalV1Schema, parsed, "RAFI_QA_REMEDIATION");
|
|
3171
|
+
const proposal = operationPayload(run.turn.result, plannerRemediationOutput);
|
|
3164
3172
|
console.log(`\nPlanner remediation: ${proposal.summary}\n${proposal.fix_instructions.map((item) => `- ${item}`).join("\n")}`);
|
|
3165
3173
|
const decision = await ask(`QA remediation approval ${revision}: ${JSON.stringify(proposal)}`, [{ id: "approve", label: "Approve" }, { id: "revise", label: "Revise" }, { id: "cancel", label: "Return to choices" }], () => select({ message: "Use this remediation for a new Builder fix session?", options: [
|
|
3166
3174
|
{ value: "approve", label: "Approve (Recommended)" }, { value: "revise", label: "Discuss/revise" }, { value: "cancel", label: "Cancel and return to choices" },
|
|
@@ -3173,7 +3181,7 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
3173
3181
|
if (isCancel(feedback))
|
|
3174
3182
|
break;
|
|
3175
3183
|
resumeSessionId = run.sessionId;
|
|
3176
|
-
instruction = `Revise the complete QA remediation proposal using this feedback: ${String(feedback)}.
|
|
3184
|
+
instruction = `Revise the complete QA remediation proposal using this feedback: ${String(feedback)}. Use the native planner-remediation contract.`;
|
|
3177
3185
|
}
|
|
3178
3186
|
}
|
|
3179
3187
|
};
|
package/dist/cli/tickets.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { streamTicketPopulation } from "../ticketPopulationPaging.js";
|
|
2
|
+
import { operationPayload } from "../structuredOperation.js";
|
|
1
3
|
import { Command } from "commander";
|
|
2
4
|
import { accent, error, sanitizeTerminalText, success, warning } from "../terminalStyle.js";
|
|
3
5
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
6
|
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
7
|
import { parse as parseYaml } from "yaml";
|
|
6
|
-
import { riskNoteRepairProposalV1Schema, zodIssues } from "rafi-spec";
|
|
8
|
+
import { riskNoteRepairOutput, riskNoteRepairProposalV1Schema, zodIssues } from "rafi-spec";
|
|
7
9
|
import { select, text, confirm, isCancel, multiselect } from "@clack/prompts";
|
|
8
10
|
import { loadConfig } from "../config.js";
|
|
9
11
|
import { Log } from "../log.js";
|
|
@@ -20,7 +22,7 @@ import { DEFAULT_TICKET_SETUP, configuredPlanningSources, configuredAppName, def
|
|
|
20
22
|
import { deactivateSource, discardStagedSourceCaptures, loadSourceRegistry, refreshSourceRegistry, registerSourceRequests, removeSource, saveSourceRegistry, } from "../sources/sourceRegistry.js";
|
|
21
23
|
import { fetchAndSnapshotUrl, snapshotExternalLocalFile } from "../tickets/sourceFetch.js";
|
|
22
24
|
import { checkpointInterview, completeInterview, createInterviewRecord, failInterview, readInterviewRecords, resumeInterviewRecord, } from "../interviews.js";
|
|
23
|
-
import { applyTicketPopulation, assertRiskPlanOnlyCorrection, authorizeTicketRetirements,
|
|
25
|
+
import { applyTicketPopulation, assertRiskPlanOnlyCorrection, authorizeTicketRetirements, materializeTicketPopulation, recoverTicketPublications, TicketPopulationValidationError } from "../ticketPopulation.js";
|
|
24
26
|
import { loadTickets, saveTickets, validateTicketDefs } from "../tickets/ticketLoader.js";
|
|
25
27
|
import { resolveTicketPaths } from "../tickets/config.js";
|
|
26
28
|
import { formatAllTicketDetails, formatTicketDetails, getAllTicketDetails, getTicketDetails } from "../tickets/details.js";
|
|
@@ -31,6 +33,7 @@ import { listTicketGroups, previewTicketGroupRepair, repairTicketGroups } from "
|
|
|
31
33
|
import { StateDb } from "../tickets/stateDb.js";
|
|
32
34
|
import { ticketDefinitionDigest } from "../tickets/riskWaivers.js";
|
|
33
35
|
import { hasRollbackOrMitigationPlan } from "../tickets/ticketLoader.js";
|
|
36
|
+
import { migrateTrackerRules } from "../tickets/renderMarkdown.js";
|
|
34
37
|
let nestedTicketCommandDepth = 0;
|
|
35
38
|
class TicketInterviewCancelled extends Error {
|
|
36
39
|
}
|
|
@@ -195,23 +198,13 @@ Return ticket content using Foreman's schema:
|
|
|
195
198
|
- title, area, priority, size, risk, depends_on, summary, acceptance, required_tests, likely_files, rollback, notes.
|
|
196
199
|
- Keep dependencies, acceptance criteria, testing expectations, file hints, risk notes, and implementation notes from the source material. For each ticket, use only the mapped approved slice's acceptance and required tests; do not copy plan-level acceptance criteria or add requirements about sibling slices or unfinished tickets. A ticket's acceptance must remain implementable and verifiable when its own slice is delivered.
|
|
197
200
|
- Preserve additive-slice independence without inventing an absence test: each ticket's own behavior and tests should work against the shared approved baseline and must not depend on sibling implementation. Do not add wording such as "with the sibling absent/present," "without [sibling] present," or "with this slice alone" unless the approved plan explicitly requires sibling absence testing or an isolated branch. Otherwise use positive own-ticket checks, the shared project tests, and the combined-result check.
|
|
198
|
-
-
|
|
201
|
+
- Rafi copies documentation policy and QA preparation decisions directly from the approved slice. Do not include these host-owned fields in your proposal or invent additional requirements. Preserve the approved acceptance and tests.
|
|
199
202
|
- File names and likely_files are implementation hints, not an exhaustive allowlist. Do not invent acceptance criteria or tests that require an exact changed-file list; allow other relevant files needed to complete the approved work. Preserve explicit user-established security, privacy, access, and other safety restrictions. Rafi manages ticket lifecycle state; do not require the Builder to mark tickets done in a document.
|
|
200
203
|
- For every Medium or High risk ticket, \`rollback\` is required. It must state a concrete rollback or mitigation action: what is disabled, reverted, or contained, and how recovery or impact is verified. \`notes\` may add context but never satisfies this requirement. Low-risk tickets may use \`rollback: null\`.
|
|
201
204
|
- Do not store mutable status/progress fields in .tickets/tickets.yaml.
|
|
202
205
|
- Dependencies must reference slice_ref values. Include the exact IDs of existing populated tickets whose removed slices should become obsolete.
|
|
203
206
|
|
|
204
|
-
If source content does not cleanly map to the
|
|
205
|
-
STEP_STATUS: needs_input | question="..." choices="..."
|
|
206
|
-
|
|
207
|
-
Output one JSON object between ${TICKET_POPULATION_PROPOSAL_START} and ${TICKET_POPULATION_PROPOSAL_END} with shape:
|
|
208
|
-
{"version":1,"plan_id":"approved plan ID","revision":1,"tickets":[{"slice_ref":"slc_low","title":"...","area":"...","priority":"P1","size":"S","risk":"Low","summary":"...","acceptance":["..."],"required_tests":["..."],"likely_files":["..."],"documentation":{"required":false,"targets":[],"criteria":[]},"depends_on":[],"rollback":null,"notes":null},{"slice_ref":"slc_medium","title":"...","area":"...","priority":"P1","size":"S","risk":"Medium","summary":"...","acceptance":["..."],"required_tests":["..."],"likely_files":["..."],"documentation":{"required":false,"targets":[],"criteria":[]},"depends_on":["slc_low"],"rollback":"Disable the feature flag, restore the previous handler, and verify the affected error rate returns to baseline.","notes":null}],"retirements":["exact existing ticket ID"]}
|
|
209
|
-
Triple-check that every approved slice appears exactly once.
|
|
210
|
-
|
|
211
|
-
End with exactly one marker line as the final non-empty line:
|
|
212
|
-
STEP_STATUS: done | summary="populated Foreman tickets from existing project ticket sources"
|
|
213
|
-
or
|
|
214
|
-
STEP_STATUS: blocked | reason="why ticket population cannot proceed"`;
|
|
207
|
+
If source content does not cleanly map to the approved plan, return a needs-input or blocked result instead of guessing. Return the full proposal in result.payload of the native ticket-population response. The proposal contains version, plan_id, revision, tickets, and retirements. Do not allocate final ticket IDs.`;
|
|
215
208
|
}
|
|
216
209
|
/** A bounded correction loop ended without a publishable proposal. */
|
|
217
210
|
export class TicketPopulationRiskCorrectionExhaustedError extends Error {
|
|
@@ -531,6 +524,18 @@ async function cmdSetupUpdateCli(opts) {
|
|
|
531
524
|
saveSourceRegistry(dir, patch.sourceRegistry);
|
|
532
525
|
registrySaved = true;
|
|
533
526
|
syncTicketLimits(dir, next.limits);
|
|
527
|
+
if (isTicketsInitialized(dir)) {
|
|
528
|
+
const ticketsConfig = loadTicketsConfig(dir);
|
|
529
|
+
const trackerRulesPath = join(dir, ticketsConfig.paths.trackerRules);
|
|
530
|
+
if (existsSync(trackerRulesPath)) {
|
|
531
|
+
const before = readFileSync(trackerRulesPath, "utf8");
|
|
532
|
+
const migrated = migrateTrackerRules(before, ticketsConfig.paths.progressDoc);
|
|
533
|
+
if (migrated.disposition !== "unchanged") {
|
|
534
|
+
writeFileSync(trackerRulesPath, migrated.content, "utf8");
|
|
535
|
+
console.log(`foreman tickets setup: ${migrated.disposition === "replaced-legacy" ? "upgraded" : "refreshed"} Rafi-managed tracker rules`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
534
539
|
console.log(`foreman tickets setup: updated ticket setup in ${join(dir, "rafi-config.yaml")}`);
|
|
535
540
|
if (isTicketsInitialized(dir)) {
|
|
536
541
|
const rows = cmdQueue(dir, 1);
|
|
@@ -1465,8 +1470,14 @@ export async function cmdPopulateCli(opts) {
|
|
|
1465
1470
|
if (contextSources.length) {
|
|
1466
1471
|
console.log(`foreman tickets: context sources ${contextSources.join(" ")}`);
|
|
1467
1472
|
}
|
|
1468
|
-
|
|
1469
|
-
|
|
1473
|
+
const population = (instruction, correction) => streamTicketPopulation({
|
|
1474
|
+
projectDir: dir, interviewId: planningRecord.id, correction, instruction,
|
|
1475
|
+
checkpoint: scope => { planningRecord = checkpointInterview(dir, planningRecord, { decisions: { ...planningRecord.decisions, populationArtifact: scope } }); },
|
|
1476
|
+
dispatch: (instruction, policy) => foreman.runInstruction(instruction, policy),
|
|
1477
|
+
});
|
|
1478
|
+
const initialPopulation = await population(buildPopulateInstruction(approvedPlan.path, contextSources, ticketsConfig.paths.progressDoc), 0);
|
|
1479
|
+
let turn = initialPopulation.turn;
|
|
1480
|
+
planningRecord = checkpointInterview(dir, planningRecord, { runtime: { runtime: roleBuilder.runtime, model: roleBuilder.model, sessionId: builder.sessionId() }, decisions: { ...planningRecord.decisions, proposalResponse: turn.result.text, structuredResponse: turn.result.structured } });
|
|
1470
1481
|
log.write("ticket-populate", {
|
|
1471
1482
|
statusKind: turn.status.kind,
|
|
1472
1483
|
summary: turn.status.summary,
|
|
@@ -1484,7 +1495,9 @@ export async function cmdPopulateCli(opts) {
|
|
|
1484
1495
|
throw new Error(`needs human — ${turn.status.error ?? "builder did not emit done"}`);
|
|
1485
1496
|
}
|
|
1486
1497
|
const existing = loadTickets(resolveTicketPaths(ticketsConfig, dir).tickets);
|
|
1487
|
-
|
|
1498
|
+
if (!turn.result.selectedDispatch)
|
|
1499
|
+
throw new Error("Ticket population response has no sealed dispatch tuple");
|
|
1500
|
+
let proposal = initialPopulation.proposal;
|
|
1488
1501
|
let proposalArtifact = savePopulationProposalArtifact(dir, planningRecord.id, approvedPlan, proposal);
|
|
1489
1502
|
let materialized;
|
|
1490
1503
|
let populationWaiver;
|
|
@@ -1510,18 +1523,22 @@ export async function cmdPopulateCli(opts) {
|
|
|
1510
1523
|
continue;
|
|
1511
1524
|
}
|
|
1512
1525
|
console.log(`foreman tickets: ${ids} need risk plans; asking Ticket Maker for a targeted correction (${correction + 1}/2)`);
|
|
1513
|
-
const
|
|
1526
|
+
const correctedPopulation = await population(`Correct the existing ticket population proposal only. Do not change plan_id, revision, slice_ref mappings, ticket IDs, dependencies, scope, ownership, acceptance criteria, tests, retirements, or any field except \`rollback\` for these tickets: ${ids}. Each listed ticket is Medium or High risk and requires a concrete rollback or mitigation plan in \`rollback\`; \`notes\` cannot satisfy this requirement. Return the complete replacement proposal through native ticket-population pages.`, correction + 1);
|
|
1527
|
+
const correctionTurn = correctedPopulation.turn;
|
|
1514
1528
|
if (correctionTurn.result.isError || (correctionTurn.status.kind !== "done" && correctionTurn.status.kind !== "plan_complete")) {
|
|
1515
1529
|
throw new Error(`Ticket Maker could not correct missing risk plans for ${ids}: ${correctionTurn.status.reason ?? correctionTurn.status.error ?? correctionTurn.result.text.slice(0, 200)}`);
|
|
1516
1530
|
}
|
|
1517
1531
|
turn = correctionTurn;
|
|
1518
1532
|
planningRecord = checkpointInterview(dir, planningRecord, {
|
|
1519
1533
|
runtime: { runtime: roleBuilder.runtime, model: roleBuilder.model, sessionId: builder.sessionId() },
|
|
1520
|
-
decisions: { ...planningRecord.decisions, proposalResponse: turn.result.text, riskPlanCorrections: correction + 1 },
|
|
1534
|
+
decisions: { ...planningRecord.decisions, proposalResponse: turn.result.text, structuredResponse: turn.result.structured, riskPlanCorrections: correction + 1 },
|
|
1521
1535
|
});
|
|
1522
|
-
const corrected =
|
|
1536
|
+
const corrected = correctedPopulation.proposal;
|
|
1523
1537
|
assertRiskPlanOnlyCorrection(proposal, corrected, err.repairableRiskSliceRefs);
|
|
1524
1538
|
proposal = corrected;
|
|
1539
|
+
if (!turn.result.selectedDispatch)
|
|
1540
|
+
throw new Error("Ticket population correction has no sealed dispatch tuple");
|
|
1541
|
+
// A correction is immutable lineage, never a rewrite of the terminal proposal.
|
|
1525
1542
|
proposalArtifact = savePopulationProposalArtifact(dir, planningRecord.id, approvedPlan, proposal);
|
|
1526
1543
|
}
|
|
1527
1544
|
}
|
|
@@ -1584,13 +1601,12 @@ export async function cmdRepairRiskNotesCli(opts) {
|
|
|
1584
1601
|
"You are Ticket Maker in read-only repair mode. Do not edit files.",
|
|
1585
1602
|
"For every listed ticket, supply a concrete rollback or mitigation plan. Do not change any other field or add/remove tickets.",
|
|
1586
1603
|
`Tickets requiring repair:\n${JSON.stringify(targets.map(ticket => ({ id: ticket.id, title: ticket.title, risk: ticket.risk, summary: ticket.summary, acceptance: ticket.acceptance, required_tests: ticket.required_tests })), null, 2)}`,
|
|
1587
|
-
`
|
|
1588
|
-
"End with STEP_STATUS: done | summary=\"risk-note repair proposed\"",
|
|
1604
|
+
`Submit the native risk-note-repair payload: {"version":1,"tickets":[{"id":"T001","rollback":"Concrete reversal or mitigation steps."}]}.`,
|
|
1589
1605
|
].join("\n\n");
|
|
1590
|
-
const turn = await foreman.runInstruction(instruction);
|
|
1606
|
+
const turn = await foreman.runInstruction(instruction, { output: riskNoteRepairOutput });
|
|
1591
1607
|
if (turn.result.isError || (turn.status.kind !== "done" && turn.status.kind !== "plan_complete"))
|
|
1592
1608
|
throw new Error(`Ticket Maker could not propose risk-plan repairs: ${turn.status.reason ?? turn.status.error ?? turn.result.text.slice(0, 300)}`);
|
|
1593
|
-
const proposal =
|
|
1609
|
+
const proposal = operationPayload(turn.result, riskNoteRepairOutput);
|
|
1594
1610
|
const repaired = applyRiskNoteRepairProposal(current, proposal);
|
|
1595
1611
|
for (const target of targets)
|
|
1596
1612
|
console.log(` ${target.id}: ${proposal.tickets.find(item => item.id === target.id).rollback.trim()}`);
|
package/dist/continuity.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ContinuityDelta, ResolvedAgentSettings } from "rafi-spec";
|
|
2
2
|
import type { BuilderAdapter, CompactResult, ContextUsage, NativeAutoCompactionPolicy, NativeCompaction, ProviderSessionUsage, ProviderSettingSwitch, RuntimeFailure, TurnResult } from "./adapters/types.js";
|
|
3
|
+
import type { BuilderHandoffRequest } from "./handoffs.js";
|
|
3
4
|
export declare const CONTINUITY_MARKER = "RAFI_CONTINUITY_DELTA:";
|
|
4
5
|
export declare class ContinuityValidationError extends Error {
|
|
5
6
|
readonly problems: string[];
|
|
@@ -22,12 +23,8 @@ export declare class SessionUnavailableContinuityError extends Error {
|
|
|
22
23
|
readonly guidedRecovery: boolean;
|
|
23
24
|
constructor(runId: string, role: "builder" | "qa", failure: RuntimeFailure);
|
|
24
25
|
}
|
|
26
|
+
export declare const CONTINUITY_REQUIRED_INSTRUCTION = "Include the required bounded continuity object in the operation response; continuity cannot be null for this session.";
|
|
25
27
|
export declare function validateContinuityDelta(value: unknown): ContinuityDelta;
|
|
26
|
-
export declare function parseContinuityDelta(text: string): {
|
|
27
|
-
delta?: ContinuityDelta;
|
|
28
|
-
cleanText: string;
|
|
29
|
-
error?: ContinuityValidationError;
|
|
30
|
-
};
|
|
31
28
|
export declare function mergeContinuityDeltas(deltas: readonly ContinuityDelta[]): ContinuityDelta;
|
|
32
29
|
export declare function baselineContinuityDelta(nextAction?: string): ContinuityDelta;
|
|
33
30
|
export declare function continuityInstruction(role?: "builder" | "qa"): string;
|
|
@@ -53,7 +50,7 @@ export interface ContinuityAdapterOptions {
|
|
|
53
50
|
/** Explicit compatibility recovery may replace a dead predecessor lease only after its first valid checkpoint. */
|
|
54
51
|
replaceRecoveryLeaseAfterCheckpoint?: boolean;
|
|
55
52
|
/** Host-owned validated Builder handoff request processing at a safe turn boundary. */
|
|
56
|
-
handleHandoffRequest?: (text: string, predecessor: BuilderAdapter, frozenAction: string) => Promise<BuilderAdapter | undefined>;
|
|
53
|
+
handleHandoffRequest?: (text: string | BuilderHandoffRequest, predecessor: BuilderAdapter, frozenAction: string) => Promise<BuilderAdapter | undefined>;
|
|
57
54
|
/** QA journals each provider turn separately; wrappers must not repair or redispatch inside one host turn. */
|
|
58
55
|
durableSingleTurn?: boolean;
|
|
59
56
|
}
|
|
@@ -79,15 +76,16 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
79
76
|
/** Establish an explicitly requested fresh owner before context probes or work. */
|
|
80
77
|
validateFreshRecovery(): Promise<void>;
|
|
81
78
|
get agent(): "claude" | "codex";
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
logicalActionId?: string;
|
|
86
|
-
purpose?: import("./providerPhase.js").ProviderTurnPurpose;
|
|
87
|
-
}): Promise<TurnResult>;
|
|
79
|
+
/** Every confirmed dispatch publishes a checkpoint, so continuity is mandatory. */
|
|
80
|
+
structuredDispatchRequirement(): import("./adapters/types.js").StructuredDispatchRequirement;
|
|
81
|
+
sendTurn(instruction: string, policy?: import("./adapters/types.js").TurnRequest): Promise<TurnResult>;
|
|
88
82
|
private sendTurnOwned;
|
|
83
|
+
postConfirmedDispatch(reference: import("./adapters/types.js").ConfirmedDispatchReference): Promise<TurnResult>;
|
|
89
84
|
observeEvents(listener: (event: import("./adapters/types.js").BuilderEvent) => void): () => void;
|
|
90
85
|
acceptHandbackTurn(turn: TurnResult): void;
|
|
86
|
+
/** Explicit restart recovery of the already accepted action; never repeats acceptance. */
|
|
87
|
+
resumePlannedContinuation(): Promise<TurnResult | undefined>;
|
|
88
|
+
recoverConfirmedPublications(): Promise<void>;
|
|
91
89
|
sessionId(): string | undefined;
|
|
92
90
|
sessionRef(): import("rafi-spec").ProviderSessionRefV1 | undefined;
|
|
93
91
|
prepareSession(): Promise<import("rafi-spec").ProviderSessionRefV1>;
|
|
@@ -109,9 +107,7 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
109
107
|
/** Keep the observable wrapper and event stream stable while moving to an accepted successor. */
|
|
110
108
|
adoptValidatedSuccessor(successor: BuilderAdapter): Promise<void>;
|
|
111
109
|
private publish;
|
|
112
|
-
private moveRecoveryLeaseAfterCheckpoint;
|
|
113
110
|
private recordSessionUnavailable;
|
|
114
|
-
private reconstructionHandoff;
|
|
115
111
|
private revision;
|
|
116
112
|
private pumpEvents;
|
|
117
113
|
}
|