ai-foreman 1.7.26 → 1.7.28
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/dist/adapters/codex.d.ts +12 -0
- package/dist/adapters/codex.js +50 -1
- package/dist/adapters/recovering.d.ts +4 -0
- package/dist/adapters/recovering.js +10 -0
- package/dist/adapters/sessionFailure.d.ts +6 -0
- package/dist/adapters/sessionFailure.js +19 -0
- package/dist/adapters/types.d.ts +6 -0
- package/dist/branch/currentGuard.d.ts +8 -0
- package/dist/branch/currentGuard.js +5 -0
- package/dist/branch/runner.js +6 -2
- package/dist/buildApproval.js +2 -1
- package/dist/buildRuns.d.ts +12 -0
- package/dist/buildRuns.js +50 -0
- package/dist/buildWorkContext.js +1 -1
- package/dist/buildWorkMigration.d.ts +10 -0
- package/dist/buildWorkMigration.js +18 -2
- package/dist/cli/start.d.ts +10 -1
- package/dist/cli/start.js +186 -31
- package/dist/cli/state.js +39 -1
- package/dist/cli/tickets.d.ts +37 -0
- package/dist/cli/tickets.js +367 -12
- package/dist/config.js +14 -4
- package/dist/continuity.d.ts +1 -0
- package/dist/continuity.js +5 -0
- package/dist/foreman.js +89 -8
- package/dist/graph/preparation.js +6 -3
- package/dist/graph/semantic.js +2 -1
- package/dist/graph/turn.js +8 -1
- package/dist/handoffProtocol.js +2 -1
- package/dist/handoffs.js +7 -19
- package/dist/hostQaComparison.d.ts +50 -0
- package/dist/hostQaComparison.js +195 -0
- package/dist/log.d.ts +1 -1
- package/dist/managerPacket.js +1 -1
- package/dist/observability.d.ts +2 -0
- package/dist/observability.js +2 -1
- package/dist/permissions/policy.js +32 -0
- package/dist/planChanges.js +1 -1
- package/dist/projectDiagnostics.js +30 -0
- package/dist/providerQuestions.js +4 -24
- package/dist/qaBuildGate.d.ts +6 -1
- package/dist/qaBuildGate.js +76 -16
- package/dist/qaBuilderCoverage.d.ts +2 -1
- package/dist/qaBuilderCoverage.js +58 -7
- package/dist/qaBuilderEvidence.d.ts +18 -0
- package/dist/qaBuilderEvidence.js +60 -0
- package/dist/qaContractCoverage.d.ts +12 -1
- package/dist/qaContractCoverage.js +34 -2
- package/dist/qaContractDelivery.d.ts +2 -2
- package/dist/qaContractDelivery.js +88 -10
- package/dist/qaEffectiveConfig.js +2 -1
- package/dist/qaFailureDelivery.d.ts +23 -0
- package/dist/qaFailureDelivery.js +87 -6
- package/dist/qaHostFacts.d.ts +2 -0
- package/dist/qaHostFacts.js +11 -0
- package/dist/qaPreparation.d.ts +38 -1
- package/dist/qaPreparation.js +372 -31
- package/dist/qaPreparationInputs.js +5 -1
- package/dist/qaPreparationPolicy.d.ts +2 -1
- package/dist/qaPreparationPolicy.js +39 -9
- package/dist/qaPreparationStore.d.ts +4 -0
- package/dist/qaPreparationStore.js +8 -0
- package/dist/qaRecovery.d.ts +7 -0
- package/dist/qaRecovery.js +73 -1
- package/dist/qaReview.js +58 -9
- package/dist/qaReviewEvidence.d.ts +6 -0
- package/dist/qaReviewEvidence.js +37 -0
- package/dist/qaRollbackRehearsal.d.ts +2 -0
- package/dist/qaRollbackRehearsal.js +78 -0
- package/dist/qaSnapshot.d.ts +3 -1
- package/dist/qaSnapshot.js +3 -2
- package/dist/qaVerificationContract.d.ts +16 -0
- package/dist/qaVerificationContract.js +183 -17
- package/dist/sources/sourceRegistry.js +16 -5
- package/dist/stateTransfer.d.ts +14 -0
- package/dist/stateTransfer.js +51 -0
- package/dist/statusReporter.d.ts +8 -0
- package/dist/statusReporter.js +5 -0
- package/dist/supervisedStart.js +23 -0
- package/dist/ticketPlanning.d.ts +373 -0
- package/dist/ticketPlanning.js +34 -3
- package/dist/ticketPopulation.d.ts +28 -3
- package/dist/ticketPopulation.js +127 -15
- package/dist/tickets/details.d.ts +2 -1
- package/dist/tickets/details.js +7 -3
- package/dist/tickets/eligibility.d.ts +6 -0
- package/dist/tickets/eligibility.js +12 -1
- package/dist/tickets/riskWaivers.d.ts +3 -0
- package/dist/tickets/riskWaivers.js +12 -0
- package/dist/tickets/stateDb.d.ts +10 -0
- package/dist/tickets/stateDb.js +23 -0
- package/dist/tickets/ticketLoader.d.ts +9 -0
- package/dist/tickets/ticketLoader.js +15 -2
- package/dist/tickets/ticketSchema.d.ts +14 -1
- package/dist/tickets/ticketSchema.js +2 -1
- package/dist/tickets/validate.js +11 -1
- package/dist/workflowDb.js +24 -3
- package/package.json +9 -4
package/dist/cli/start.js
CHANGED
|
@@ -31,8 +31,9 @@ import { loadRoleBundle } from "../roles.js";
|
|
|
31
31
|
import { ensureBuildRuntimeReadyForCommand } from "./runtimeAuthPrompt.js";
|
|
32
32
|
import { resolveAgentForProject } from "./runtimeSelection.js";
|
|
33
33
|
import { isTicketsInitialized, loadTicketsConfig, resolveTicketPaths } from "../tickets/config.js";
|
|
34
|
-
import { loadTickets } from "../tickets/ticketLoader.js";
|
|
34
|
+
import { hasRollbackOrMitigationPlan, loadTickets } from "../tickets/ticketLoader.js";
|
|
35
35
|
import { StateDb } from "../tickets/stateDb.js";
|
|
36
|
+
import { ticketDefinitionDigest } from "../tickets/riskWaivers.js";
|
|
36
37
|
import { applySharedDeliveryBranch, applySizeBranchPolicy, buildBranchAuditInstruction, buildBranchPlan, parseAuditDependencies } from "../branch/planner.js";
|
|
37
38
|
import { currentGitRef, ensureCleanBaseWorktree, generatedTrackerDirtyPaths } from "../branch/git.js";
|
|
38
39
|
import { captureDirtyBaseline } from "../branch/dirtyBaseline.js";
|
|
@@ -40,7 +41,8 @@ import { formatGitHubFailure, preflightGh } from "../branch/github.js";
|
|
|
40
41
|
import { preflightGlab } from "../branch/gitlab.js";
|
|
41
42
|
import { readDeliveryUnitSession, runBranchPlan } from "../branch/runner.js";
|
|
42
43
|
import { branchPlanLogMetadata, presentBranchPlan } from "../branch/presentation.js";
|
|
43
|
-
import { buildRunSessionBinding, checkpointBuildRun, completeBuildRun, createBuildRun, currentBranchRecoveryTicket, currentBranchTicketProgress, recoveryExecutionTickets, finishRecoveredTicketScope, heartbeatBuildRun, persistBuildSession, persistCurrentBuildSession, projectBuildRecovery, readBuildRuns, releaseBuildLease, resumeBuildRun } from "../buildRuns.js";
|
|
44
|
+
import { branchTicketScopeProgress, buildRunSessionBinding, checkpointBuildRun, completeBuildRun, createBuildRun, currentBranchRecoveryTicket, currentBranchTicketProgress, recoveryExecutionTickets, finishBranchTicketScope, finishRecoveredTicketScope, heartbeatBuildRun, persistBuildSession, persistCurrentBuildSession, projectBuildRecovery, readBuildRuns, releaseBuildLease, resumeBuildRun } from "../buildRuns.js";
|
|
45
|
+
import { parseProviderContract, qaRemediationProposalV1Schema } from "rafi-spec";
|
|
44
46
|
import { parse as parseYaml } from "yaml";
|
|
45
47
|
import { findResumableBranchSessions, formatBranchContinueCommand, formatBranchSummaryFollowupCommands, } from "../branch/resume.js";
|
|
46
48
|
import { detectGitProvider, loadTicketSetupConfig } from "../tickets/setupConfig.js";
|
|
@@ -51,6 +53,8 @@ import { fireTerminalBell } from "../notify.js";
|
|
|
51
53
|
import { WorkflowDb, readCurrentWorkflowLease } from "../workflowDb.js";
|
|
52
54
|
import { makeLogPath as makeRoleLogPath, readOnlyPermissionConfig, runRoleInstruction } from "../agentRun.js";
|
|
53
55
|
import { eligibleTickets, evaluateTicketEligibility } from "../tickets/eligibility.js";
|
|
56
|
+
import { globallyEligibleTickets } from "../tickets/eligibility.js";
|
|
57
|
+
import { cmdValidate } from "../tickets/commands.js";
|
|
54
58
|
import { BUILTIN_BRANCH_PREFIX, validateBranchPrefix } from "../branch/prefix.js";
|
|
55
59
|
import { captureCurrentWorkflowSessionIdentity, CurrentWorkflowChangedError, CurrentWorkflowGuardAdapter } from "../branch/currentGuard.js";
|
|
56
60
|
import { ContinuityAdapter, ContinuityRecoveryRequiredError, SessionUnavailableContinuityError } from "../continuity.js";
|
|
@@ -61,7 +65,7 @@ import { SessionUnavailableError } from "../adapters/sessionFailure.js";
|
|
|
61
65
|
import { resolveProviderSessionAvailability } from "../sessionAvailability.js";
|
|
62
66
|
import { ObservabilityStore, RunObserver } from "../observability.js";
|
|
63
67
|
import { loadProjectAutonomyConfig, resolveAutonomyPolicy, resolveQaEnablement } from "../recoveryPolicy.js";
|
|
64
|
-
import { loadQaRecoveryPacket, recoverPendingQaRecoveryPublications } from "../qaRecovery.js";
|
|
68
|
+
import { canonicalizeQaRecoveryRequirements, loadQaRecoveryPacket, recoverPendingQaRecoveryPublications } from "../qaRecovery.js";
|
|
65
69
|
import { qaDigest } from "../qaProtocolV2.js";
|
|
66
70
|
import { acceptQaRuntimeHandoff, describeQaRuntimeHandle, frozenQaRuntimeSettings } from "../qaRuntime.js";
|
|
67
71
|
const FOREMAN_VERSION = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
@@ -79,7 +83,7 @@ function pendingQaRecoveryPacket(projectDir, runId, ticketId) {
|
|
|
79
83
|
const selected = ticketId ? pending.find((item) => item.ticketId === ticketId) : pending[0];
|
|
80
84
|
if (!selected || selected.pendingAction === "resolved")
|
|
81
85
|
return undefined;
|
|
82
|
-
const packet = loadQaRecoveryPacket(selected.packetPath);
|
|
86
|
+
const packet = canonicalizeQaRecoveryRequirements(loadQaRecoveryPacket(selected.packetPath));
|
|
83
87
|
const state = db.qaRecoveryHead(runId, packet.manifest.ticketId);
|
|
84
88
|
if (!state || packet.manifest.packetDigest !== state.packetDigest || packet.manifest.runId !== runId
|
|
85
89
|
|| packet.manifest.ticketId !== state.ticketId || packet.manifest.packetId !== state.packetId
|
|
@@ -422,6 +426,71 @@ export function validateStartOptions(opts, requireCount) {
|
|
|
422
426
|
throw new Error("Preparation launch requires both run and token");
|
|
423
427
|
if (opts.launchToken && !opts.preparationRun && !opts.recoverRun)
|
|
424
428
|
throw new Error("Launch token requires a preparation or recovery run");
|
|
429
|
+
if (opts.auditLogging !== undefined && !["off", "summary", "trace"].includes(String(opts.auditLogging)))
|
|
430
|
+
throw new Error("--audit-logging must be off, summary, or trace");
|
|
431
|
+
}
|
|
432
|
+
export function resolveAuditLoggingMode(cli, configured, environment = process.env.RAFI_AUDIT_LOGGING) {
|
|
433
|
+
const value = cli ?? configured ?? environment ?? "summary";
|
|
434
|
+
if (!["off", "summary", "trace"].includes(String(value)))
|
|
435
|
+
throw new Error("audit logging must be off, summary, or trace");
|
|
436
|
+
return String(value);
|
|
437
|
+
}
|
|
438
|
+
function configuredAuditLoggingMode(path) {
|
|
439
|
+
if (!existsSync(path))
|
|
440
|
+
return undefined;
|
|
441
|
+
const raw = parseYaml(readFileSync(path, "utf8"));
|
|
442
|
+
return raw?.observability?.mode === undefined ? undefined : resolveAuditLoggingMode(undefined, raw.observability.mode, undefined);
|
|
443
|
+
}
|
|
444
|
+
/** A current waiver is an already-recorded decision: surface it, never demand a second start-time approval. */
|
|
445
|
+
function warnRiskPlanWaivers(projectDir, ticketIds) {
|
|
446
|
+
if (!ticketIds.length || !isTicketsInitialized(projectDir))
|
|
447
|
+
return;
|
|
448
|
+
const paths = resolveTicketPaths(loadTicketsConfig(projectDir), projectDir);
|
|
449
|
+
const definitions = new Map(loadTickets(paths.tickets).map((ticket) => [ticket.id, ticket]));
|
|
450
|
+
const db = new StateDb(paths.stateDb, { readOnly: true });
|
|
451
|
+
try {
|
|
452
|
+
const waived = ticketIds.filter((id) => {
|
|
453
|
+
const ticket = definitions.get(id);
|
|
454
|
+
return ticket && ticket.risk !== "Low" && !hasRollbackOrMitigationPlan(ticket.rollback)
|
|
455
|
+
&& Boolean(db.getTicketRiskWaiver(id, ticketDefinitionDigest(ticket)));
|
|
456
|
+
});
|
|
457
|
+
if (waived.length)
|
|
458
|
+
console.warn(`foreman: warning: ${waived.join(", ")} have explicitly waived missing rollback or mitigation plans.`);
|
|
459
|
+
}
|
|
460
|
+
finally {
|
|
461
|
+
db.close();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
/** Preserve the distinction between a staged handoff and an unattempted acceptance. */
|
|
465
|
+
export function recordHandoffAcceptanceNotAttempted(projectDir, runId, role, generation, stage, detail) {
|
|
466
|
+
const db = new WorkflowDb(projectDir);
|
|
467
|
+
try {
|
|
468
|
+
db.appendContinuityEvent({
|
|
469
|
+
runId,
|
|
470
|
+
role: "host",
|
|
471
|
+
kind: "handoff_acceptance_not_attempted",
|
|
472
|
+
payload: { generation, role, stage, detail },
|
|
473
|
+
authoritativeStateRevision: db.continuityHead(runId, role)?.authoritativeStateRevision ?? 0,
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
finally {
|
|
477
|
+
db.close();
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
/** Default start selection is deliberately the same eligibility model users see in the ticket queue. */
|
|
481
|
+
export function selectDefaultExecutionTickets(tickets, states, implementationLimit, steps, delivery) {
|
|
482
|
+
return globallyEligibleTickets({ tickets, states, implementationLimit, delivery }).slice(0, steps).map((ticket) => ticket.id);
|
|
483
|
+
}
|
|
484
|
+
/** Fail before runtime/provider admission when canonical ticket data is unsafe. */
|
|
485
|
+
export function assertStartTicketPreflight(projectDir) {
|
|
486
|
+
const validation = cmdValidate(projectDir);
|
|
487
|
+
const errors = validation.issues.filter((issue) => issue.severity === "error");
|
|
488
|
+
const warnings = validation.issues.filter((issue) => issue.severity === "warn");
|
|
489
|
+
for (const issue of warnings)
|
|
490
|
+
console.warn(`foreman: warning: ${issue.message}`);
|
|
491
|
+
if (errors.length) {
|
|
492
|
+
throw new Error(`ticket validation must be resolved before starting:\n${errors.map((issue) => `- ${issue.message}`).join("\n")}\nRun \`rafi tickets validate\` after repair to confirm readiness.`);
|
|
493
|
+
}
|
|
425
494
|
}
|
|
426
495
|
/** Validate generated established recovery before reserving execution authority. */
|
|
427
496
|
export function validateRecoveryArguments(args, projectDir) {
|
|
@@ -490,6 +559,7 @@ export function buildStartCommand(options = {}) {
|
|
|
490
559
|
.option("--base <ref>", "base ref for root ticket branches (default: current branch or HEAD)")
|
|
491
560
|
.option("--branch-prefix <prefix>", "branch name prefix for ticket branches")
|
|
492
561
|
.option("--show-session-cost", "show authoritative cost or trustworthy cumulative session tokens for Builder and QA")
|
|
562
|
+
.option("--audit-logging <mode>", "host-owned audit logging (off | summary | trace)", "summary")
|
|
493
563
|
.option("--hide-session-cost", "hide session cost/token usage for Builder and QA")
|
|
494
564
|
.option("--auto-compact-threshold <percent>", "initial Builder context compaction threshold (1-99)")
|
|
495
565
|
.option("--max-branch-depth <n>", "maximum selected branch stack depth", "5")
|
|
@@ -566,6 +636,17 @@ export function buildStartCommand(options = {}) {
|
|
|
566
636
|
let dirtyWork = recoveryRecord?.runDecisions?.dirtyWork;
|
|
567
637
|
if (opts.recoverRun && !recoveryRecord)
|
|
568
638
|
fail(`recoverable build run not found: ${opts.recoverRun}`);
|
|
639
|
+
// Both current-branch and branch delivery runs share this gate. A
|
|
640
|
+
// recovery resumes a previously admitted scope and is deliberately not
|
|
641
|
+
// reclassified by later tracker edits.
|
|
642
|
+
if (!recoveryRecord && isTicketsInitialized(cwd)) {
|
|
643
|
+
try {
|
|
644
|
+
assertStartTicketPreflight(cwd);
|
|
645
|
+
}
|
|
646
|
+
catch (err) {
|
|
647
|
+
fail(err instanceof Error ? err.message : String(err));
|
|
648
|
+
}
|
|
649
|
+
}
|
|
569
650
|
const autonomyProfile = opts.autonomy;
|
|
570
651
|
if (autonomyProfile && !["supervised", "balanced", "unattended"].includes(autonomyProfile))
|
|
571
652
|
fail("--autonomy must be supervised, balanced, or unattended");
|
|
@@ -610,6 +691,7 @@ export function buildStartCommand(options = {}) {
|
|
|
610
691
|
const readinessOwner = { project: cwd, runId: supervisedInvocationId, authority: localBuildAuthority(cwd) };
|
|
611
692
|
const autoApprovePlanUpdates = recoveryRecord
|
|
612
693
|
? recoveryRecord.recoveryDecision?.planUpdateApproval === "auto"
|
|
694
|
+
&& (recoveryRecord.recoveryDecision.planUpdateApprovalSource === "explicit-yes" || recoveryRecord.recoveryDecision.planUpdateApprovalSource === "explicit-choice")
|
|
613
695
|
: Boolean(opts.yes);
|
|
614
696
|
if (opts.recoveryMode) {
|
|
615
697
|
const allowed = ["exact-session", "fresh-with-handoff", "fresh-recovery-only", "guided-recovery"];
|
|
@@ -756,7 +838,7 @@ export function buildStartCommand(options = {}) {
|
|
|
756
838
|
? requestedTicketIds.length > 0
|
|
757
839
|
? requestedTicketIds
|
|
758
840
|
: branchDefaults.branchMode && recoveryRecord
|
|
759
|
-
? [...recoveryRecord.tickets]
|
|
841
|
+
? branchTicketScopeProgress(cwd, recoveryRecord)?.remaining ?? [...recoveryRecord.tickets]
|
|
760
842
|
: recoveryRecord ? [currentBranchRecoveryTicket(cwd, recoveryRecord)].filter((ticket) => ticket !== undefined) : []
|
|
761
843
|
: [];
|
|
762
844
|
if (recoveryRecord && continueTickets.some(ticket => !recoveryRecord.tickets.includes(ticket)))
|
|
@@ -868,6 +950,8 @@ export function buildStartCommand(options = {}) {
|
|
|
868
950
|
? recoveryExecutionTickets(recoveryRecord, requestedTicketIds, Boolean(opts.recoveryMode))
|
|
869
951
|
: selectedNewTicket ? [selectedNewTicket] : undefined;
|
|
870
952
|
const config = loadConfig(join(cwd, "foreman.yaml"));
|
|
953
|
+
const auditLogging = resolveAuditLoggingMode(command.getOptionValueSource("auditLogging") === "cli" ? opts.auditLogging : undefined, configuredAuditLoggingMode(join(cwd, "foreman.yaml")));
|
|
954
|
+
const observabilityConfig = { ...config.observability, mode: auditLogging, enabled: auditLogging !== "off" };
|
|
871
955
|
const stamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
872
956
|
const logPath = join(cwd, ".foreman", `${stamp}.jsonl`);
|
|
873
957
|
const log = new Log(logPath);
|
|
@@ -882,9 +966,9 @@ export function buildStartCommand(options = {}) {
|
|
|
882
966
|
console.error(`rafi: warning: ${qaResolution.deprecationWarning}`);
|
|
883
967
|
const invocationRunId = supervisedInvocationId;
|
|
884
968
|
let observabilityStore;
|
|
885
|
-
if (
|
|
969
|
+
if (observabilityConfig.enabled) {
|
|
886
970
|
try {
|
|
887
|
-
observabilityStore = new ObservabilityStore(cwd, { config:
|
|
971
|
+
observabilityStore = new ObservabilityStore(cwd, { config: observabilityConfig });
|
|
888
972
|
}
|
|
889
973
|
catch (error) {
|
|
890
974
|
console.error(`rafi: observability unavailable; build will continue: ${String(error).replace(/\s+/g, " ").slice(0, 300)}`);
|
|
@@ -1249,7 +1333,12 @@ export function buildStartCommand(options = {}) {
|
|
|
1249
1333
|
const recoveryMode = String(opts.recoveryMode ?? "");
|
|
1250
1334
|
const effectiveSessionId = recoveryMode && recoveryMode !== "exact-session" ? undefined : sessionId;
|
|
1251
1335
|
let adapter = await createRecoveringBuilder(builderCwd, effectiveSessionId, effectiveSessionId ? sessionRef : undefined);
|
|
1252
|
-
|
|
1336
|
+
// The persisted recovery record is the authority for the staged
|
|
1337
|
+
// handoff's run. During QA-only recovery the active continuity ID is
|
|
1338
|
+
// assigned later than adapter construction, so gating acceptance on
|
|
1339
|
+
// that transient variable can silently skip the acceptance branch and
|
|
1340
|
+
// leave the staged generation pending until the post-run guard fails.
|
|
1341
|
+
if (pendingHandoffGeneration !== undefined && pendingHandoffRole === "builder" && recoveryRecord?.runId) {
|
|
1253
1342
|
const service = new HandoffService(cwd);
|
|
1254
1343
|
const staged = service.loadStaged(recoveryRecord.runId, pendingHandoffGeneration);
|
|
1255
1344
|
const accepted = await service.acceptStagedWithRecovery(staged, adapter, (_handoff, runtime) => createBuilderForSettings(builderCwd, settingsForRequestedRuntime(resolvedContinuitySettings("builder"), runtime)), handoffRecoveryOptions("builder"));
|
|
@@ -1566,10 +1655,26 @@ export function buildStartCommand(options = {}) {
|
|
|
1566
1655
|
// compatibility caller still supplies the previous raw ID.
|
|
1567
1656
|
const effectiveSessionId = undefined;
|
|
1568
1657
|
void sessionId;
|
|
1569
|
-
let adapter
|
|
1658
|
+
let adapter;
|
|
1659
|
+
try {
|
|
1660
|
+
adapter = await createRecoveringQa(qaCwd, effectiveSessionId);
|
|
1661
|
+
}
|
|
1662
|
+
catch (error) {
|
|
1663
|
+
// A staged QA handoff is intentionally preserved when the successor
|
|
1664
|
+
// cannot reach a validated session (for example, host runtime
|
|
1665
|
+
// preparation fails). Record the exact pre-acceptance boundary so a
|
|
1666
|
+
// later recovery can distinguish "not attempted" from a rejected
|
|
1667
|
+
// acknowledgement without weakening the acceptance gate.
|
|
1668
|
+
if (pendingHandoffGeneration !== undefined && pendingHandoffRole === "qa" && recoveryRecord?.runId) {
|
|
1669
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1670
|
+
recordHandoffAcceptanceNotAttempted(cwd, recoveryRecord.runId, "qa", pendingHandoffGeneration, "qa-runtime-preparation", detail);
|
|
1671
|
+
log.write("handoff-acceptance-not-attempted", { role: "qa", generation: pendingHandoffGeneration, stage: "qa-runtime-preparation", detail });
|
|
1672
|
+
}
|
|
1673
|
+
throw error;
|
|
1674
|
+
}
|
|
1570
1675
|
let explicitRecoveryAccepted = false;
|
|
1571
1676
|
let handoffReceipt = { kind: "initial" };
|
|
1572
|
-
if (pendingHandoffGeneration !== undefined && pendingHandoffRole === "qa" &&
|
|
1677
|
+
if (pendingHandoffGeneration !== undefined && pendingHandoffRole === "qa" && recoveryRecord?.runId) {
|
|
1573
1678
|
const service = new HandoffService(cwd);
|
|
1574
1679
|
const staged = service.loadStaged(recoveryRecord.runId, pendingHandoffGeneration);
|
|
1575
1680
|
const accepted = await service.acceptStagedWithRecovery(staged, adapter, (_handoff, runtime) => createQaForSettings(qaCwd, settingsForRequestedRuntime(resolvedContinuitySettings("qa"), runtime)), handoffRecoveryOptions("qa"));
|
|
@@ -1716,7 +1821,7 @@ export function buildStartCommand(options = {}) {
|
|
|
1716
1821
|
cumulativeTotalTokens: nativeUsage.totalTokens, authoritativeCostUsd: nativeUsage.authoritativeCostUsd,
|
|
1717
1822
|
} } : {}),
|
|
1718
1823
|
compactionCount, compactMaximum: settings.compact_maximum ?? 10,
|
|
1719
|
-
resources: [{ label: "frozen-qa-action", content: frozenAction, authoritative: true, purpose: "Frozen QA action across the session boundary" }, ...(recovery ? recovery.resources.map((resource) => ({
|
|
1824
|
+
resources: [{ label: "frozen-qa-action", content: frozenAction, authoritative: true, purpose: "Frozen QA action across the session boundary" }, ...(recovery ? recovery.resources.filter((resource) => resource.requiredForRecovery).map((resource) => ({
|
|
1720
1825
|
label: resource.label, digest: resource.digest, authoritative: true, requiredForRecovery: resource.requiredForRecovery,
|
|
1721
1826
|
mediaType: resource.mediaType, path: resource.path, purpose: resource.purpose, bytes: resource.bytes,
|
|
1722
1827
|
})) : [])],
|
|
@@ -1822,14 +1927,31 @@ export function buildStartCommand(options = {}) {
|
|
|
1822
1927
|
if (continueTickets.length > 0) {
|
|
1823
1928
|
const sessions = findResumableBranchSessions(join(cwd, ".foreman"));
|
|
1824
1929
|
const sessionByTicket = new Map(sessions.map((session) => [session.ticket, session]));
|
|
1930
|
+
const historicalReader = new WorkflowReader(cwd);
|
|
1931
|
+
let historicalSessionTickets;
|
|
1932
|
+
try {
|
|
1933
|
+
historicalSessionTickets = new Set(historicalReader.branchResumeSessions(false).map((session) => session.ticket));
|
|
1934
|
+
}
|
|
1935
|
+
finally {
|
|
1936
|
+
historicalReader.close();
|
|
1937
|
+
}
|
|
1825
1938
|
const nodes = [];
|
|
1939
|
+
const freshTicketIds = [];
|
|
1826
1940
|
for (const ticketId of continueTickets) {
|
|
1827
1941
|
const session = sessionByTicket.get(ticketId);
|
|
1828
1942
|
if (!session) {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1943
|
+
const ticketState = states.get(ticketId)?.status ?? "planned";
|
|
1944
|
+
if (opts.resume || !recoveryRecord || recoveryRecord.branchMode !== "per-ticket" || historicalSessionTickets.has(ticketId) || !["planned", "next"].includes(ticketState)) {
|
|
1945
|
+
fail([
|
|
1946
|
+
`no resumable branch session found for ticket ${ticketId}`,
|
|
1947
|
+
branchContinueTicketHelp(cwd),
|
|
1948
|
+
].join("\n"));
|
|
1949
|
+
}
|
|
1950
|
+
// This ticket has no prior branch/session evidence and remains in a
|
|
1951
|
+
// not-started tracker state. It is new work within the already
|
|
1952
|
+
// admitted original scope, not an interrupted session to replay.
|
|
1953
|
+
freshTicketIds.push(ticketId);
|
|
1954
|
+
continue;
|
|
1833
1955
|
}
|
|
1834
1956
|
const ticket = ticketById.get(ticketId);
|
|
1835
1957
|
if (!ticket)
|
|
@@ -1857,6 +1979,20 @@ export function buildStartCommand(options = {}) {
|
|
|
1857
1979
|
deliveryUnitFinal: session.deliveryUnitFinal,
|
|
1858
1980
|
});
|
|
1859
1981
|
}
|
|
1982
|
+
if (freshTicketIds.length) {
|
|
1983
|
+
const fresh = buildBranchPlan(tickets, states, {
|
|
1984
|
+
steps: freshTicketIds.length,
|
|
1985
|
+
baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch ?? currentGitRef(cwd),
|
|
1986
|
+
branchPrefix,
|
|
1987
|
+
maxBranchDepth,
|
|
1988
|
+
rootBaseBranches: branchDefaults.rootBaseBranches,
|
|
1989
|
+
ticketIds: freshTicketIds,
|
|
1990
|
+
});
|
|
1991
|
+
if (fresh.issues.some(issue => issue.blocking) || fresh.nodes.length !== freshTicketIds.length || fresh.nodes.some(node => !freshTicketIds.includes(node.ticket.id))) {
|
|
1992
|
+
fail(`remaining branch tickets are not runnable as fresh approved work: ${fresh.issues.filter(issue => issue.blocking).map(issue => issue.message).join("; ") || "planner did not return the exact remaining scope"}`);
|
|
1993
|
+
}
|
|
1994
|
+
nodes.push(...fresh.nodes);
|
|
1995
|
+
}
|
|
1860
1996
|
plan = {
|
|
1861
1997
|
baseRef: nodes[0]?.baseRef ?? (opts.base ?? currentGitRef(cwd)),
|
|
1862
1998
|
nodes,
|
|
@@ -1939,6 +2075,7 @@ export function buildStartCommand(options = {}) {
|
|
|
1939
2075
|
stacked: selectedStacks.length > 0,
|
|
1940
2076
|
resumed: continueTickets.length > 0 || Boolean(recoveryRecord),
|
|
1941
2077
|
});
|
|
2078
|
+
warnRiskPlanWaivers(cwd, plan.nodes.map((node) => node.ticket.id));
|
|
1942
2079
|
log.write("branch-plan", {
|
|
1943
2080
|
...branchPlanLogMetadata(plan, branchPresentation),
|
|
1944
2081
|
...(auditDependencyCount === undefined ? {} : { auditDependencyCount }),
|
|
@@ -2234,8 +2371,9 @@ export function buildStartCommand(options = {}) {
|
|
|
2234
2371
|
: `foreman: inspect recovery with: ${formatRecoveryCommand(cwd)}`);
|
|
2235
2372
|
process.exit(2);
|
|
2236
2373
|
}
|
|
2237
|
-
if (pendingHandoffGeneration !== undefined)
|
|
2238
|
-
throw new
|
|
2374
|
+
if (pendingHandoffGeneration !== undefined) {
|
|
2375
|
+
throw new HandoffRecoveryPausedError(recoveryRecord?.runId ?? invocationRunId, pendingHandoffGeneration, `Recovery handoff generation ${pendingHandoffGeneration} was not consumed; the staged handoff remains available for a subsequent validated recovery`);
|
|
2376
|
+
}
|
|
2239
2377
|
clearInterval(branchHeartbeat);
|
|
2240
2378
|
if (selectedStacks.length) {
|
|
2241
2379
|
let allComplete = true;
|
|
@@ -2256,15 +2394,15 @@ export function buildStartCommand(options = {}) {
|
|
|
2256
2394
|
console.log(allComplete ? "foreman: stack publication complete; delivery is awaiting_review (no merges were attempted)" : "foreman: partial stack prefix published; resume point is preserved");
|
|
2257
2395
|
}
|
|
2258
2396
|
masterRun = withRunLocalAcceptedRuntimes(masterRun);
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2397
|
+
// A targeted QA recovery returns one completed summary. Reconcile the
|
|
2398
|
+
// original admitted scope before declaring the master run complete.
|
|
2399
|
+
masterRun = finishBranchTicketScope(cwd, masterRun, summaries.every((row) => row.buildStatus === "done") ? "branch-partial-scope-complete" : "branch-run-interrupted");
|
|
2262
2400
|
activeObserver?.finish(masterRun.status, observationSummaryMetadata(cwd, masterRun, qaEnabled, capturedBranchBuilder.make, summaries.find(row => row.pr?.url)));
|
|
2263
2401
|
observabilityStore?.closeLogFile(logPath, masterRun.status);
|
|
2264
2402
|
observabilityStore?.enforceLimits();
|
|
2265
2403
|
observabilityStore?.maintainLogs(cwd);
|
|
2266
2404
|
observabilityStore?.close();
|
|
2267
|
-
pruneRecoveryTelemetryAtCompletion(cwd,
|
|
2405
|
+
pruneRecoveryTelemetryAtCompletion(cwd, observabilityConfig.detail_retention_days);
|
|
2268
2406
|
activeObserver = undefined;
|
|
2269
2407
|
process.off("exit", finishObservationOnExit);
|
|
2270
2408
|
console.log("foreman: branch run summary");
|
|
@@ -2295,14 +2433,20 @@ export function buildStartCommand(options = {}) {
|
|
|
2295
2433
|
process.exit(failed ? 2 : 0);
|
|
2296
2434
|
}
|
|
2297
2435
|
if (!recoveryRecord && isTicketsInitialized(cwd)) {
|
|
2298
|
-
const
|
|
2436
|
+
const ticketsConfig = loadTicketsConfig(cwd);
|
|
2437
|
+
const paths = resolveTicketPaths(ticketsConfig, cwd);
|
|
2299
2438
|
const state = new StateDb(paths.stateDb);
|
|
2300
2439
|
try {
|
|
2301
|
-
|
|
2440
|
+
if (!executionTickets) {
|
|
2441
|
+
executionTickets = selectDefaultExecutionTickets(loadTickets(paths.tickets), state.getAllStates(), ticketsConfig.implementationLimit, steps, deliveryConfig);
|
|
2442
|
+
if (!executionTickets.length)
|
|
2443
|
+
fail("no eligible tickets are available to start; run `rafi tickets queue` to inspect blockers");
|
|
2444
|
+
}
|
|
2302
2445
|
}
|
|
2303
2446
|
finally {
|
|
2304
2447
|
state.close();
|
|
2305
2448
|
}
|
|
2449
|
+
warnRiskPlanWaivers(cwd, executionTickets);
|
|
2306
2450
|
}
|
|
2307
2451
|
if (grantedNetwork || opts.builderApprovals)
|
|
2308
2452
|
console.log(`foreman: requested Builder shell capabilities: network ${grantedNetwork ? "approved for this build" : "off"}; approvals ${opts.builderApprovals ? "per operation" : "never"}. Effective permissions will be verified with the provider.`);
|
|
@@ -2578,8 +2722,13 @@ export function buildStartCommand(options = {}) {
|
|
|
2578
2722
|
const qa = await (activeObserver
|
|
2579
2723
|
? activeObserver.span("qa_recovery", "Exact QA-only recovery", () => foreman.completePendingQaRecovery(ticketId))
|
|
2580
2724
|
: foreman.completePendingQaRecovery(ticketId));
|
|
2581
|
-
if (pendingHandoffGeneration !== undefined)
|
|
2582
|
-
|
|
2725
|
+
if (pendingHandoffGeneration !== undefined) {
|
|
2726
|
+
// QA can stop before allocating a provider session (for example
|
|
2727
|
+
// when host prerequisites are unavailable). Keep the staged
|
|
2728
|
+
// handoff recoverable and report that boundary explicitly; do not
|
|
2729
|
+
// turn an unattempted acceptance into a terminal build failure.
|
|
2730
|
+
throw new HandoffRecoveryPausedError(buildRun.runId, pendingHandoffGeneration, `QA handoff generation ${pendingHandoffGeneration} was not consumed because QA stopped before session acceptance; the handoff remains staged for recovery`);
|
|
2731
|
+
}
|
|
2583
2732
|
if (foreman.qaSessionId())
|
|
2584
2733
|
buildRun = persistBuildSession(cwd, buildRun, "qa", foreman.qaSessionRef() ?? foreman.qaSessionId());
|
|
2585
2734
|
buildRun = qa.outcome === "passed" || qa.outcome === "waived"
|
|
@@ -2679,8 +2828,9 @@ export function buildStartCommand(options = {}) {
|
|
|
2679
2828
|
};
|
|
2680
2829
|
const batch = () => foreman.runBatch(steps, trackerRelPath, onTicketStart, preferredTicket, executionTickets);
|
|
2681
2830
|
const result = await (activeObserver ? activeObserver.span("builder_work", "Builder and QA batch", batch) : batch());
|
|
2682
|
-
if (pendingHandoffGeneration !== undefined)
|
|
2683
|
-
throw new
|
|
2831
|
+
if (pendingHandoffGeneration !== undefined) {
|
|
2832
|
+
throw new HandoffRecoveryPausedError(buildRun.runId, pendingHandoffGeneration, `Recovery handoff generation ${pendingHandoffGeneration} was not consumed; the staged handoff remains available for a subsequent validated recovery`);
|
|
2833
|
+
}
|
|
2684
2834
|
if (foreman.builderSessionId())
|
|
2685
2835
|
buildRun = persistBuildSession(cwd, buildRun, "builder", foreman.builderAdapter().sessionRef?.() ?? foreman.builderSessionId());
|
|
2686
2836
|
if (foreman.qaSessionId())
|
|
@@ -2699,7 +2849,7 @@ export function buildStartCommand(options = {}) {
|
|
|
2699
2849
|
observabilityStore?.enforceLimits();
|
|
2700
2850
|
observabilityStore?.maintainLogs(cwd);
|
|
2701
2851
|
observabilityStore?.close();
|
|
2702
|
-
pruneRecoveryTelemetryAtCompletion(cwd,
|
|
2852
|
+
pruneRecoveryTelemetryAtCompletion(cwd, observabilityConfig.detail_retention_days);
|
|
2703
2853
|
activeObserver = undefined;
|
|
2704
2854
|
process.off("exit", finishObservationOnExit);
|
|
2705
2855
|
console.log(`\n${accent("foreman:")} ${result.completed}/${result.requested} step(s) completed`);
|
|
@@ -3003,9 +3153,14 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
3003
3153
|
const match = /RAFI_QA_REMEDIATION_START\s*([\s\S]*?)\s*RAFI_QA_REMEDIATION_END/.exec(run.turn.result.text);
|
|
3004
3154
|
if (!match)
|
|
3005
3155
|
throw new Error("Planner remediation did not return the required structured proposal");
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
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");
|
|
3009
3164
|
console.log(`\nPlanner remediation: ${proposal.summary}\n${proposal.fix_instructions.map((item) => `- ${item}`).join("\n")}`);
|
|
3010
3165
|
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: [
|
|
3011
3166
|
{ value: "approve", label: "Approve (Recommended)" }, { value: "revise", label: "Discuss/revise" }, { value: "cancel", label: "Cancel and return to choices" },
|
package/dist/cli/state.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import { exportStateBundle, hasTransferableState, importStateBundle, inspectStateBundle } from "../stateTransfer.js";
|
|
3
|
+
import { exportStateBundle, hasTransferableState, importStateBundle, inspectBuildProjectIdentity, inspectStateBundle, rebindBuildProjectIdentity } from "../stateTransfer.js";
|
|
4
4
|
export function buildStateCommand(opts = {}) {
|
|
5
5
|
const command = new Command("state")
|
|
6
6
|
.description("Export, inspect, and import portable Rafi local state bundles.");
|
|
@@ -65,5 +65,43 @@ export function buildStateCommand(opts = {}) {
|
|
|
65
65
|
console.log(` previous state backup: ${result.backupDir}`);
|
|
66
66
|
console.log(" provider sessions are historical only; resume with `rafi build:resume --fresh-with-handoff` if exact session recovery fails");
|
|
67
67
|
});
|
|
68
|
+
command
|
|
69
|
+
.command("rebind")
|
|
70
|
+
.description("Inspect or explicitly rebind copied local build state to this checkout.")
|
|
71
|
+
.argument("[project]", "project directory", ".")
|
|
72
|
+
.option("-y, --yes", "confirm rebinding copied local state")
|
|
73
|
+
.action(async (project, options) => {
|
|
74
|
+
const root = opts.resolveProject ? opts.resolveProject(project) : resolve(project);
|
|
75
|
+
const status = inspectBuildProjectIdentity(root);
|
|
76
|
+
if (!status.recoveryDatabasePresent) {
|
|
77
|
+
console.log(`rafi state rebind: no local recovery database at ${status.currentRoot}; nothing to rebind`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (!status.storedRoot) {
|
|
81
|
+
console.log(`rafi state rebind: local recovery database at ${status.currentRoot} predates project-root binding; nothing to rebind`);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
console.log("rafi state rebind:");
|
|
85
|
+
console.log(` stored path: ${status.storedRoot}`);
|
|
86
|
+
console.log(` current path: ${status.currentRoot}`);
|
|
87
|
+
if (status.matches) {
|
|
88
|
+
console.log(" status: already bound to this checkout");
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
console.log(" status: copied or moved local build state");
|
|
92
|
+
console.log(" rebinding preserves local history and records the path change; it does not merge state from the original checkout.");
|
|
93
|
+
if (!options.yes) {
|
|
94
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
95
|
+
throw new Error("rebinding changes local build-state identity; rerun with --yes after reviewing the stored and current paths");
|
|
96
|
+
const { confirm, isCancel } = await import("@clack/prompts");
|
|
97
|
+
const approved = await confirm({ message: "Rebind this copied local build state to the current checkout?", initialValue: false });
|
|
98
|
+
if (isCancel(approved) || !approved) {
|
|
99
|
+
console.log("rafi state rebind: cancelled; no state changed");
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
rebindBuildProjectIdentity(root);
|
|
104
|
+
console.log(`rafi state rebind: rebound copied local state to ${status.currentRoot}`);
|
|
105
|
+
});
|
|
68
106
|
return command;
|
|
69
107
|
}
|
package/dist/cli/tickets.d.ts
CHANGED
|
@@ -5,7 +5,29 @@ import type { EffortLevel } from "../adapters/types.js";
|
|
|
5
5
|
import { type RoleBuilderOptions } from "../agentRun.js";
|
|
6
6
|
import type { TicketsConfig } from "../tickets/config.js";
|
|
7
7
|
import { type TicketPopulateRuntimeMode, type TicketSourceConfig, type TicketsSetupConfig } from "../tickets/setupConfig.js";
|
|
8
|
+
/** Commander preserves comma-delimited values as one argument; accept both documented forms. */
|
|
9
|
+
export declare function normalizeRiskWaiverTicketIds(values: readonly string[] | undefined): string[];
|
|
10
|
+
/** Record an explicit, definition-bound exception for existing risk tickets. */
|
|
11
|
+
export declare function waiveTicketRiskNotes(projectDir: string, ticketIds: string[], reason: string, actor?: string): void;
|
|
12
|
+
export interface RiskNoteRepairProposal {
|
|
13
|
+
version: 1;
|
|
14
|
+
tickets: Array<{
|
|
15
|
+
id: string;
|
|
16
|
+
rollback: string;
|
|
17
|
+
}>;
|
|
18
|
+
}
|
|
19
|
+
export declare function extractRiskNoteRepairProposal(output: string): RiskNoteRepairProposal;
|
|
20
|
+
/** Validate a deliberately narrow repair: only exactly the missing rollback fields may change. */
|
|
21
|
+
export declare function applyRiskNoteRepairProposal(tickets: import("../tickets/ticketSchema.js").TicketDef[], proposal: RiskNoteRepairProposal): import("../tickets/ticketSchema.js").TicketDef[];
|
|
8
22
|
export declare function buildPopulateInstruction(approvedPlanPath: string, contextSources?: string[], progressDoc?: string): string;
|
|
23
|
+
/** A bounded correction loop ended without a publishable proposal. */
|
|
24
|
+
export declare class TicketPopulationRiskCorrectionExhaustedError extends Error {
|
|
25
|
+
readonly ticketIds: string[];
|
|
26
|
+
readonly projectDir: string;
|
|
27
|
+
readonly interviewId: string;
|
|
28
|
+
readonly proposalFile: string;
|
|
29
|
+
constructor(ticketIds: string[], projectDir: string, interviewId: string, proposalFile: string);
|
|
30
|
+
}
|
|
9
31
|
export declare function resolvePopulateSources(projectDir: string, explicitSources: string[] | undefined, ticketsConfig: TicketsConfig): string[] | undefined;
|
|
10
32
|
export declare function buildPopulateAgentRunOptions(opts: {
|
|
11
33
|
projectDir: string;
|
|
@@ -27,10 +49,25 @@ interface PopulateCommandOptions {
|
|
|
27
49
|
fast?: boolean;
|
|
28
50
|
yes?: boolean;
|
|
29
51
|
authorizeRetire?: string[];
|
|
52
|
+
waiveRiskNotes?: string[];
|
|
53
|
+
waiverReason?: string;
|
|
54
|
+
waiverActor?: string;
|
|
30
55
|
resumeSession?: string;
|
|
56
|
+
proposalFile?: string;
|
|
31
57
|
}
|
|
32
58
|
export declare function combineRequirementSelections(selected: string[], additions: string): string[];
|
|
33
59
|
export declare function cmdPopulateCli(opts: PopulateCommandOptions): Promise<void>;
|
|
60
|
+
interface RepairRiskNotesOptions {
|
|
61
|
+
project?: string;
|
|
62
|
+
agent?: string;
|
|
63
|
+
model?: string;
|
|
64
|
+
effort?: string;
|
|
65
|
+
fast?: boolean;
|
|
66
|
+
apply?: boolean;
|
|
67
|
+
yes?: boolean;
|
|
68
|
+
}
|
|
69
|
+
/** Ask Ticket Maker for a reviewable, rollback-only repair of published tickets. */
|
|
70
|
+
export declare function cmdRepairRiskNotesCli(opts: RepairRiskNotesOptions): Promise<void>;
|
|
34
71
|
/** Resolve only the strategy. Context documents never select an import mode. */
|
|
35
72
|
export declare function resolvePopulationMode(setup: TicketsSetupConfig | undefined): TicketPopulateRuntimeMode;
|
|
36
73
|
export interface PopulationContextSelection {
|