substrate-ai 0.20.147 → 0.20.149

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.
@@ -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-nQ_Txd5T.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-ByQadq3J.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, 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-DT378h1M.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-DlWwZpfO.js";
4
+ import { ADVISORY_NOTES, BRANCH_PREFIX, 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, createGitWorktreeManager, createPipelineRun, createRequirement, 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-C2CAZDep.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-knD2uWkz.js";
6
6
  import { WorkGraphRepository, detectCycles } from "./work-graph-repository-DZyJv5pV.js";
7
7
  import { deriveExitCode, routeDecision } from "./decision-router-BAPpON_C.js";
8
- import { runInteractivePrompt } from "./interactive-prompt-xJ3K-IUQ.js";
8
+ import { runInteractivePrompt } from "./interactive-prompt-CMBlj6UF.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";
@@ -15,14 +15,12 @@ import yaml from "js-yaml";
15
15
  import * as actualFS from "node:fs";
16
16
  import { accessSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, unwatchFile, watchFile, writeFileSync } from "node:fs";
17
17
  import { exec, execFile, execFileSync, execSync, spawn } from "node:child_process";
18
- import * as path$5 from "node:path";
19
- import * as path$4 from "node:path";
20
18
  import * as path$2 from "node:path";
21
19
  import path, { basename as basename$1, dirname as dirname$1, extname as extname$1, isAbsolute, join as join$1, posix, relative, resolve as resolve$1, win32 } from "node:path";
22
20
  import { tmpdir } from "node:os";
23
21
  import { createHash, randomUUID } from "node:crypto";
24
22
  import { z } from "zod";
25
- import { access as access$1, copyFile, lstat, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, readlink, realpath, rename, rm, stat as stat$1, unlink as unlink$1, writeFile as writeFile$1 } from "node:fs/promises";
23
+ import { access as access$1, lstat, mkdir as mkdir$1, readFile as readFile$1, readdir as readdir$1, readlink, realpath, rename, stat as stat$1, unlink as unlink$1, writeFile as writeFile$1 } from "node:fs/promises";
26
24
  import { existsSync as existsSync$1, lstatSync, mkdirSync as mkdirSync$1, readFileSync as readFileSync$1, readdir as readdir$2, readdirSync as readdirSync$1, readlinkSync, realpathSync as realpathSync$1, unlinkSync as unlinkSync$1, writeFileSync as writeFileSync$1 } from "fs";
27
25
  import { promisify } from "node:util";
28
26
  import { fileURLToPath } from "node:url";
@@ -34,780 +32,6 @@ import { channel, tracingChannel } from "node:diagnostics_channel";
34
32
  import Stream from "node:stream";
35
33
  import { StringDecoder } from "node:string_decoder";
36
34
 
37
- //#region packages/core/dist/git/git-utils.js
38
- const MIN_GIT_MAJOR = 2;
39
- const MIN_GIT_MINOR = 20;
40
- /**
41
- * Spawn a git subprocess with the given args.
42
- *
43
- * @param args - Arguments to pass to git (e.g., ['worktree', 'add', ...])
44
- * @param options - Optional spawn options (cwd, env)
45
- * @returns - Object with stdout, stderr, and exit code
46
- */
47
- function spawnGit(args, options) {
48
- return new Promise((resolve$6) => {
49
- const proc$1 = spawn("git", args, {
50
- cwd: options?.cwd,
51
- env: options?.env ?? process.env,
52
- stdio: [
53
- "ignore",
54
- "pipe",
55
- "pipe"
56
- ]
57
- });
58
- let stdout = "";
59
- let stderr = "";
60
- proc$1.stdout?.on("data", (chunk) => {
61
- stdout += chunk.toString();
62
- });
63
- proc$1.stderr?.on("data", (chunk) => {
64
- stderr += chunk.toString();
65
- });
66
- proc$1.on("close", (code) => {
67
- resolve$6({
68
- stdout: stdout.trim(),
69
- stderr: stderr.trim(),
70
- code: code ?? 1
71
- });
72
- });
73
- proc$1.on("error", (err) => {
74
- resolve$6({
75
- stdout: "",
76
- stderr: err.message,
77
- code: 1
78
- });
79
- });
80
- });
81
- }
82
- /**
83
- * Get the installed git version string.
84
- *
85
- * @returns Version string like "2.42.0"
86
- * @throws Error if git is not installed or version cannot be parsed
87
- */
88
- async function getGitVersion() {
89
- const result = await spawnGit(["--version"]);
90
- if (result.code !== 0) throw new Error(`git --version failed: ${result.stderr}`);
91
- const match$2 = /git version\s+(\d+\.\d+(?:\.\d+)?)/.exec(result.stdout);
92
- if (match$2 === null || match$2[1] === void 0) throw new Error(`Unable to parse git version from output: "${result.stdout}"`);
93
- return match$2[1];
94
- }
95
- /**
96
- * Parse a git version string into major/minor/patch components.
97
- *
98
- * @param versionString - Version string like "2.42.0" or "2.20"
99
- * @returns - Parsed version components
100
- */
101
- function parseGitVersion(versionString) {
102
- const parts = versionString.split(".").map(Number);
103
- return {
104
- major: parts[0] ?? 0,
105
- minor: parts[1] ?? 0,
106
- patch: parts[2] ?? 0
107
- };
108
- }
109
- /**
110
- * Check if the given git version string is >= 2.20.0.
111
- *
112
- * @param version - Version string like "2.42.0"
113
- * @returns - true if version >= 2.20.0
114
- */
115
- function isGitVersionSupported(version) {
116
- const { major, minor } = parseGitVersion(version);
117
- if (major > MIN_GIT_MAJOR) return true;
118
- if (major === MIN_GIT_MAJOR && minor >= MIN_GIT_MINOR) return true;
119
- return false;
120
- }
121
- /**
122
- * Verify that git is installed and version >= 2.20.
123
- *
124
- * @throws Error with clear message if git is not installed or too old
125
- */
126
- async function verifyGitVersion() {
127
- let version;
128
- try {
129
- version = await getGitVersion();
130
- } catch (err) {
131
- throw new Error(`git is not installed or could not be executed. Please install git 2.20 or newer. Details: ${String(err)}`);
132
- }
133
- if (!isGitVersionSupported(version)) {
134
- const { major, minor, patch } = parseGitVersion(version);
135
- throw new Error(`Git version ${major}.${minor}.${patch} is too old. Substrate requires git 2.20 or newer. Please upgrade git: https://git-scm.com/downloads`);
136
- }
137
- }
138
- /**
139
- * Decide whether a registered worktree from a prior dispatch can be reclaimed
140
- * for a fresh re-run. Safe only when there is nothing to lose: no uncommitted
141
- * changes AND no commits on the branch beyond the base branch. A negative
142
- * `commitsAhead` means the ahead-count could not be determined → not safe.
143
- *
144
- * Pure + exported for testing (the git I/O that produces the inputs lives in
145
- * createWorktree).
146
- */
147
- function decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch) {
148
- if (hasUncommittedChanges) return {
149
- safe: false,
150
- reason: "it has uncommitted changes that are NOT on the branch"
151
- };
152
- if (commitsAhead > 0) return {
153
- safe: false,
154
- reason: `its branch has ${String(commitsAhead)} commit(s) beyond ${baseBranch}`
155
- };
156
- if (commitsAhead < 0) return {
157
- safe: false,
158
- reason: "its state could not be verified as safe to discard"
159
- };
160
- return { safe: true };
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
- }
222
- async function createWorktree(projectRoot, taskId, branchName, baseBranch, copyFiles = []) {
223
- const worktreePath = path$5.join(projectRoot, ".substrate-worktrees", taskId);
224
- const worktreeExists = await access$1(worktreePath).then(() => true).catch((err) => {
225
- if (err.code === "ENOENT") return false;
226
- throw err;
227
- });
228
- if (worktreeExists) {
229
- const listResult = await spawnGit([
230
- "worktree",
231
- "list",
232
- "--porcelain"
233
- ], { cwd: projectRoot });
234
- const registeredPaths = listResult.stdout.split("\n").filter((line) => line.startsWith("worktree ")).map((line) => line.slice(9).trim());
235
- const isRegistered = registeredPaths.includes(worktreePath);
236
- if (!isRegistered) await rm(worktreePath, {
237
- recursive: true,
238
- force: true
239
- });
240
- else {
241
- const statusResult = await spawnGit(["status", "--porcelain"], { cwd: worktreePath });
242
- const hasUncommittedChanges = statusResult.code === 0 && statusResult.stdout.trim().length > 0;
243
- const aheadResult = await spawnGit([
244
- "rev-list",
245
- "--count",
246
- `${baseBranch}..${branchName}`
247
- ], { cwd: projectRoot });
248
- const commitsAhead = aheadResult.code === 0 ? Number.parseInt(aheadResult.stdout.trim(), 10) || 0 : -1;
249
- const decision = decideWorktreeReclaim(hasUncommittedChanges, commitsAhead, baseBranch);
250
- if (!decision.safe) throw new Error(`Worktree at ${worktreePath} is already registered (branch: ${branchName}) and ${decision.reason}.\nIt was preserved from a prior dispatch for inspection — inspect before removing.\n\nTo remove and re-dispatch:\n substrate worktrees --cleanup ${taskId}\n\nTo remove all substrate worktrees:\n substrate worktrees --cleanup`);
251
- await spawnGit([
252
- "worktree",
253
- "remove",
254
- "--force",
255
- worktreePath
256
- ], { cwd: projectRoot });
257
- await spawnGit([
258
- "branch",
259
- "-D",
260
- branchName
261
- ], { cwd: projectRoot });
262
- }
263
- }
264
- const addResult = await spawnGit([
265
- "worktree",
266
- "add",
267
- worktreePath,
268
- "-b",
269
- branchName,
270
- baseBranch
271
- ], { cwd: projectRoot });
272
- if (addResult.code !== 0) throw new Error(`git worktree add failed for task "${taskId}": ${addResult.stderr || addResult.stdout}`);
273
- await copyFilesToWorktree(projectRoot, worktreePath, copyFiles);
274
- return { worktreePath };
275
- }
276
- /**
277
- * Copy files from a source directory into a target worktree.
278
- *
279
- * Skips missing files silently (intentional — config like `[".env",
280
- * ".env.local"]` should not blow up if `.env.local` doesn't exist in the
281
- * parent checkout). Creates parent directories for nested paths.
282
- *
283
- * Exported for testability; not part of the public worktree API.
284
- */
285
- async function copyFilesToWorktree(sourceRoot, worktreePath, files) {
286
- if (files.length === 0) return;
287
- for (const relativePath of files) {
288
- if (path$5.isAbsolute(relativePath) || relativePath.split(path$5.sep).includes("..")) continue;
289
- const sourcePath = path$5.join(sourceRoot, relativePath);
290
- const destPath = path$5.join(worktreePath, relativePath);
291
- try {
292
- await access$1(sourcePath);
293
- } catch {
294
- continue;
295
- }
296
- const destDir = path$5.dirname(destPath);
297
- if (destDir !== worktreePath) await mkdir$1(destDir, { recursive: true });
298
- await copyFile(sourcePath, destPath);
299
- }
300
- }
301
- /**
302
- * Remove a git worktree by path.
303
- *
304
- * Uses `git worktree remove --force` to handle unclean worktrees.
305
- *
306
- * @param worktreePath - Absolute path to the worktree directory
307
- * @param projectRoot - Absolute path to the git repository root
308
- * @throws - Error if git command fails
309
- */
310
- async function removeWorktree(worktreePath, projectRoot) {
311
- const spawnOpts = {};
312
- if (projectRoot !== void 0) spawnOpts.cwd = projectRoot;
313
- const result = await spawnGit([
314
- "worktree",
315
- "remove",
316
- "--force",
317
- worktreePath
318
- ], spawnOpts);
319
- if (result.code !== 0) throw new Error(`git worktree remove failed for "${worktreePath}": ${result.stderr || result.stdout}`);
320
- }
321
- /**
322
- * Delete a git branch using `git branch -D`.
323
- *
324
- * @param branchName - Branch name to delete (e.g., "substrate/story-abc123")
325
- * @param projectRoot - Absolute path to the git repository root
326
- * @returns - true if branch was successfully deleted, false otherwise
327
- */
328
- async function removeBranch(branchName, projectRoot) {
329
- const spawnOpts = {};
330
- if (projectRoot !== void 0) spawnOpts.cwd = projectRoot;
331
- const result = await spawnGit([
332
- "branch",
333
- "-D",
334
- branchName
335
- ], spawnOpts);
336
- if (result.code !== 0) return false;
337
- return true;
338
- }
339
- /**
340
- * Scan the worktrees base directory and return paths of all found worktree directories.
341
- *
342
- * @param projectRoot - Absolute path to the git repository root
343
- * @param baseDirectory - Relative directory name for worktrees (default: '.substrate-worktrees')
344
- * @returns - Array of absolute worktree directory paths
345
- */
346
- async function getOrphanedWorktrees(projectRoot, baseDirectory = ".substrate-worktrees") {
347
- const worktreesDir = path$5.join(projectRoot, baseDirectory);
348
- try {
349
- await access$1(worktreesDir);
350
- } catch {
351
- return [];
352
- }
353
- let entries;
354
- try {
355
- entries = await readdir$1(worktreesDir, { withFileTypes: true });
356
- } catch {
357
- return [];
358
- }
359
- return entries.filter((entry) => entry.isDirectory()).map((entry) => path$5.join(worktreesDir, entry.name));
360
- }
361
- /**
362
- * Simulate a merge using git merge --no-commit --no-ff without committing.
363
- *
364
- * This runs in the target branch's working directory (the project root or
365
- * worktree path). The simulation must be aborted after checking conflicts
366
- * via abortMerge().
367
- *
368
- * @param branchName - The source branch to simulate merging
369
- * @param cwd - Working directory (must be on the target branch)
370
- * @returns - true if merge would be clean, false if there are conflicts
371
- */
372
- async function simulateMerge(branchName, cwd) {
373
- const result = await spawnGit([
374
- "merge",
375
- "--no-commit",
376
- "--no-ff",
377
- branchName
378
- ], { cwd });
379
- if (result.code === 0) return true;
380
- return false;
381
- }
382
- /**
383
- * Abort a merge in progress using git merge --abort.
384
- *
385
- * Should be called after simulateMerge() to clean up the merge state,
386
- * regardless of whether conflicts were found.
387
- *
388
- * @param cwd - Working directory (same as used for simulateMerge)
389
- */
390
- async function abortMerge(cwd) {
391
- await spawnGit(["merge", "--abort"], { cwd });
392
- }
393
- /**
394
- * Get a list of files with conflicts during a merge.
395
- *
396
- * Must be called while a merge is in progress (after simulateMerge() and
397
- * before abortMerge()).
398
- *
399
- * @param cwd - Working directory of the repository
400
- * @returns - Array of conflicting file paths
401
- */
402
- async function getConflictingFiles(cwd) {
403
- const result = await spawnGit([
404
- "diff",
405
- "--name-only",
406
- "--diff-filter=U"
407
- ], { cwd });
408
- if (result.code !== 0) return [];
409
- if (result.stdout.trim() === "") return [];
410
- return result.stdout.trim().split("\n").filter((f$1) => f$1.trim().length > 0);
411
- }
412
- /**
413
- * Perform an actual merge using git merge --no-ff.
414
- *
415
- * Should only be called after detectConflicts() confirms there are no conflicts.
416
- * Creates a merge commit even if fast-forward is possible (--no-ff ensures history).
417
- *
418
- * @param branchName - The source branch to merge
419
- * @param cwd - Working directory (must be on the target branch)
420
- * @returns - true if merge succeeded, false otherwise
421
- */
422
- async function performMerge(branchName, cwd) {
423
- const result = await spawnGit([
424
- "merge",
425
- "--no-ff",
426
- branchName
427
- ], { cwd });
428
- if (result.code !== 0) return false;
429
- return true;
430
- }
431
- /**
432
- * Get a list of files changed in the most recent merge.
433
- *
434
- * Uses git diff --name-only HEAD~1..HEAD to find files in the merge commit.
435
- * Falls back to empty array if commit history is insufficient.
436
- *
437
- * @param cwd - Working directory of the repository
438
- * @returns - Array of file paths that were merged
439
- */
440
- async function getMergedFiles(cwd) {
441
- const result = await spawnGit([
442
- "diff",
443
- "--name-only",
444
- "HEAD~1..HEAD"
445
- ], { cwd });
446
- if (result.code !== 0) {
447
- const altResult = await spawnGit([
448
- "show",
449
- "--name-only",
450
- "--format=",
451
- "HEAD"
452
- ], { cwd });
453
- if (altResult.code !== 0) return [];
454
- return altResult.stdout.trim().split("\n").filter((f$1) => f$1.trim().length > 0);
455
- }
456
- if (result.stdout.trim() === "") return [];
457
- return result.stdout.trim().split("\n").filter((f$1) => f$1.trim().length > 0);
458
- }
459
-
460
- //#endregion
461
- //#region packages/core/dist/git/git-worktree-manager-impl.js
462
- /**
463
- * Branch name prefix for substrate per-story branches.
464
- *
465
- * Exported as the canonical source of truth so consumers (orchestrator,
466
- * integration tests, tooling) can compose branch names without
467
- * independently encoding the prefix. v0.20.82 production bug:
468
- * `orchestrator-impl.ts:4290` hardcoded `substrate/story-${storyKey}`
469
- * while this module created `substrate/task-${taskId}` — the resulting
470
- * merge-to-main looked at a non-existent branch. Recurrence prevention:
471
- * all branch-name construction MUST import this constant.
472
- */
473
- const BRANCH_PREFIX = "substrate/story-";
474
- const DEFAULT_WORKTREE_BASE = ".substrate-worktrees";
475
- var GitWorktreeManagerImpl = class {
476
- _eventBus;
477
- _projectRoot;
478
- _baseDirectory;
479
- _db;
480
- _logger;
481
- /** v0.20.109: files to copy from project root into each new worktree (e.g. `.env`). */
482
- _copyFiles;
483
- /** Bound listener references for cleanup in shutdown() */
484
- _onTaskReady;
485
- _onTaskComplete;
486
- _onTaskFailed;
487
- constructor(eventBus, projectRoot, baseDirectory = DEFAULT_WORKTREE_BASE, db = null, logger$27, copyFiles = []) {
488
- this._eventBus = eventBus;
489
- this._projectRoot = projectRoot;
490
- this._baseDirectory = baseDirectory;
491
- this._db = db;
492
- this._logger = logger$27 ?? createStderrLogger("git-worktree-manager");
493
- this._copyFiles = copyFiles;
494
- this._onTaskReady = ({ taskId }) => {
495
- this._handleTaskReady(taskId).catch((err) => {
496
- this._logger.error({
497
- taskId,
498
- err
499
- }, "Unhandled error in _handleTaskReady");
500
- });
501
- };
502
- this._onTaskComplete = ({ taskId }) => {
503
- this._handleTaskDone(taskId);
504
- };
505
- this._onTaskFailed = ({ taskId }) => {
506
- this._handleTaskDone(taskId);
507
- };
508
- }
509
- async initialize() {
510
- this._logger.info({ projectRoot: this._projectRoot }, "GitWorktreeManager.initialize()");
511
- await this.verifyGitVersion();
512
- const cleaned = await this.cleanupAllWorktrees();
513
- if (cleaned > 0) this._logger.info({ cleaned }, "Recovered orphaned worktrees on startup");
514
- this._eventBus.on("task:ready", this._onTaskReady);
515
- this._eventBus.on("task:complete", this._onTaskComplete);
516
- this._eventBus.on("task:failed", this._onTaskFailed);
517
- this._logger.info("GitWorktreeManager initialized");
518
- }
519
- async shutdown() {
520
- this._logger.info("GitWorktreeManager.shutdown()");
521
- this._eventBus.off("task:ready", this._onTaskReady);
522
- this._eventBus.off("task:complete", this._onTaskComplete);
523
- this._eventBus.off("task:failed", this._onTaskFailed);
524
- await this.cleanupAllWorktrees();
525
- this._logger.info("GitWorktreeManager shutdown complete");
526
- }
527
- async _handleTaskReady(taskId) {
528
- this._logger.debug({ taskId }, "task:ready — creating worktree");
529
- try {
530
- await this.createWorktree(taskId);
531
- } catch (err) {
532
- this._logger.error({
533
- taskId,
534
- err
535
- }, "Failed to create worktree for task");
536
- }
537
- }
538
- async _handleTaskDone(taskId) {
539
- this._logger.debug({ taskId }, "task done — cleaning up worktree");
540
- try {
541
- await this.cleanupWorktree(taskId);
542
- } catch (err) {
543
- this._logger.warn({
544
- taskId,
545
- err
546
- }, "Failed to cleanup worktree for task");
547
- }
548
- }
549
- async createWorktree(taskId, baseBranch = "main") {
550
- if (!taskId || taskId.trim().length === 0) throw new Error("createWorktree: taskId must be a non-empty string");
551
- const branchName = BRANCH_PREFIX + taskId;
552
- const worktreePath = this.getWorktreePath(taskId);
553
- this._logger.debug({
554
- taskId,
555
- branchName,
556
- worktreePath,
557
- baseBranch,
558
- copyFiles: this._copyFiles
559
- }, "createWorktree");
560
- const copyFiles = this._copyFiles.includes(".substrate/project-profile.yaml") ? this._copyFiles : [...this._copyFiles, ".substrate/project-profile.yaml"];
561
- await createWorktree(this._projectRoot, taskId, branchName, baseBranch, copyFiles);
562
- const createdAt = new Date();
563
- this._eventBus.emit("worktree:created", {
564
- taskId,
565
- branchName,
566
- worktreePath,
567
- createdAt
568
- });
569
- const info = {
570
- taskId,
571
- branchName,
572
- worktreePath,
573
- createdAt
574
- };
575
- this._logger.info({
576
- taskId,
577
- branchName,
578
- worktreePath
579
- }, "Worktree created");
580
- return info;
581
- }
582
- async cleanupWorktree(taskId, opts) {
583
- const branchName = BRANCH_PREFIX + taskId;
584
- const worktreePath = this.getWorktreePath(taskId);
585
- this._logger.debug({
586
- taskId,
587
- branchName,
588
- worktreePath,
589
- force: opts?.force === true
590
- }, "cleanupWorktree");
591
- if (opts?.force !== true) {
592
- const decision = await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchName);
593
- const blockingReasons = opts?.keepBranch === true ? decision.reasons.filter((r) => r.includes("uncommitted")) : decision.reasons;
594
- if (blockingReasons.length > 0) throw new Error(`refusing to clean up worktree for "${taskId}": ${blockingReasons.join("; ")}.\nInspect first (git -C ${worktreePath} status; git log ${branchName} --oneline) or re-run with --force to discard.`);
595
- }
596
- let worktreeExists = false;
597
- try {
598
- await access$1(worktreePath);
599
- worktreeExists = true;
600
- } catch {
601
- this._logger.debug({
602
- taskId,
603
- worktreePath
604
- }, "cleanupWorktree: worktree does not exist, skipping removal");
605
- }
606
- if (worktreeExists) try {
607
- await removeWorktree(worktreePath, this._projectRoot);
608
- } catch (err) {
609
- this._logger.warn({
610
- taskId,
611
- worktreePath,
612
- err
613
- }, "removeWorktree failed during cleanup");
614
- }
615
- if (opts?.keepBranch !== true) try {
616
- await removeBranch(branchName, this._projectRoot);
617
- } catch (err) {
618
- this._logger.warn({
619
- taskId,
620
- branchName,
621
- err
622
- }, "removeBranch failed during cleanup");
623
- }
624
- this._eventBus.emit("worktree:removed", {
625
- taskId,
626
- branchName
627
- });
628
- this._logger.info({
629
- taskId,
630
- branchName
631
- }, "Worktree cleaned up");
632
- }
633
- async cleanupAllWorktrees(opts) {
634
- this._logger.debug({
635
- projectRoot: this._projectRoot,
636
- force: opts?.force === true
637
- }, "cleanupAllWorktrees");
638
- const orphanedPaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
639
- let cleaned = 0;
640
- for (const worktreePath of orphanedPaths) {
641
- const taskId = path$4.basename(worktreePath);
642
- const branchGuardName = BRANCH_PREFIX + taskId;
643
- const decision = opts?.force === true ? {
644
- safe: true,
645
- reasons: []
646
- } : await inspectWorktreeRemovalSafety(worktreePath, this._projectRoot, branchGuardName);
647
- if (!decision.safe) {
648
- this._logger.warn({
649
- taskId,
650
- worktreePath,
651
- reasons: decision.reasons
652
- }, "cleanupAllWorktrees: preserving worktree — removal would destroy work (use `substrate worktrees cleanup --force` to discard)");
653
- continue;
654
- }
655
- let worktreeRemoved = false;
656
- try {
657
- await removeWorktree(worktreePath, this._projectRoot);
658
- worktreeRemoved = true;
659
- this._logger.debug({
660
- taskId,
661
- worktreePath
662
- }, "cleanupAllWorktrees: removed orphaned worktree");
663
- } catch (err) {
664
- this._logger.warn({
665
- taskId,
666
- worktreePath,
667
- err
668
- }, "cleanupAllWorktrees: failed to remove worktree");
669
- }
670
- const branchName = BRANCH_PREFIX + taskId;
671
- try {
672
- const branchRemoved = await removeBranch(branchName, this._projectRoot);
673
- if (branchRemoved) this._logger.debug({
674
- taskId,
675
- branchName
676
- }, "cleanupAllWorktrees: removed orphaned branch");
677
- } catch (err) {
678
- this._logger.warn({
679
- taskId,
680
- branchName,
681
- err
682
- }, "cleanupAllWorktrees: failed to remove branch");
683
- }
684
- if (worktreeRemoved) cleaned++;
685
- }
686
- if (cleaned > 0) this._logger.info({ cleaned }, "cleanupAllWorktrees: recovered orphaned worktrees");
687
- return cleaned;
688
- }
689
- async detectConflicts(taskId, targetBranch = "main") {
690
- if (!taskId || taskId.trim().length === 0) throw new Error("detectConflicts: taskId must be a non-empty string");
691
- const branchName = BRANCH_PREFIX + taskId;
692
- const worktreePath = this.getWorktreePath(taskId);
693
- this._logger.debug({
694
- taskId,
695
- branchName,
696
- targetBranch
697
- }, "detectConflicts");
698
- try {
699
- await access$1(worktreePath);
700
- } catch {
701
- throw new Error(`detectConflicts: Worktree for task "${taskId}" not found at "${worktreePath}". The worktree may have already been cleaned up.`);
702
- }
703
- const mergeClean = await simulateMerge(branchName, this._projectRoot);
704
- let conflictingFiles = [];
705
- try {
706
- if (!mergeClean) conflictingFiles = await getConflictingFiles(this._projectRoot);
707
- } finally {
708
- await abortMerge(this._projectRoot);
709
- }
710
- const report = {
711
- hasConflicts: !mergeClean || conflictingFiles.length > 0,
712
- conflictingFiles,
713
- taskId,
714
- targetBranch
715
- };
716
- if (report.hasConflicts) this._eventBus.emit("worktree:conflict", {
717
- taskId,
718
- branch: branchName,
719
- conflictingFiles: report.conflictingFiles
720
- });
721
- this._logger.info({
722
- taskId,
723
- hasConflicts: report.hasConflicts,
724
- conflictCount: conflictingFiles.length
725
- }, "Conflict detection complete");
726
- return report;
727
- }
728
- async mergeWorktree(taskId, targetBranch = "main") {
729
- if (!taskId || taskId.trim().length === 0) throw new Error("mergeWorktree: taskId must be a non-empty string");
730
- const branchName = BRANCH_PREFIX + taskId;
731
- this._logger.debug({
732
- taskId,
733
- branchName,
734
- targetBranch
735
- }, "mergeWorktree");
736
- const conflictReport = await this.detectConflicts(taskId, targetBranch);
737
- if (conflictReport.hasConflicts) {
738
- this._logger.info({
739
- taskId,
740
- conflictCount: conflictReport.conflictingFiles.length
741
- }, "Merge skipped due to conflicts");
742
- return {
743
- success: false,
744
- mergedFiles: [],
745
- conflicts: conflictReport
746
- };
747
- }
748
- const mergeSuccess = await performMerge(branchName, this._projectRoot);
749
- if (!mergeSuccess) throw new Error(`mergeWorktree: git merge --no-ff failed for task "${taskId}" branch "${branchName}"`);
750
- const mergedFiles = await getMergedFiles(this._projectRoot);
751
- this._eventBus.emit("worktree:merged", {
752
- taskId,
753
- branch: branchName,
754
- mergedFiles
755
- });
756
- const result = {
757
- success: true,
758
- mergedFiles
759
- };
760
- this._logger.info({
761
- taskId,
762
- branchName,
763
- mergedFileCount: mergedFiles.length
764
- }, "Worktree merged successfully");
765
- return result;
766
- }
767
- async listWorktrees() {
768
- this._logger.debug({
769
- projectRoot: this._projectRoot,
770
- baseDirectory: this._baseDirectory
771
- }, "listWorktrees");
772
- const worktreePaths = await getOrphanedWorktrees(this._projectRoot, this._baseDirectory);
773
- const results = [];
774
- for (const worktreePath of worktreePaths) {
775
- const taskId = path$4.basename(worktreePath);
776
- const branchName = BRANCH_PREFIX + taskId;
777
- let createdAt;
778
- try {
779
- const { stat: stat$2 } = await import("node:fs/promises");
780
- const stats = await stat$2(worktreePath);
781
- createdAt = stats.birthtime ?? stats.ctime;
782
- } catch {
783
- createdAt = new Date();
784
- }
785
- results.push({
786
- taskId,
787
- branchName,
788
- worktreePath,
789
- createdAt
790
- });
791
- }
792
- this._logger.debug({ count: results.length }, "listWorktrees: found worktrees");
793
- return results;
794
- }
795
- getWorktreePath(taskId) {
796
- return path$4.join(this._projectRoot, this._baseDirectory, taskId);
797
- }
798
- async verifyGitVersion() {
799
- try {
800
- await verifyGitVersion();
801
- } catch (err) {
802
- throw new Error(`GitWorktreeManager: git version check failed: ${String(err)}`);
803
- }
804
- }
805
- };
806
- function createGitWorktreeManager(options) {
807
- return new GitWorktreeManagerImpl(options.eventBus, options.projectRoot, options.baseDirectory, options.db ?? null, options.logger, options.copyFiles);
808
- }
809
-
810
- //#endregion
811
35
  //#region src/modules/methodology-pack/schemas.ts
812
36
  /**
813
37
  * A reference to a context value to inject into a step prompt.
@@ -2961,7 +2185,7 @@ These on-disk files back the new autonomy commands. External monitors (dashboard
2961
2185
  - \`.substrate/current-run-id\` — plain text file containing the latest run ID; consulted by the canonical run-discovery chain.
2962
2186
  - \`.substrate/notifications/<run-id>-<timestamp>.json\` — operator halt notifications written by the Recovery Engine when \`--halt-on\` triggers; deleted by \`substrate report\` after read.
2963
2187
  - \`pending_proposals[]\` field in the run manifest — Recovery Engine Tier B re-scope proposals collected here for next-morning operator review. Back-pressure pauses dispatching at \`>= 2\` proposals (work-graph-aware) or \`>= 5\` (safety valve).
2964
- - \`.substrate-worktrees/story-<key>/\` — per-story git worktree directories created during dispatch on branch \`substrate/story-<key>\`. **Substrate auto-commits** the dispatched agent's output to the branch after SHIP_IT with a \`feat(story-N-M): <title>\` message (v0.20.86+ — substrate does not rely on the agent committing). Pre-commit hooks fire on the substrate commit. The branch is then merged to the base branch and the worktree is removed. On verification failure or auto-commit failure, the worktree and branch are preserved for \`substrate reconcile-from-disk\` inspection. Use \`--no-worktree\` (or \`SUBSTRATE_NO_WORKTREE=1\`) to disable per-story worktrees entirely; dispatch then runs against the parent project tree.
2188
+ - Per-story git worktrees are created during dispatch on branch \`substrate/story-<key>\`. H4.2: the default base is EXTERNAL — \`~/.substrate/worktrees/<projectname>-<hash8>/<key>/\` — so agents have no parent repo above their worktree; set \`worktree.base: in-repo\` in .substrate/config.yaml to restore the legacy \`.substrate-worktrees/<key>/\` location (tooling that globbed the old path should use \`substrate worktrees list\`). **Substrate auto-commits** the dispatched agent's output to the branch after SHIP_IT with a \`feat(story-N-M): <title>\` message (v0.20.86+ — substrate does not rely on the agent committing). Pre-commit hooks fire on the substrate commit. The branch is then merged to the base branch and the worktree is removed. On verification failure or auto-commit failure, the worktree and branch are preserved for \`substrate reconcile-from-disk\` inspection. Use \`--no-worktree\` (or \`SUBSTRATE_NO_WORKTREE=1\`) to disable per-story worktrees entirely; dispatch then runs against the parent project tree.
2965
2189
 
2966
2190
  ## Environment Variables
2967
2191
 
@@ -3574,8 +2798,8 @@ var GrammarLoader = class {
3574
2798
  throw err;
3575
2799
  }
3576
2800
  }
3577
- _loadModule(path$6) {
3578
- return __require(path$6);
2801
+ _loadModule(path$4) {
2802
+ return __require(path$4);
3579
2803
  }
3580
2804
  };
3581
2805
 
@@ -7192,10 +6416,10 @@ function computeStoryFileFidelity(storyFileContent, namedPaths) {
7192
6416
  };
7193
6417
  const missing = [];
7194
6418
  const present = [];
7195
- for (const path$6 of namedPaths) {
7196
- const basename$2 = path$6.includes("/") ? path$6.slice(path$6.lastIndexOf("/") + 1) : path$6;
7197
- if (storyFileContent.includes(path$6) || storyFileContent.includes(basename$2)) present.push(path$6);
7198
- else missing.push(path$6);
6419
+ for (const path$4 of namedPaths) {
6420
+ const basename$2 = path$4.includes("/") ? path$4.slice(path$4.lastIndexOf("/") + 1) : path$4;
6421
+ if (storyFileContent.includes(path$4) || storyFileContent.includes(basename$2)) present.push(path$4);
6422
+ else missing.push(path$4);
7199
6423
  }
7200
6424
  return {
7201
6425
  missing,
@@ -8901,7 +8125,7 @@ function extractReferencedFiles(storyContent) {
8901
8125
  const matches = storyContent.match(filePathRegex) ?? [];
8902
8126
  const freq = new Map();
8903
8127
  for (const m of matches) freq.set(m, (freq.get(m) ?? 0) + 1);
8904
- const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([path$6]) => path$6);
8128
+ const sorted = [...freq.entries()].sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).map(([path$4]) => path$4);
8905
8129
  return sorted.filter((p) => !p.includes(".test."));
8906
8130
  }
8907
8131
  function extractFilesInScope(storyContent) {
@@ -9606,11 +8830,26 @@ const logger$16 = createLogger("compiled-workflows:merge-to-main");
9606
8830
  */
9607
8831
  async function runMergeToMain(params) {
9608
8832
  const { storyKey, branchName, startBranch, worktreeManager, eventBus, projectRoot } = params;
8833
+ const mergeStrategy = params.mergeStrategy ?? "ff-only";
9609
8834
  logger$16.info({
9610
8835
  storyKey,
9611
8836
  branchName,
9612
- startBranch
8837
+ startBranch,
8838
+ mergeStrategy
9613
8839
  }, "Starting merge-to-main phase");
8840
+ const dirtiedFiles = listParentDirtyIntersection(branchName, startBranch, projectRoot);
8841
+ if (dirtiedFiles.length > 0) {
8842
+ logger$16.warn({
8843
+ storyKey,
8844
+ branchName,
8845
+ dirtiedFiles
8846
+ }, "parent working tree has uncommitted changes to files in the story diff — refusing to merge");
8847
+ return {
8848
+ success: false,
8849
+ reason: "parent-tree-dirtied-by-run",
8850
+ dirtiedFiles
8851
+ };
8852
+ }
9614
8853
  const ffSuccess = tryFfMerge(branchName, projectRoot);
9615
8854
  if (ffSuccess) {
9616
8855
  logger$16.info({
@@ -9620,6 +8859,17 @@ async function runMergeToMain(params) {
9620
8859
  await cleanupAfterSuccessfulMerge(storyKey, branchName, worktreeManager, projectRoot);
9621
8860
  return { success: true };
9622
8861
  }
8862
+ if (mergeStrategy === "ff-only") {
8863
+ logger$16.warn({
8864
+ storyKey,
8865
+ branchName,
8866
+ startBranch
8867
+ }, "fast-forward not possible (base moved since the story branched) and merge_strategy is ff-only — refusing 3-way merge. Set finalization.merge_strategy: three-way to allow it (required for concurrent multi-story runs), or integrate the branch manually.");
8868
+ return {
8869
+ success: false,
8870
+ reason: "ff-only-merge-not-possible"
8871
+ };
8872
+ }
9623
8873
  logger$16.info({
9624
8874
  storyKey,
9625
8875
  branchName
@@ -9651,6 +8901,47 @@ async function runMergeToMain(params) {
9651
8901
  };
9652
8902
  }
9653
8903
  /**
8904
+ * H3.3 (AC1): files that are both dirty in the parent working tree and part of
8905
+ * the story branch's diff against the start branch. Best-effort: if either
8906
+ * git query fails, returns [] (the merge itself will surface genuine issues).
8907
+ */
8908
+ function listParentDirtyIntersection(branchName, startBranch, projectRoot) {
8909
+ try {
8910
+ const statusOut = execFileSync("git", ["status", "--porcelain"], {
8911
+ cwd: projectRoot,
8912
+ encoding: "utf-8",
8913
+ stdio: [
8914
+ "ignore",
8915
+ "pipe",
8916
+ "pipe"
8917
+ ]
8918
+ });
8919
+ const dirty = new Set(statusOut.split("\n").filter((line) => line.trim().length > 0).map((line) => {
8920
+ const body = line.slice(3).trim();
8921
+ const arrow = body.indexOf(" -> ");
8922
+ return arrow === -1 ? body : body.slice(arrow + 4);
8923
+ }));
8924
+ if (dirty.size === 0) return [];
8925
+ const diffOut = execFileSync("git", [
8926
+ "diff",
8927
+ "--name-only",
8928
+ `${startBranch}...${branchName}`
8929
+ ], {
8930
+ cwd: projectRoot,
8931
+ encoding: "utf-8",
8932
+ stdio: [
8933
+ "ignore",
8934
+ "pipe",
8935
+ "pipe"
8936
+ ]
8937
+ });
8938
+ return diffOut.split("\n").filter((f$1) => f$1.trim().length > 0).filter((f$1) => dirty.has(f$1));
8939
+ } catch (err) {
8940
+ logger$16.warn({ err }, "parent-clean precondition check failed (best-effort) — proceeding to merge");
8941
+ return [];
8942
+ }
8943
+ }
8944
+ /**
9654
8945
  * Attempt a fast-forward merge of `branchName` into the current branch.
9655
8946
  *
9656
8947
  * @param branchName - Branch to merge
@@ -16746,6 +16037,63 @@ function createImplementationOrchestrator(deps) {
16746
16037
  }, "merge-to-main pre-flight verification failed — proceeding with merge phase");
16747
16038
  }
16748
16039
  const finalizationMode = config.finalizationMode ?? "merge";
16040
+ const epicGateCommand = config.epicGateCommand;
16041
+ if (epicGateCommand !== void 0 && epicGateCommand.trim() !== "" && finalizationMode !== "branch") {
16042
+ const epicOf = (key) => key.includes("-") ? key.slice(0, key.lastIndexOf("-")) : key;
16043
+ const epicId = epicOf(storyKey);
16044
+ const TERMINAL_PHASES = ["COMPLETE", "ESCALATED"];
16045
+ const isLastOfEpic = [..._stories.entries()].filter(([key]) => key !== storyKey && epicOf(key) === epicId).every(([, st]) => TERMINAL_PHASES.includes(st.phase));
16046
+ if (isLastOfEpic) {
16047
+ logger$27.info({
16048
+ storyKey,
16049
+ epicId,
16050
+ epicGateCommand
16051
+ }, "H3.4: last story of epic — running epic gate command");
16052
+ let gateOutput = "";
16053
+ let gatePassed = false;
16054
+ try {
16055
+ gateOutput = execSync(epicGateCommand, {
16056
+ cwd: projectRoot,
16057
+ encoding: "utf-8",
16058
+ stdio: [
16059
+ "ignore",
16060
+ "pipe",
16061
+ "pipe"
16062
+ ],
16063
+ timeout: 6e5,
16064
+ maxBuffer: 10 * 1024 * 1024
16065
+ });
16066
+ gatePassed = true;
16067
+ } catch (gateCmdErr) {
16068
+ const e = gateCmdErr;
16069
+ gateOutput = `${e.stdout ?? ""}${e.stderr ?? ""}`.trim() || (e.message ?? String(gateCmdErr));
16070
+ }
16071
+ if (!gatePassed) {
16072
+ logger$27.error({
16073
+ storyKey,
16074
+ epicId,
16075
+ output: gateOutput.slice(0, 500)
16076
+ }, "H3.4: epic gate command failed — halting finalization; branch preserved");
16077
+ updateStory(storyKey, {
16078
+ phase: "ESCALATED",
16079
+ error: "epic-gate-failed",
16080
+ completedAt: new Date().toISOString()
16081
+ });
16082
+ await emitEscalation({
16083
+ storyKey,
16084
+ lastVerdict: "epic-gate-failed",
16085
+ reviewCycles: completedReviewCycles,
16086
+ issues: [`epic gate command failed for epic ${epicId} (last story ${storyKey}): \`${epicGateCommand}\`\noutput (truncated):\n${gateOutput.slice(0, 4e3)}`]
16087
+ });
16088
+ await persistState();
16089
+ return "terminal";
16090
+ }
16091
+ logger$27.info({
16092
+ storyKey,
16093
+ epicId
16094
+ }, "H3.4: epic gate passed");
16095
+ }
16096
+ }
16749
16097
  if (finalizationMode === "branch" || finalizationMode === "pr") {
16750
16098
  let prUrl;
16751
16099
  if (finalizationMode === "pr") try {
@@ -16822,7 +16170,8 @@ function createImplementationOrchestrator(deps) {
16822
16170
  startBranch: _orchestratorStartBranch,
16823
16171
  worktreeManager: _worktreeManager,
16824
16172
  eventBus,
16825
- projectRoot
16173
+ projectRoot,
16174
+ mergeStrategy: config.mergeStrategy ?? "ff-only"
16826
16175
  });
16827
16176
  } catch (mergeErr) {
16828
16177
  const errMsg = mergeErr instanceof Error ? mergeErr.message : String(mergeErr);
@@ -16845,21 +16194,28 @@ function createImplementationOrchestrator(deps) {
16845
16194
  return "terminal";
16846
16195
  }
16847
16196
  if (!mergeResult.success) {
16197
+ const failReason = mergeResult.reason ?? "merge-conflict-detected";
16198
+ const issues = [];
16199
+ if (failReason === "parent-tree-dirtied-by-run") issues.push(`parent working tree has uncommitted changes to ${mergeResult.dirtiedFiles?.length ?? 0} file(s) the story also modified: ${(mergeResult.dirtiedFiles ?? []).join(", ")} — merging would entangle unreviewed parent edits with verified story content. Commit or stash the parent changes, then merge ${branchName} manually.`);
16200
+ else if (failReason === "ff-only-merge-not-possible") issues.push(`${_orchestratorStartBranch ?? "the start branch"} moved since ${branchName} was created and merge_strategy is ff-only — substrate will not synthesize a merge commit. Set finalization.merge_strategy: three-way (required for concurrent multi-story runs) or integrate the branch manually.`);
16201
+ else issues.push(`merge conflict in ${mergeResult.conflictingFiles?.length ?? 0} file(s): ${(mergeResult.conflictingFiles ?? []).join(", ")}`);
16848
16202
  logger$27.warn({
16849
16203
  storyKey,
16850
16204
  branchName,
16851
- conflictingFiles: mergeResult.conflictingFiles
16852
- }, "merge-to-main conflict — escalating story with merge-conflict-detected");
16205
+ reason: failReason,
16206
+ conflictingFiles: mergeResult.conflictingFiles,
16207
+ dirtiedFiles: mergeResult.dirtiedFiles
16208
+ }, `merge-to-main failed — escalating story with ${failReason}`);
16853
16209
  updateStory(storyKey, {
16854
16210
  phase: "ESCALATED",
16855
- error: "merge-conflict-detected",
16211
+ error: failReason,
16856
16212
  completedAt: new Date().toISOString()
16857
16213
  });
16858
16214
  await emitEscalation({
16859
16215
  storyKey,
16860
- lastVerdict: "merge-conflict-detected",
16216
+ lastVerdict: failReason,
16861
16217
  reviewCycles: completedReviewCycles,
16862
- issues: [`merge conflict in ${mergeResult.conflictingFiles?.length ?? 0} file(s): ${(mergeResult.conflictingFiles ?? []).join(", ")}`]
16218
+ issues
16863
16219
  });
16864
16220
  await persistState();
16865
16221
  return "terminal";
@@ -18116,7 +17472,13 @@ function createImplementationOrchestrator(deps) {
18116
17472
  }).trim();
18117
17473
  logger$27.info({ orchestratorStartBranch: _orchestratorStartBranch }, "Captured orchestrator start branch for merge-to-main");
18118
17474
  } catch (branchErr) {
18119
- logger$27.warn({ err: branchErr }, "Failed to capture orchestrator start branch — merge-to-main will skip worktree integration");
17475
+ if (config.noWorktree !== true) {
17476
+ logger$27.error({ err: branchErr }, "FATAL: failed to capture the orchestrator start branch (git rev-parse --abbrev-ref HEAD). Worktree finalization cannot work without it. Fix git in the project root or re-run with --no-worktree.");
17477
+ _state = "FAILED";
17478
+ _completedAt = new Date().toISOString();
17479
+ return getStatus();
17480
+ }
17481
+ logger$27.warn({ err: branchErr }, "Failed to capture orchestrator start branch — merge-to-main will skip worktree integration (--no-worktree run)");
18120
17482
  }
18121
17483
  for (const key of storyKeys) {
18122
17484
  const pendingState = {
@@ -19841,8 +19203,8 @@ async function resolveContext(ref, deps, runId, params, stepOutputs) {
19841
19203
  return params[key] ?? "";
19842
19204
  }
19843
19205
  if (source.startsWith("decision:")) {
19844
- const path$6 = source.slice(9);
19845
- const [phase, category] = path$6.split(".");
19206
+ const path$4 = source.slice(9);
19207
+ const [phase, category] = path$4.split(".");
19846
19208
  if (!phase || !category) return "";
19847
19209
  const decisions = await getDecisionsByPhaseForRun(deps.db, runId, phase);
19848
19210
  const filtered = decisions.filter((d) => d.category === category);
@@ -19913,8 +19275,8 @@ async function runSteps(steps, deps, runId, phase, params) {
19913
19275
  for (const ref of step.context) {
19914
19276
  let value;
19915
19277
  if (ref.source.startsWith("decision:")) {
19916
- const path$6 = ref.source.slice(9);
19917
- const [decPhase, decCategory] = path$6.split(".");
19278
+ const path$4 = ref.source.slice(9);
19279
+ const [decPhase, decCategory] = path$4.split(".");
19918
19280
  if (decPhase && decCategory) {
19919
19281
  const decisions = await getDecisionsByPhaseForRun(deps.db, runId, decPhase);
19920
19282
  const filtered = decisions.filter((d) => d.category === decCategory);
@@ -31002,8 +30364,8 @@ var require_uri_all = __commonJS({ "node_modules/uri-js/dist/es5/uri.all.js"(exp
31002
30364
  wsComponents.secure = void 0;
31003
30365
  }
31004
30366
  if (wsComponents.resourceName) {
31005
- var _wsComponents$resourc = wsComponents.resourceName.split("?"), _wsComponents$resourc2 = slicedToArray(_wsComponents$resourc, 2), path$6 = _wsComponents$resourc2[0], query = _wsComponents$resourc2[1];
31006
- wsComponents.path = path$6 && path$6 !== "/" ? path$6 : void 0;
30367
+ var _wsComponents$resourc = wsComponents.resourceName.split("?"), _wsComponents$resourc2 = slicedToArray(_wsComponents$resourc, 2), path$4 = _wsComponents$resourc2[0], query = _wsComponents$resourc2[1];
30368
+ wsComponents.path = path$4 && path$4 !== "/" ? path$4 : void 0;
31007
30369
  wsComponents.query = query;
31008
30370
  wsComponents.resourceName = void 0;
31009
30371
  }
@@ -31344,12 +30706,12 @@ var require_util = __commonJS({ "node_modules/ajv/lib/compile/util.js"(exports,
31344
30706
  return "'" + escapeQuotes(str) + "'";
31345
30707
  }
31346
30708
  function getPathExpr(currentPath, expr, jsonPointers, isNumber) {
31347
- var path$6 = jsonPointers ? "'/' + " + expr + (isNumber ? "" : ".replace(/~/g, '~0').replace(/\\//g, '~1')") : isNumber ? "'[' + " + expr + " + ']'" : "'[\\'' + " + expr + " + '\\']'";
31348
- return joinPaths(currentPath, path$6);
30709
+ var path$4 = jsonPointers ? "'/' + " + expr + (isNumber ? "" : ".replace(/~/g, '~0').replace(/\\//g, '~1')") : isNumber ? "'[' + " + expr + " + ']'" : "'[\\'' + " + expr + " + '\\']'";
30710
+ return joinPaths(currentPath, path$4);
31349
30711
  }
31350
30712
  function getPath(currentPath, prop, jsonPointers) {
31351
- var path$6 = jsonPointers ? toQuotedString("/" + escapeJsonPointer(prop)) : toQuotedString(getProperty(prop));
31352
- return joinPaths(currentPath, path$6);
30713
+ var path$4 = jsonPointers ? toQuotedString("/" + escapeJsonPointer(prop)) : toQuotedString(getProperty(prop));
30714
+ return joinPaths(currentPath, path$4);
31353
30715
  }
31354
30716
  var JSON_POINTER$1 = /^\/(?:[^~]|~0|~1)*$/;
31355
30717
  var RELATIVE_JSON_POINTER$1 = /^([0-9]+)(#|\/(?:[^~]|~0|~1)*)?$/;
@@ -35558,8 +34920,8 @@ var ToolRegistry = class {
35558
34920
  if (!valid) {
35559
34921
  const errors = validate$1.errors ?? [];
35560
34922
  const messages = errors.map((e) => {
35561
- const path$6 = e.instancePath ?? e.dataPath ?? "";
35562
- return `${path$6} ${e.message ?? ""}`.trim();
34923
+ const path$4 = e.instancePath ?? e.dataPath ?? "";
34924
+ return `${path$4} ${e.message ?? ""}`.trim();
35563
34925
  }).join(", ");
35564
34926
  return {
35565
34927
  content: `Validation failed for tool '${name}': ${messages}`,
@@ -42265,10 +41627,10 @@ var PathBase = class {
42265
41627
  /**
42266
41628
  * Get the Path object referenced by the string path, resolved from this Path
42267
41629
  */
42268
- resolve(path$6) {
42269
- if (!path$6) return this;
42270
- const rootPath = this.getRootString(path$6);
42271
- const dir = path$6.substring(rootPath.length);
41630
+ resolve(path$4) {
41631
+ if (!path$4) return this;
41632
+ const rootPath = this.getRootString(path$4);
41633
+ const dir = path$4.substring(rootPath.length);
42272
41634
  const dirParts = dir.split(this.splitSep);
42273
41635
  const result = rootPath ? this.getRoot(rootPath).#resolveParts(dirParts) : this.#resolveParts(dirParts);
42274
41636
  return result;
@@ -42923,8 +42285,8 @@ var PathWin32 = class PathWin32 extends PathBase {
42923
42285
  /**
42924
42286
  * @internal
42925
42287
  */
42926
- getRootString(path$6) {
42927
- return win32.parse(path$6).root;
42288
+ getRootString(path$4) {
42289
+ return win32.parse(path$4).root;
42928
42290
  }
42929
42291
  /**
42930
42292
  * @internal
@@ -42969,8 +42331,8 @@ var PathPosix = class PathPosix extends PathBase {
42969
42331
  /**
42970
42332
  * @internal
42971
42333
  */
42972
- getRootString(path$6) {
42973
- return path$6.startsWith("/") ? "/" : "";
42334
+ getRootString(path$4) {
42335
+ return path$4.startsWith("/") ? "/" : "";
42974
42336
  }
42975
42337
  /**
42976
42338
  * @internal
@@ -43063,9 +42425,9 @@ var PathScurryBase = class {
43063
42425
  /**
43064
42426
  * Get the depth of a provided path, string, or the cwd
43065
42427
  */
43066
- depth(path$6 = this.cwd) {
43067
- if (typeof path$6 === "string") path$6 = this.cwd.resolve(path$6);
43068
- return path$6.depth();
42428
+ depth(path$4 = this.cwd) {
42429
+ if (typeof path$4 === "string") path$4 = this.cwd.resolve(path$4);
42430
+ return path$4.depth();
43069
42431
  }
43070
42432
  /**
43071
42433
  * Return the cache of child entries. Exposed so subclasses can create
@@ -43446,9 +42808,9 @@ var PathScurryBase = class {
43446
42808
  process$1();
43447
42809
  return results;
43448
42810
  }
43449
- chdir(path$6 = this.cwd) {
42811
+ chdir(path$4 = this.cwd) {
43450
42812
  const oldCwd = this.cwd;
43451
- this.cwd = typeof path$6 === "string" ? this.cwd.resolve(path$6) : path$6;
42813
+ this.cwd = typeof path$4 === "string" ? this.cwd.resolve(path$4) : path$4;
43452
42814
  this.cwd[setAsCwd](oldCwd);
43453
42815
  }
43454
42816
  };
@@ -43832,8 +43194,8 @@ var MatchRecord = class {
43832
43194
  this.store.set(target, current === void 0 ? n$1 : n$1 & current);
43833
43195
  }
43834
43196
  entries() {
43835
- return [...this.store.entries()].map(([path$6, n$1]) => [
43836
- path$6,
43197
+ return [...this.store.entries()].map(([path$4, n$1]) => [
43198
+ path$4,
43837
43199
  !!(n$1 & 2),
43838
43200
  !!(n$1 & 1)
43839
43201
  ]);
@@ -44009,9 +43371,9 @@ var GlobUtil = class {
44009
43371
  signal;
44010
43372
  maxDepth;
44011
43373
  includeChildMatches;
44012
- constructor(patterns, path$6, opts) {
43374
+ constructor(patterns, path$4, opts) {
44013
43375
  this.patterns = patterns;
44014
- this.path = path$6;
43376
+ this.path = path$4;
44015
43377
  this.opts = opts;
44016
43378
  this.#sep = !opts.posix && opts.platform === "win32" ? "\\" : "/";
44017
43379
  this.includeChildMatches = opts.includeChildMatches !== false;
@@ -44032,11 +43394,11 @@ var GlobUtil = class {
44032
43394
  });
44033
43395
  }
44034
43396
  }
44035
- #ignored(path$6) {
44036
- return this.seen.has(path$6) || !!this.#ignore?.ignored?.(path$6);
43397
+ #ignored(path$4) {
43398
+ return this.seen.has(path$4) || !!this.#ignore?.ignored?.(path$4);
44037
43399
  }
44038
- #childrenIgnored(path$6) {
44039
- return !!this.#ignore?.childrenIgnored?.(path$6);
43400
+ #childrenIgnored(path$4) {
43401
+ return !!this.#ignore?.childrenIgnored?.(path$4);
44040
43402
  }
44041
43403
  pause() {
44042
43404
  this.paused = true;
@@ -44218,8 +43580,8 @@ var GlobUtil = class {
44218
43580
  };
44219
43581
  var GlobWalker = class extends GlobUtil {
44220
43582
  matches = new Set();
44221
- constructor(patterns, path$6, opts) {
44222
- super(patterns, path$6, opts);
43583
+ constructor(patterns, path$4, opts) {
43584
+ super(patterns, path$4, opts);
44223
43585
  }
44224
43586
  matchEmit(e) {
44225
43587
  this.matches.add(e);
@@ -44246,8 +43608,8 @@ var GlobWalker = class extends GlobUtil {
44246
43608
  };
44247
43609
  var GlobStream = class extends GlobUtil {
44248
43610
  results;
44249
- constructor(patterns, path$6, opts) {
44250
- super(patterns, path$6, opts);
43611
+ constructor(patterns, path$4, opts) {
43612
+ super(patterns, path$4, opts);
44251
43613
  this.results = new Minipass({
44252
43614
  signal: this.signal,
44253
43615
  objectMode: true
@@ -46953,6 +46315,8 @@ async function runRunAction(options) {
46953
46315
  let meshProjectId;
46954
46316
  let configRetryBudget;
46955
46317
  let configFinalizationMode;
46318
+ let configMergeStrategy;
46319
+ let configEpicGateCommand;
46956
46320
  let configWorktreeCopyFiles;
46957
46321
  try {
46958
46322
  const configSystem = createConfigSystem({ projectConfigDir: dbDir });
@@ -46980,6 +46344,8 @@ async function runRunAction(options) {
46980
46344
  }
46981
46345
  if (typeof cfg.retry_budget === "number") configRetryBudget = cfg.retry_budget;
46982
46346
  if (cfg.finalization?.mode !== void 0) configFinalizationMode = cfg.finalization.mode;
46347
+ if (cfg.finalization?.merge_strategy !== void 0) configMergeStrategy = cfg.finalization.merge_strategy;
46348
+ if (cfg.finalization?.epic_gate_command !== void 0) configEpicGateCommand = cfg.finalization.epic_gate_command;
46983
46349
  if (Array.isArray(cfg.worktree?.copy_files) && cfg.worktree.copy_files.length > 0) {
46984
46350
  configWorktreeCopyFiles = [...cfg.worktree.copy_files];
46985
46351
  logger.info({ copyFiles: configWorktreeCopyFiles }, "Loaded worktree.copy_files from config");
@@ -47080,6 +46446,8 @@ async function runRunAction(options) {
47080
46446
  agentId,
47081
46447
  ...noWorktree === true ? { noWorktree: true } : {},
47082
46448
  ...finalization !== void 0 ? { finalizationMode: finalization } : configFinalizationMode !== void 0 ? { finalizationMode: configFinalizationMode } : {},
46449
+ ...configMergeStrategy !== void 0 ? { mergeStrategy: configMergeStrategy } : {},
46450
+ ...configEpicGateCommand !== void 0 ? { epicGateCommand: configEpicGateCommand } : {},
47083
46451
  ...configWorktreeCopyFiles !== void 0 ? { worktreeCopyFiles: configWorktreeCopyFiles } : {}
47084
46452
  });
47085
46453
  let storyKeys = [...parsedStoryKeys];
@@ -47644,6 +47012,8 @@ async function runRunAction(options) {
47644
47012
  probeAuthorStateIntegrating: resolvedProbeAuthorStateIntegrating,
47645
47013
  ...noWorktree === true ? { noWorktree: true } : {},
47646
47014
  ...finalization !== void 0 ? { finalizationMode: finalization } : configFinalizationMode !== void 0 ? { finalizationMode: configFinalizationMode } : {},
47015
+ ...configMergeStrategy !== void 0 ? { mergeStrategy: configMergeStrategy } : {},
47016
+ ...configEpicGateCommand !== void 0 ? { epicGateCommand: configEpicGateCommand } : {},
47647
47017
  ...configWorktreeCopyFiles !== void 0 ? { worktreeCopyFiles: configWorktreeCopyFiles } : {}
47648
47018
  },
47649
47019
  projectRoot,
@@ -47867,7 +47237,7 @@ async function runRunAction(options) {
47867
47237
  }
47868
47238
  }
47869
47239
  async function runFullPipeline(options) {
47870
- const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType, probeAuthor, probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating, noWorktree, worktreeCopyFiles: fpWorktreeCopyFiles, finalizationMode: fpFinalizationMode } = options;
47240
+ const { packName, packPath, dbDir, dbPath, startPhase, stopAfter, concept, concurrency, outputFormat, projectRoot, events: eventsFlag, skipUx, research: researchFlag, skipResearch: skipResearchFlag, skipPreflight, skipVerification, maxReviewCycles = 2, retryBudget, registry: injectedRegistry, tokenCeilings, stories: explicitStories, telemetryEnabled: fullTelemetryEnabled, telemetryPort: fullTelemetryPort, agentId, meshUrl: fpMeshUrl, meshProjectId: fpMeshProjectId, engineType: fpEngineType, probeAuthor, probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating, noWorktree, worktreeCopyFiles: fpWorktreeCopyFiles, finalizationMode: fpFinalizationMode, mergeStrategy: fpMergeStrategy, epicGateCommand: fpEpicGateCommand } = options;
47871
47241
  if (!existsSync$1(dbDir)) mkdirSync$1(dbDir, { recursive: true });
47872
47242
  let doltServerFull = null;
47873
47243
  try {
@@ -48178,6 +47548,8 @@ async function runFullPipeline(options) {
48178
47548
  ...fpProbeAuthorStateIntegrating !== void 0 ? { probeAuthorStateIntegrating: fpProbeAuthorStateIntegrating } : {},
48179
47549
  ...noWorktree === true ? { noWorktree: true } : {},
48180
47550
  ...fpFinalizationMode !== void 0 ? { finalizationMode: fpFinalizationMode } : {},
47551
+ ...fpMergeStrategy !== void 0 ? { mergeStrategy: fpMergeStrategy } : {},
47552
+ ...fpEpicGateCommand !== void 0 ? { epicGateCommand: fpEpicGateCommand } : {},
48181
47553
  ...fpWorktreeCopyFiles !== void 0 ? { worktreeCopyFiles: fpWorktreeCopyFiles } : {}
48182
47554
  },
48183
47555
  projectRoot,
@@ -48348,7 +47720,7 @@ async function runFullPipeline(options) {
48348
47720
  */
48349
47721
  async function emitPreDispatchVersionAdvisory(currentVersion) {
48350
47722
  if (process.env["SUBSTRATE_NO_UPDATE_CHECK"] === "1") return;
48351
- const { createVersionManager } = await import("./version-manager-impl-BWqvme_f.js");
47723
+ const { createVersionManager } = await import("./version-manager-impl-DizCvYy8.js");
48352
47724
  const vm = createVersionManager();
48353
47725
  const result = await vm.checkForUpdates(true);
48354
47726
  const gap = classifyVersionGap(currentVersion, result.latestVersion);
@@ -48454,5 +47826,5 @@ function registerRunCommand(program, version = "0.0.0", projectRoot = process.cw
48454
47826
  }
48455
47827
 
48456
47828
  //#endregion
48457
- 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 };
48458
- //# sourceMappingURL=run-BBVZdkEo.js.map
47829
+ 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, 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 };
47830
+ //# sourceMappingURL=run-BAUlPom3.js.map