ai-foreman 1.7.20 → 1.7.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -2
- package/dist/activity.d.ts +3 -0
- package/dist/activity.js +18 -6
- package/dist/adapters/claude.d.ts +16 -0
- package/dist/adapters/claude.js +28 -3
- package/dist/adapters/codex.d.ts +20 -0
- package/dist/adapters/codex.js +99 -5
- package/dist/adapters/recovering.d.ts +9 -0
- package/dist/adapters/recovering.js +10 -0
- package/dist/adapters/types.d.ts +15 -0
- package/dist/agentRun.d.ts +7 -0
- package/dist/agentRun.js +24 -13
- package/dist/branch/currentGuard.d.ts +1 -0
- package/dist/branch/currentGuard.js +1 -0
- package/dist/branch/finalization.js +6 -1
- package/dist/branch/git.d.ts +1 -0
- package/dist/branch/git.js +15 -1
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +90 -4
- package/dist/buildAdmission.js +2 -0
- package/dist/buildAssignment.d.ts +33 -0
- package/dist/buildAssignment.js +215 -0
- package/dist/buildInterventions.d.ts +90 -0
- package/dist/buildInterventions.js +334 -0
- package/dist/buildOwnershipReconciliation.d.ts +19 -0
- package/dist/buildOwnershipReconciliation.js +41 -0
- package/dist/buildOwnershipRepair.d.ts +33 -0
- package/dist/buildOwnershipRepair.js +111 -0
- package/dist/buildRuns.d.ts +6 -0
- package/dist/buildRuns.js +23 -17
- package/dist/buildSyntheticWork.d.ts +2 -0
- package/dist/buildSyntheticWork.js +30 -0
- package/dist/buildWorkAdmission.d.ts +17 -0
- package/dist/buildWorkAdmission.js +57 -0
- package/dist/buildWorkAuthorization.d.ts +2 -0
- package/dist/buildWorkAuthorization.js +23 -0
- package/dist/buildWorkContext.d.ts +3 -0
- package/dist/buildWorkContext.js +6 -0
- package/dist/buildWorkMigration.d.ts +9 -0
- package/dist/buildWorkMigration.js +206 -0
- package/dist/buildWorkReconciliation.d.ts +12 -0
- package/dist/buildWorkReconciliation.js +70 -0
- package/dist/builderGuidanceFollowup.d.ts +15 -0
- package/dist/builderGuidanceFollowup.js +34 -0
- package/dist/cli/doctor.js +3 -2
- package/dist/cli/graph.d.ts +2 -0
- package/dist/cli/graph.js +75 -0
- package/dist/cli/manager.d.ts +8 -0
- package/dist/cli/manager.js +234 -12
- package/dist/cli/recovery.js +3 -2
- package/dist/cli/runtimeAuthPrompt.js +28 -8
- package/dist/cli/start.js +152 -48
- package/dist/cli/status.js +20 -5
- package/dist/cli/tickets.js +9 -7
- package/dist/continuity.d.ts +11 -0
- package/dist/continuity.js +22 -6
- package/dist/foreman.d.ts +10 -2
- package/dist/foreman.js +330 -39
- package/dist/graph/boundary.d.ts +15 -0
- package/dist/graph/boundary.js +50 -0
- package/dist/graph/bridge.d.ts +9 -0
- package/dist/graph/bridge.js +146 -0
- package/dist/graph/bridge.py +205 -0
- package/dist/graph/config.d.ts +11 -0
- package/dist/graph/config.js +40 -0
- package/dist/graph/corpus.d.ts +16 -0
- package/dist/graph/corpus.js +188 -0
- package/dist/graph/derived.d.ts +17 -0
- package/dist/graph/derived.js +73 -0
- package/dist/graph/host.d.ts +4 -0
- package/dist/graph/host.js +160 -0
- package/dist/graph/install.d.ts +3 -0
- package/dist/graph/install.js +50 -0
- package/dist/graph/lifecycle.d.ts +11 -0
- package/dist/graph/lifecycle.js +124 -0
- package/dist/graph/maintenance.d.ts +43 -0
- package/dist/graph/maintenance.js +301 -0
- package/dist/graph/manager.d.ts +9 -0
- package/dist/graph/manager.js +92 -0
- package/dist/graph/preparation.d.ts +41 -0
- package/dist/graph/preparation.js +43 -0
- package/dist/graph/read.d.ts +27 -0
- package/dist/graph/read.js +152 -0
- package/dist/graph/semantic.d.ts +17 -0
- package/dist/graph/semantic.js +27 -0
- package/dist/graph/session.d.ts +8 -0
- package/dist/graph/session.js +92 -0
- package/dist/graph/sources.d.ts +4 -0
- package/dist/graph/sources.js +11 -0
- package/dist/graph/storage.d.ts +38 -0
- package/dist/graph/storage.js +159 -0
- package/dist/graph/turn.d.ts +17 -0
- package/dist/graph/turn.js +360 -0
- package/dist/graph/util.d.ts +5 -0
- package/dist/graph/util.js +51 -0
- package/dist/handoffs.d.ts +1 -0
- package/dist/handoffs.js +5 -1
- package/dist/humanDecision.d.ts +21 -2
- package/dist/humanDecision.js +169 -11
- package/dist/index.js +2 -0
- package/dist/installOwnership.d.ts +46 -0
- package/dist/installOwnership.js +440 -0
- package/dist/linuxProbeInventory.d.ts +12 -0
- package/dist/linuxProbeInventory.js +48 -0
- package/dist/log.d.ts +1 -1
- package/dist/log.js +7 -1
- package/dist/managerEvidence.d.ts +25 -0
- package/dist/managerEvidence.js +326 -0
- package/dist/managerEvidenceArtifacts.d.ts +38 -0
- package/dist/managerEvidenceArtifacts.js +118 -0
- package/dist/managerPacket.d.ts +2 -0
- package/dist/managerPacket.js +23 -0
- package/dist/markers.d.ts +1 -1
- package/dist/markers.js +2 -2
- package/dist/observability.js +1 -1
- package/dist/processIdentity.js +7 -0
- package/dist/providerPhase.d.ts +13 -0
- package/dist/providerPhase.js +26 -0
- package/dist/qaAuthorizedDisposition.d.ts +24 -0
- package/dist/qaAuthorizedDisposition.js +26 -0
- package/dist/qaBuildGate.d.ts +4 -0
- package/dist/qaBuildGate.js +216 -0
- package/dist/qaBuilderCoverage.d.ts +5 -0
- package/dist/qaBuilderCoverage.js +78 -0
- package/dist/qaContractCoverage.d.ts +10 -0
- package/dist/qaContractCoverage.js +55 -0
- package/dist/qaContractDelivery.d.ts +23 -0
- package/dist/qaContractDelivery.js +145 -0
- package/dist/qaContractFreshness.d.ts +15 -0
- package/dist/qaContractFreshness.js +36 -0
- package/dist/qaDeliveryJournal.d.ts +10 -0
- package/dist/qaDeliveryJournal.js +16 -1
- package/dist/qaEffectiveConfig.d.ts +20 -0
- package/dist/qaEffectiveConfig.js +40 -0
- package/dist/qaEvidenceReader.d.ts +14 -0
- package/dist/qaEvidenceReader.js +176 -0
- package/dist/qaFailureDelivery.d.ts +2 -0
- package/dist/qaFailureDelivery.js +107 -10
- package/dist/qaHandbackMigration.d.ts +1 -1
- package/dist/qaHandbackMigration.js +3 -3
- package/dist/qaPreparation.d.ts +26 -0
- package/dist/qaPreparation.js +150 -0
- package/dist/qaPreparationInputs.d.ts +8 -0
- package/dist/qaPreparationInputs.js +47 -0
- package/dist/qaPreparationMetrics.d.ts +145 -0
- package/dist/qaPreparationMetrics.js +120 -0
- package/dist/qaPreparationPolicy.d.ts +5 -0
- package/dist/qaPreparationPolicy.js +51 -0
- package/dist/qaPreparationStore.d.ts +157 -0
- package/dist/qaPreparationStore.js +412 -0
- package/dist/qaProtocolV2.d.ts +28 -0
- package/dist/qaProtocolV2.js +12 -0
- package/dist/qaReview.d.ts +6 -0
- package/dist/qaReview.js +234 -24
- package/dist/qaTimeline.d.ts +18 -0
- package/dist/qaTimeline.js +123 -0
- package/dist/qaVerificationContract.d.ts +17 -0
- package/dist/qaVerificationContract.js +236 -0
- package/dist/readinessCleanup.js +12 -5
- package/dist/recoveryGuidance.d.ts +9 -0
- package/dist/recoveryGuidance.js +13 -0
- package/dist/runtimeAuth.d.ts +1 -0
- package/dist/runtimeAuth.js +5 -1
- package/dist/runtimeReadiness.d.ts +1 -0
- package/dist/runtimeReadiness.js +13 -1
- package/dist/sessionIdentity.d.ts +2 -0
- package/dist/sessionIdentity.js +14 -0
- package/dist/sessionLifecycle.js +3 -3
- package/dist/sources/sourceRegistry.js +7 -0
- package/dist/stateTransfer.js +50 -1
- package/dist/statusReporter.d.ts +1 -0
- package/dist/statusReporter.js +1 -0
- package/dist/supervisedStart.js +18 -8
- package/dist/terminalStyle.d.ts +21 -0
- package/dist/terminalStyle.js +50 -0
- package/dist/ticketPopulation.d.ts +1 -0
- package/dist/ticketPopulation.js +5 -1
- package/dist/tickets/commands.js +2 -2
- package/dist/tickets/queue.js +1 -1
- package/dist/tickets/ticketSchema.d.ts +198 -0
- package/dist/tickets/ticketSchema.js +2 -0
- package/dist/workflowDb.d.ts +51 -5
- package/dist/workflowDb.js +338 -7
- package/dist/workflowReader.d.ts +109 -2
- package/dist/workflowReader.js +48 -4
- package/package.json +84 -7
- package/scripts/copy-graph-assets.mjs +3 -0
- package/scripts/graph-benchmark.mjs +20 -0
- package/scripts/graph-evaluation.mjs +85 -0
package/dist/cli/start.js
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
|
+
import { finalQaRoleInstructions, resolveEffectiveQaConfiguration } from "../qaEffectiveConfig.js";
|
|
2
|
+
import { graphContext, sendGraphTurn } from "../graph/turn.js";
|
|
3
|
+
import { initializeSyntheticWork } from "../buildSyntheticWork.js";
|
|
4
|
+
import { admitApprovedTicket } from "../buildWorkAuthorization.js";
|
|
1
5
|
import { WorkflowReader } from "../workflowReader.js";
|
|
2
6
|
import { localBuildAuthority, canonicalProject, withBuildInvocation } from "../buildAdmission.js";
|
|
3
|
-
import { formatRecoveryCommand } from "../recoveryGuidance.js";
|
|
7
|
+
import { formatRecoveryCommand, formatDecisionCommands, formatExactRunRecovery } from "../recoveryGuidance.js";
|
|
4
8
|
import { durableReadOnlyProposal } from "../readOnlyProposal.js";
|
|
5
9
|
import { captureFrozenQaSource } from "../qaSnapshot.js";
|
|
6
10
|
import { finishStartAdmission, superviseStart, supervisedRunId } from "../supervisedStart.js";
|
|
7
|
-
import {
|
|
11
|
+
import { assertBuildAssignmentReconciled } from "../buildAssignment.js";
|
|
12
|
+
import { durableHumanDecision, HumanDecisionRequired, HumanDecisionCancelled, servicePendingHumanDecisions } from "../humanDecision.js";
|
|
8
13
|
import { requiresBuildApproval, buildScopeRevision, createBuildApprovalGate } from "../buildApproval.js";
|
|
9
14
|
import { Command, Option } from "commander";
|
|
15
|
+
import { accent, error, sanitizeTerminalText, success, warning } from "../terminalStyle.js";
|
|
10
16
|
import { resolve, join, relative } from "node:path";
|
|
11
17
|
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
12
18
|
import { execFileSync } from "node:child_process";
|
|
@@ -55,7 +61,6 @@ import { ObservabilityStore, RunObserver } from "../observability.js";
|
|
|
55
61
|
import { loadProjectAutonomyConfig, resolveAutonomyPolicy, resolveQaEnablement } from "../recoveryPolicy.js";
|
|
56
62
|
import { loadQaRecoveryPacket, recoverPendingQaRecoveryPublications } from "../qaRecovery.js";
|
|
57
63
|
import { qaDigest } from "../qaProtocolV2.js";
|
|
58
|
-
import { loadSkill } from "special-agents";
|
|
59
64
|
import { acceptQaRuntimeHandoff, describeQaRuntimeHandle, frozenQaRuntimeSettings } from "../qaRuntime.js";
|
|
60
65
|
const FOREMAN_VERSION = JSON.parse(readFileSync(new URL("../../package.json", import.meta.url), "utf8")).version;
|
|
61
66
|
function fail(message) {
|
|
@@ -147,11 +152,10 @@ export function formatResumeGuidance(executable, projectDir, steps, sessionId, r
|
|
|
147
152
|
return [
|
|
148
153
|
...run.decisions.flatMap(decision => [
|
|
149
154
|
`foreman: input required: ${decision.prompt}`,
|
|
150
|
-
|
|
151
|
-
` rafi build:decide ${shellQuote(projectDir)} --run ${shellQuote(run.runId)} --decision ${shellQuote(decision.decisionId)} --choice <choice-id>`,
|
|
155
|
+
...formatDecisionCommands(projectDir, run.runId, decision, executable),
|
|
152
156
|
]),
|
|
153
|
-
"foreman: resume this run with:",
|
|
154
|
-
` ${
|
|
157
|
+
executable === "rafi" ? "foreman: resume this run with:" : "foreman: review recovery for this run with:",
|
|
158
|
+
` ${formatExactRunRecovery(projectDir, run.runId, executable)}`,
|
|
155
159
|
];
|
|
156
160
|
if (executable === "rafi") {
|
|
157
161
|
return [
|
|
@@ -486,6 +490,8 @@ export function buildStartCommand() {
|
|
|
486
490
|
.option("--auto-compact-threshold <percent>", "initial Builder context compaction threshold (1-99)")
|
|
487
491
|
.option("--max-branch-depth <n>", "maximum selected branch stack depth", "5")
|
|
488
492
|
.option("--pr-ready", "create ready-for-review PRs instead of draft PRs")
|
|
493
|
+
.option("--builder-network", "request build-scoped builder internet access with separate explicit approval; rechecked on resume")
|
|
494
|
+
.option("--builder-approvals", "allow the builder to request explicit per-operation sandbox approvals; QA stays restricted")
|
|
489
495
|
.option("--keep-worktrees", "keep successful ticket worktrees for inspection")
|
|
490
496
|
.option("--ticket <id>", "select one new ticket, or identify recovery tickets with --resume/--continue/--recover-run", collectTicket, [])
|
|
491
497
|
.option("--skip-delivery-unit <id>", "skip one unfinished delivery unit for this run", collectTicket, [])
|
|
@@ -558,6 +564,34 @@ export function buildStartCommand() {
|
|
|
558
564
|
try {
|
|
559
565
|
if (await superviseStart(cwd, supervisedInvocationId, opts.supervisor === false ? { ...frozenPolicy, supervisorEnabled: false } : frozenPolicy, { steps: opts.steps, stacks: opts.stacks, detach: opts.detach, launchToken: opts.launchToken, startArgs: savedStartArguments(command, cwd, opts) }))
|
|
560
566
|
return;
|
|
567
|
+
if (recoveryRecord || opts.preparationRun) {
|
|
568
|
+
assertBuildAssignmentReconciled(cwd, supervisedInvocationId);
|
|
569
|
+
if (!await servicePendingHumanDecisions({ projectDir: cwd, runId: supervisedInvocationId, tickets: requestedTicketIds.length ? requestedTicketIds : recoveryRecord?.tickets.length ? recoveryRecord.tickets : undefined, scopeRevision: isTicketsInitialized(cwd) ? buildScopeRevision(cwd) : undefined }))
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
const requestedNetwork = Boolean(opts.builderNetwork || recoveryRecord?.builderCapabilities?.requestedNetwork);
|
|
573
|
+
let grantedNetwork = false;
|
|
574
|
+
if (requestedNetwork) {
|
|
575
|
+
const permissionDb = new WorkflowDb(cwd);
|
|
576
|
+
let networkKey;
|
|
577
|
+
try {
|
|
578
|
+
networkKey = permissionDb.pendingHumanDecisions(supervisedInvocationId).find(decision => decision.interruptionId.startsWith("builder-network:"))?.interruptionId ?? `builder-network:${process.pid}:${randomUUID()}`;
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
permissionDb.close();
|
|
582
|
+
}
|
|
583
|
+
const networkDecision = await durableHumanDecision({ projectDir: cwd, runId: supervisedInvocationId, key: networkKey,
|
|
584
|
+
prompt: "Allow the builder shell to access the internet for this build? This permits network use by any builder command, not only downloads. Workspace write confinement remains enforced. QA stays offline. Recovery requires approval again; --yes does not approve this permission.",
|
|
585
|
+
choices: [{ id: "allow-network", label: "Allow builder internet for this build" }, { id: "stay-offline", label: "Stay offline and use a verified local bundle" }],
|
|
586
|
+
operation: async () => {
|
|
587
|
+
const action = await select({ message: "Allow builder internet for this build?", options: [{ value: "stay-offline", label: "Stay offline" }, { value: "allow-network", label: "Allow builder internet for this build" }] });
|
|
588
|
+
return isCancel(action) ? undefined : action;
|
|
589
|
+
} });
|
|
590
|
+
if (networkDecision === undefined || isCancel(networkDecision))
|
|
591
|
+
return;
|
|
592
|
+
grantedNetwork = networkDecision === "allow-network";
|
|
593
|
+
}
|
|
594
|
+
const builderCapabilities = { requestedNetwork, grantedNetwork, ...(grantedNetwork ? { approvedAt: new Date().toISOString(), grantWorkerPid: process.pid } : {}) };
|
|
561
595
|
const readinessOwner = { project: cwd, runId: supervisedInvocationId, authority: localBuildAuthority(cwd) };
|
|
562
596
|
const autoApprovePlanUpdates = recoveryRecord
|
|
563
597
|
? recoveryRecord.recoveryDecision?.planUpdateApproval === "auto"
|
|
@@ -799,7 +833,7 @@ export function buildStartCommand() {
|
|
|
799
833
|
}
|
|
800
834
|
const recoveryTicket = !branchMode && recoveryRecord ? continueTickets[0] : undefined;
|
|
801
835
|
const preferredTicket = selectedNewTicket ?? recoveryTicket;
|
|
802
|
-
|
|
836
|
+
let executionTickets = !branchMode && recoveryRecord && recoveryRecord.tickets.length
|
|
803
837
|
? recoveryExecutionTickets(recoveryRecord, requestedTicketIds, Boolean(opts.recoveryMode))
|
|
804
838
|
: selectedNewTicket ? [selectedNewTicket] : undefined;
|
|
805
839
|
const config = loadConfig(join(cwd, "foreman.yaml"));
|
|
@@ -826,11 +860,15 @@ export function buildStartCommand() {
|
|
|
826
860
|
}
|
|
827
861
|
}
|
|
828
862
|
let activeObserver = observabilityStore ? new RunObserver(observabilityStore, invocationRunId) : undefined;
|
|
829
|
-
const finishObservationOnExit = () => {
|
|
863
|
+
const finishObservationOnExit = (exitCode) => {
|
|
830
864
|
if (!activeObserver)
|
|
831
865
|
return;
|
|
832
|
-
|
|
833
|
-
|
|
866
|
+
const reader = new WorkflowReader(cwd);
|
|
867
|
+
const durable = reader.getRun(invocationRunId);
|
|
868
|
+
reader.close();
|
|
869
|
+
const outcome = durable?.status === "cancelled" ? "cancelled" : durable?.checkpoint === "waiting-for-human" ? "waiting-for-human" : durable?.status === "failed" ? "failed" : "interrupted";
|
|
870
|
+
activeObserver.finish(outcome, { checkpoint: durable?.checkpoint, failureCategory: `process-exit:${exitCode}` });
|
|
871
|
+
observabilityStore?.closeLogFile(logPath, outcome);
|
|
834
872
|
observabilityStore?.close();
|
|
835
873
|
activeObserver = undefined;
|
|
836
874
|
};
|
|
@@ -864,6 +902,8 @@ export function buildStartCommand() {
|
|
|
864
902
|
workspaceIdentity: !branchMode ? captureCurrentWorkflowSessionIdentity(builderCwd) : captureWorkspaceIdentity(builderCwd),
|
|
865
903
|
ticketId: sessionRef?.ticketId,
|
|
866
904
|
deliveryUnitId: sessionRef?.deliveryUnitId,
|
|
905
|
+
networkAccess: grantedNetwork,
|
|
906
|
+
approvalPolicy: opts.builderApprovals ? "on-request" : "never",
|
|
867
907
|
permission: createPermissionHandler(builderPolicy, log, { durable: () => ({ projectDir: cwd, runId: activeContinuityRunId ?? invocationRunId, ticketId: activeObserver?.context().ticketId }) }),
|
|
868
908
|
onQuestionTrace: (trace) => log.write("question-round-trip", { ...trace }),
|
|
869
909
|
onLifecycleTrace: (trace) => log.write("provider-lifecycle", { ...trace }),
|
|
@@ -871,7 +911,7 @@ export function buildStartCommand() {
|
|
|
871
911
|
fast: builderFast,
|
|
872
912
|
systemPromptAppend: roleBundle.system || undefined,
|
|
873
913
|
skills: roleBundle.skills.length > 0 ? roleBundle.skills : undefined,
|
|
874
|
-
autoCompactThresholdPercent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ??
|
|
914
|
+
autoCompactThresholdPercent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ?? 65,
|
|
875
915
|
observer: activeObserver,
|
|
876
916
|
};
|
|
877
917
|
const adapter = agent === "codex"
|
|
@@ -894,13 +934,13 @@ export function buildStartCommand() {
|
|
|
894
934
|
role, source: roleDefaults.builder ? "project" : "provider", make: agent, model: model ?? "default",
|
|
895
935
|
reasoning: builderEffort ?? "default", fast: Boolean(builderFast), session_strategy: roleDefaults.builder?.session_strategy ?? "compact",
|
|
896
936
|
settings_revision: settingsRevision, display_session_cost: sessionCostOverride ?? roleDefaults.builder?.display_session_cost ?? false,
|
|
897
|
-
auto_compact_threshold_percent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ??
|
|
937
|
+
auto_compact_threshold_percent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ?? 65,
|
|
898
938
|
compact_maximum: roleDefaults.builder?.compact_maximum ?? 10,
|
|
899
939
|
} : {
|
|
900
940
|
role, source: roleDefaults.qa ? "project" : "provider", make: qaAgent, model: qaModel ?? "default",
|
|
901
941
|
reasoning: qaEffort ?? "default", fast: qaFast, session_strategy: roleDefaults.qa?.session_strategy ?? "compact",
|
|
902
942
|
settings_revision: settingsRevision, display_session_cost: sessionCostOverride ?? roleDefaults.qa?.display_session_cost ?? false,
|
|
903
|
-
auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ??
|
|
943
|
+
auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ?? 65,
|
|
904
944
|
compact_maximum: roleDefaults.qa?.compact_maximum ?? 10,
|
|
905
945
|
};
|
|
906
946
|
const liveRoleSettings = (role, base) => {
|
|
@@ -953,22 +993,9 @@ export function buildStartCommand() {
|
|
|
953
993
|
});
|
|
954
994
|
const qaRuntimeMetadata = new WeakMap();
|
|
955
995
|
const resolveQaRuntimeMetadata = (qaCwd, settings) => {
|
|
956
|
-
const
|
|
957
|
-
const effectiveRoleInstructions =
|
|
958
|
-
|
|
959
|
-
const runtimeRoot = settings.make === "codex" ? ".codex" : ".claude";
|
|
960
|
-
const path = [join(qaCwd, runtimeRoot, "skills", name, "SKILL.md"), join(qaCwd, ".agents", "skills", name, "SKILL.md")].find(existsSync);
|
|
961
|
-
if (path) {
|
|
962
|
-
const content = `## ${name}\n${readFileSync(path, "utf8").trim()}`;
|
|
963
|
-
return { name, path, content, digest: createHash("sha256").update(content).digest("hex") };
|
|
964
|
-
}
|
|
965
|
-
const bundled = loadSkill(name);
|
|
966
|
-
if (!bundled.body?.trim())
|
|
967
|
-
throw new Error(`QA runtime ${settings.make} skill ${name} has no exact dispatchable content`);
|
|
968
|
-
const content = `## ${bundled.name}\n${bundled.body.trim()}`;
|
|
969
|
-
return { name, path: `special-agents:${name}`, content, digest: createHash("sha256").update(content).digest("hex") };
|
|
970
|
-
});
|
|
971
|
-
return { settings: structuredClone(settings), effectiveRoleInstructions, skills };
|
|
996
|
+
const effective = resolveEffectiveQaConfiguration(cwd, settings);
|
|
997
|
+
const effectiveRoleInstructions = finalQaRoleInstructions(effective.qaRules);
|
|
998
|
+
return { settings: structuredClone(settings), effectiveRoleInstructions, skills: effective.skills.map(skill => ({ ...skill, digest: createHash("sha256").update(skill.content).digest("hex") })) };
|
|
972
999
|
};
|
|
973
1000
|
const createBuilderForSettings = async (builderCwd, settings) => {
|
|
974
1001
|
const ready = await ensureBuildRuntimeReadyForCommand(builderCwd, settings.make, { build: readinessOwner, durable: { projectDir: cwd, runId: activeContinuityRunId ?? invocationRunId, scopeRevision: buildScopeRevision(cwd, approvalConsequences) }, timeoutMs: frozenPolicy.runtimeDeadlines?.preparation_ms,
|
|
@@ -994,6 +1021,8 @@ export function buildStartCommand() {
|
|
|
994
1021
|
shutdownTimeoutMs: frozenPolicy.runtimeDeadlines?.shutdown_ms,
|
|
995
1022
|
turnDeadlineMs: frozenPolicy.runtimeDeadlines?.turn_ms,
|
|
996
1023
|
model: settings.model === "default" ? undefined : settings.model,
|
|
1024
|
+
networkAccess: grantedNetwork,
|
|
1025
|
+
approvalPolicy: opts.builderApprovals ? "on-request" : "never",
|
|
997
1026
|
permission: createPermissionHandler(policy, log, { durable: () => ({ projectDir: cwd, runId: activeContinuityRunId ?? invocationRunId, ticketId: activeObserver?.context().ticketId }) }),
|
|
998
1027
|
onQuestionTrace: (trace) => log.write("question-round-trip", { ...trace }),
|
|
999
1028
|
onLifecycleTrace: (trace) => log.write("provider-lifecycle", { ...trace }),
|
|
@@ -1677,6 +1706,7 @@ export function buildStartCommand() {
|
|
|
1677
1706
|
if (branchDefaults.createReview && branchDefaults.reviewProvider) {
|
|
1678
1707
|
await ensureReviewProviderReady(cwd, branchDefaults.reviewProvider, log, Boolean(opts.yes));
|
|
1679
1708
|
}
|
|
1709
|
+
console.log(`foreman: builder capability request: workspace writes, shell network ${grantedNetwork ? "approved for this build" : "off"}, approvals ${opts.builderApprovals ? "per operation" : "never"}; effective policy requires provider confirmation. Use a provenance-verified local source/dependency bundle for offline acquisition.`);
|
|
1680
1710
|
const ready = await ensureBuildRuntimeReadyForCommand(cwd, agent, { build: readinessOwner, durable: { projectDir: cwd, runId: activeContinuityRunId ?? invocationRunId, scopeRevision: buildScopeRevision(cwd, approvalConsequences) }, timeoutMs: frozenPolicy.runtimeDeadlines?.preparation_ms,
|
|
1681
1711
|
label: "start",
|
|
1682
1712
|
yes: Boolean(opts.yes),
|
|
@@ -1755,7 +1785,7 @@ export function buildStartCommand() {
|
|
|
1755
1785
|
try {
|
|
1756
1786
|
auditBuilder = await createBuilder(cwd);
|
|
1757
1787
|
auditViewer = printEvents(auditBuilder.events());
|
|
1758
|
-
const audit = await auditBuilder
|
|
1788
|
+
const audit = await sendGraphTurn(auditBuilder, buildBranchAuditInstruction(plan.nodes.map((node) => node.ticket)), graphContext(cwd, auditBuilder.sessionRef?.()?.cwd ?? cwd, "branch-dependency-audit", plan.nodes.map(node => node.ticket.id).join(" ")), { purpose: "planning" });
|
|
1759
1789
|
if (audit.isError)
|
|
1760
1790
|
throw new Error(audit.text);
|
|
1761
1791
|
const auditDependencies = parseAuditDependencies(audit.text);
|
|
@@ -1860,23 +1890,23 @@ export function buildStartCommand() {
|
|
|
1860
1890
|
make: agent, model: model ?? "default", reasoning: builderEffort ?? "default", fast: Boolean(builderFast),
|
|
1861
1891
|
session_strategy: roleDefaults.builder?.session_strategy ?? "compact", settings_revision: settingsRevision,
|
|
1862
1892
|
display_session_cost: sessionCostOverride ?? roleDefaults.builder?.display_session_cost ?? false,
|
|
1863
|
-
auto_compact_threshold_percent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ??
|
|
1893
|
+
auto_compact_threshold_percent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ?? 65,
|
|
1864
1894
|
compact_maximum: roleDefaults.builder?.compact_maximum ?? 10,
|
|
1865
1895
|
};
|
|
1866
1896
|
const capturedBranchQa = {
|
|
1867
1897
|
role: "qa", source: roleDefaults.qa ? "project" : "provider", make: qaAgent, model: qaModel ?? "default",
|
|
1868
1898
|
reasoning: qaEffort ?? "default", fast: qaFast, session_strategy: roleDefaults.qa?.session_strategy ?? "compact", settings_revision: settingsRevision,
|
|
1869
1899
|
display_session_cost: sessionCostOverride ?? roleDefaults.qa?.display_session_cost ?? false,
|
|
1870
|
-
auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ??
|
|
1900
|
+
auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ?? 65,
|
|
1871
1901
|
compact_maximum: roleDefaults.qa?.compact_maximum ?? 10,
|
|
1872
1902
|
};
|
|
1873
1903
|
const firstResumeRef = plan.nodes[0] ? resumeSessionByTicket.get(plan.nodes[0].ticket.id)?.sessionRef : undefined;
|
|
1874
1904
|
let masterRun = recoveryRecord ? resumeBuildRun(cwd, recoveryRecord.runId, { builder: capturedBranchBuilder, qa: capturedBranchQa, builderSessionId: firstResumeRef?.sessionId ?? null, builderSessionRef: firstResumeRef ?? null, expectedRecoveryDecisionDigest: opts.recoveryDecisionDigest }) : createBuildRun({
|
|
1875
1905
|
runId: invocationRunId,
|
|
1876
|
-
tickets: plan.nodes.map((node) => node.ticket.id), deliveryUnit: selectedStacks.length ? selectedStacks.map((stack) => stack.id).join(",") : deliveryRun?.unit.id,
|
|
1906
|
+
builderCapabilities, tickets: plan.nodes.map((node) => node.ticket.id), deliveryUnit: selectedStacks.length ? selectedStacks.map((stack) => stack.id).join(",") : deliveryRun?.unit.id,
|
|
1877
1907
|
repositoryRoot: cwd, branchMode: branchPresentation.allocationMode, baseRef: plan.baseRef, builder: capturedBranchBuilder, qa: capturedBranchQa,
|
|
1878
1908
|
frozenPolicy, autonomyProfile, qaEnabled,
|
|
1879
|
-
runDecisions: { workMode: "branch-per-ticket", workModeSource, branchPrefix, branchPrefixSource: resolvedPrefix.source, autoCompactThresholdPercent: capturedBranchBuilder.auto_compact_threshold_percent ??
|
|
1909
|
+
runDecisions: { workMode: "branch-per-ticket", workModeSource, branchPrefix, branchPrefixSource: resolvedPrefix.source, autoCompactThresholdPercent: capturedBranchBuilder.auto_compact_threshold_percent ?? 65, thresholdSource: thresholdOverride === undefined ? "project" : "cli" },
|
|
1880
1910
|
});
|
|
1881
1911
|
activeContinuityRunId = masterRun.runId;
|
|
1882
1912
|
if (requiresBuildApproval(cwd, plan.nodes.map(node => node.ticket.id), autoApprovePlanUpdates, approvalConsequences) && plan.issues.every((issue) => !issue.blocking)) {
|
|
@@ -1888,10 +1918,25 @@ export function buildStartCommand() {
|
|
|
1888
1918
|
],
|
|
1889
1919
|
}) });
|
|
1890
1920
|
if (isCancel(action) || action === "cancel") {
|
|
1921
|
+
const cancelled = action === "cancel";
|
|
1922
|
+
if (cancelled) {
|
|
1923
|
+
const decisions = new WorkflowDb(cwd);
|
|
1924
|
+
try {
|
|
1925
|
+
decisions.cancelPendingHumanDecisions(invocationRunId, "User cancelled build; pending decisions superseded");
|
|
1926
|
+
}
|
|
1927
|
+
finally {
|
|
1928
|
+
decisions.close();
|
|
1929
|
+
}
|
|
1930
|
+
}
|
|
1931
|
+
masterRun = releaseBuildLease(cwd, checkpointBuildRun(cwd, masterRun, cancelled ? "cancelled" : "input-paused", { status: cancelled ? "cancelled" : "recoverable" }), cancelled ? "cancelled" : "recoverable");
|
|
1932
|
+
activeObserver?.finish(cancelled ? "cancelled" : "paused", { checkpoint: cancelled ? "user-cancel" : "prompt-eof-or-cancel" });
|
|
1891
1933
|
console.log("ai-foreman: cancelled");
|
|
1892
1934
|
process.exit(0);
|
|
1893
1935
|
}
|
|
1894
1936
|
}
|
|
1937
|
+
for (const node of plan.nodes)
|
|
1938
|
+
admitApprovedTicket(cwd, masterRun.runId, node.ticket.id, buildScopeRevision(cwd), `build invocation ${invocationRunId}: approved branch plan`);
|
|
1939
|
+
masterRun = checkpointBuildRun(cwd, masterRun, "capabilities-resolved", { builderCapabilities });
|
|
1895
1940
|
const qaResumedRecovery = recoveryRecord ? pendingQaRecoveryPacket(cwd, masterRun.runId, requestedTicketIds[0]) : undefined;
|
|
1896
1941
|
let qaProtocolResumeTicket;
|
|
1897
1942
|
if (recoveryRecord && !qaResumedRecovery) {
|
|
@@ -2149,6 +2194,17 @@ export function buildStartCommand() {
|
|
|
2149
2194
|
fireTerminalBell(config.notifications.terminal_bell);
|
|
2150
2195
|
process.exit(failed ? 2 : 0);
|
|
2151
2196
|
}
|
|
2197
|
+
if (!recoveryRecord && isTicketsInitialized(cwd)) {
|
|
2198
|
+
const paths = resolveTicketPaths(loadTicketsConfig(cwd), cwd);
|
|
2199
|
+
const state = new StateDb(paths.stateDb);
|
|
2200
|
+
try {
|
|
2201
|
+
executionTickets = executionTickets ?? loadTickets(paths.tickets).filter(ticket => !["done", "canceled", "cancelled", "obsolete"].includes(state.getState(ticket.id)?.status ?? "planned")).sort((a, b) => a.order - b.order).slice(0, steps).map(ticket => ticket.id);
|
|
2202
|
+
}
|
|
2203
|
+
finally {
|
|
2204
|
+
state.close();
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
console.log(`foreman: builder capability request: workspace writes, shell network ${grantedNetwork ? "approved for this build" : "off"}, approvals ${opts.builderApprovals ? "per operation" : "never"}; effective policy requires provider confirmation. Use a provenance-verified local source/dependency bundle for offline acquisition.`);
|
|
2152
2208
|
const ready = await ensureBuildRuntimeReadyForCommand(cwd, agent, { build: readinessOwner, durable: { projectDir: cwd, runId: activeContinuityRunId ?? invocationRunId, scopeRevision: buildScopeRevision(cwd, approvalConsequences) }, timeoutMs: frozenPolicy.runtimeDeadlines?.preparation_ms,
|
|
2153
2209
|
label: "start",
|
|
2154
2210
|
yes: Boolean(opts.yes),
|
|
@@ -2183,16 +2239,17 @@ export function buildStartCommand() {
|
|
|
2183
2239
|
make: agent, model: model ?? "default", reasoning: builderEffort ?? "default", fast: Boolean(builderFast),
|
|
2184
2240
|
session_strategy: roleDefaults.builder?.session_strategy ?? "compact", settings_revision: settingsRevision,
|
|
2185
2241
|
display_session_cost: sessionCostOverride ?? roleDefaults.builder?.display_session_cost ?? false,
|
|
2186
|
-
auto_compact_threshold_percent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ??
|
|
2242
|
+
auto_compact_threshold_percent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ?? 65,
|
|
2187
2243
|
compact_maximum: roleDefaults.builder?.compact_maximum ?? 10,
|
|
2188
2244
|
};
|
|
2189
|
-
const capturedQa = { role: "qa", source: roleDefaults.qa ? "project" : "provider", make: qaAgent, model: qaModel ?? "default", reasoning: qaEffort ?? "default", fast: qaFast, session_strategy: roleDefaults.qa?.session_strategy ?? "compact", display_session_cost: sessionCostOverride ?? roleDefaults.qa?.display_session_cost ?? false, auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ??
|
|
2245
|
+
const capturedQa = { role: "qa", source: roleDefaults.qa ? "project" : "provider", make: qaAgent, model: qaModel ?? "default", reasoning: qaEffort ?? "default", fast: qaFast, session_strategy: roleDefaults.qa?.session_strategy ?? "compact", display_session_cost: sessionCostOverride ?? roleDefaults.qa?.display_session_cost ?? false, auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ?? 65, compact_maximum: roleDefaults.qa?.compact_maximum ?? 10, settings_revision: settingsRevision };
|
|
2190
2246
|
let buildRun = recoveryRecord ? resumeBuildRun(cwd, recoveryRecord.runId, { builder: capturedBuilder, qa: capturedQa, builderSessionId: resumeSessionId ?? null, builderSessionRef: resumeSessionRef ?? null, expectedRecoveryDecisionDigest: opts.recoveryDecisionDigest }) : createBuildRun({
|
|
2191
2247
|
runId: invocationRunId,
|
|
2192
|
-
tickets: [], repositoryRoot: cwd, branchMode: "current", baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch, builder: capturedBuilder, qa: capturedQa,
|
|
2248
|
+
tickets: [...(executionTickets ?? [])], builderCapabilities, authorizedBatch: { tickets: [...(executionTickets ?? [])], requestedSteps: steps, scopeRevision: buildScopeRevision(cwd, approvalConsequences), startedTickets: [] }, repositoryRoot: cwd, branchMode: "current", baseRef: opts.base ?? loadTicketSetupConfig(cwd)?.build.base_branch, builder: capturedBuilder, qa: capturedQa,
|
|
2193
2249
|
frozenPolicy, autonomyProfile, qaEnabled,
|
|
2194
|
-
runDecisions: { workMode: "current", workModeSource, branchPrefix, branchPrefixSource: resolvedPrefix.source, autoCompactThresholdPercent: capturedBuilder.auto_compact_threshold_percent ??
|
|
2250
|
+
runDecisions: { workMode: "current", workModeSource, branchPrefix, branchPrefixSource: resolvedPrefix.source, autoCompactThresholdPercent: capturedBuilder.auto_compact_threshold_percent ?? 65, thresholdSource: thresholdOverride === undefined ? "project" : "cli" },
|
|
2195
2251
|
});
|
|
2252
|
+
buildRun = checkpointBuildRun(cwd, buildRun, "capabilities-resolved", { builderCapabilities });
|
|
2196
2253
|
activeObserver?.store.attachExecutionLease(activeObserver.executionId, readCurrentWorkflowLease(cwd)?.generation);
|
|
2197
2254
|
activeContinuityRunId = buildRun.runId;
|
|
2198
2255
|
const qaResumedRecovery = recoveryRecord ? pendingQaRecoveryPacket(cwd, buildRun.runId, requestedTicketIds[0]) : undefined;
|
|
@@ -2346,11 +2403,11 @@ export function buildStartCommand() {
|
|
|
2346
2403
|
builderFast ? "fast" : null,
|
|
2347
2404
|
qaEnabled ? null : "qa=off",
|
|
2348
2405
|
].filter(Boolean).join(" ");
|
|
2349
|
-
console.log(
|
|
2350
|
-
console.log(
|
|
2406
|
+
console.log(`${accent("foreman:")} driving a ${sanitizeTerminalText(agent)} builder through ${steps} step(s)${modifiers ? ` [${sanitizeTerminalText(modifiers)}]` : ""}`);
|
|
2407
|
+
console.log(`${accent("foreman:")} project ${sanitizeTerminalText(cwd)}`);
|
|
2351
2408
|
if (trackerRelPath)
|
|
2352
|
-
console.log(
|
|
2353
|
-
console.log(
|
|
2409
|
+
console.log(`${accent("foreman:")} tracker ${sanitizeTerminalText(trackerRelPath)}`);
|
|
2410
|
+
console.log(`${accent("foreman:")} log ${sanitizeTerminalText(logPath)}\n`);
|
|
2354
2411
|
const viewer = printEvents(builder.events());
|
|
2355
2412
|
try {
|
|
2356
2413
|
if (recoveryRecord && currentBranchTicketProgress(cwd, buildRun)?.remaining.length === 0) {
|
|
@@ -2439,6 +2496,18 @@ export function buildStartCommand() {
|
|
|
2439
2496
|
],
|
|
2440
2497
|
}) });
|
|
2441
2498
|
if (isCancel(action) || action === "cancel") {
|
|
2499
|
+
const cancelled = action === "cancel";
|
|
2500
|
+
if (cancelled) {
|
|
2501
|
+
const decisions = new WorkflowDb(cwd);
|
|
2502
|
+
try {
|
|
2503
|
+
decisions.cancelPendingHumanDecisions(invocationRunId, "User cancelled build; pending decisions superseded");
|
|
2504
|
+
}
|
|
2505
|
+
finally {
|
|
2506
|
+
decisions.close();
|
|
2507
|
+
}
|
|
2508
|
+
}
|
|
2509
|
+
buildRun = releaseBuildLease(cwd, checkpointBuildRun(cwd, buildRun, cancelled ? "cancelled" : "input-paused", { status: cancelled ? "cancelled" : "recoverable" }), cancelled ? "cancelled" : "recoverable");
|
|
2510
|
+
activeObserver?.finish(cancelled ? "cancelled" : "paused", { checkpoint: cancelled ? "user-cancel" : "prompt-eof-or-cancel" });
|
|
2442
2511
|
console.log("ai-foreman: cancelled");
|
|
2443
2512
|
statusReporter.stop("cancelled");
|
|
2444
2513
|
await foreman.close();
|
|
@@ -2453,7 +2522,9 @@ export function buildStartCommand() {
|
|
|
2453
2522
|
message: "Your feedback:",
|
|
2454
2523
|
validate: (v) => (v?.trim() ? undefined : "Please enter some feedback"),
|
|
2455
2524
|
}) });
|
|
2456
|
-
if (isCancel(fb)) {
|
|
2525
|
+
if (isCancel(fb) || fb === undefined) {
|
|
2526
|
+
buildRun = releaseBuildLease(cwd, checkpointBuildRun(cwd, buildRun, "input-paused", { status: "recoverable" }), "recoverable");
|
|
2527
|
+
activeObserver?.finish("paused", { checkpoint: "feedback-eof-or-cancel" });
|
|
2457
2528
|
console.log("ai-foreman: cancelled");
|
|
2458
2529
|
statusReporter.stop("cancelled");
|
|
2459
2530
|
await foreman.close();
|
|
@@ -2466,10 +2537,16 @@ export function buildStartCommand() {
|
|
|
2466
2537
|
}
|
|
2467
2538
|
});
|
|
2468
2539
|
await ensureApproved();
|
|
2540
|
+
if (!isTicketsInitialized(cwd)) {
|
|
2541
|
+
const synthetic = initializeSyntheticWork(cwd, buildRun.runId, steps, `Explicit unticketed build invocation ${invocationRunId}`, foreman.approvedPlanText());
|
|
2542
|
+
buildRun = checkpointBuildRun(cwd, buildRun, "synthetic-work-admitted", { tickets: synthetic });
|
|
2543
|
+
}
|
|
2469
2544
|
const onTicketStart = async (ticketId) => {
|
|
2470
2545
|
await ensureApproved();
|
|
2546
|
+
admitApprovedTicket(cwd, buildRun.runId, ticketId, buildScopeRevision(cwd), `build invocation ${invocationRunId}: approved ticket selection`);
|
|
2471
2547
|
const tickets = buildRun.tickets.includes(ticketId) ? buildRun.tickets : [...buildRun.tickets, ticketId];
|
|
2472
|
-
buildRun = checkpointBuildRun(cwd, buildRun, "ticket-selected", { tickets, currentTicket: ticketId
|
|
2548
|
+
buildRun = checkpointBuildRun(cwd, buildRun, "ticket-selected", { tickets, currentTicket: ticketId,
|
|
2549
|
+
...(buildRun.authorizedBatch ? { authorizedBatch: { ...buildRun.authorizedBatch, approvalRevisions: [...new Set([...(buildRun.authorizedBatch.approvalRevisions ?? [buildRun.authorizedBatch.scopeRevision]), buildScopeRevision(cwd, approvalConsequences)])], startedTickets: [...new Set([...buildRun.authorizedBatch.startedTickets, ticketId])] } } : {}) });
|
|
2473
2550
|
};
|
|
2474
2551
|
const batch = () => foreman.runBatch(steps, trackerRelPath, onTicketStart, preferredTicket, executionTickets);
|
|
2475
2552
|
const result = await (activeObserver ? activeObserver.span("builder_work", "Builder and QA batch", batch) : batch());
|
|
@@ -2496,8 +2573,12 @@ export function buildStartCommand() {
|
|
|
2496
2573
|
pruneRecoveryTelemetryAtCompletion(cwd, config.observability.detail_retention_days);
|
|
2497
2574
|
activeObserver = undefined;
|
|
2498
2575
|
process.off("exit", finishObservationOnExit);
|
|
2499
|
-
console.log(`\
|
|
2500
|
-
|
|
2576
|
+
console.log(`\n${accent("foreman:")} ${result.completed}/${result.requested} step(s) completed`);
|
|
2577
|
+
const safeOutcome = sanitizeTerminalText(result.outcome);
|
|
2578
|
+
const resultOutcome = /success|complete|pass/i.test(safeOutcome) ? success(safeOutcome)
|
|
2579
|
+
: /fail|error/i.test(safeOutcome) ? error(safeOutcome)
|
|
2580
|
+
: /warn|partial|blocked/i.test(safeOutcome) ? warning(safeOutcome) : safeOutcome;
|
|
2581
|
+
console.log(`${accent("foreman:")} outcome — ${resultOutcome}`);
|
|
2501
2582
|
if (result.detail)
|
|
2502
2583
|
console.log(`foreman: ${result.detail}`);
|
|
2503
2584
|
if (buildRun.status === "completed")
|
|
@@ -2561,6 +2642,13 @@ export function buildStartCommand() {
|
|
|
2561
2642
|
console.error(`foreman: resume this run with: ${formatRecoveryCommand(cwd)}`);
|
|
2562
2643
|
process.exit(2);
|
|
2563
2644
|
}
|
|
2645
|
+
if (err instanceof HumanDecisionCancelled) {
|
|
2646
|
+
buildRun = releaseBuildLease(cwd, checkpointBuildRun(cwd, buildRun, "cancelled", { status: "cancelled" }), "cancelled");
|
|
2647
|
+
activeObserver?.finish("cancelled", { checkpoint: "question-menu" });
|
|
2648
|
+
await foreman.close().catch(() => { });
|
|
2649
|
+
console.log("ai-foreman: cancelled");
|
|
2650
|
+
process.exit(0);
|
|
2651
|
+
}
|
|
2564
2652
|
if (err instanceof HumanDecisionRequired) {
|
|
2565
2653
|
buildRun = releaseBuildLease(cwd, checkpointBuildRun(cwd, buildRun, "waiting-for-human", { status: "recoverable" }), "recoverable");
|
|
2566
2654
|
await foreman.close().catch(() => { });
|
|
@@ -2585,6 +2673,16 @@ export function buildStartCommand() {
|
|
|
2585
2673
|
fail(`run failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2586
2674
|
}
|
|
2587
2675
|
}
|
|
2676
|
+
catch (error) {
|
|
2677
|
+
if (error instanceof HumanDecisionCancelled) {
|
|
2678
|
+
console.log("ai-foreman: cancelled");
|
|
2679
|
+
return;
|
|
2680
|
+
}
|
|
2681
|
+
if (!(error instanceof HumanDecisionRequired))
|
|
2682
|
+
throw error;
|
|
2683
|
+
console.error(error.message);
|
|
2684
|
+
process.exitCode = 2;
|
|
2685
|
+
}
|
|
2588
2686
|
finally {
|
|
2589
2687
|
finishStartAdmission(cwd, supervisedInvocationId);
|
|
2590
2688
|
}
|
|
@@ -2680,8 +2778,11 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
2680
2778
|
throw new Error("QA nonconvergence requires its owning build run");
|
|
2681
2779
|
const scope = createHash("sha256").update(JSON.stringify([buildScopeRevision(projectDir), context.ticket, context.history])).digest("hex");
|
|
2682
2780
|
let round = 0;
|
|
2781
|
+
let waiverDecisionId;
|
|
2683
2782
|
const ask = (phase, choices, operation) => durableHumanDecision({
|
|
2684
2783
|
projectDir, runId, ticketId: context.ticket.id, observer: observer?.(), defer: noninteractive,
|
|
2784
|
+
onAnswered: decision => { if (phase.startsWith("QA waiver confirmation") && decision.selectedChoiceId === "yes")
|
|
2785
|
+
waiverDecisionId = decision.decisionId; },
|
|
2685
2786
|
key: `qa-nonconvergence:${scope}:${round}:${phase}`, prompt: `${context.ticket.id}: ${phase}`, choices, operation,
|
|
2686
2787
|
});
|
|
2687
2788
|
while (true) {
|
|
@@ -2702,7 +2803,10 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
2702
2803
|
] }));
|
|
2703
2804
|
if (approved === "yes") {
|
|
2704
2805
|
const db = new WorkflowDb(projectDir);
|
|
2806
|
+
let authorizationRef;
|
|
2705
2807
|
try {
|
|
2808
|
+
if (db.qaPreparationStore().policy(runId)?.mode === "enforce")
|
|
2809
|
+
authorizationRef = db.authorizeContractQaWaiver(runId, context.ticket.id, waiverDecisionId);
|
|
2706
2810
|
const lease = db.currentLease();
|
|
2707
2811
|
if (lease) {
|
|
2708
2812
|
const detail = JSON.stringify({ decision: "waive", ticket: context.ticket.id, unresolved: context.history, at: new Date().toISOString() });
|
|
@@ -2714,7 +2818,7 @@ export function createQaNonconvergenceHandler(projectDir, noninteractive, planne
|
|
|
2714
2818
|
db.close();
|
|
2715
2819
|
}
|
|
2716
2820
|
console.log(`foreman: QA WAIVER recorded for ${context.ticket.id}; validation_result will be failed`);
|
|
2717
|
-
return { action: "waive" };
|
|
2821
|
+
return { action: "waive", ...(authorizationRef ? { authorizationRef } : {}) };
|
|
2718
2822
|
}
|
|
2719
2823
|
continue;
|
|
2720
2824
|
}
|
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
|
@@ -61,6 +61,14 @@ 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;
|
|
@@ -74,7 +82,9 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
74
82
|
handback?: boolean;
|
|
75
83
|
responseOnly?: boolean;
|
|
76
84
|
logicalActionId?: string;
|
|
85
|
+
purpose?: import("./providerPhase.js").ProviderTurnPurpose;
|
|
77
86
|
}): Promise<TurnResult>;
|
|
87
|
+
private sendTurnOwned;
|
|
78
88
|
observeEvents(listener: (event: import("./adapters/types.js").BuilderEvent) => void): () => void;
|
|
79
89
|
acceptHandbackTurn(turn: TurnResult): void;
|
|
80
90
|
sessionId(): string | undefined;
|
|
@@ -90,6 +100,7 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
90
100
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
91
101
|
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
92
102
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
103
|
+
graphRuntimeSettings(): Pick<import("./adapters/types.js").BuilderAdapterOptions, "model" | "effort" | "fast" | "runtimeExecutable"> | undefined;
|
|
93
104
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
94
105
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
95
106
|
events(): AsyncIterable<import("./adapters/types.js").BuilderEvent>;
|