substrate-ai 0.20.138 → 0.20.141

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.
Files changed (27) hide show
  1. package/dist/adapter-registry-qU0IkbbQ.js +4 -0
  2. package/dist/cli/index.js +41 -37
  3. package/dist/{decision-router-DblHY8se.js → decision-router-BAPpON_C.js} +4 -2
  4. package/dist/{decisions-fBdZmqOK.js → decisions-Dvw5HhyM.js} +1 -1
  5. package/dist/{dist-DnvGvKLu.js → dist-DiNK6QOD.js} +104 -17
  6. package/dist/{errors-i3s5sshZ.js → errors-B21JUhcc.js} +2 -2
  7. package/dist/{experimenter-DU2Cv51N.js → experimenter-D55L011w.js} +1 -1
  8. package/dist/{health-2Kfa00-H.js → health-BQRsVP6s.js} +3 -3
  9. package/dist/{health-Caa24SFM.js → health-Cm-pA1mZ.js} +3 -3
  10. package/dist/{index-BJEANS9i.d.ts → index-BqVBOAO0.d.ts} +25 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/{interactive-prompt-B91mG5v4.js → interactive-prompt-CgerI5Y6.js} +2 -2
  14. package/dist/{manifest-read-DJFbGhSd.js → manifest-read-Doh2Ion5.js} +17 -3
  15. package/dist/modules/interactive-prompt/index.js +3 -3
  16. package/dist/{routing-DFIBY9Yk.js → routing-_PGfe_cD.js} +1 -1
  17. package/dist/{run-Ppco-3HT.js → run-BHRkyn_H.js} +6 -6
  18. package/dist/{run-Dbwlb4Md.js → run-CRvusFIv.js} +674 -183
  19. package/dist/src/modules/decision-router/index.d.ts +1 -1
  20. package/dist/src/modules/decision-router/index.js +1 -1
  21. package/dist/src/modules/recovery-engine/index.d.ts +9 -1
  22. package/dist/{upgrade-CnIsoLpT.js → upgrade-CTj4hi8J.js} +2 -2
  23. package/dist/{upgrade-yULZIAAW.js → upgrade-DB7KzD9c.js} +2 -2
  24. package/dist/{version-manager-impl-C7ZhqLX3.js → version-manager-impl-HJMonRJi.js} +1 -1
  25. package/package.json +3 -3
  26. package/packs/bmad/prompts/probe-author.md +17 -0
  27. package/dist/adapter-registry-Bpa7ESBV.js +0 -4
@@ -1,11 +1,11 @@
1
- import { BMAD_BASELINE_TOKENS_FULL, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-2Kfa00-H.js";
1
+ import { BMAD_BASELINE_TOKENS_FULL, FileKvStore, STOP_AFTER_VALID_PHASES, STORY_KEY_PATTERN, VALID_PHASES, __commonJS, __require, __toESM, buildPipelineStatusOutput, createDatabaseAdapter, formatOutput, formatPipelineSummary, formatTokenTelemetry, inspectProcessTree, parseDbTimestampAsUtc, validateStoryKey } from "./health-BQRsVP6s.js";
2
2
  import { createLogger } from "./logger-KeHncl-f.js";
3
3
  import { TypedEventBusImpl, createEventBus, createTuiApp, isTuiCapable, printNonTtyWarning, sleep } from "./helpers-CElYrONe.js";
4
- import { ADVISORY_NOTES, CODEX_SANDBOX_BLOCK_HINT, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, createStderrLogger, detectCodexSandboxBlock, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-DnvGvKLu.js";
5
- import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, parseRuntimeProbes, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery } from "./manifest-read-DJFbGhSd.js";
4
+ import { ADVISORY_NOTES, CLAUDE_AUTH_FAILURE_HINT, CODEX_SANDBOX_BLOCK_HINT, Categorizer, ConsumerAnalyzer, DEFAULT_GLOBAL_SETTINGS, DispatcherImpl, DoltClient, ESCALATION_DIAGNOSIS, EXPERIMENT_RESULT, EfficiencyScorer, IngestionServer, LogTurnAnalyzer, OPERATIONAL_FINDING, Recommender, RoutingRecommender, RoutingResolver, RoutingTelemetry, RoutingTokenAccumulator, RoutingTuner, STORY_METRICS, STORY_OUTCOME, SubstrateConfigSchema, TEST_EXPANSION_FINDING, TEST_PLAN, TelemetryNormalizer, TelemetryPipeline, TurnAnalyzer, addTokenUsage, aggregateTokenUsageForRun, aggregateTokenUsageForStory, callLLM, classifyVersionGap, createConfigSystem, createDatabaseAdapter$1, createDecision, createPipelineRun, createRequirement, createStderrLogger, detectClaudeAuthFailure, detectCodexSandboxBlock, detectInterfaceChanges, getArtifactByTypeForRun, getArtifactsByRun, getDecisionsByCategory, getDecisionsByPhase, getDecisionsByPhaseForRun, getLatestRun, getPipelineRunById, getRunMetrics, getRunningPipelineRuns, getStoryMetricsForRun, getTokenUsageSummary, initSchema, listRequirements, loadModelRoutingConfig, registerArtifact, swallowDebug, updatePipelineRun, updatePipelineRunConfig, upsertDecision, writeRunMetrics, writeStoryMetrics } from "./dist-DiNK6QOD.js";
5
+ import { FindingsInjector, RunManifest, RuntimeProbeListSchema, applyConfigToGraph, createDefaultVerificationPipeline, createGraphOrchestrator, createSdlcCodeReviewHandler, createSdlcCreateStoryHandler, createSdlcDevStoryHandler, createSdlcPhaseHandler, detectsEventDrivenAC, detectsStateIntegratingAC, extractTargetFilesFromStoryContent, parseRuntimeProbes, renderFindings, resolveGraphPath, resolveMainRepoRoot, runAcTraceabilityCheck, runStaleVerificationRecovery } from "./manifest-read-Doh2Ion5.js";
6
6
  import { WorkGraphRepository, detectCycles } from "./work-graph-repository-DZyJv5pV.js";
7
- import { deriveExitCode, routeDecision } from "./decision-router-DblHY8se.js";
8
- import { runInteractivePrompt } from "./interactive-prompt-B91mG5v4.js";
7
+ import { deriveExitCode, routeDecision } from "./decision-router-BAPpON_C.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-CgerI5Y6.js";
9
9
  import { runRecoveryEngine } from "./recovery-engine-BKGBeBnW.js";
10
10
  import { basename, dirname, extname, join } from "path";
11
11
  import { access, readFile, readdir, stat } from "fs/promises";
@@ -159,6 +159,66 @@ function decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch)
159
159
  };
160
160
  return { safe: true };
161
161
  }
162
+ /**
163
+ * Decide whether removing a story worktree AND deleting its branch is safe
164
+ * (H0.3, field findings #17/#19). Unsafe when:
165
+ * - the worktree has uncommitted changes (removal destroys the only copy), or
166
+ * - the branch carries commits not reachable from the project's current HEAD
167
+ * (branch -D destroys them — this is where H0.1's wip checkpoints live), or
168
+ * - either state could not be determined (negative unmergedCommits).
169
+ *
170
+ * Pure + exported for testing (mirrors decideWorktreeReclaim; the git I/O that
171
+ * produces the inputs lives in inspectWorktreeRemovalSafety).
172
+ */
173
+ function decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName) {
174
+ const reasons = [];
175
+ if (hasUncommittedChanges) {
176
+ const preview = uncommittedFiles.slice(0, 10).join(", ");
177
+ const more = uncommittedFiles.length > 10 ? ` (+${String(uncommittedFiles.length - 10)} more)` : "";
178
+ reasons.push(`the worktree has ${String(uncommittedFiles.length)} uncommitted change(s) that removal would destroy` + (preview.length > 0 ? `: ${preview}${more}` : ""));
179
+ }
180
+ if (unmergedCommits > 0) reasons.push(`branch ${branchName} carries ${String(unmergedCommits)} commit(s) not reachable from the current HEAD — deleting the branch would destroy them`);
181
+ if (unmergedCommits < 0) reasons.push("the branch state could not be verified as safe to discard");
182
+ return {
183
+ safe: reasons.length === 0,
184
+ reasons
185
+ };
186
+ }
187
+ /**
188
+ * Gather the inputs for `decideWorktreeRemoval` from git. Thin I/O wrapper:
189
+ * - `git status --porcelain` inside the worktree (skipped when the directory
190
+ * is missing — nothing on disk to lose)
191
+ * - `git rev-list --count HEAD..<branch>` in the project root (commits the
192
+ * branch has that the current checkout does not; 0 when the branch is
193
+ * merged or absent, -1 when the count could not be determined)
194
+ */
195
+ async function inspectWorktreeRemovalSafety(worktreePath, projectRoot, branchName) {
196
+ let hasUncommittedChanges = false;
197
+ let uncommittedFiles = [];
198
+ const worktreeOnDisk = await access$1(worktreePath).then(() => true).catch(() => false);
199
+ if (worktreeOnDisk) {
200
+ const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
201
+ if (statusResult.code === 0) {
202
+ uncommittedFiles = statusResult.stdout.split("\n").filter((line) => line.trim().length > 0).map((line) => line.slice(3).trim());
203
+ hasUncommittedChanges = uncommittedFiles.length > 0;
204
+ } else return decideWorktreeRemoval(false, [], -1, branchName);
205
+ }
206
+ const branchExists = (await spawnGit([
207
+ "rev-parse",
208
+ "--verify",
209
+ branchName
210
+ ], { cwd: projectRoot })).code === 0;
211
+ let unmergedCommits = 0;
212
+ if (branchExists) {
213
+ const aheadResult = await spawnGit([
214
+ "rev-list",
215
+ "--count",
216
+ `HEAD..${branchName}`
217
+ ], { cwd: projectRoot });
218
+ unmergedCommits = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
219
+ }
220
+ return decideWorktreeRemoval(hasUncommittedChanges, uncommittedFiles, unmergedCommits, branchName);
221
+ }
162
222
  async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyFiles = []) {
163
223
  const worktreePath = path$5.join(projectRoot, ".substrate-worktrees", taskId);
164
224
  const worktreeExists = await access$1(worktreePath).then(() => true).catch((err) => {
@@ -518,14 +578,19 @@ var GitWorktreeManagerImpl = class {
518
578
  }, "Worktree created");
519
579
  return info;
520
580
  }
521
- async cleanupWorktree(taskId) {
581
+ async cleanupWorktree(taskId, opts) {
522
582
  const branchName = BRANCH_PREFIX + taskId;
523
583
  const worktreePath = this.getWorktreePath(taskId);
524
584
  this._logger.debug({
525
585
  taskId,
526
586
  branchName,
527
- worktreePath
587
+ worktreePath,
588
+ force: opts?.force === true
528
589
  }, "cleanupWorktree");
590
+ if (opts?.force !== true) {
591
+ const decision = await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchName);
592
+ if (!decision.safe) throw new Error(`refusing to clean up worktree for "${taskId}": ${decision.reasons.join("; ")}.\nInspect first (git -C ${worktreePath} status; git log ${branchName} --oneline) or re-run with --force to discard.`);
593
+ }
529
594
  let worktreeExists = false;
530
595
  try {
531
596
  await access$1(worktreePath);
@@ -563,12 +628,28 @@ var GitWorktreeManagerImpl = class {
563
628
  branchName
564
629
  }, "Worktree cleaned up");
565
630
  }
566
- async cleanupAllWorktrees() {
567
- this._logger.debug({ projectRoot: this._projectRoot }, "cleanupAllWorktrees");
631
+ async cleanupAllWorktrees(opts) {
632
+ this._logger.debug({
633
+ projectRoot: this._projectRoot,
634
+ force: opts?.force === true
635
+ }, "cleanupAllWorktrees");
568
636
  const orphanedPaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
569
637
  let cleaned = 0;
570
638
  for (const worktreePath of orphanedPaths) {
571
639
  const taskId = path$4.basename(worktreePath);
640
+ const branchGuardName = BRANCH_PREFIX + taskId;
641
+ const decision = opts?.force === true ? {
642
+ safe: true,
643
+ reasons: []
644
+ } : await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchGuardName);
645
+ if (!decision.safe) {
646
+ this._logger.warn({
647
+ taskId,
648
+ worktreePath,
649
+ reasons: decision.reasons
650
+ }, "cleanupAllWorktrees: preserving worktree — removal would destroy work (use `substrate worktrees cleanup --force` to discard)");
651
+ continue;
652
+ }
572
653
  let worktreeRemoved = false;
573
654
  try {
574
655
  await removeWorktree(worktreePath, this._projectRoot);
@@ -6813,6 +6894,21 @@ async function runCreateStory(deps, params) {
6813
6894
  if (dispatchResult.parsed === null) {
6814
6895
  const details = dispatchResult.parseError ?? "No YAML block found in output";
6815
6896
  const rawSnippet = dispatchResult.output ? dispatchResult.output.slice(0, 1e3) : "(empty)";
6897
+ const authSignature = detectClaudeAuthFailure(dispatchResult.output);
6898
+ if (authSignature !== null) {
6899
+ logger$21.error({
6900
+ epicId,
6901
+ storyKey,
6902
+ authSignature,
6903
+ rawOutputSnippet: rawSnippet
6904
+ }, "Create-story dispatch died on authentication");
6905
+ return {
6906
+ result: "failed",
6907
+ error: "auth-failure",
6908
+ details: `Claude CLI authentication failure (matched: "${authSignature}"). Agent output: ${rawSnippet}`,
6909
+ tokenUsage
6910
+ };
6911
+ }
6816
6912
  logger$21.warn({
6817
6913
  epicId,
6818
6914
  storyKey,
@@ -7549,6 +7645,104 @@ async function commitDevStoryOutput(storyKey, storyTitle, filesModified, working
7549
7645
  };
7550
7646
  }
7551
7647
  /**
7648
+ * Commit the worktree's dirty state as a recovery checkpoint (H0.1, field
7649
+ * finding #17): on every failure path the story branch — not the worktree's
7650
+ * working tree — must be the durable copy of the agent's work. Before this,
7651
+ * a verification-failed or escalated story left its only copy as uncommitted
7652
+ * files, and any `git worktree remove --force` (operator cleanup, orphan
7653
+ * sweep) destroyed it permanently.
7654
+ *
7655
+ * Semantics differ from `commitDevStoryOutput` deliberately:
7656
+ * - Stages EVERYTHING dirty (`git add -A`; .gitignore still applies) — a
7657
+ * checkpoint must not guess which files matter.
7658
+ * - Bypasses hooks (`--no-verify`): this is a recovery snapshot, not a
7659
+ * deliverable. Deliverable `feat(story-…)` commits keep hooks; wip
7660
+ * checkpoints never merge on their own — `reconcile-from-disk` and the
7661
+ * operator decide what to do with them.
7662
+ * - Message prefix `wip(story-<key>):` so tooling can tell checkpoints from
7663
+ * deliverables (reconcile-from-disk greps both, prefers feat).
7664
+ *
7665
+ * Never throws; all failure modes return a structured status.
7666
+ */
7667
+ async function checkpointStoryWorktree(storyKey, reason, workingDir) {
7668
+ if (!existsSync(workingDir)) return { status: "no-changes" };
7669
+ try {
7670
+ execSync("git add -A", {
7671
+ cwd: workingDir,
7672
+ stdio: [
7673
+ "ignore",
7674
+ "pipe",
7675
+ "pipe"
7676
+ ],
7677
+ timeout: 3e4
7678
+ });
7679
+ } catch (err) {
7680
+ const stderr = err instanceof Error && "stderr" in err ? String(err.stderr ?? err.message) : err instanceof Error ? err.message : String(err);
7681
+ return {
7682
+ status: "failed",
7683
+ stderr: `git add -A failed: ${stderr}`
7684
+ };
7685
+ }
7686
+ const cachedCheck = spawn("git", [
7687
+ "diff",
7688
+ "--cached",
7689
+ "--quiet"
7690
+ ], {
7691
+ cwd: workingDir,
7692
+ stdio: [
7693
+ "ignore",
7694
+ "pipe",
7695
+ "pipe"
7696
+ ]
7697
+ });
7698
+ const cachedStatus = await new Promise((res) => {
7699
+ cachedCheck.on("close", (code) => res(code ?? 0));
7700
+ cachedCheck.on("error", () => res(0));
7701
+ });
7702
+ if (cachedStatus === 0) return { status: "no-changes" };
7703
+ const cleanReason = (reason.split("\n")[0] ?? "checkpoint").split("").filter((ch) => ch.charCodeAt(0) >= 32).join("").slice(0, 100);
7704
+ const message = `wip(story-${storyKey}): ${cleanReason}`;
7705
+ try {
7706
+ execSync(`git commit --no-verify -m ${JSON.stringify(message)}`, {
7707
+ cwd: workingDir,
7708
+ stdio: [
7709
+ "ignore",
7710
+ "pipe",
7711
+ "pipe"
7712
+ ],
7713
+ timeout: 6e4
7714
+ });
7715
+ } catch (err) {
7716
+ const stderr = err instanceof Error && "stderr" in err ? String(err.stderr ?? err.message) : err instanceof Error ? err.message : String(err);
7717
+ return {
7718
+ status: "failed",
7719
+ stderr: `git commit --no-verify failed: ${stderr}`
7720
+ };
7721
+ }
7722
+ let sha = "";
7723
+ try {
7724
+ sha = execSync("git rev-parse HEAD", {
7725
+ cwd: workingDir,
7726
+ encoding: "utf-8",
7727
+ stdio: [
7728
+ "ignore",
7729
+ "pipe",
7730
+ "pipe"
7731
+ ],
7732
+ timeout: 5e3
7733
+ }).trim();
7734
+ } catch {}
7735
+ logger$20.info({
7736
+ storyKey,
7737
+ sha,
7738
+ reason: cleanReason
7739
+ }, "checkpointStoryWorktree: committed recovery checkpoint");
7740
+ return {
7741
+ status: "committed",
7742
+ sha
7743
+ };
7744
+ }
7745
+ /**
7552
7746
  * Check whether the repo at `cwd` has at least one commit (HEAD resolves).
7553
7747
  * Returns false for fresh repos with no commits, avoiding `fatal: bad revision 'HEAD'`.
7554
7748
  * Synchronous (execSync) to keep it simple — this is a fast local check.
@@ -12562,6 +12756,48 @@ var CostGovernanceChecker = class {
12562
12756
  }
12563
12757
  };
12564
12758
 
12759
+ //#endregion
12760
+ //#region src/modules/implementation-orchestrator/dispatch-telemetry-aggregation.ts
12761
+ /**
12762
+ * Aggregates turn counts and token usage across an array of dispatch records.
12763
+ *
12764
+ * Returns an object with `total_turns` and/or `total_tokens` populated only
12765
+ * when at least one record carries the corresponding telemetry:
12766
+ * - If NO record has `turns` data → `total_turns` is absent.
12767
+ * - If NO record has `tokens` data → `total_tokens` is absent.
12768
+ * - If SOME records have turns/tokens and some don't, the available data is
12769
+ * summed; records without the field do not contribute zero.
12770
+ *
12771
+ * Absence MUST NOT be treated as zero by consumers — use `?? null` at call
12772
+ * sites per the `PerStoryState.total_turns` / `total_tokens` semantics.
12773
+ *
12774
+ * @param dispatchRecords - Array of per-dispatch records for one story.
12775
+ * Pass `_storyAgents.get(storyKey) ?? []` from the orchestrator. In the
12776
+ * current implementation those records don't carry token/turn data, so
12777
+ * the result is `{}` — this is the documented known gap.
12778
+ * @returns Partial telemetry aggregate: `{}` when no data, populated when present.
12779
+ */
12780
+ function aggregateStoryDispatchTelemetry(dispatchRecords) {
12781
+ if (dispatchRecords.length === 0) return {};
12782
+ let turnsSum;
12783
+ let tokensInput;
12784
+ let tokensOutput;
12785
+ for (const record of dispatchRecords) {
12786
+ if (record.turns !== void 0) turnsSum = (turnsSum ?? 0) + record.turns;
12787
+ if (record.tokens !== void 0) {
12788
+ tokensInput = (tokensInput ?? 0) + record.tokens.input;
12789
+ tokensOutput = (tokensOutput ?? 0) + record.tokens.output;
12790
+ }
12791
+ }
12792
+ const result = {};
12793
+ if (turnsSum !== void 0) result.total_turns = turnsSum;
12794
+ if (tokensInput !== void 0 && tokensOutput !== void 0) result.total_tokens = {
12795
+ input: tokensInput,
12796
+ output: tokensOutput
12797
+ };
12798
+ return result;
12799
+ }
12800
+
12565
12801
  //#endregion
12566
12802
  //#region src/modules/work-graph/epic-ingester.ts
12567
12803
  var EpicIngester = class {
@@ -13545,6 +13781,7 @@ function createImplementationOrchestrator(deps) {
13545
13781
  const _phaseEndMs = new Map();
13546
13782
  const _storyDispatches = new Map();
13547
13783
  const _storyAgents = new Map();
13784
+ const _storyWorktrees = new Map();
13548
13785
  const _storyRetryCount = new Map();
13549
13786
  let _completedDispatches = 0;
13550
13787
  let _maxConcurrentActual = 0;
@@ -13554,6 +13791,7 @@ function createImplementationOrchestrator(deps) {
13554
13791
  const _costChecker = new CostGovernanceChecker();
13555
13792
  let _costWarningEmitted = false;
13556
13793
  let _budgetExhausted = false;
13794
+ let _authFailureHalted = false;
13557
13795
  let _shutdownRequested = false;
13558
13796
  let _inFlightCount = 0;
13559
13797
  let _drainResolve = null;
@@ -13860,6 +14098,38 @@ function createImplementationOrchestrator(deps) {
13860
14098
  * diagnosis to the decision store (Story 22-3).
13861
14099
  */
13862
14100
  async function emitEscalation(payload) {
14101
+ const escalationWorktree = _storyWorktrees.get(payload.storyKey);
14102
+ if (escalationWorktree !== void 0) {
14103
+ const cp = await checkpointStoryWorktree(payload.storyKey, `escalation: ${payload.escalationReason ?? payload.lastVerdict}`, escalationWorktree);
14104
+ if (cp.status === "committed") {
14105
+ logger$27.info({
14106
+ storyKey: payload.storyKey,
14107
+ sha: cp.sha
14108
+ }, "escalation checkpoint: uncommitted worktree state preserved on story branch");
14109
+ if (runManifest !== null && cp.sha) runManifest.patchStoryState(payload.storyKey, { checkpoint_sha: cp.sha }).catch((err) => logger$27.warn({
14110
+ err,
14111
+ storyKey: payload.storyKey
14112
+ }, "patchStoryState(checkpoint_sha, escalation) failed — pipeline continues"));
14113
+ } else if (cp.status === "failed") logger$27.warn({
14114
+ storyKey: payload.storyKey,
14115
+ stderr: cp.stderr.slice(0, 500)
14116
+ }, "escalation checkpoint failed — worktree state remains uncommitted (inspect before any cleanup)");
14117
+ }
14118
+ if (escalationWorktree !== void 0 && projectRoot !== void 0) {
14119
+ const leakedFiles = detectWorkOutsideWorktree(escalationWorktree, projectRoot, checkGitDiffFiles);
14120
+ if (leakedFiles.length > 0) {
14121
+ const leakMsg = `PARENT-TREE LEAK: the MAIN checkout at ${projectRoot} carries ${String(leakedFiles.length)} uncommitted change(s) during this story's escalation (e.g. ${leakedFiles.slice(0, 10).join(", ")}). Story output may have landed OUTSIDE the worktree — inspect \`git -C ${projectRoot} status\` BEFORE any cleanup; reconcile-from-disk inspects the branch, not main, and will not see these files.`;
14122
+ payload.issues = [...payload.issues, leakMsg];
14123
+ eventBus.emit("orchestrator:story-warn", {
14124
+ storyKey: payload.storyKey,
14125
+ msg: leakMsg
14126
+ });
14127
+ logger$27.warn({
14128
+ storyKey: payload.storyKey,
14129
+ leakedFileCount: leakedFiles.length
14130
+ }, "escalation parent-tree leak detected");
14131
+ }
14132
+ }
13863
14133
  const diagnosis = generateEscalationDiagnosis(payload.issues, payload.reviewCycles, payload.lastVerdict);
13864
14134
  eventBus.emit("orchestrator:story-escalated", {
13865
14135
  ...payload,
@@ -14173,6 +14443,7 @@ function createImplementationOrchestrator(deps) {
14173
14443
  if (!noWorktree && _worktreeManager !== void 0 && projectRoot !== void 0) {
14174
14444
  const wt = await _worktreeManager.createWorktree(storyKey);
14175
14445
  effectiveProjectRoot = wt.worktreePath;
14446
+ _storyWorktrees.set(storyKey, wt.worktreePath);
14176
14447
  }
14177
14448
  let _capturedStoryTitle;
14178
14449
  await waitIfPaused();
@@ -14335,6 +14606,20 @@ function createImplementationOrchestrator(deps) {
14335
14606
  completedAt: new Date().toISOString()
14336
14607
  });
14337
14608
  await writeStoryMetricsBestEffort(storyKey, "failed", 0);
14609
+ const authSignature = createResult.error === "auth-failure" ? createResult.details ?? "auth-failure" : detectClaudeAuthFailure(errMsg);
14610
+ if (authSignature !== null) {
14611
+ const authDetail = createResult.details ?? errMsg;
14612
+ await emitEscalation({
14613
+ storyKey,
14614
+ lastVerdict: "auth-failure",
14615
+ reviewCycles: 0,
14616
+ issues: [authDetail, CLAUDE_AUTH_FAILURE_HINT],
14617
+ escalationReason: "auth-failure"
14618
+ });
14619
+ await triggerAuthFailureHalt(storyKey, authSignature.slice(0, 200));
14620
+ await persistState();
14621
+ return;
14622
+ }
14338
14623
  const codexHint = detectCodexSandboxBlock(errMsg);
14339
14624
  await emitEscalation({
14340
14625
  storyKey,
@@ -14902,6 +15187,10 @@ function createImplementationOrchestrator(deps) {
14902
15187
  ]
14903
15188
  }).trim();
14904
15189
  } catch {}
15190
+ if (baselineHeadSha !== void 0 && runManifest !== null) runManifest.patchStoryState(storyKey, { baseline_sha: baselineHeadSha }).catch((err) => logger$27.warn({
15191
+ err,
15192
+ storyKey
15193
+ }, "patchStoryState(baseline_sha) failed — pipeline continues"));
14905
15194
  try {
14906
15195
  let storyContentForAnalysis = "";
14907
15196
  try {
@@ -15282,6 +15571,19 @@ function createImplementationOrchestrator(deps) {
15282
15571
  completedAt: new Date().toISOString()
15283
15572
  });
15284
15573
  await writeStoryMetricsBestEffort(storyKey, "failed", 0);
15574
+ const devAuthSignature = detectClaudeAuthFailure(errMsg);
15575
+ if (devAuthSignature !== null) {
15576
+ await emitEscalation({
15577
+ storyKey,
15578
+ lastVerdict: "auth-failure",
15579
+ reviewCycles: 0,
15580
+ issues: [errMsg, CLAUDE_AUTH_FAILURE_HINT],
15581
+ escalationReason: "auth-failure"
15582
+ });
15583
+ await triggerAuthFailureHalt(storyKey, devAuthSignature);
15584
+ await persistState();
15585
+ return;
15586
+ }
15285
15587
  await emitEscalation({
15286
15588
  storyKey,
15287
15589
  lastVerdict: "dev-story-exception",
@@ -15360,6 +15662,49 @@ function createImplementationOrchestrator(deps) {
15360
15662
  }
15361
15663
  }
15362
15664
  }
15665
+ let _devOutputCommitHookFailure;
15666
+ const _commitFirstEligible = !noWorktree && _worktreeManager !== void 0 && effectiveProjectRoot !== void 0 && effectiveProjectRoot !== projectRoot;
15667
+ if (_commitFirstEligible && effectiveProjectRoot !== void 0) {
15668
+ const dirtyNow = await getGitChangedFiles(effectiveProjectRoot);
15669
+ if (dirtyNow.length > 0) if (devStoryWasSuccess) {
15670
+ const earlyCommit = await commitDevStoryOutput(storyKey, _capturedStoryTitle, dirtyNow, effectiveProjectRoot);
15671
+ if (earlyCommit.status === "committed") {
15672
+ logger$27.info({
15673
+ storyKey,
15674
+ sha: earlyCommit.sha,
15675
+ fileCount: earlyCommit.filesStaged.length
15676
+ }, "commit-first: dev-story output committed to story branch");
15677
+ if (runManifest !== null && earlyCommit.sha) runManifest.patchStoryState(storyKey, { commit_sha: earlyCommit.sha }).catch((err) => logger$27.warn({
15678
+ err,
15679
+ storyKey
15680
+ }, "patchStoryState(commit_sha, commit-first) failed — pipeline continues"));
15681
+ } else if (earlyCommit.status === "failed") {
15682
+ _devOutputCommitHookFailure = earlyCommit.stderr;
15683
+ const cp = await checkpointStoryWorktree(storyKey, "dev-story output (deliverable commit rejected by hooks)", effectiveProjectRoot);
15684
+ logger$27.warn({
15685
+ storyKey,
15686
+ checkpoint: cp.status,
15687
+ hookStderr: earlyCommit.stderr.slice(0, 500)
15688
+ }, "commit-first: hooks rejected feat commit — work preserved as wip checkpoint");
15689
+ if (cp.status === "committed" && runManifest !== null && cp.sha) runManifest.patchStoryState(storyKey, { checkpoint_sha: cp.sha }).catch((err) => logger$27.warn({
15690
+ err,
15691
+ storyKey
15692
+ }, "patchStoryState(checkpoint_sha) failed — pipeline continues"));
15693
+ }
15694
+ } else {
15695
+ const cp = await checkpointStoryWorktree(storyKey, "dev-story partial output", effectiveProjectRoot);
15696
+ if (cp.status === "committed") {
15697
+ logger$27.info({
15698
+ storyKey,
15699
+ sha: cp.sha
15700
+ }, "commit-first: partial dev-story output checkpointed");
15701
+ if (runManifest !== null && cp.sha) runManifest.patchStoryState(storyKey, { checkpoint_sha: cp.sha }).catch((err) => logger$27.warn({
15702
+ err,
15703
+ storyKey
15704
+ }, "patchStoryState(checkpoint_sha) failed — pipeline continues"));
15705
+ }
15706
+ }
15707
+ }
15363
15708
  endPhase(storyKey, "dev-story", devStoryModel);
15364
15709
  let _buildPassed = false;
15365
15710
  {
@@ -15871,6 +16216,19 @@ function createImplementationOrchestrator(deps) {
15871
16216
  }
15872
16217
  }
15873
16218
  if (!shouldFallThroughToComplete) {
16219
+ if (effectiveProjectRoot !== void 0 && effectiveProjectRoot !== projectRoot) {
16220
+ const cp = await checkpointStoryWorktree(storyKey, `verification-failed: ${verificationFailReason ?? "tier-a-fail"}`, effectiveProjectRoot);
16221
+ if (cp.status === "committed") {
16222
+ logger$27.info({
16223
+ storyKey,
16224
+ sha: cp.sha
16225
+ }, "verification-failed checkpoint: uncommitted worktree state preserved on story branch");
16226
+ if (runManifest !== null && cp.sha) runManifest.patchStoryState(storyKey, { checkpoint_sha: cp.sha }).catch((err) => logger$27.warn({
16227
+ err,
16228
+ storyKey
16229
+ }, "patchStoryState(checkpoint_sha, verification-failed) failed — pipeline continues"));
16230
+ }
16231
+ }
15874
16232
  updateStory(storyKey, {
15875
16233
  phase: "VERIFICATION_FAILED",
15876
16234
  completedAt: new Date().toISOString()
@@ -15915,6 +16273,242 @@ function createImplementationOrchestrator(deps) {
15915
16273
  await persistState();
15916
16274
  return "completed";
15917
16275
  }
16276
+ async function finalizeStory(completedReviewCycles) {
16277
+ if (!noWorktree && _worktreeManager !== void 0 && _orchestratorStartBranch !== void 0 && projectRoot !== void 0) {
16278
+ const branchName = `${BRANCH_PREFIX}${storyKey}`;
16279
+ if (effectiveProjectRoot !== void 0) {
16280
+ const dirty = await getGitChangedFiles(effectiveProjectRoot);
16281
+ const commitResult = await commitDevStoryOutput(storyKey, _capturedStoryTitle, dirty, effectiveProjectRoot);
16282
+ if (commitResult.status === "no-changes") {
16283
+ if (_devOutputCommitHookFailure !== void 0) {
16284
+ logger$27.error({
16285
+ storyKey,
16286
+ stderr: _devOutputCommitHookFailure
16287
+ }, "deliverable commit was rejected by hooks at dev-story end and was never resolved — escalating");
16288
+ updateStory(storyKey, {
16289
+ phase: "ESCALATED",
16290
+ error: "dev-story-commit-failed",
16291
+ completedAt: new Date().toISOString()
16292
+ });
16293
+ await emitEscalation({
16294
+ storyKey,
16295
+ lastVerdict: "dev-story-commit-failed",
16296
+ reviewCycles: completedReviewCycles,
16297
+ issues: [`substrate auto-commit was rejected by pre-commit hooks at dev-story end and the rejection was never resolved: ${_devOutputCommitHookFailure}`, "The work IS preserved as a wip(story-…) checkpoint on the story branch (hooks bypassed) — it was deliberately not merged."]
16298
+ });
16299
+ await persistState();
16300
+ return "terminal";
16301
+ }
16302
+ let branchAdvancedSinceBaseline = false;
16303
+ if (baselineHeadSha) try {
16304
+ const headNow = execSync("git rev-parse HEAD", {
16305
+ cwd: effectiveProjectRoot,
16306
+ encoding: "utf-8",
16307
+ stdio: [
16308
+ "ignore",
16309
+ "pipe",
16310
+ "pipe"
16311
+ ],
16312
+ timeout: 5e3
16313
+ }).trim();
16314
+ branchAdvancedSinceBaseline = headNow !== baselineHeadSha;
16315
+ } catch {}
16316
+ if (!branchAdvancedSinceBaseline) {
16317
+ logger$27.warn({
16318
+ storyKey,
16319
+ reason: commitResult.reason
16320
+ }, "dev-story produced no committable changes — escalating instead of running merge-to-main on an unchanged branch");
16321
+ updateStory(storyKey, {
16322
+ phase: "ESCALATED",
16323
+ error: `dev-story-no-commit: ${commitResult.reason}`,
16324
+ completedAt: new Date().toISOString()
16325
+ });
16326
+ await emitEscalation({
16327
+ storyKey,
16328
+ lastVerdict: "dev-story-no-commit",
16329
+ reviewCycles: completedReviewCycles,
16330
+ issues: [`dev-story phase reached SHIP_IT but produced no committable changes (reason: ${commitResult.reason})`]
16331
+ });
16332
+ await persistState();
16333
+ return "terminal";
16334
+ }
16335
+ logger$27.info({ storyKey }, "working tree clean at finalize but branch already advanced (commit-first) — proceeding to merge");
16336
+ }
16337
+ if (commitResult.status === "failed") {
16338
+ logger$27.error({
16339
+ storyKey,
16340
+ stderr: commitResult.stderr
16341
+ }, "substrate auto-commit failed — escalating story");
16342
+ updateStory(storyKey, {
16343
+ phase: "ESCALATED",
16344
+ error: "dev-story-commit-failed",
16345
+ completedAt: new Date().toISOString()
16346
+ });
16347
+ await emitEscalation({
16348
+ storyKey,
16349
+ lastVerdict: "dev-story-commit-failed",
16350
+ reviewCycles: completedReviewCycles,
16351
+ issues: [`substrate auto-commit failed: ${commitResult.stderr}`]
16352
+ });
16353
+ await persistState();
16354
+ return "terminal";
16355
+ }
16356
+ let finalizeCommitSha;
16357
+ if (commitResult.status === "committed") {
16358
+ finalizeCommitSha = commitResult.sha || void 0;
16359
+ logger$27.info({
16360
+ storyKey,
16361
+ sha: commitResult.sha,
16362
+ fileCount: commitResult.filesStaged.length
16363
+ }, "substrate auto-committed dev-story output before merge-to-main");
16364
+ } else try {
16365
+ finalizeCommitSha = execSync("git rev-parse HEAD", {
16366
+ cwd: effectiveProjectRoot,
16367
+ encoding: "utf-8",
16368
+ stdio: [
16369
+ "ignore",
16370
+ "pipe",
16371
+ "pipe"
16372
+ ],
16373
+ timeout: 5e3
16374
+ }).trim();
16375
+ } catch {}
16376
+ if (runManifest !== null && finalizeCommitSha) {
16377
+ const statePatch = { commit_sha: finalizeCommitSha };
16378
+ if (storyFilePath !== void 0) try {
16379
+ Object.assign(statePatch, captureReconstructionInput(storyFilePath, storyKey, runManifest.baseDir, runManifest.runId, effectiveProjectRoot));
16380
+ } catch (inputErr) {
16381
+ logger$27.warn({
16382
+ err: inputErr,
16383
+ storyKey
16384
+ }, "reconstruction phase-input capture failed — pipeline continues (commit_sha still recorded)");
16385
+ }
16386
+ try {
16387
+ const dispatchTelemetry = aggregateStoryDispatchTelemetry(_storyAgents.get(storyKey) ?? []);
16388
+ if (dispatchTelemetry.total_turns !== void 0) statePatch.total_turns = dispatchTelemetry.total_turns;
16389
+ if (dispatchTelemetry.total_tokens !== void 0) statePatch.total_tokens = dispatchTelemetry.total_tokens;
16390
+ } catch (telemetryErr) {
16391
+ logger$27.warn({
16392
+ err: telemetryErr,
16393
+ storyKey
16394
+ }, "aggregateStoryDispatchTelemetry failed — pipeline continues (commit_sha still recorded)");
16395
+ }
16396
+ runManifest.patchStoryState(storyKey, statePatch).catch((err) => logger$27.warn({
16397
+ err,
16398
+ storyKey
16399
+ }, "patchStoryState(commit_sha/phase-input/telemetry) failed — pipeline continues"));
16400
+ }
16401
+ }
16402
+ try {
16403
+ const branchSha = execSync(`git rev-parse ${branchName}`, {
16404
+ cwd: effectiveProjectRoot ?? projectRoot,
16405
+ encoding: "utf-8",
16406
+ stdio: [
16407
+ "ignore",
16408
+ "pipe",
16409
+ "pipe"
16410
+ ],
16411
+ timeout: 5e3
16412
+ }).trim();
16413
+ const startSha = execSync(`git rev-parse ${_orchestratorStartBranch}`, {
16414
+ cwd: projectRoot,
16415
+ encoding: "utf-8",
16416
+ stdio: [
16417
+ "ignore",
16418
+ "pipe",
16419
+ "pipe"
16420
+ ],
16421
+ timeout: 5e3
16422
+ }).trim();
16423
+ if (branchSha === startSha) {
16424
+ logger$27.warn({
16425
+ storyKey,
16426
+ branchSha,
16427
+ startSha,
16428
+ branchName
16429
+ }, "merge-to-main gate: branch did not advance from start commit — escalating instead of running a no-op merge");
16430
+ updateStory(storyKey, {
16431
+ phase: "ESCALATED",
16432
+ error: "dev-story-no-commit",
16433
+ completedAt: new Date().toISOString()
16434
+ });
16435
+ await emitEscalation({
16436
+ storyKey,
16437
+ lastVerdict: "dev-story-no-commit",
16438
+ reviewCycles: completedReviewCycles,
16439
+ issues: [`branch ${branchName} did not advance past start commit ${startSha} — merge-to-main would be a no-op`]
16440
+ });
16441
+ await persistState();
16442
+ return "terminal";
16443
+ }
16444
+ } catch (gateErr) {
16445
+ logger$27.warn({
16446
+ storyKey,
16447
+ err: gateErr instanceof Error ? gateErr.message : String(gateErr)
16448
+ }, "merge-to-main pre-flight verification failed — proceeding with merge phase");
16449
+ }
16450
+ logger$27.info({
16451
+ storyKey,
16452
+ branchName,
16453
+ startBranch: _orchestratorStartBranch
16454
+ }, "Invoking merge-to-main phase");
16455
+ let mergeResult;
16456
+ try {
16457
+ mergeResult = await enqueueMerge({
16458
+ storyKey,
16459
+ branchName,
16460
+ startBranch: _orchestratorStartBranch,
16461
+ worktreeManager: _worktreeManager,
16462
+ eventBus,
16463
+ projectRoot
16464
+ });
16465
+ } catch (mergeErr) {
16466
+ const errMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
16467
+ logger$27.error({
16468
+ storyKey,
16469
+ err: mergeErr
16470
+ }, "merge-to-main phase threw unexpectedly — escalating story");
16471
+ updateStory(storyKey, {
16472
+ phase: "ESCALATED",
16473
+ error: `merge-to-main-error: ${errMsg}`,
16474
+ completedAt: new Date().toISOString()
16475
+ });
16476
+ await emitEscalation({
16477
+ storyKey,
16478
+ lastVerdict: "merge-to-main-error",
16479
+ reviewCycles: completedReviewCycles,
16480
+ issues: [`merge-to-main phase threw: ${errMsg}`]
16481
+ });
16482
+ await persistState();
16483
+ return "terminal";
16484
+ }
16485
+ if (!mergeResult.success) {
16486
+ logger$27.warn({
16487
+ storyKey,
16488
+ branchName,
16489
+ conflictingFiles: mergeResult.conflictingFiles
16490
+ }, "merge-to-main conflict — escalating story with merge-conflict-detected");
16491
+ updateStory(storyKey, {
16492
+ phase: "ESCALATED",
16493
+ error: "merge-conflict-detected",
16494
+ completedAt: new Date().toISOString()
16495
+ });
16496
+ await emitEscalation({
16497
+ storyKey,
16498
+ lastVerdict: "merge-conflict-detected",
16499
+ reviewCycles: completedReviewCycles,
16500
+ issues: [`merge conflict in ${mergeResult.conflictingFiles?.length ?? 0} file(s): ${(mergeResult.conflictingFiles ?? []).join(", ")}`]
16501
+ });
16502
+ await persistState();
16503
+ return "terminal";
16504
+ }
16505
+ logger$27.info({
16506
+ storyKey,
16507
+ branchName
16508
+ }, "merge-to-main phase completed successfully");
16509
+ }
16510
+ return "finalized";
16511
+ }
15918
16512
  while (keepReviewing) {
15919
16513
  await waitIfPaused();
15920
16514
  if (_state !== "RUNNING") return;
@@ -16169,6 +16763,13 @@ function createImplementationOrchestrator(deps) {
16169
16763
  } catch {}
16170
16764
  updateStory(storyKey, { lastVerdict: verdict });
16171
16765
  await persistState();
16766
+ if (runManifest !== null) {
16767
+ const persistedVerdict = reviewResult.agentVerdict ?? verdict;
16768
+ runManifest.patchStoryState(storyKey, { verdict: persistedVerdict }).catch((err) => logger$27.warn({
16769
+ err,
16770
+ storyKey
16771
+ }, "patchStoryState(verdict) failed — pipeline continues"));
16772
+ }
16172
16773
  {
16173
16774
  const totalTokens = reviewResult.tokenUsage ? reviewResult.tokenUsage.input + reviewResult.tokenUsage.output : 0;
16174
16775
  const totalTokensK = totalTokens > 0 ? `${Math.round(totalTokens / 1e3)}K` : "0";
@@ -16309,178 +16910,8 @@ function createImplementationOrchestrator(deps) {
16309
16910
  error: expansionErr instanceof Error ? expansionErr.message : String(expansionErr)
16310
16911
  }, "Test expansion failed — story verdict unchanged");
16311
16912
  }
16312
- if (!noWorktree && _worktreeManager !== void 0 && _orchestratorStartBranch !== void 0 && projectRoot !== void 0) {
16313
- const branchName = `${BRANCH_PREFIX}${storyKey}`;
16314
- if (effectiveProjectRoot !== void 0) {
16315
- const dirty = await getGitChangedFiles(effectiveProjectRoot);
16316
- const commitResult = await commitDevStoryOutput(storyKey, _capturedStoryTitle, dirty, effectiveProjectRoot);
16317
- if (commitResult.status === "no-changes") {
16318
- logger$27.warn({
16319
- storyKey,
16320
- reason: commitResult.reason
16321
- }, "dev-story produced no committable changes — escalating instead of running merge-to-main on an unchanged branch");
16322
- updateStory(storyKey, {
16323
- phase: "ESCALATED",
16324
- error: `dev-story-no-commit: ${commitResult.reason}`,
16325
- completedAt: new Date().toISOString()
16326
- });
16327
- await emitEscalation({
16328
- storyKey,
16329
- lastVerdict: "dev-story-no-commit",
16330
- reviewCycles: completedReviewCycles,
16331
- issues: [`dev-story phase reached SHIP_IT but produced no committable changes (reason: ${commitResult.reason})`]
16332
- });
16333
- await persistState();
16334
- return;
16335
- }
16336
- if (commitResult.status === "failed") {
16337
- logger$27.error({
16338
- storyKey,
16339
- stderr: commitResult.stderr
16340
- }, "substrate auto-commit failed — escalating story");
16341
- updateStory(storyKey, {
16342
- phase: "ESCALATED",
16343
- error: "dev-story-commit-failed",
16344
- completedAt: new Date().toISOString()
16345
- });
16346
- await emitEscalation({
16347
- storyKey,
16348
- lastVerdict: "dev-story-commit-failed",
16349
- reviewCycles: completedReviewCycles,
16350
- issues: [`substrate auto-commit failed: ${commitResult.stderr}`]
16351
- });
16352
- await persistState();
16353
- return;
16354
- }
16355
- logger$27.info({
16356
- storyKey,
16357
- sha: commitResult.sha,
16358
- fileCount: commitResult.filesStaged.length
16359
- }, "substrate auto-committed dev-story output before merge-to-main");
16360
- if (runManifest !== null && commitResult.sha) {
16361
- const statePatch = { commit_sha: commitResult.sha };
16362
- if (storyFilePath !== void 0) try {
16363
- Object.assign(statePatch, captureReconstructionInput(storyFilePath, storyKey, runManifest.baseDir, runManifest.runId, effectiveProjectRoot));
16364
- } catch (inputErr) {
16365
- logger$27.warn({
16366
- err: inputErr,
16367
- storyKey
16368
- }, "reconstruction phase-input capture failed — pipeline continues (commit_sha still recorded)");
16369
- }
16370
- runManifest.patchStoryState(storyKey, statePatch).catch((err) => logger$27.warn({
16371
- err,
16372
- storyKey
16373
- }, "patchStoryState(commit_sha/phase-input) failed — pipeline continues"));
16374
- }
16375
- }
16376
- try {
16377
- const branchSha = execSync(`git rev-parse ${branchName}`, {
16378
- cwd: effectiveProjectRoot ?? projectRoot,
16379
- encoding: "utf-8",
16380
- stdio: [
16381
- "ignore",
16382
- "pipe",
16383
- "pipe"
16384
- ],
16385
- timeout: 5e3
16386
- }).trim();
16387
- const startSha = execSync(`git rev-parse ${_orchestratorStartBranch}`, {
16388
- cwd: projectRoot,
16389
- encoding: "utf-8",
16390
- stdio: [
16391
- "ignore",
16392
- "pipe",
16393
- "pipe"
16394
- ],
16395
- timeout: 5e3
16396
- }).trim();
16397
- if (branchSha === startSha) {
16398
- logger$27.warn({
16399
- storyKey,
16400
- branchSha,
16401
- startSha,
16402
- branchName
16403
- }, "merge-to-main gate: branch did not advance from start commit — escalating instead of running a no-op merge");
16404
- updateStory(storyKey, {
16405
- phase: "ESCALATED",
16406
- error: "dev-story-no-commit",
16407
- completedAt: new Date().toISOString()
16408
- });
16409
- await emitEscalation({
16410
- storyKey,
16411
- lastVerdict: "dev-story-no-commit",
16412
- reviewCycles: completedReviewCycles,
16413
- issues: [`branch ${branchName} did not advance past start commit ${startSha} — merge-to-main would be a no-op`]
16414
- });
16415
- await persistState();
16416
- return;
16417
- }
16418
- } catch (gateErr) {
16419
- logger$27.warn({
16420
- storyKey,
16421
- err: gateErr instanceof Error ? gateErr.message : String(gateErr)
16422
- }, "merge-to-main pre-flight verification failed — proceeding with merge phase");
16423
- }
16424
- logger$27.info({
16425
- storyKey,
16426
- branchName,
16427
- startBranch: _orchestratorStartBranch
16428
- }, "Invoking merge-to-main phase");
16429
- let mergeResult;
16430
- try {
16431
- mergeResult = await enqueueMerge({
16432
- storyKey,
16433
- branchName,
16434
- startBranch: _orchestratorStartBranch,
16435
- worktreeManager: _worktreeManager,
16436
- eventBus,
16437
- projectRoot
16438
- });
16439
- } catch (mergeErr) {
16440
- const errMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
16441
- logger$27.error({
16442
- storyKey,
16443
- err: mergeErr
16444
- }, "merge-to-main phase threw unexpectedly — escalating story");
16445
- updateStory(storyKey, {
16446
- phase: "ESCALATED",
16447
- error: `merge-to-main-error: ${errMsg}`,
16448
- completedAt: new Date().toISOString()
16449
- });
16450
- await emitEscalation({
16451
- storyKey,
16452
- lastVerdict: "merge-to-main-error",
16453
- reviewCycles: completedReviewCycles,
16454
- issues: [`merge-to-main phase threw: ${errMsg}`]
16455
- });
16456
- await persistState();
16457
- return;
16458
- }
16459
- if (!mergeResult.success) {
16460
- logger$27.warn({
16461
- storyKey,
16462
- branchName,
16463
- conflictingFiles: mergeResult.conflictingFiles
16464
- }, "merge-to-main conflict — escalating story with merge-conflict-detected");
16465
- updateStory(storyKey, {
16466
- phase: "ESCALATED",
16467
- error: "merge-conflict-detected",
16468
- completedAt: new Date().toISOString()
16469
- });
16470
- await emitEscalation({
16471
- storyKey,
16472
- lastVerdict: "merge-conflict-detected",
16473
- reviewCycles: completedReviewCycles,
16474
- issues: [`merge conflict in ${mergeResult.conflictingFiles?.length ?? 0} file(s): ${(mergeResult.conflictingFiles ?? []).join(", ")}`]
16475
- });
16476
- await persistState();
16477
- return;
16478
- }
16479
- logger$27.info({
16480
- storyKey,
16481
- branchName
16482
- }, "merge-to-main phase completed successfully");
16483
- }
16913
+ const finalizeOutcome = await finalizeStory(completedReviewCycles);
16914
+ if (finalizeOutcome === "terminal") return;
16484
16915
  keepReviewing = false;
16485
16916
  return;
16486
16917
  }
@@ -16607,6 +17038,7 @@ function createImplementationOrchestrator(deps) {
16607
17038
  }
16608
17039
  });
16609
17040
  if (outcome === "verification-failed") return;
17041
+ if (await finalizeStory(finalReviewCycles) === "terminal") return;
16610
17042
  keepReviewing = false;
16611
17043
  return;
16612
17044
  }
@@ -16816,6 +17248,7 @@ function createImplementationOrchestrator(deps) {
16816
17248
  }
16817
17249
  });
16818
17250
  if (outcome === "verification-failed") return;
17251
+ if (await finalizeStory(finalReviewCycles) === "terminal") return;
16819
17252
  keepReviewing = false;
16820
17253
  return;
16821
17254
  }
@@ -16899,6 +17332,64 @@ function createImplementationOrchestrator(deps) {
16899
17332
  * @param result - The ceiling check result
16900
17333
  * @param manifest - The current run manifest data
16901
17334
  */
17335
+ /**
17336
+ * H0.4 (field finding #10): halt the entire run when a dispatch dies on
17337
+ * authentication. Auth failures are environmental, not story-specific —
17338
+ * every subsequent dispatch fails identically, so continuing burns the
17339
+ * whole batch (the field run lost ~25 minutes and two runs to this).
17340
+ * Mirrors handleCeilingExceeded: routes through the Decision Router
17341
+ * (severity fatal → halts under every --halt-on policy), sweeps PENDING
17342
+ * stories to ESCALATED with a named reason, and reuses _budgetExhausted
17343
+ * to stop the dispatch loop.
17344
+ */
17345
+ async function triggerAuthFailureHalt(triggeredStoryKey, matchedSignature) {
17346
+ if (_authFailureHalted) return;
17347
+ _authFailureHalted = true;
17348
+ _budgetExhausted = true;
17349
+ const runId = config.pipelineRunId ?? "unknown";
17350
+ const reason = `agent authentication failure on story ${triggeredStoryKey} (matched: "${matchedSignature}") — all subsequent dispatches would fail identically`;
17351
+ let haltPolicy = "critical";
17352
+ if (runManifest !== null) try {
17353
+ const manifest = await runManifest.read();
17354
+ haltPolicy = manifest.cli_flags.halt_on ?? "critical";
17355
+ } catch {}
17356
+ const routeResult = routeDecision("auth-failure", haltPolicy);
17357
+ eventBus.emit("decision:halt", {
17358
+ runId,
17359
+ decisionType: "auth-failure",
17360
+ severity: routeResult.severity,
17361
+ reason
17362
+ });
17363
+ await runInteractivePrompt({
17364
+ runId,
17365
+ decisionType: "auth-failure",
17366
+ severity: routeResult.severity,
17367
+ summary: reason,
17368
+ defaultAction: routeResult.defaultAction,
17369
+ choices: ["abort-run"],
17370
+ onHaltSkipped: (payload) => {
17371
+ eventBus.emit("decision:halt-skipped-non-interactive", payload);
17372
+ }
17373
+ }).catch((err) => {
17374
+ logger$27.warn({ err }, "interactive prompt failed during auth-failure halt — halting anyway");
17375
+ });
17376
+ for (const [key, state] of _stories) if (state.phase === "PENDING" && key !== triggeredStoryKey) {
17377
+ updateStory(key, {
17378
+ phase: "ESCALATED",
17379
+ error: "auth-failure-halt",
17380
+ completedAt: new Date().toISOString()
17381
+ });
17382
+ if (runManifest !== null && runManifest !== void 0) runManifest.patchStoryState(key, {
17383
+ status: "escalated",
17384
+ escalation_reason: "auth-failure-halt"
17385
+ }).catch(() => {});
17386
+ }
17387
+ logger$27.error({
17388
+ runId,
17389
+ triggeredStoryKey,
17390
+ matchedSignature
17391
+ }, "RUN HALTED: agent authentication failure — fix credentials and re-run");
17392
+ }
16902
17393
  async function handleCeilingExceeded(triggeredStoryKey, remainingInGroup, result, manifest) {
16903
17394
  const haltPolicy = manifest.cli_flags.halt_on ?? "critical";
16904
17395
  const routeResult = routeDecision("cost-ceiling-exhausted", haltPolicy);
@@ -47442,7 +47933,7 @@ async function runFullPipeline(options) {
47442
47933
  */
47443
47934
  async function emitPreDispatchVersionAdvisory(currentVersion) {
47444
47935
  if (process.env["SUBSTRATE_NO_UPDATE_CHECK"] === "1") return;
47445
- const { createVersionManager } = await import("./version-manager-impl-C7ZhqLX3.js");
47936
+ const { createVersionManager } = await import("./version-manager-impl-HJMonRJi.js");
47446
47937
  const vm = createVersionManager();
47447
47938
  const result = await vm.checkForUpdates(true);
47448
47939
  const gap = classifyVersionGap(currentVersion, result.latestVersion);
@@ -47539,4 +48030,4 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
47539
48030
 
47540
48031
  //#endregion
47541
48032
  export { AdapterTelemetryPersistence, AppError, DoltRepoMapMetaRepository, DoltSymbolRepository, ERR_REPO_MAP_STORAGE_WRITE, EpicIngester, GLOBSTAR$1 as GLOBSTAR, GitClient, GrammarLoader, Minimatch$1 as Minimatch, Minipass, RepoMapInjector, RepoMapModule, RepoMapQueryEngine, RepoMapStorage, SymbolParser, createContextCompiler, createDispatcher, createEventEmitter, createGitWorktreeManager, createImplementationOrchestrator, createPackLoader, createPhaseOrchestrator, createStopAfterGate, createTelemetryAdvisor, escape$1 as escape, formatPhaseCompletionSummary, getFactoryRunSummaries, getScenarioResultsForRun, getTwinRunsForRun, listGraphRuns, normalizeGraphSummaryToStatus, registerExportCommand, registerFactoryCommand, registerRunCommand, registerScenariosCommand, resolveMaxReviewCycles, resolveProbeAuthorStateIntegrating, resolveStoryKeys, runAnalysisPhase, runPlanningPhase, runProbeAuthor, runRunAction, runSolutioningPhase, unescape$1 as unescape, validateStopAfterFromConflict, wireNdjsonEmitter };
47542
- //# sourceMappingURL=run-Dbwlb4Md.js.map
48033
+ //# sourceMappingURL=run-CRvusFIv.js.map