@nathapp/nax 0.68.7 → 0.69.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/nax.js +802 -414
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -20944,12 +20944,21 @@ function parseAcpxJsonLine(line, state) {
20944
20944
  }
20945
20945
  if (update.sessionUpdate === "usage_update") {
20946
20946
  const activity = { kind: "usage_update" };
20947
- if (typeof update.used === "number") {
20947
+ const metaUsage = update._meta != null && typeof update._meta === "object" ? update._meta.usage : undefined;
20948
+ if (metaUsage != null && typeof metaUsage === "object") {
20949
+ const inp = metaUsage.inputTokens ?? metaUsage.input_tokens;
20950
+ if (typeof inp === "number")
20951
+ activity.inputTokens = inp;
20952
+ const out = metaUsage.outputTokens ?? metaUsage.output_tokens;
20953
+ if (typeof out === "number")
20954
+ activity.outputTokens = out;
20955
+ }
20956
+ if (activity.outputTokens == null && typeof update.used === "number") {
20948
20957
  activity.outputTokens = update.used;
20949
20958
  }
20950
20959
  if (typeof update.cost?.amount === "number") {
20951
20960
  activity.costUsd = update.cost.amount;
20952
- state.exactCostUsd = update.cost.amount;
20961
+ state.exactCostUsd = activity.costUsd;
20953
20962
  }
20954
20963
  return activity;
20955
20964
  }
@@ -27497,6 +27506,9 @@ function getContextFiles(story) {
27497
27506
  const files = story.contextFiles ?? story.relevantFiles ?? [];
27498
27507
  return files.map((f) => typeof f === "string" ? f : f.path);
27499
27508
  }
27509
+ function getExpectedFiles(story) {
27510
+ return story.expectedFiles ?? [];
27511
+ }
27500
27512
  function isStalled(prd) {
27501
27513
  const remaining = prd.userStories.filter((s) => s.status !== "passed" && s.status !== "skipped");
27502
27514
  if (remaining.length === 0)
@@ -27772,6 +27784,24 @@ function validateStory(raw, index, allIds) {
27772
27784
  }
27773
27785
  }
27774
27786
  }
27787
+ const rawExpectedFiles = s.expectedFiles;
27788
+ const expectedFiles = [];
27789
+ if (Array.isArray(rawExpectedFiles)) {
27790
+ for (const f of rawExpectedFiles) {
27791
+ if (typeof f !== "string")
27792
+ continue;
27793
+ const trimmed = f.trim();
27794
+ if (trimmed === "")
27795
+ continue;
27796
+ if (trimmed.startsWith("/")) {
27797
+ throw new Error(`[schema] story[${index}].expectedFiles entry must be relative (no absolute paths): "${trimmed}"`);
27798
+ }
27799
+ if (trimmed.includes("..")) {
27800
+ throw new Error(`[schema] story[${index}].expectedFiles entry must not contain '..': "${trimmed}"`);
27801
+ }
27802
+ expectedFiles.push(trimmed);
27803
+ }
27804
+ }
27775
27805
  const VALID_VERIFIED_BY_KINDS = ["test", "symbol", "file"];
27776
27806
  let verifiedBy;
27777
27807
  if (s.verifiedBy !== undefined && s.verifiedBy !== null) {
@@ -27805,6 +27835,7 @@ function validateStory(raw, index, allIds) {
27805
27835
  },
27806
27836
  ...workdir !== undefined ? { workdir } : {},
27807
27837
  ...contextFiles.length > 0 ? { contextFiles } : {},
27838
+ ...expectedFiles.length > 0 ? { expectedFiles } : {},
27808
27839
  ...suggestedCriteria !== undefined ? { suggestedCriteria } : {},
27809
27840
  ...verifiedBy !== undefined ? { verifiedBy } : {},
27810
27841
  ...intent !== undefined ? { intent } : {}
@@ -29366,6 +29397,12 @@ function renderErrorSection(sections, byType) {
29366
29397
 
29367
29398
  // src/context/builder.ts
29368
29399
  import path2 from "path";
29400
+ function readContextMessage(relativeFilePath) {
29401
+ return `_Path: \`${relativeFilePath}\` \u2014 read this file before implementing._`;
29402
+ }
29403
+ function createIntentMessage(relativeFilePath) {
29404
+ return `_Path: \`${relativeFilePath}\` \u2014 this file does not exist yet; you will CREATE it as part of this story._`;
29405
+ }
29369
29406
  function sortContextElements(elements) {
29370
29407
  return [...elements].sort((a, b) => {
29371
29408
  if (a.priority !== b.priority)
@@ -29493,7 +29530,6 @@ async function addTestCoverageElement(elements, storyContext, story) {
29493
29530
  }
29494
29531
  }
29495
29532
  async function addFileElements(elements, storyContext, story) {
29496
- const MAX_FILES3 = 5;
29497
29533
  const fileInjection = storyContext.config?.context?.fileInjection;
29498
29534
  let contextFiles = getContextFiles(story);
29499
29535
  const parentFiles = getParentOutputFiles(story, storyContext.prd?.userStories ?? []);
@@ -29530,26 +29566,51 @@ async function addFileElements(elements, storyContext, story) {
29530
29566
  });
29531
29567
  }
29532
29568
  }
29533
- if (contextFiles.length === 0)
29569
+ const expectedFiles = getExpectedFiles(story);
29570
+ if (contextFiles.length === 0 && expectedFiles.length === 0)
29534
29571
  return;
29535
- const filesToLoad = contextFiles.slice(0, MAX_FILES3);
29536
29572
  const { workdir } = storyContext;
29537
29573
  if (!workdir) {
29538
29574
  getLogger().warn("context", "workdir not set \u2014 cannot load context files", { storyId: story.id });
29539
29575
  return;
29540
29576
  }
29577
+ const expectedSet = new Set(expectedFiles);
29578
+ const surfaced = new Set;
29579
+ const filesToLoad = contextFiles.slice(0, FILE_INJECTION_MAX_FILES);
29541
29580
  for (let i = 0;i < filesToLoad.length; i++) {
29542
29581
  const relativeFilePath = filesToLoad[i];
29582
+ surfaced.add(relativeFilePath);
29543
29583
  const absolutePath = path2.resolve(workdir, relativeFilePath);
29544
- const file3 = Bun.file(absolutePath);
29545
- if (!await file3.exists()) {
29546
- getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
29584
+ if (await Bun.file(absolutePath).exists()) {
29585
+ elements.push(createFileContext(relativeFilePath, readContextMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
29547
29586
  continue;
29548
29587
  }
29549
- elements.push(createFileContext(relativeFilePath, `_Path: \`${relativeFilePath}\` \u2014 read this file before implementing._`, 60 - i));
29588
+ if (expectedSet.has(relativeFilePath)) {
29589
+ elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
29590
+ getLogger().debug("context", "Context file does not exist yet \u2014 treated as to-be-created", {
29591
+ storyId: story.id,
29592
+ filePath: relativeFilePath
29593
+ });
29594
+ } else {
29595
+ getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
29596
+ }
29550
29597
  }
29598
+ await addCreateIntentElements(elements, workdir, expectedFiles, surfaced);
29551
29599
  }
29552
- var _contextBuilderDeps;
29600
+ async function addCreateIntentElements(elements, workdir, expectedFiles, surfaced) {
29601
+ let idx = 0;
29602
+ for (const relativeFilePath of expectedFiles.slice(0, FILE_INJECTION_MAX_FILES)) {
29603
+ if (surfaced.has(relativeFilePath))
29604
+ continue;
29605
+ const absolutePath = path2.resolve(workdir, relativeFilePath);
29606
+ if (await Bun.file(absolutePath).exists())
29607
+ continue;
29608
+ elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - FILE_INJECTION_MAX_FILES - idx));
29609
+ surfaced.add(relativeFilePath);
29610
+ idx++;
29611
+ }
29612
+ }
29613
+ var _contextBuilderDeps, FILE_INJECTION_MAX_FILES = 5, FILE_CONTEXT_PRIORITY_BASE = 60;
29553
29614
  var init_builder = __esm(() => {
29554
29615
  init_logger2();
29555
29616
  init_prd();
@@ -34222,6 +34283,7 @@ ${outputFormat}`, overridable: false }
34222
34283
  });
34223
34284
 
34224
34285
  // src/operations/plan-refine.ts
34286
+ import { join as join20 } from "path";
34225
34287
  function hasToken(text, tokens) {
34226
34288
  const lower = text.toLowerCase();
34227
34289
  return tokens.some((token) => lower.includes(token));
@@ -34284,6 +34346,80 @@ async function readSpecDriftViolations(input) {
34284
34346
  return [];
34285
34347
  }
34286
34348
  }
34349
+ function collectUpstreamProducedFiles(story, byId) {
34350
+ const produced = new Set;
34351
+ const seen = new Set;
34352
+ const stack = [...story.dependencies ?? []];
34353
+ while (stack.length > 0) {
34354
+ const depId = stack.pop();
34355
+ if (!depId || seen.has(depId))
34356
+ continue;
34357
+ seen.add(depId);
34358
+ const dep = byId.get(depId);
34359
+ if (!dep)
34360
+ continue;
34361
+ for (const filePath of getExpectedFiles(dep))
34362
+ produced.add(filePath);
34363
+ stack.push(...dep.dependencies ?? []);
34364
+ }
34365
+ return produced;
34366
+ }
34367
+ async function normalizeStoryFiles(story, workdir, fileExists, upstreamProduced) {
34368
+ const contextFiles = story.contextFiles ?? [];
34369
+ if (contextFiles.length === 0)
34370
+ return { story, changed: false };
34371
+ const logger = getSafeLogger();
34372
+ const expected = new Set(getExpectedFiles(story));
34373
+ const kept = [];
34374
+ const moved = [];
34375
+ for (const entry of contextFiles) {
34376
+ const filePath = typeof entry === "string" ? entry : entry.path;
34377
+ const factId = typeof entry === "string" ? undefined : entry.factId;
34378
+ if (expected.has(filePath) || await fileExists(join20(workdir, filePath))) {
34379
+ kept.push(entry);
34380
+ continue;
34381
+ }
34382
+ if (upstreamProduced.has(filePath)) {
34383
+ kept.push(entry);
34384
+ logger?.debug("plan", "Kept cross-story produced file in contextFiles (upstream dependency creates it)", {
34385
+ storyId: story.id,
34386
+ filePath
34387
+ });
34388
+ continue;
34389
+ }
34390
+ if (factId) {
34391
+ logger?.warn("plan", "Context file cites a manifest fact but is absent on disk", {
34392
+ storyId: story.id,
34393
+ filePath,
34394
+ factId
34395
+ });
34396
+ kept.push(entry);
34397
+ continue;
34398
+ }
34399
+ moved.push(filePath);
34400
+ }
34401
+ if (moved.length === 0)
34402
+ return { story, changed: false };
34403
+ const newExpected = [...getExpectedFiles(story)];
34404
+ for (const filePath of moved) {
34405
+ if (!newExpected.includes(filePath))
34406
+ newExpected.push(filePath);
34407
+ }
34408
+ logger?.info("plan", "Moved absent contextFiles entries to expectedFiles (story creates them)", {
34409
+ storyId: story.id,
34410
+ moved
34411
+ });
34412
+ return { story: { ...story, contextFiles: kept, expectedFiles: newExpected }, changed: true };
34413
+ }
34414
+ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
34415
+ if (!workdir)
34416
+ return prd;
34417
+ const byId = new Map(prd.userStories.map((story) => [story.id, story]));
34418
+ const results = await Promise.all(prd.userStories.map((story) => normalizeStoryFiles(story, workdir, fileExists, collectUpstreamProducedFiles(story, byId))));
34419
+ if (!results.some((r) => r.changed))
34420
+ return prd;
34421
+ return { ...prd, userStories: results.map((r) => r.story) };
34422
+ }
34287
34423
  var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
34288
34424
  var init_plan_refine = __esm(() => {
34289
34425
  init_retry();
@@ -34405,7 +34541,7 @@ ${outputFormat}`,
34405
34541
  if (ctx.config.plan.specGuard) {
34406
34542
  warnOnSpecDrift(validated, input.featureName);
34407
34543
  }
34408
- return validated;
34544
+ return await normalizeCreatedContextFiles(validated, input.workdir, ctx.fileExists);
34409
34545
  },
34410
34546
  recover: async (input, ctx) => {
34411
34547
  const content = await ctx.readFile(input.outputPath);
@@ -35391,11 +35527,11 @@ function extractTestCode(output) {
35391
35527
 
35392
35528
  // src/acceptance/generator.ts
35393
35529
  import { existsSync as existsSync5 } from "fs";
35394
- import { join as join20 } from "path";
35530
+ import { join as join21 } from "path";
35395
35531
  function resolvePytestBin(packageDir) {
35396
35532
  if (packageDir) {
35397
35533
  for (const venvDir of [".venv", "venv", "env"]) {
35398
- const candidate = join20(packageDir, venvDir, "bin", "pytest");
35534
+ const candidate = join21(packageDir, venvDir, "bin", "pytest");
35399
35535
  if (existsSync5(candidate))
35400
35536
  return candidate;
35401
35537
  }
@@ -38004,14 +38140,14 @@ var init_plan_critic_llm = __esm(() => {
38004
38140
 
38005
38141
  // src/context/greenfield.ts
38006
38142
  import { readdir as readdir2 } from "fs/promises";
38007
- import { join as join21 } from "path";
38143
+ import { join as join22 } from "path";
38008
38144
  async function scanForTestFiles(dir, testPatterns, isRootCall = true) {
38009
38145
  const results = [];
38010
38146
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
38011
38147
  try {
38012
38148
  const entries = await readdir2(dir, { withFileTypes: true });
38013
38149
  for (const entry of entries) {
38014
- const fullPath = join21(dir, entry.name);
38150
+ const fullPath = join22(dir, entry.name);
38015
38151
  if (entry.isDirectory()) {
38016
38152
  if (ignoreDirs.has(entry.name))
38017
38153
  continue;
@@ -38216,13 +38352,13 @@ __export(exports_runners, {
38216
38352
  _regressionRunnerDeps: () => _regressionRunnerDeps
38217
38353
  });
38218
38354
  import { existsSync as existsSync6 } from "fs";
38219
- import { join as join22 } from "path";
38355
+ import { join as join23 } from "path";
38220
38356
  async function verifyAssets(workingDirectory, expectedFiles) {
38221
38357
  if (!expectedFiles || expectedFiles.length === 0)
38222
38358
  return { success: true, missingFiles: [] };
38223
38359
  const missingFiles = [];
38224
38360
  for (const file3 of expectedFiles) {
38225
- if (!existsSync6(join22(workingDirectory, file3)))
38361
+ if (!existsSync6(join23(workingDirectory, file3)))
38226
38362
  missingFiles.push(file3);
38227
38363
  }
38228
38364
  if (missingFiles.length > 0) {
@@ -38668,7 +38804,7 @@ var init_apply_test_edit_declarations = __esm(() => {
38668
38804
  });
38669
38805
 
38670
38806
  // src/operations/validate-mock-structure-files.ts
38671
- import { join as join23 } from "path";
38807
+ import { join as join24 } from "path";
38672
38808
  async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
38673
38809
  const fileExists = deps?.fileExists ?? defaultFileExists;
38674
38810
  const valid = [];
@@ -38681,7 +38817,7 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
38681
38817
  const files = d.files ?? [d.file];
38682
38818
  let allValid = true;
38683
38819
  for (const file3 of files) {
38684
- const absolutePath = join23(packageDir, file3);
38820
+ const absolutePath = join24(packageDir, file3);
38685
38821
  const exists = await fileExists(absolutePath);
38686
38822
  if (!exists) {
38687
38823
  allValid = false;
@@ -39975,7 +40111,7 @@ var init_lint_parsing = __esm(() => {
39975
40111
  });
39976
40112
 
39977
40113
  // src/review/scoped-lint.ts
39978
- import { join as join24, relative as relative10 } from "path";
40114
+ import { join as join25, relative as relative10 } from "path";
39979
40115
  function shellQuotePath4(path5) {
39980
40116
  return `'${path5.replaceAll("'", "'\\''")}'`;
39981
40117
  }
@@ -40023,7 +40159,7 @@ function uniqueFiles(files) {
40023
40159
  async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
40024
40160
  const inScope = [];
40025
40161
  for (const relPath of files) {
40026
- const absPath = join24(workdir, relPath);
40162
+ const absPath = join25(workdir, relPath);
40027
40163
  const exists = await _scopedLintDeps.fileExists(absPath);
40028
40164
  if (!exists)
40029
40165
  continue;
@@ -42177,7 +42313,7 @@ Output ONLY the JSON object. Do not include markdown fences or explanation.`;
42177
42313
  buildRefineContinuation(outputFilePath, specGuard = false) {
42178
42314
  const specGuardItems = specGuard ? `
42179
42315
  #### orphan-acs
42180
- Every acceptance criterion in the PRD must trace back to a requirement stated in the spec. An AC that introduces scope the spec never mentions \u2014 new enum values, new status codes, new config keys, extra validation rules, invented helper behaviour \u2014 is scope bleed from candidate-PRD merging. Delete it, or reduce it to exactly what the spec says.
42316
+ Every acceptance criterion in the PRD must trace back to a requirement stated in the spec. An AC that introduces scope the spec never mentions \u2014 new enum values, new status codes, new config keys, extra validation rules, invented helper behaviour \u2014 is scope bleed from candidate-PRD merging. Delete it, or reduce it to exactly what the spec says. **\`suggestedCriteria\` entries are exempt from this rule** \u2014 they are intentionally out-of-spec edge cases and must be preserved unchanged.
42181
42317
 
42182
42318
  #### no-behavior-degradation
42183
42319
  No acceptance criterion may use a deprecated verification tag (\`[grep]\`, \`[file]\`, \`[verbatim]\`) or contain a shell-command pattern (\`grep -\`, \`wc\`, \`|\` inside a backtick span). These signal a file-content check that the agent cannot implement as a runtime test. Rewrite any such AC as a behavioural assertion: what the function returns, throws, or emits \u2014 not what the source file contains.` : "";
@@ -42308,9 +42444,9 @@ Based on your Step 2 analysis, create stories that produce CODE CHANGES.
42308
42444
 
42309
42445
  ${buildSharedQualityRules(specContent, projectProfile)}
42310
42446
 
42311
- For each story, set "contextFiles" to the key source files the agent should read before implementing (max 5 per story). Use your Step 2 analysis to identify the most relevant files. Leave empty for greenfield stories with no existing files to reference.
42447
+ For each story, set "contextFiles" to the key source files the agent should read before implementing (max 5 per story). Use your Step 2 analysis to identify the most relevant files. Leave empty for greenfield stories with no existing files to reference. Set "expectedFiles" to the NEW files the story creates.
42312
42448
 
42313
- **\`contextFiles\` rule \u2014 existing files only.** Only list paths that already exist in the repo today. Files the story will CREATE belong in the description (under "Files touched" or "Approach"), never in contextFiles. The pipeline verifies every contextFiles entry against the filesystem; new-file paths placed here are treated as missing-context warnings and may block the plan.`;
42449
+ ${CONTEXT_VS_EXPECTED_FILES_RULE}`;
42314
42450
  const suggestedCriteriaField = specContent.trim() ? `
42315
42451
  "suggestedCriteria": ["string \u2014 optional. Behavioral edge cases or negative paths you identified that are NOT in the spec. Plain assertions only \u2014 observable outputs, return values, state changes, or error conditions. No implementation details or vague descriptions. Omit this field if empty."],` : "";
42316
42452
  const outputDirective = outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
@@ -42332,7 +42468,8 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
42332
42468
  "title": "string \u2014 concise story title",
42333
42469
  "description": "string \u2014 detailed description of the story",
42334
42470
  "acceptanceCriteria": ["string \u2014 behavioral, testable criteria. Format: 'When [X], then [Y]'. One assertion per AC. Never include quality gates."],${suggestedCriteriaField}
42335
- "contextFiles": ["string \u2014 key source files the agent should read (max 5, relative paths)"],
42471
+ "contextFiles": ["string \u2014 EXISTING source files the agent should read (max 5, relative paths)"],
42472
+ ${EXPECTED_FILES_SCHEMA_FIELD}
42336
42473
  "tags": ["string \u2014 routing tags, e.g. feature, security, api"],
42337
42474
  "dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
42338
42475
  "status": "pending",
@@ -42405,9 +42542,9 @@ Every concrete claim referencing existing code must cite [F-NNN] or [S-NNN] from
42405
42542
 
42406
42543
  ${buildSharedQualityRules(input.specContent, input.projectProfile)}
42407
42544
 
42408
- For each story, set "contextFiles" to the key source files the implementer should read before starting (max 5 per story). Cite manifest factIds where relevant.
42545
+ For each story, set "contextFiles" to the key source files the implementer should read before starting (max 5 per story). Cite manifest factIds where relevant. Set "expectedFiles" to the NEW files the story creates.
42409
42546
 
42410
- **\`contextFiles\` rule \u2014 existing files only.** Only list paths that already exist in the repo today (cited via manifest factIds where possible). Files the story will CREATE belong in the description (under "Files touched" or "Approach"), never in contextFiles. Uncited paths that do not exist on disk are flagged by the pipeline.
42547
+ ${CONTEXT_VS_EXPECTED_FILES_RULE}
42411
42548
 
42412
42549
  ## Output Schema
42413
42550
 
@@ -42423,7 +42560,8 @@ Produce a JSON object with this exact structure. Field names are mandatory \u201
42423
42560
  "title": "string \u2014 concise story title",
42424
42561
  "description": "string \u2014 detailed description of what to implement",
42425
42562
  "acceptanceCriteria": ["string \u2014 behavioral criterion, format: 'When [X], then [Y]'. One assertion per item."],${suggestedCriteriaField}
42426
- "contextFiles": ["string \u2014 relative paths the implementer should read (max 5)"],
42563
+ "contextFiles": ["string \u2014 EXISTING relative paths the implementer should read (max 5)"],
42564
+ ${EXPECTED_FILES_SCHEMA_FIELD}
42427
42565
  "tags": ["string"],
42428
42566
  "dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
42429
42567
  "routing": {
@@ -42464,6 +42602,9 @@ ${rows.join(`
42464
42602
  `)}
42465
42603
  `;
42466
42604
  }
42605
+ var CONTEXT_VS_EXPECTED_FILES_RULE = `**\`contextFiles\` rule \u2014 files readable when this story runs.** List paths that already exist in the repo today, PLUS any file an UPSTREAM dependency story creates (it does not exist now but will exist by the time this story runs, because dependencies execute first). The pipeline verifies every \`contextFiles\` entry against the filesystem; a path that exists neither on disk nor in an upstream dependency's outputs is treated as a missing-context warning.
42606
+
42607
+ **\`expectedFiles\` rule \u2014 files THIS story CREATES.** List every NEW file this story authors (relative paths). A file this story creates belongs here, NEVER in \`contextFiles\` \u2014 these are the story's outputs, not files to read first. A file created by an upstream dependency and only read/modified here belongs in \`contextFiles\`, NOT here (this story does not author it). A single path may appear in \`contextFiles\` (an existing sibling to mirror) AND \`expectedFiles\` (the new file itself), but the same path must never be in both.`, EXPECTED_FILES_SCHEMA_FIELD = `"expectedFiles": ["string \u2014 NEW files this story creates (relative paths, omit if none)"],`;
42467
42608
  var init_plan_builder = __esm(() => {
42468
42609
  init_config();
42469
42610
  });
@@ -43392,7 +43533,7 @@ var init_call = __esm(() => {
43392
43533
 
43393
43534
  // src/runtime/cost-aggregator.ts
43394
43535
  import { mkdirSync as mkdirSync2 } from "fs";
43395
- import { join as join25 } from "path";
43536
+ import { join as join26 } from "path";
43396
43537
  function makeCorrelationId() {
43397
43538
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
43398
43539
  }
@@ -43583,7 +43724,7 @@ class CostAggregator {
43583
43724
  if (events.length === 0 && errors3.length === 0)
43584
43725
  return;
43585
43726
  mkdirSync2(this._drainDir, { recursive: true });
43586
- const path5 = join25(this._drainDir, `${this._runId}.jsonl`);
43727
+ const path5 = join26(this._drainDir, `${this._runId}.jsonl`);
43587
43728
  const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
43588
43729
  await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
43589
43730
  `)}
@@ -43623,7 +43764,7 @@ var init_cost_aggregator = __esm(() => {
43623
43764
  // src/runtime/prompt-auditor.ts
43624
43765
  import { appendFileSync } from "fs";
43625
43766
  import { mkdir as mkdir4 } from "fs/promises";
43626
- import { join as join26 } from "path";
43767
+ import { join as join27 } from "path";
43627
43768
  function createNoOpPromptAuditor() {
43628
43769
  return {
43629
43770
  record() {},
@@ -43689,8 +43830,8 @@ class PromptAuditor {
43689
43830
  _jsonlPath;
43690
43831
  _featureDir;
43691
43832
  constructor(runId, flushDir, featureName) {
43692
- this._featureDir = join26(flushDir, featureName);
43693
- this._jsonlPath = join26(this._featureDir, `${runId}.jsonl`);
43833
+ this._featureDir = join27(flushDir, featureName);
43834
+ this._jsonlPath = join27(this._featureDir, `${runId}.jsonl`);
43694
43835
  }
43695
43836
  record(entry) {
43696
43837
  this._enqueue(entry);
@@ -43739,7 +43880,7 @@ class PromptAuditor {
43739
43880
  const auditEntry = entry;
43740
43881
  const filename = deriveTxtFilename(auditEntry);
43741
43882
  try {
43742
- await _promptAuditorDeps.write(join26(this._featureDir, filename), buildTxtContent(auditEntry));
43883
+ await _promptAuditorDeps.write(join27(this._featureDir, filename), buildTxtContent(auditEntry));
43743
43884
  } catch (err) {
43744
43885
  throw tagAuditError(err, "txt");
43745
43886
  }
@@ -44851,7 +44992,7 @@ var init_pid_registry = __esm(() => {
44851
44992
  // src/session/manager-deps.ts
44852
44993
  import { randomUUID as randomUUID3 } from "crypto";
44853
44994
  import { mkdir as mkdir5 } from "fs/promises";
44854
- import { isAbsolute as isAbsolute9, join as join27, relative as relative11, sep as sep2 } from "path";
44995
+ import { isAbsolute as isAbsolute9, join as join28, relative as relative11, sep as sep2 } from "path";
44855
44996
  function resolveProjectDirFromScratchDir(scratchDir) {
44856
44997
  const marker = `${sep2}.nax${sep2}features${sep2}`;
44857
44998
  const markerIdx = scratchDir.lastIndexOf(marker);
@@ -44872,7 +45013,7 @@ var init_manager_deps = __esm(() => {
44872
45013
  now: () => new Date().toISOString(),
44873
45014
  nowMs: () => Date.now(),
44874
45015
  uuid: () => randomUUID3(),
44875
- sessionScratchDir: (projectDir, featureName, sessionId) => join27(projectDir, ".nax", "features", featureName, "sessions", sessionId),
45016
+ sessionScratchDir: (projectDir, featureName, sessionId) => join28(projectDir, ".nax", "features", featureName, "sessions", sessionId),
44876
45017
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
44877
45018
  await mkdir5(scratchDir, { recursive: true });
44878
45019
  const { handle: _handle, ...persistable } = descriptor;
@@ -44883,7 +45024,7 @@ var init_manager_deps = __esm(() => {
44883
45024
  persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
44884
45025
  }
44885
45026
  }
44886
- await Bun.write(join27(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
45027
+ await Bun.write(join28(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
44887
45028
  }
44888
45029
  };
44889
45030
  });
@@ -45634,7 +45775,7 @@ __export(exports_runtime, {
45634
45775
  CostAggregator: () => CostAggregator,
45635
45776
  AgentStreamEventBus: () => AgentStreamEventBus
45636
45777
  });
45637
- import { basename as basename5, join as join28 } from "path";
45778
+ import { basename as basename5, join as join29 } from "path";
45638
45779
  function createRuntime(config2, workdir, opts) {
45639
45780
  const runId = crypto.randomUUID();
45640
45781
  const controller = new AbortController;
@@ -45650,10 +45791,10 @@ function createRuntime(config2, workdir, opts) {
45650
45791
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
45651
45792
  const globalDir = globalOutputDir();
45652
45793
  const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
45653
- const costDir = join28(outputDir, "cost");
45794
+ const costDir = join29(outputDir, "cost");
45654
45795
  const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
45655
45796
  const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
45656
- const auditDir = config2.agent?.promptAudit?.dir ?? join28(outputDir, "prompt-audit");
45797
+ const auditDir = config2.agent?.promptAudit?.dir ?? join29(outputDir, "prompt-audit");
45657
45798
  let promptAuditor;
45658
45799
  if (opts?.promptAuditor) {
45659
45800
  promptAuditor = opts.promptAuditor;
@@ -45804,9 +45945,9 @@ async function allSettledBounded(tasks, limit) {
45804
45945
 
45805
45946
  // src/context/injector.ts
45806
45947
  import { existsSync as existsSync8 } from "fs";
45807
- import { join as join29 } from "path";
45948
+ import { join as join30 } from "path";
45808
45949
  async function detectNode(workdir) {
45809
- const pkgPath = join29(workdir, "package.json");
45950
+ const pkgPath = join30(workdir, "package.json");
45810
45951
  if (!existsSync8(pkgPath))
45811
45952
  return null;
45812
45953
  try {
@@ -45823,7 +45964,7 @@ async function detectNode(workdir) {
45823
45964
  }
45824
45965
  }
45825
45966
  async function detectGo(workdir) {
45826
- const goMod = join29(workdir, "go.mod");
45967
+ const goMod = join30(workdir, "go.mod");
45827
45968
  if (!existsSync8(goMod))
45828
45969
  return null;
45829
45970
  try {
@@ -45847,7 +45988,7 @@ async function detectGo(workdir) {
45847
45988
  }
45848
45989
  }
45849
45990
  async function detectRust(workdir) {
45850
- const cargoPath = join29(workdir, "Cargo.toml");
45991
+ const cargoPath = join30(workdir, "Cargo.toml");
45851
45992
  if (!existsSync8(cargoPath))
45852
45993
  return null;
45853
45994
  try {
@@ -45863,8 +46004,8 @@ async function detectRust(workdir) {
45863
46004
  }
45864
46005
  }
45865
46006
  async function detectPython(workdir) {
45866
- const pyproject = join29(workdir, "pyproject.toml");
45867
- const requirements = join29(workdir, "requirements.txt");
46007
+ const pyproject = join30(workdir, "pyproject.toml");
46008
+ const requirements = join30(workdir, "requirements.txt");
45868
46009
  if (!existsSync8(pyproject) && !existsSync8(requirements))
45869
46010
  return null;
45870
46011
  try {
@@ -45883,7 +46024,7 @@ async function detectPython(workdir) {
45883
46024
  }
45884
46025
  }
45885
46026
  async function detectPhp(workdir) {
45886
- const composerPath = join29(workdir, "composer.json");
46027
+ const composerPath = join30(workdir, "composer.json");
45887
46028
  if (!existsSync8(composerPath))
45888
46029
  return null;
45889
46030
  try {
@@ -45896,7 +46037,7 @@ async function detectPhp(workdir) {
45896
46037
  }
45897
46038
  }
45898
46039
  async function detectRuby(workdir) {
45899
- const gemfile = join29(workdir, "Gemfile");
46040
+ const gemfile = join30(workdir, "Gemfile");
45900
46041
  if (!existsSync8(gemfile))
45901
46042
  return null;
45902
46043
  try {
@@ -45908,9 +46049,9 @@ async function detectRuby(workdir) {
45908
46049
  }
45909
46050
  }
45910
46051
  async function detectJvm(workdir) {
45911
- const pom = join29(workdir, "pom.xml");
45912
- const gradle = join29(workdir, "build.gradle");
45913
- const gradleKts = join29(workdir, "build.gradle.kts");
46052
+ const pom = join30(workdir, "pom.xml");
46053
+ const gradle = join30(workdir, "build.gradle");
46054
+ const gradleKts = join30(workdir, "build.gradle.kts");
45914
46055
  if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
45915
46056
  return null;
45916
46057
  try {
@@ -45918,7 +46059,7 @@ async function detectJvm(workdir) {
45918
46059
  const content2 = await Bun.file(pom).text();
45919
46060
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
45920
46061
  const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
45921
- const lang2 = existsSync8(join29(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
46062
+ const lang2 = existsSync8(join30(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
45922
46063
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
45923
46064
  }
45924
46065
  const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
@@ -46172,7 +46313,7 @@ var init_windsurf = __esm(() => {
46172
46313
 
46173
46314
  // src/context/generator.ts
46174
46315
  import { existsSync as existsSync9 } from "fs";
46175
- import { join as join30, relative as relative12 } from "path";
46316
+ import { join as join31, relative as relative12 } from "path";
46176
46317
  async function loadContextContent(options, config2) {
46177
46318
  if (!_generatorDeps.existsSync(options.contextPath)) {
46178
46319
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -46190,7 +46331,7 @@ async function generateFor(agent, options, config2) {
46190
46331
  try {
46191
46332
  const context = await loadContextContent(options, config2);
46192
46333
  const content = generator.generate(context);
46193
- const outputPath = join30(options.outputDir, generator.outputFile);
46334
+ const outputPath = join31(options.outputDir, generator.outputFile);
46194
46335
  validateFilePath(outputPath, options.outputDir);
46195
46336
  if (!options.dryRun) {
46196
46337
  await _generatorDeps.writeFile(outputPath, content);
@@ -46208,7 +46349,7 @@ async function generateAll(options, config2, agentFilter) {
46208
46349
  for (const [agentKey, generator] of entries) {
46209
46350
  try {
46210
46351
  const content = generator.generate(context);
46211
- const outputPath = join30(options.outputDir, generator.outputFile);
46352
+ const outputPath = join31(options.outputDir, generator.outputFile);
46212
46353
  validateFilePath(outputPath, options.outputDir);
46213
46354
  if (!options.dryRun) {
46214
46355
  await _generatorDeps.writeFile(outputPath, content);
@@ -46228,7 +46369,7 @@ async function discoverPackages(repoRoot) {
46228
46369
  const glob = new Bun.Glob(pattern);
46229
46370
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
46230
46371
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
46231
- const pkgAbsolute = join30(repoRoot, pkgRelative);
46372
+ const pkgAbsolute = join31(repoRoot, pkgRelative);
46232
46373
  if (!seen.has(pkgAbsolute)) {
46233
46374
  seen.add(pkgAbsolute);
46234
46375
  packages.push(pkgAbsolute);
@@ -46260,14 +46401,14 @@ async function discoverWorkspacePackages2(repoRoot) {
46260
46401
  }
46261
46402
  }
46262
46403
  }
46263
- const turboPath = join30(repoRoot, "turbo.json");
46404
+ const turboPath = join31(repoRoot, "turbo.json");
46264
46405
  try {
46265
46406
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
46266
46407
  if (Array.isArray(turbo.packages)) {
46267
46408
  await resolveGlobs(turbo.packages);
46268
46409
  }
46269
46410
  } catch {}
46270
- const pkgPath = join30(repoRoot, "package.json");
46411
+ const pkgPath = join31(repoRoot, "package.json");
46271
46412
  try {
46272
46413
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
46273
46414
  const ws = pkg.workspaces;
@@ -46275,7 +46416,7 @@ async function discoverWorkspacePackages2(repoRoot) {
46275
46416
  if (patterns.length > 0)
46276
46417
  await resolveGlobs(patterns);
46277
46418
  } catch {}
46278
- const pnpmPath = join30(repoRoot, "pnpm-workspace.yaml");
46419
+ const pnpmPath = join31(repoRoot, "pnpm-workspace.yaml");
46279
46420
  try {
46280
46421
  const raw = await _generatorDeps.readTextFile(pnpmPath);
46281
46422
  const lines = raw.split(`
@@ -46301,7 +46442,7 @@ async function discoverWorkspacePackages2(repoRoot) {
46301
46442
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
46302
46443
  const resolvedRepoRoot = repoRoot ?? packageDir;
46303
46444
  const relativePkgPath = relative12(resolvedRepoRoot, packageDir);
46304
- const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
46445
+ const contextPath = join31(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
46305
46446
  if (!_generatorDeps.existsSync(contextPath)) {
46306
46447
  return [
46307
46448
  {
@@ -46369,7 +46510,7 @@ var init_generator2 = __esm(() => {
46369
46510
  });
46370
46511
 
46371
46512
  // src/analyze/scanner.ts
46372
- import { join as join31 } from "path";
46513
+ import { join as join32 } from "path";
46373
46514
  function resolveFrameworkAndRunner(language, pkg) {
46374
46515
  if (language === "go")
46375
46516
  return { framework: "", testRunner: "go-test" };
@@ -46391,7 +46532,7 @@ async function scanSourceRoots(workdir) {
46391
46532
  });
46392
46533
  try {
46393
46534
  const language = await deps.detectLanguage(workdir);
46394
- const pkg = await deps.readPackageJson(join31(workdir, "package.json"));
46535
+ const pkg = await deps.readPackageJson(join32(workdir, "package.json"));
46395
46536
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
46396
46537
  return [{ path: ".", language, framework, testRunner }];
46397
46538
  } catch {
@@ -46409,9 +46550,9 @@ async function scanSourceRoots(workdir) {
46409
46550
  packages = packages.slice(0, MAX_SOURCE_ROOTS);
46410
46551
  }
46411
46552
  return Promise.all(packages.map(async (pkgPath) => {
46412
- const pkgDir = pkgPath === "." ? workdir : join31(workdir, pkgPath);
46553
+ const pkgDir = pkgPath === "." ? workdir : join32(workdir, pkgPath);
46413
46554
  const language = await deps.detectLanguage(pkgDir);
46414
- const pkg = await deps.readPackageJson(join31(pkgDir, "package.json"));
46555
+ const pkg = await deps.readPackageJson(join32(pkgDir, "package.json"));
46415
46556
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
46416
46557
  return { path: pkgPath, language, framework, testRunner };
46417
46558
  }));
@@ -46444,7 +46585,7 @@ var init_analyze = __esm(() => {
46444
46585
  });
46445
46586
 
46446
46587
  // src/debate/pre-phase/grounder.ts
46447
- import { join as join32 } from "path";
46588
+ import { join as join33 } from "path";
46448
46589
  async function buildCodebaseContext(workdir) {
46449
46590
  const roots = await _grounderDeps.scanSourceRoots(workdir);
46450
46591
  return buildSourceRootsSection(normalizeRoots(workdir, roots));
@@ -46456,7 +46597,7 @@ function normalizeRoots(workdir, roots) {
46456
46597
  }));
46457
46598
  }
46458
46599
  async function writeManifestArtifact(ctx, manifest) {
46459
- const manifestPath = join32(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46600
+ const manifestPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46460
46601
  await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
46461
46602
  }
46462
46603
  var _grounderDeps, grounderStrategy = async (ctx) => {
@@ -46799,7 +46940,7 @@ function formatSpecDeltas(blockers, manifest) {
46799
46940
 
46800
46941
  // src/debate/verifiers/checks.ts
46801
46942
  import { existsSync as defaultExistsSync } from "fs";
46802
- import { join as join33 } from "path";
46943
+ import { join as join34 } from "path";
46803
46944
  function checkFilesExist(prd, workdir, deps) {
46804
46945
  const existsSync10 = deps?.existsSync ?? defaultExistsSync;
46805
46946
  const findings = [];
@@ -46809,7 +46950,7 @@ function checkFilesExist(prd, workdir, deps) {
46809
46950
  for (const entry of story.contextFiles) {
46810
46951
  const filePath = typeof entry === "string" ? entry : entry.path;
46811
46952
  const factId = typeof entry === "string" ? undefined : entry.factId;
46812
- const absPath = join33(workdir, filePath);
46953
+ const absPath = join34(workdir, filePath);
46813
46954
  if (existsSync10(absPath))
46814
46955
  continue;
46815
46956
  if (factId) {
@@ -46920,7 +47061,7 @@ var init_checks3 = () => {};
46920
47061
 
46921
47062
  // src/debate/verifiers/plan-checklist.ts
46922
47063
  import { existsSync as existsSync10 } from "fs";
46923
- import { join as join34 } from "path";
47064
+ import { join as join35 } from "path";
46924
47065
  function parsePrd(output) {
46925
47066
  if (!output)
46926
47067
  return null;
@@ -46931,7 +47072,7 @@ function parsePrd(output) {
46931
47072
  }
46932
47073
  }
46933
47074
  async function loadManifest(ctx) {
46934
- const manifestPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
47075
+ const manifestPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46935
47076
  const raw = await _planChecklistDeps.readFile(manifestPath);
46936
47077
  if (!raw)
46937
47078
  return null;
@@ -46944,7 +47085,7 @@ async function loadManifest(ctx) {
46944
47085
  }
46945
47086
  }
46946
47087
  async function emitSpecDeltas(ctx, blockers, manifest) {
46947
- const artifactPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
47088
+ const artifactPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
46948
47089
  const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
46949
47090
  await _planChecklistDeps.write(artifactPath, content);
46950
47091
  return artifactPath;
@@ -47290,7 +47431,7 @@ var init_runner_plan_helpers = __esm(() => {
47290
47431
  });
47291
47432
 
47292
47433
  // src/debate/runner-plan.ts
47293
- import { join as join35 } from "path";
47434
+ import { join as join36 } from "path";
47294
47435
  async function runPlan(ctx, taskContext, outputFormat, opts) {
47295
47436
  const logger = _debateSessionDeps.getSafeLogger();
47296
47437
  const config2 = ctx.stageConfig;
@@ -47349,7 +47490,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
47349
47490
  sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
47350
47491
  proposers: ctx.stageConfig.proposers
47351
47492
  });
47352
- const outputPaths = resolved.map((_, i) => join35(opts.outputDir, `prd-debate-${i}.json`));
47493
+ const outputPaths = resolved.map((_, i) => join36(opts.outputDir, `prd-debate-${i}.json`));
47353
47494
  const successful = [];
47354
47495
  let rebuttalList;
47355
47496
  if (selectorKind === "verifier-pick") {
@@ -49592,9 +49733,9 @@ function validateFeatureName(feature) {
49592
49733
 
49593
49734
  // src/plan/critic.ts
49594
49735
  import { mkdir as mkdir6 } from "fs/promises";
49595
- import { dirname as dirname7, join as join38 } from "path";
49736
+ import { dirname as dirname7, join as join39 } from "path";
49596
49737
  async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
49597
- const path7 = join38(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
49738
+ const path7 = join39(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
49598
49739
  await mkdir6(dirname7(path7), { recursive: true });
49599
49740
  await Bun.write(path7, formatSpecDeltas(findings, manifest));
49600
49741
  return path7;
@@ -50807,9 +50948,9 @@ __export(exports_plan_decompose, {
50807
50948
  runReplanLoop: () => runReplanLoop,
50808
50949
  planDecomposeCommand: () => planDecomposeCommand
50809
50950
  });
50810
- import { join as join39 } from "path";
50951
+ import { join as join40 } from "path";
50811
50952
  async function planDecomposeCommand(workdir, config2, options) {
50812
- const prdPath = join39(workdir, ".nax", "features", options.feature, "prd.json");
50953
+ const prdPath = join40(workdir, ".nax", "features", options.feature, "prd.json");
50813
50954
  if (!_planDeps.existsSync(prdPath)) {
50814
50955
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
50815
50956
  stage: "decompose",
@@ -50983,7 +51124,7 @@ var init_plan_decompose = __esm(() => {
50983
51124
 
50984
51125
  // src/cli/plan-runtime.ts
50985
51126
  import { existsSync as existsSync15 } from "fs";
50986
- import { join as join40 } from "path";
51127
+ import { join as join41 } from "path";
50987
51128
  function isRuntimeWithAgentManager(value) {
50988
51129
  return typeof value === "object" && value !== null && "agentManager" in value;
50989
51130
  }
@@ -51035,7 +51176,7 @@ var init_plan_runtime = __esm(() => {
51035
51176
  writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
51036
51177
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
51037
51178
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
51038
- readPackageJson: (workdir) => Bun.file(join40(workdir, "package.json")).json().catch(() => null),
51179
+ readPackageJson: (workdir) => Bun.file(join41(workdir, "package.json")).json().catch(() => null),
51039
51180
  spawnSync: (cmd, opts) => {
51040
51181
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
51041
51182
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -51420,7 +51561,7 @@ var init_metrics = __esm(() => {
51420
51561
 
51421
51562
  // src/commands/common.ts
51422
51563
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
51423
- import { join as join41, resolve as resolve13 } from "path";
51564
+ import { join as join42, resolve as resolve13 } from "path";
51424
51565
  function resolveProject(options = {}) {
51425
51566
  const { dir, feature } = options;
51426
51567
  let projectRoot;
@@ -51428,12 +51569,12 @@ function resolveProject(options = {}) {
51428
51569
  let configPath;
51429
51570
  if (dir) {
51430
51571
  projectRoot = realpathSync3(resolve13(dir));
51431
- naxDir = join41(projectRoot, ".nax");
51572
+ naxDir = join42(projectRoot, ".nax");
51432
51573
  if (!existsSync16(naxDir)) {
51433
51574
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
51434
51575
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
51435
51576
  }
51436
- configPath = join41(naxDir, "config.json");
51577
+ configPath = join42(naxDir, "config.json");
51437
51578
  if (!existsSync16(configPath)) {
51438
51579
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
51439
51580
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -51441,17 +51582,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
51441
51582
  } else {
51442
51583
  const found = findProjectRoot(process.cwd());
51443
51584
  if (!found) {
51444
- const cwdNaxDir = join41(process.cwd(), ".nax");
51585
+ const cwdNaxDir = join42(process.cwd(), ".nax");
51445
51586
  if (existsSync16(cwdNaxDir)) {
51446
- const cwdConfigPath = join41(cwdNaxDir, "config.json");
51587
+ const cwdConfigPath = join42(cwdNaxDir, "config.json");
51447
51588
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
51448
51589
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
51449
51590
  }
51450
51591
  throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
51451
51592
  }
51452
51593
  projectRoot = found;
51453
- naxDir = join41(projectRoot, ".nax");
51454
- configPath = join41(naxDir, "config.json");
51594
+ naxDir = join42(projectRoot, ".nax");
51595
+ configPath = join42(naxDir, "config.json");
51455
51596
  }
51456
51597
  let featureDir;
51457
51598
  if (feature) {
@@ -51460,8 +51601,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
51460
51601
  } catch (error48) {
51461
51602
  throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
51462
51603
  }
51463
- const featuresDir = join41(naxDir, "features");
51464
- featureDir = join41(featuresDir, feature);
51604
+ const featuresDir = join42(naxDir, "features");
51605
+ featureDir = join42(featuresDir, feature);
51465
51606
  if (!existsSync16(featureDir)) {
51466
51607
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
51467
51608
  const availableMsg = availableFeatures.length > 0 ? `
@@ -51494,7 +51635,7 @@ async function resolveProjectAsync(options = {}) {
51494
51635
  }
51495
51636
  const isPlainName = !dir.includes("/") && !dir.includes("\\");
51496
51637
  if (isPlainName) {
51497
- const registryIdentityPath = join41(globalConfigDir(), dir, ".identity");
51638
+ const registryIdentityPath = join42(globalConfigDir(), dir, ".identity");
51498
51639
  const identityFile = Bun.file(registryIdentityPath);
51499
51640
  if (await identityFile.exists()) {
51500
51641
  try {
@@ -51526,12 +51667,12 @@ function findProjectRoot(startDir) {
51526
51667
  let current = resolve13(startDir);
51527
51668
  let depth = 0;
51528
51669
  while (depth < MAX_DIRECTORY_DEPTH) {
51529
- const naxDir = join41(current, ".nax");
51530
- const configPath = join41(naxDir, "config.json");
51670
+ const naxDir = join42(current, ".nax");
51671
+ const configPath = join42(naxDir, "config.json");
51531
51672
  if (existsSync16(configPath)) {
51532
51673
  return realpathSync3(current);
51533
51674
  }
51534
- const parent = join41(current, "..");
51675
+ const parent = join42(current, "..");
51535
51676
  if (parent === current) {
51536
51677
  break;
51537
51678
  }
@@ -52690,10 +52831,10 @@ var init_effectiveness = __esm(() => {
52690
52831
 
52691
52832
  // src/execution/progress.ts
52692
52833
  import { appendFile as appendFile2, mkdir as mkdir7 } from "fs/promises";
52693
- import { join as join44 } from "path";
52834
+ import { join as join45 } from "path";
52694
52835
  async function appendProgress(featureDir, storyId, status, message) {
52695
52836
  await mkdir7(featureDir, { recursive: true });
52696
- const progressPath = join44(featureDir, "progress.txt");
52837
+ const progressPath = join45(featureDir, "progress.txt");
52697
52838
  const timestamp = new Date().toISOString();
52698
52839
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
52699
52840
  `;
@@ -52887,7 +53028,7 @@ var init_completion = __esm(() => {
52887
53028
 
52888
53029
  // src/constitution/loader.ts
52889
53030
  import { existsSync as existsSync19 } from "fs";
52890
- import { join as join45 } from "path";
53031
+ import { join as join46 } from "path";
52891
53032
  function truncateToTokens(text, maxTokens) {
52892
53033
  const maxChars = maxTokens * 3;
52893
53034
  if (text.length <= maxChars) {
@@ -52909,7 +53050,7 @@ async function loadConstitution(projectDir, config2) {
52909
53050
  }
52910
53051
  let combinedContent = "";
52911
53052
  if (!config2.skipGlobal) {
52912
- const globalPath = join45(globalConfigDir(), config2.path);
53053
+ const globalPath = join46(globalConfigDir(), config2.path);
52913
53054
  if (existsSync19(globalPath)) {
52914
53055
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
52915
53056
  const globalFile = Bun.file(validatedPath);
@@ -52919,7 +53060,7 @@ async function loadConstitution(projectDir, config2) {
52919
53060
  }
52920
53061
  }
52921
53062
  }
52922
- const projectPath = join45(projectDir, config2.path);
53063
+ const projectPath = join46(projectDir, config2.path);
52923
53064
  if (existsSync19(projectPath)) {
52924
53065
  const validatedPath = validateFilePath(projectPath, projectDir);
52925
53066
  const projectFile = Bun.file(validatedPath);
@@ -53762,6 +53903,9 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
53762
53903
  });
53763
53904
  }
53764
53905
  logUnifiedReviewPhaseStart(ctx.storyId, opName);
53906
+ if (ctx.storyId) {
53907
+ pipelineEventBus.emit({ type: "story:step", storyId: ctx.storyId, step: opName });
53908
+ }
53765
53909
  const phaseStartedAt = Date.now();
53766
53910
  const scope = ctx.runtime.costAggregator.openScope();
53767
53911
  try {
@@ -54116,6 +54260,7 @@ var init_story_orchestrator = __esm(() => {
54116
54260
  init_logger2();
54117
54261
  init_operations();
54118
54262
  init_call();
54263
+ init_event_bus();
54119
54264
  init_prepare_inputs();
54120
54265
  init_git();
54121
54266
  _storyOrchestratorDeps = {
@@ -54182,7 +54327,7 @@ var init_story_orchestrator = __esm(() => {
54182
54327
  });
54183
54328
 
54184
54329
  // src/execution/build-plan-for-strategy.ts
54185
- import { join as join46 } from "path";
54330
+ import { join as join47 } from "path";
54186
54331
  function requiresInitialRefCapture(strategy) {
54187
54332
  return isThreeSessionStrategy(strategy);
54188
54333
  }
@@ -54228,7 +54373,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
54228
54373
  }
54229
54374
  if (shouldRunRectification(config2) && inputs.rectification) {
54230
54375
  const sink = makeDeclarationSink();
54231
- const packageDir = join46(ctx.packageDir, story.workdir ?? "");
54376
+ const packageDir = join47(ctx.packageDir, story.workdir ?? "");
54232
54377
  const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
54233
54378
  const strategies = [];
54234
54379
  if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
@@ -55780,7 +55925,7 @@ function buildFrontmatter(story, ctx, role) {
55780
55925
  }
55781
55926
 
55782
55927
  // src/cli/prompts-tdd.ts
55783
- import { join as join47 } from "path";
55928
+ import { join as join48 } from "path";
55784
55929
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
55785
55930
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
55786
55931
  TddPromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
@@ -55799,7 +55944,7 @@ ${frontmatter}---
55799
55944
 
55800
55945
  ${session.prompt}`;
55801
55946
  if (outputDir) {
55802
- const promptFile = join47(outputDir, `${story.id}.${session.role}.md`);
55947
+ const promptFile = join48(outputDir, `${story.id}.${session.role}.md`);
55803
55948
  await Bun.write(promptFile, fullOutput);
55804
55949
  logger.info("cli", "Written TDD prompt file", {
55805
55950
  storyId: story.id,
@@ -55815,7 +55960,7 @@ ${"=".repeat(80)}`);
55815
55960
  }
55816
55961
  }
55817
55962
  if (outputDir && ctx.contextMarkdown) {
55818
- const contextFile = join47(outputDir, `${story.id}.context.md`);
55963
+ const contextFile = join48(outputDir, `${story.id}.context.md`);
55819
55964
  const frontmatter = buildFrontmatter(story, ctx);
55820
55965
  const contextOutput = `---
55821
55966
  ${frontmatter}---
@@ -55830,16 +55975,16 @@ var init_prompts_tdd = __esm(() => {
55830
55975
 
55831
55976
  // src/cli/prompts-main.ts
55832
55977
  import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
55833
- import { join as join48 } from "path";
55978
+ import { join as join49 } from "path";
55834
55979
  async function promptsCommand(options) {
55835
55980
  const logger = getLogger();
55836
55981
  const { feature, workdir, config: config2, storyId, outputDir } = options;
55837
- const naxDir = join48(workdir, ".nax");
55982
+ const naxDir = join49(workdir, ".nax");
55838
55983
  if (!existsSync20(naxDir)) {
55839
55984
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
55840
55985
  }
55841
- const featureDir = join48(naxDir, "features", feature);
55842
- const prdPath = join48(featureDir, "prd.json");
55986
+ const featureDir = join49(naxDir, "features", feature);
55987
+ const prdPath = join49(featureDir, "prd.json");
55843
55988
  if (!existsSync20(prdPath)) {
55844
55989
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
55845
55990
  }
@@ -55906,10 +56051,10 @@ ${frontmatter}---
55906
56051
 
55907
56052
  ${ctx.prompt}`;
55908
56053
  if (outputDir) {
55909
- const promptFile = join48(outputDir, `${story.id}.prompt.md`);
56054
+ const promptFile = join49(outputDir, `${story.id}.prompt.md`);
55910
56055
  await Bun.write(promptFile, fullOutput);
55911
56056
  if (ctx.contextMarkdown) {
55912
- const contextFile = join48(outputDir, `${story.id}.context.md`);
56057
+ const contextFile = join49(outputDir, `${story.id}.context.md`);
55913
56058
  const contextOutput = `---
55914
56059
  ${frontmatter}---
55915
56060
 
@@ -55945,12 +56090,12 @@ var init_prompts_main = __esm(() => {
55945
56090
 
55946
56091
  // src/cli/prompts-init.ts
55947
56092
  import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
55948
- import { join as join49 } from "path";
56093
+ import { join as join50 } from "path";
55949
56094
  async function promptsInitCommand(options) {
55950
56095
  const { workdir, force = false, autoWireConfig = true } = options;
55951
- const templatesDir = join49(workdir, ".nax", "templates");
56096
+ const templatesDir = join50(workdir, ".nax", "templates");
55952
56097
  mkdirSync4(templatesDir, { recursive: true });
55953
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join49(templatesDir, f)));
56098
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join50(templatesDir, f)));
55954
56099
  if (existingFiles.length > 0 && !force) {
55955
56100
  _promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
55956
56101
  Pass --force to overwrite existing templates.`);
@@ -55958,7 +56103,7 @@ async function promptsInitCommand(options) {
55958
56103
  }
55959
56104
  const written = [];
55960
56105
  for (const template of TEMPLATE_ROLES) {
55961
- const filePath = join49(templatesDir, template.file);
56106
+ const filePath = join50(templatesDir, template.file);
55962
56107
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
55963
56108
  const content = TEMPLATE_HEADER + roleBody;
55964
56109
  await Bun.write(filePath, content);
@@ -55974,7 +56119,7 @@ async function promptsInitCommand(options) {
55974
56119
  return written;
55975
56120
  }
55976
56121
  async function autoWirePromptsConfig(workdir) {
55977
- const configPath = join49(workdir, "nax.config.json");
56122
+ const configPath = join50(workdir, "nax.config.json");
55978
56123
  if (!existsSync21(configPath)) {
55979
56124
  const exampleConfig = JSON.stringify({
55980
56125
  prompts: {
@@ -56144,7 +56289,7 @@ __export(exports_init_context, {
56144
56289
  });
56145
56290
  import { existsSync as existsSync22 } from "fs";
56146
56291
  import { mkdir as mkdir8 } from "fs/promises";
56147
- import { basename as basename9, join as join50 } from "path";
56292
+ import { basename as basename9, join as join51 } from "path";
56148
56293
  async function findFiles(dir, maxFiles = 200) {
56149
56294
  try {
56150
56295
  const proc = Bun.spawnSync([
@@ -56172,7 +56317,7 @@ async function findFiles(dir, maxFiles = 200) {
56172
56317
  return [];
56173
56318
  }
56174
56319
  async function readPackageManifest(projectRoot) {
56175
- const packageJsonPath = join50(projectRoot, "package.json");
56320
+ const packageJsonPath = join51(projectRoot, "package.json");
56176
56321
  if (!existsSync22(packageJsonPath)) {
56177
56322
  return null;
56178
56323
  }
@@ -56190,7 +56335,7 @@ async function readPackageManifest(projectRoot) {
56190
56335
  }
56191
56336
  }
56192
56337
  async function readReadmeSnippet(projectRoot) {
56193
- const readmePath = join50(projectRoot, "README.md");
56338
+ const readmePath = join51(projectRoot, "README.md");
56194
56339
  if (!existsSync22(readmePath)) {
56195
56340
  return null;
56196
56341
  }
@@ -56208,7 +56353,7 @@ async function detectEntryPoints(projectRoot) {
56208
56353
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
56209
56354
  const found = [];
56210
56355
  for (const candidate of candidates) {
56211
- const path13 = join50(projectRoot, candidate);
56356
+ const path13 = join51(projectRoot, candidate);
56212
56357
  if (existsSync22(path13)) {
56213
56358
  found.push(candidate);
56214
56359
  }
@@ -56219,7 +56364,7 @@ async function detectConfigFiles(projectRoot) {
56219
56364
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
56220
56365
  const found = [];
56221
56366
  for (const candidate of candidates) {
56222
- const path13 = join50(projectRoot, candidate);
56367
+ const path13 = join51(projectRoot, candidate);
56223
56368
  if (existsSync22(path13)) {
56224
56369
  found.push(candidate);
56225
56370
  }
@@ -56380,8 +56525,8 @@ function generatePackageContextTemplate(packagePath) {
56380
56525
  }
56381
56526
  async function initPackage(repoRoot, packagePath, force = false) {
56382
56527
  const logger = getLogger();
56383
- const naxDir = join50(repoRoot, ".nax", "mono", packagePath);
56384
- const contextPath = join50(naxDir, "context.md");
56528
+ const naxDir = join51(repoRoot, ".nax", "mono", packagePath);
56529
+ const contextPath = join51(naxDir, "context.md");
56385
56530
  if (existsSync22(contextPath) && !force) {
56386
56531
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
56387
56532
  return;
@@ -56395,8 +56540,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
56395
56540
  }
56396
56541
  async function initContext(projectRoot, options = {}) {
56397
56542
  const logger = getLogger();
56398
- const naxDir = join50(projectRoot, ".nax");
56399
- const contextPath = join50(naxDir, "context.md");
56543
+ const naxDir = join51(projectRoot, ".nax");
56544
+ const contextPath = join51(naxDir, "context.md");
56400
56545
  if (existsSync22(contextPath) && !options.force) {
56401
56546
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
56402
56547
  return;
@@ -56426,9 +56571,9 @@ var init_init_context = __esm(() => {
56426
56571
 
56427
56572
  // src/cli/init-detect.ts
56428
56573
  import { existsSync as existsSync23, readFileSync } from "fs";
56429
- import { join as join51 } from "path";
56574
+ import { join as join52 } from "path";
56430
56575
  function readPackageJson(projectRoot) {
56431
- const pkgPath = join51(projectRoot, "package.json");
56576
+ const pkgPath = join52(projectRoot, "package.json");
56432
56577
  if (!existsSync23(pkgPath))
56433
56578
  return;
56434
56579
  try {
@@ -56471,41 +56616,41 @@ function detectStack(projectRoot) {
56471
56616
  };
56472
56617
  }
56473
56618
  function detectRuntime(projectRoot) {
56474
- if (existsSync23(join51(projectRoot, "bun.lockb")) || existsSync23(join51(projectRoot, "bunfig.toml"))) {
56619
+ if (existsSync23(join52(projectRoot, "bun.lockb")) || existsSync23(join52(projectRoot, "bunfig.toml"))) {
56475
56620
  return "bun";
56476
56621
  }
56477
- if (existsSync23(join51(projectRoot, "package-lock.json")) || existsSync23(join51(projectRoot, "yarn.lock")) || existsSync23(join51(projectRoot, "pnpm-lock.yaml"))) {
56622
+ if (existsSync23(join52(projectRoot, "package-lock.json")) || existsSync23(join52(projectRoot, "yarn.lock")) || existsSync23(join52(projectRoot, "pnpm-lock.yaml"))) {
56478
56623
  return "node";
56479
56624
  }
56480
56625
  return "unknown";
56481
56626
  }
56482
56627
  function detectLanguage2(projectRoot) {
56483
- if (existsSync23(join51(projectRoot, "tsconfig.json")))
56628
+ if (existsSync23(join52(projectRoot, "tsconfig.json")))
56484
56629
  return "typescript";
56485
- if (existsSync23(join51(projectRoot, "pyproject.toml")) || existsSync23(join51(projectRoot, "setup.py"))) {
56630
+ if (existsSync23(join52(projectRoot, "pyproject.toml")) || existsSync23(join52(projectRoot, "setup.py"))) {
56486
56631
  return "python";
56487
56632
  }
56488
- if (existsSync23(join51(projectRoot, "Cargo.toml")))
56633
+ if (existsSync23(join52(projectRoot, "Cargo.toml")))
56489
56634
  return "rust";
56490
- if (existsSync23(join51(projectRoot, "go.mod")))
56635
+ if (existsSync23(join52(projectRoot, "go.mod")))
56491
56636
  return "go";
56492
56637
  return "unknown";
56493
56638
  }
56494
56639
  function detectLinter(projectRoot) {
56495
- if (existsSync23(join51(projectRoot, "biome.json")) || existsSync23(join51(projectRoot, "biome.jsonc"))) {
56640
+ if (existsSync23(join52(projectRoot, "biome.json")) || existsSync23(join52(projectRoot, "biome.jsonc"))) {
56496
56641
  return "biome";
56497
56642
  }
56498
- if (existsSync23(join51(projectRoot, ".eslintrc.json")) || existsSync23(join51(projectRoot, ".eslintrc.js")) || existsSync23(join51(projectRoot, "eslint.config.js"))) {
56643
+ if (existsSync23(join52(projectRoot, ".eslintrc.json")) || existsSync23(join52(projectRoot, ".eslintrc.js")) || existsSync23(join52(projectRoot, "eslint.config.js"))) {
56499
56644
  return "eslint";
56500
56645
  }
56501
56646
  return "unknown";
56502
56647
  }
56503
56648
  function detectMonorepo(projectRoot) {
56504
- if (existsSync23(join51(projectRoot, "turbo.json")))
56649
+ if (existsSync23(join52(projectRoot, "turbo.json")))
56505
56650
  return "turborepo";
56506
- if (existsSync23(join51(projectRoot, "nx.json")))
56651
+ if (existsSync23(join52(projectRoot, "nx.json")))
56507
56652
  return "nx";
56508
- if (existsSync23(join51(projectRoot, "pnpm-workspace.yaml")))
56653
+ if (existsSync23(join52(projectRoot, "pnpm-workspace.yaml")))
56509
56654
  return "pnpm-workspaces";
56510
56655
  const pkg = readPackageJson(projectRoot);
56511
56656
  if (pkg?.workspaces)
@@ -56649,7 +56794,7 @@ __export(exports_init, {
56649
56794
  });
56650
56795
  import { existsSync as existsSync24 } from "fs";
56651
56796
  import { mkdir as mkdir9 } from "fs/promises";
56652
- import { join as join52 } from "path";
56797
+ import { join as join53 } from "path";
56653
56798
  function validateProjectName(name) {
56654
56799
  if (!name)
56655
56800
  return { valid: false, error: "name must be non-empty" };
@@ -56685,7 +56830,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
56685
56830
  }
56686
56831
  async function updateGitignore(projectRoot) {
56687
56832
  const logger = getLogger();
56688
- const gitignorePath = join52(projectRoot, ".gitignore");
56833
+ const gitignorePath = join53(projectRoot, ".gitignore");
56689
56834
  let existing = "";
56690
56835
  if (existsSync24(gitignorePath)) {
56691
56836
  existing = await Bun.file(gitignorePath).text();
@@ -56771,7 +56916,7 @@ async function initGlobal() {
56771
56916
  await mkdir9(globalDir, { recursive: true });
56772
56917
  logger.info("init", "Created global config directory", { path: globalDir });
56773
56918
  }
56774
- const configPath = join52(globalDir, "config.json");
56919
+ const configPath = join53(globalDir, "config.json");
56775
56920
  if (!existsSync24(configPath)) {
56776
56921
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
56777
56922
  `);
@@ -56779,14 +56924,14 @@ async function initGlobal() {
56779
56924
  } else {
56780
56925
  logger.info("init", "Global config already exists", { path: configPath });
56781
56926
  }
56782
- const constitutionPath = join52(globalDir, "constitution.md");
56927
+ const constitutionPath = join53(globalDir, "constitution.md");
56783
56928
  if (!existsSync24(constitutionPath)) {
56784
56929
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
56785
56930
  logger.info("init", "Created global constitution", { path: constitutionPath });
56786
56931
  } else {
56787
56932
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
56788
56933
  }
56789
- const hooksDir = join52(globalDir, "hooks");
56934
+ const hooksDir = join53(globalDir, "hooks");
56790
56935
  if (!existsSync24(hooksDir)) {
56791
56936
  await mkdir9(hooksDir, { recursive: true });
56792
56937
  logger.info("init", "Created global hooks directory", { path: hooksDir });
@@ -56819,7 +56964,7 @@ async function initProject(projectRoot, options) {
56819
56964
  if (detectedName && !options?.force) {
56820
56965
  const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
56821
56966
  if (collision.collision && collision.existing) {
56822
- const configPath2 = join52(projectDir, "config.json");
56967
+ const configPath2 = join53(projectDir, "config.json");
56823
56968
  throw new NaxError([
56824
56969
  `Project name collision: "${detectedName}"`,
56825
56970
  ` This project: ${projectRoot}`,
@@ -56847,7 +56992,7 @@ async function initProject(projectRoot, options) {
56847
56992
  linter: stack.linter,
56848
56993
  monorepo: stack.monorepo
56849
56994
  });
56850
- const configPath = join52(projectDir, "config.json");
56995
+ const configPath = join53(projectDir, "config.json");
56851
56996
  if (!existsSync24(configPath)) {
56852
56997
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
56853
56998
  `);
@@ -56856,14 +57001,14 @@ async function initProject(projectRoot, options) {
56856
57001
  logger.info("init", "Project config already exists", { path: configPath });
56857
57002
  }
56858
57003
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
56859
- const constitutionPath = join52(projectDir, "constitution.md");
57004
+ const constitutionPath = join53(projectDir, "constitution.md");
56860
57005
  if (!existsSync24(constitutionPath) || options?.force) {
56861
57006
  await Bun.write(constitutionPath, buildConstitution(stack));
56862
57007
  logger.info("init", "Created project constitution", { path: constitutionPath });
56863
57008
  } else {
56864
57009
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
56865
57010
  }
56866
- const hooksDir = join52(projectDir, "hooks");
57011
+ const hooksDir = join53(projectDir, "hooks");
56867
57012
  if (!existsSync24(hooksDir)) {
56868
57013
  await mkdir9(hooksDir, { recursive: true });
56869
57014
  logger.info("init", "Created project hooks directory", { path: hooksDir });
@@ -58298,12 +58443,12 @@ var init_loader4 = __esm(() => {
58298
58443
  });
58299
58444
 
58300
58445
  // src/utils/paths.ts
58301
- import { join as join63 } from "path";
58446
+ import { join as join64 } from "path";
58302
58447
  function getRunsDir() {
58303
- return process.env.NAX_RUNS_DIR ?? join63(globalConfigDir(), "runs");
58448
+ return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
58304
58449
  }
58305
58450
  function getEventsRootDir() {
58306
- return join63(globalConfigDir(), "events");
58451
+ return join64(globalConfigDir(), "events");
58307
58452
  }
58308
58453
  var init_paths3 = __esm(() => {
58309
58454
  init_paths();
@@ -58363,7 +58508,7 @@ var init_command_argv = __esm(() => {
58363
58508
  });
58364
58509
 
58365
58510
  // src/hooks/runner.ts
58366
- import { join as join70 } from "path";
58511
+ import { join as join71 } from "path";
58367
58512
  function createDrainDeadline2(deadlineMs) {
58368
58513
  let timeoutId;
58369
58514
  const promise2 = new Promise((resolve16) => {
@@ -58382,14 +58527,14 @@ async function loadHooksConfig(projectDir, globalDir) {
58382
58527
  let globalHooks = { hooks: {} };
58383
58528
  let projectHooks = { hooks: {} };
58384
58529
  let skipGlobal = false;
58385
- const projectPath = join70(projectDir, "hooks.json");
58530
+ const projectPath = join71(projectDir, "hooks.json");
58386
58531
  const projectData = await loadJsonFile(projectPath, "hooks");
58387
58532
  if (projectData) {
58388
58533
  projectHooks = projectData;
58389
58534
  skipGlobal = projectData.skipGlobal ?? false;
58390
58535
  }
58391
58536
  if (!skipGlobal && globalDir) {
58392
- const globalPath = join70(globalDir, "hooks.json");
58537
+ const globalPath = join71(globalDir, "hooks.json");
58393
58538
  const globalData = await loadJsonFile(globalPath, "hooks");
58394
58539
  if (globalData) {
58395
58540
  globalHooks = globalData;
@@ -58559,7 +58704,7 @@ var package_default;
58559
58704
  var init_package = __esm(() => {
58560
58705
  package_default = {
58561
58706
  name: "@nathapp/nax",
58562
- version: "0.68.7",
58707
+ version: "0.69.0",
58563
58708
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
58564
58709
  type: "module",
58565
58710
  bin: {
@@ -58654,8 +58799,8 @@ var init_version = __esm(() => {
58654
58799
  NAX_VERSION = package_default.version;
58655
58800
  NAX_COMMIT = (() => {
58656
58801
  try {
58657
- if (/^[0-9a-f]{6,10}$/.test("0ba99b3b"))
58658
- return "0ba99b3b";
58802
+ if (/^[0-9a-f]{6,10}$/.test("ce4d8f0e"))
58803
+ return "ce4d8f0e";
58659
58804
  } catch {}
58660
58805
  try {
58661
58806
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -59532,15 +59677,15 @@ var init_acceptance_loop = __esm(() => {
59532
59677
 
59533
59678
  // src/session/scratch-purge.ts
59534
59679
  import { mkdir as mkdir13, rename, rm } from "fs/promises";
59535
- import { dirname as dirname12, join as join71 } from "path";
59680
+ import { dirname as dirname12, join as join72 } from "path";
59536
59681
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
59537
- const sessionsDir = join71(projectDir, ".nax", "features", featureName, "sessions");
59682
+ const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
59538
59683
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
59539
59684
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
59540
59685
  let purged = 0;
59541
59686
  for (const sessionId of sessionIds) {
59542
- const sessionDir = join71(sessionsDir, sessionId);
59543
- const descriptorPath = join71(sessionDir, "descriptor.json");
59687
+ const sessionDir = join72(sessionsDir, sessionId);
59688
+ const descriptorPath = join72(sessionDir, "descriptor.json");
59544
59689
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
59545
59690
  continue;
59546
59691
  let lastActivityAt;
@@ -59556,7 +59701,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
59556
59701
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
59557
59702
  continue;
59558
59703
  if (archiveInsteadOfDelete) {
59559
- const archiveDest = join71(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
59704
+ const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
59560
59705
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
59561
59706
  } else {
59562
59707
  await _scratchPurgeDeps.remove(sessionDir);
@@ -59936,6 +60081,7 @@ async function handleRunCompletion(options) {
59936
60081
  const regressionMode = config2.execution.regressionGate?.mode;
59937
60082
  if (options.skipRegression) {} else if (regressionMode === "deferred" && config2.quality.commands.test) {
59938
60083
  statusWriter.setPostRunPhase("regression", { status: "running" });
60084
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "regression" });
59939
60085
  const regressionResult = await _runCompletionDeps.runDeferredRegression({
59940
60086
  config: config2,
59941
60087
  prd,
@@ -59950,6 +60096,7 @@ async function handleRunCompletion(options) {
59950
60096
  });
59951
60097
  if (regressionResult.success) {
59952
60098
  statusWriter.setPostRunPhase("regression", { status: "passed", lastRunAt });
60099
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: true });
59953
60100
  } else {
59954
60101
  statusWriter.setPostRunPhase("regression", {
59955
60102
  status: "failed",
@@ -59957,6 +60104,7 @@ async function handleRunCompletion(options) {
59957
60104
  affectedStories: regressionResult.affectedStories,
59958
60105
  lastRunAt
59959
60106
  });
60107
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: false });
59960
60108
  for (const storyId of regressionResult.affectedStories) {
59961
60109
  const story = prd.userStories.find((s) => s.id === storyId);
59962
60110
  if (story) {
@@ -60021,6 +60169,9 @@ async function handleRunCompletion(options) {
60021
60169
  }
60022
60170
  let pluginGateFailed = false;
60023
60171
  const deferredReview = options.deferredReview;
60172
+ if (deferredReview !== undefined) {
60173
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "review", passed: !deferredReview.anyFailed });
60174
+ }
60024
60175
  if (deferredReview?.anyFailed) {
60025
60176
  const failedReviewers = deferredReview.reviewerResults.filter((r) => !r.passed).map((r) => r.name);
60026
60177
  pluginGateFailed = config2.review.pluginMode === "gating";
@@ -60288,12 +60439,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
60288
60439
 
60289
60440
  // src/pipeline/subscribers/events-writer.ts
60290
60441
  import { appendFile as appendFile4, mkdir as mkdir14 } from "fs/promises";
60291
- import { basename as basename13, join as join72 } from "path";
60442
+ import { basename as basename13, join as join73 } from "path";
60292
60443
  function wireEventsWriter(bus, feature, runId, workdir) {
60293
60444
  const logger = getSafeLogger();
60294
60445
  const project = basename13(workdir);
60295
- const eventsDir = join72(getEventsRootDir(), project);
60296
- const eventsFile = join72(eventsDir, "events.jsonl");
60446
+ const eventsDir = join73(getEventsRootDir(), project);
60447
+ const eventsFile = join73(eventsDir, "events.jsonl");
60297
60448
  let dirReady = false;
60298
60449
  const write = (line) => {
60299
60450
  return (async () => {
@@ -60474,12 +60625,12 @@ var init_interaction2 = __esm(() => {
60474
60625
 
60475
60626
  // src/pipeline/subscribers/registry.ts
60476
60627
  import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
60477
- import { basename as basename14, join as join73 } from "path";
60628
+ import { basename as basename14, join as join74 } from "path";
60478
60629
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
60479
60630
  const logger = getSafeLogger();
60480
60631
  const project = basename14(workdir);
60481
- const runDir = join73(getRunsDir(), `${project}-${feature}-${runId}`);
60482
- const metaFile = join73(runDir, "meta.json");
60632
+ const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
60633
+ const metaFile = join74(runDir, "meta.json");
60483
60634
  const unsub = bus.on("run:started", (_ev) => {
60484
60635
  return (async () => {
60485
60636
  try {
@@ -60489,8 +60640,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
60489
60640
  project,
60490
60641
  feature,
60491
60642
  workdir,
60492
- statusPath: join73(outputDir, "features", feature, "status.json"),
60493
- eventsDir: join73(outputDir, "features", feature, "runs"),
60643
+ statusPath: join74(outputDir, "features", feature, "status.json"),
60644
+ eventsDir: join74(outputDir, "features", feature, "runs"),
60494
60645
  registeredAt: new Date().toISOString()
60495
60646
  };
60496
60647
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -60736,7 +60887,7 @@ var init_types9 = __esm(() => {
60736
60887
 
60737
60888
  // src/worktree/dependencies.ts
60738
60889
  import { existsSync as existsSync31 } from "fs";
60739
- import { join as join74 } from "path";
60890
+ import { join as join75 } from "path";
60740
60891
  async function prepareWorktreeDependencies(options) {
60741
60892
  const mode = options.config.execution.worktreeDependencies.mode;
60742
60893
  const resolvedCwd = resolveDependencyCwd(options);
@@ -60750,7 +60901,7 @@ async function prepareWorktreeDependencies(options) {
60750
60901
  }
60751
60902
  }
60752
60903
  function resolveDependencyCwd(options) {
60753
- return options.storyWorkdir ? join74(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60904
+ return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60754
60905
  }
60755
60906
  function resolveInheritedDependencies(options, resolvedCwd) {
60756
60907
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -60760,7 +60911,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
60760
60911
  }
60761
60912
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
60762
60913
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
60763
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join74(directory, filename))));
60914
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
60764
60915
  }
60765
60916
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
60766
60917
  const setupCommand = config2.execution.worktreeDependencies.setupCommand;
@@ -60824,13 +60975,13 @@ __export(exports_manager, {
60824
60975
  });
60825
60976
  import { existsSync as existsSync32, symlinkSync } from "fs";
60826
60977
  import { mkdir as mkdir16 } from "fs/promises";
60827
- import { join as join75 } from "path";
60978
+ import { join as join76 } from "path";
60828
60979
 
60829
60980
  class WorktreeManager {
60830
60981
  async ensureGitExcludes(projectRoot) {
60831
60982
  const logger = getSafeLogger();
60832
- const infoDir = join75(projectRoot, ".git", "info");
60833
- const excludePath = join75(infoDir, "exclude");
60983
+ const infoDir = join76(projectRoot, ".git", "info");
60984
+ const excludePath = join76(infoDir, "exclude");
60834
60985
  try {
60835
60986
  await mkdir16(infoDir, { recursive: true });
60836
60987
  let existing = "";
@@ -60857,7 +61008,7 @@ ${missing.join(`
60857
61008
  }
60858
61009
  async create(projectRoot, storyId) {
60859
61010
  validateStoryId(storyId);
60860
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
61011
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
60861
61012
  const branchName = `nax/${storyId}`;
60862
61013
  try {
60863
61014
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -60898,9 +61049,9 @@ ${missing.join(`
60898
61049
  }
60899
61050
  throw new Error(`Failed to create worktree: ${String(error48)}`);
60900
61051
  }
60901
- const envSource = join75(projectRoot, ".env");
61052
+ const envSource = join76(projectRoot, ".env");
60902
61053
  if (existsSync32(envSource)) {
60903
- const envTarget = join75(worktreePath, ".env");
61054
+ const envTarget = join76(worktreePath, ".env");
60904
61055
  try {
60905
61056
  symlinkSync(envSource, envTarget, "file");
60906
61057
  } catch (error48) {
@@ -60911,7 +61062,7 @@ ${missing.join(`
60911
61062
  }
60912
61063
  async remove(projectRoot, storyId) {
60913
61064
  validateStoryId(storyId);
60914
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
61065
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
60915
61066
  const branchName = `nax/${storyId}`;
60916
61067
  try {
60917
61068
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -61723,10 +61874,10 @@ var init_merge_conflict_rectify = __esm(() => {
61723
61874
  });
61724
61875
 
61725
61876
  // src/execution/pipeline-result-handler.ts
61726
- import { join as join76 } from "path";
61877
+ import { join as join77 } from "path";
61727
61878
  async function removeWorktreeDirectory(projectRoot, storyId) {
61728
61879
  const logger = getSafeLogger();
61729
- const worktreePath = join76(projectRoot, ".nax-wt", storyId);
61880
+ const worktreePath = join77(projectRoot, ".nax-wt", storyId);
61730
61881
  try {
61731
61882
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
61732
61883
  cwd: projectRoot,
@@ -61943,7 +62094,7 @@ var init_pipeline_result_handler = __esm(() => {
61943
62094
 
61944
62095
  // src/execution/iteration-runner.ts
61945
62096
  import { existsSync as existsSync33 } from "fs";
61946
- import { join as join77 } from "path";
62097
+ import { join as join78 } from "path";
61947
62098
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
61948
62099
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
61949
62100
  if (ctx.dryRun) {
@@ -61968,7 +62119,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
61968
62119
  const storyStartTime = Date.now();
61969
62120
  let effectiveWorkdir = ctx.workdir;
61970
62121
  if (ctx.config.execution.storyIsolation === "worktree") {
61971
- const worktreePath = join77(ctx.workdir, ".nax-wt", story.id);
62122
+ const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
61972
62123
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
61973
62124
  if (!worktreeExists) {
61974
62125
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -61988,7 +62139,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
61988
62139
  }
61989
62140
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
61990
62141
  const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
61991
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join77(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
62142
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
61992
62143
  let dependencyContext;
61993
62144
  if (ctx.config.execution.storyIsolation === "worktree") {
61994
62145
  try {
@@ -62015,7 +62166,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62015
62166
  };
62016
62167
  }
62017
62168
  }
62018
- const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join77(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join77(ctx.workdir, story.workdir) : ctx.workdir;
62169
+ const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join78(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join78(ctx.workdir, story.workdir) : ctx.workdir;
62019
62170
  const pipelineContext = {
62020
62171
  config: effectiveConfig,
62021
62172
  rootConfig: ctx.config,
@@ -62217,7 +62368,7 @@ __export(exports_parallel_worker, {
62217
62368
  buildWorktreePipelineContext: () => buildWorktreePipelineContext,
62218
62369
  _parallelWorkerDeps: () => _parallelWorkerDeps
62219
62370
  });
62220
- import { join as join78 } from "path";
62371
+ import { join as join79 } from "path";
62221
62372
  function buildWorktreePipelineContext(base, _story) {
62222
62373
  return { ...base, prd: structuredClone(base.prd) };
62223
62374
  }
@@ -62240,7 +62391,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
62240
62391
  story,
62241
62392
  stories: [story],
62242
62393
  projectDir: context.projectDir,
62243
- workdir: dependencyContext.cwd ?? (story.workdir ? join78(worktreePath, story.workdir) : worktreePath),
62394
+ workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
62244
62395
  worktreeDependencyContext: dependencyContext,
62245
62396
  routing,
62246
62397
  storyGitRef: storyGitRef ?? undefined
@@ -62617,6 +62768,7 @@ async function executeUnified(ctx, initialPrd) {
62617
62768
  if (isComplete(prd)) {
62618
62769
  logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
62619
62770
  const naxIgnoreIndex = await getRunNaxIgnoreIndex(prd);
62771
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
62620
62772
  deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
62621
62773
  return buildResult2("completed");
62622
62774
  }
@@ -62670,6 +62822,7 @@ async function executeUnified(ctx, initialPrd) {
62670
62822
  return buildResult2("pre-merge-aborted");
62671
62823
  }
62672
62824
  logger?.debug("execution", "Running deferred review");
62825
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
62673
62826
  deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
62674
62827
  logger?.debug("execution", "Deferred review done \u2014 returning completed");
62675
62828
  return buildResult2("completed");
@@ -63125,7 +63278,7 @@ async function writeStatusFile(filePath, status) {
63125
63278
  var init_status_file = () => {};
63126
63279
 
63127
63280
  // src/execution/status-writer.ts
63128
- import { join as join79 } from "path";
63281
+ import { join as join80 } from "path";
63129
63282
 
63130
63283
  class StatusWriter {
63131
63284
  statusFile;
@@ -63244,7 +63397,7 @@ class StatusWriter {
63244
63397
  if (!this._prd)
63245
63398
  return;
63246
63399
  const safeLogger = getSafeLogger();
63247
- const featureStatusPath = join79(featureDir, "status.json");
63400
+ const featureStatusPath = join80(featureDir, "status.json");
63248
63401
  const write = async () => {
63249
63402
  try {
63250
63403
  const base = this.getSnapshot(totalCost, iterations);
@@ -63678,7 +63831,7 @@ __export(exports_run_initialization, {
63678
63831
  initializeRun: () => initializeRun,
63679
63832
  _reconcileDeps: () => _reconcileDeps
63680
63833
  });
63681
- import { join as join80 } from "path";
63834
+ import { join as join81 } from "path";
63682
63835
  async function reconcileState(prd, prdPath, workdir, config2) {
63683
63836
  const logger = getSafeLogger();
63684
63837
  let reconciledCount = 0;
@@ -63695,7 +63848,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
63695
63848
  });
63696
63849
  continue;
63697
63850
  }
63698
- const effectiveWorkdir = story.workdir ? join80(workdir, story.workdir) : workdir;
63851
+ const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
63699
63852
  try {
63700
63853
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
63701
63854
  if (!reviewResult.success) {
@@ -93488,7 +93641,7 @@ __export(exports_curator, {
93488
93641
  });
93489
93642
  import { readdirSync as readdirSync8 } from "fs";
93490
93643
  import { unlink as unlink4 } from "fs/promises";
93491
- import { basename as basename15, join as join82 } from "path";
93644
+ import { basename as basename15, join as join83 } from "path";
93492
93645
  function getProjectKey(config2, projectDir) {
93493
93646
  return config2.name?.trim() || basename15(projectDir);
93494
93647
  }
@@ -93571,7 +93724,7 @@ async function curatorStatus(options) {
93571
93724
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93572
93725
  const projectKey = getProjectKey(config2, resolved.projectDir);
93573
93726
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93574
- const runsDir = join82(outputDir, "runs");
93727
+ const runsDir = join83(outputDir, "runs");
93575
93728
  const runIds = listRunIds(runsDir);
93576
93729
  let runId;
93577
93730
  if (options.run) {
@@ -93588,8 +93741,8 @@ async function curatorStatus(options) {
93588
93741
  runId = runIds[runIds.length - 1];
93589
93742
  }
93590
93743
  console.log(`Run: ${runId}`);
93591
- const runDir = join82(runsDir, runId);
93592
- const observationsPath = join82(runDir, "observations.jsonl");
93744
+ const runDir = join83(runsDir, runId);
93745
+ const observationsPath = join83(runDir, "observations.jsonl");
93593
93746
  const observations = await parseObservations(observationsPath);
93594
93747
  const counts = new Map;
93595
93748
  for (const obs of observations) {
@@ -93599,7 +93752,7 @@ async function curatorStatus(options) {
93599
93752
  for (const [kind, count] of counts.entries()) {
93600
93753
  console.log(` ${kind}: ${count}`);
93601
93754
  }
93602
- const proposalsPath = join82(runDir, "curator-proposals.md");
93755
+ const proposalsPath = join83(runDir, "curator-proposals.md");
93603
93756
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93604
93757
  if (proposalText !== null) {
93605
93758
  console.log("");
@@ -93613,8 +93766,8 @@ async function curatorCommit(options) {
93613
93766
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93614
93767
  const projectKey = getProjectKey(config2, resolved.projectDir);
93615
93768
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93616
- const runDir = join82(outputDir, "runs", options.runId);
93617
- const proposalsPath = join82(runDir, "curator-proposals.md");
93769
+ const runDir = join83(outputDir, "runs", options.runId);
93770
+ const proposalsPath = join83(runDir, "curator-proposals.md");
93618
93771
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93619
93772
  if (proposalText === null) {
93620
93773
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -93630,7 +93783,7 @@ async function curatorCommit(options) {
93630
93783
  const dropFileState = new Map;
93631
93784
  const skippedDrops = new Set;
93632
93785
  for (const drop2 of drops) {
93633
- const targetPath = join82(resolved.projectDir, drop2.canonicalFile);
93786
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
93634
93787
  if (!dropFileState.has(targetPath)) {
93635
93788
  const fileExists2 = await Bun.file(targetPath).exists();
93636
93789
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -93664,7 +93817,7 @@ async function curatorCommit(options) {
93664
93817
  if (skippedDrops.has(drop2)) {
93665
93818
  continue;
93666
93819
  }
93667
- const targetPath = join82(resolved.projectDir, drop2.canonicalFile);
93820
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
93668
93821
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
93669
93822
  const filtered = filterDropContent(existing, drop2.description);
93670
93823
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -93673,7 +93826,7 @@ async function curatorCommit(options) {
93673
93826
  }
93674
93827
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
93675
93828
  for (const add2 of adds) {
93676
- const targetPath = join82(resolved.projectDir, add2.canonicalFile);
93829
+ const targetPath = join83(resolved.projectDir, add2.canonicalFile);
93677
93830
  const content = buildAddContent(add2);
93678
93831
  await _curatorCmdDeps.appendFile(targetPath, content);
93679
93832
  modifiedFiles.add(targetPath);
@@ -93710,7 +93863,7 @@ async function curatorDryrun(options) {
93710
93863
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93711
93864
  const projectKey = getProjectKey(config2, resolved.projectDir);
93712
93865
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93713
- const runsDir = join82(outputDir, "runs");
93866
+ const runsDir = join83(outputDir, "runs");
93714
93867
  const runIds = listRunIds(runsDir);
93715
93868
  if (runIds.length === 0) {
93716
93869
  console.log("No runs found.");
@@ -93721,7 +93874,7 @@ async function curatorDryrun(options) {
93721
93874
  console.log(`Run ${options.run} not found in ${runsDir}.`);
93722
93875
  return;
93723
93876
  }
93724
- const observationsPath = join82(runsDir, runId, "observations.jsonl");
93877
+ const observationsPath = join83(runsDir, runId, "observations.jsonl");
93725
93878
  const observations = await parseObservations(observationsPath);
93726
93879
  const thresholds = getThresholds(config2);
93727
93880
  const proposals = runHeuristics(observations, thresholds);
@@ -93762,12 +93915,12 @@ async function curatorGc(options) {
93762
93915
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
93763
93916
  const projectKey = getProjectKey(config2, resolved.projectDir);
93764
93917
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93765
- const perRunsDir = join82(outputDir, "runs");
93918
+ const perRunsDir = join83(outputDir, "runs");
93766
93919
  for (const runId of uniqueRunIds) {
93767
93920
  if (!keepSet.has(runId)) {
93768
- const runDir = join82(perRunsDir, runId);
93769
- await _curatorCmdDeps.removeFile(join82(runDir, "observations.jsonl"));
93770
- await _curatorCmdDeps.removeFile(join82(runDir, "curator-proposals.md"));
93921
+ const runDir = join83(perRunsDir, runId);
93922
+ await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
93923
+ await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
93771
93924
  }
93772
93925
  }
93773
93926
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -93812,7 +93965,7 @@ var init_curator2 = __esm(() => {
93812
93965
  init_source();
93813
93966
  import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
93814
93967
  import { homedir as homedir3 } from "os";
93815
- import { basename as basename16, join as join83 } from "path";
93968
+ import { basename as basename16, join as join84 } from "path";
93816
93969
 
93817
93970
  // node_modules/commander/esm.mjs
93818
93971
  var import__ = __toESM(require_commander(), 1);
@@ -93836,12 +93989,12 @@ init_errors();
93836
93989
  init_operations();
93837
93990
 
93838
93991
  // src/plan/strategies/context-builder.ts
93839
- import { join as join37 } from "path";
93992
+ import { join as join38 } from "path";
93840
93993
  init_config();
93841
93994
  init_errors();
93842
93995
  init_interaction();
93843
93996
  async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93844
- const naxDir = join37(workdir, ".nax");
93997
+ const naxDir = join38(workdir, ".nax");
93845
93998
  if (!deps.existsSync(naxDir)) {
93846
93999
  throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
93847
94000
  stage: "plan",
@@ -93849,8 +94002,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93849
94002
  });
93850
94003
  }
93851
94004
  validateFeatureName(options.feature);
93852
- const outputDir = join37(naxDir, "features", options.feature);
93853
- const outputPath = join37(outputDir, "prd.json");
94005
+ const outputDir = join38(naxDir, "features", options.feature);
94006
+ const outputPath = join38(outputDir, "prd.json");
93854
94007
  const [specContent, sourceRoots, pkg] = await Promise.all([
93855
94008
  deps.readFile(options.from),
93856
94009
  deps.scanSourceRoots(workdir),
@@ -93865,7 +94018,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93865
94018
  ...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
93866
94019
  ];
93867
94020
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
93868
- const packageJson = await deps.readPackageJsonAt(join37(workdir, relativePath, "package.json"));
94021
+ const packageJson = await deps.readPackageJsonAt(join38(workdir, relativePath, "package.json"));
93869
94022
  return buildPackageSummary(relativePath, packageJson);
93870
94023
  }));
93871
94024
  const projectName = detectProjectName(workdir, pkg);
@@ -94182,7 +94335,8 @@ class RefinePlanStrategy {
94182
94335
  packages: ctx.relativePackages,
94183
94336
  packageDetails: ctx.packageDetails,
94184
94337
  projectProfile: ctx.config.project,
94185
- specGuard: ctx.config.plan.specGuard ?? false
94338
+ specGuard: ctx.config.plan.specGuard ?? false,
94339
+ workdir: ctx.workdir
94186
94340
  });
94187
94341
  return writeOrRecoverPrd(ctx, prd);
94188
94342
  } catch (err) {
@@ -94467,7 +94621,7 @@ init_interaction();
94467
94621
  init_prd();
94468
94622
  init_runtime();
94469
94623
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
94470
- import { basename as basename7, join as join42, resolve as resolve14 } from "path";
94624
+ import { basename as basename7, join as join43, resolve as resolve14 } from "path";
94471
94625
  var _statusFeaturesDeps = {
94472
94626
  projectOutputDir,
94473
94627
  loadConfig
@@ -94481,7 +94635,7 @@ function isPidAlive(pid) {
94481
94635
  }
94482
94636
  }
94483
94637
  async function loadStatusFile(featureDir) {
94484
- const statusPath = join42(featureDir, "status.json");
94638
+ const statusPath = join43(featureDir, "status.json");
94485
94639
  if (!existsSync17(statusPath)) {
94486
94640
  return null;
94487
94641
  }
@@ -94496,7 +94650,7 @@ async function loadProjectStatusFile(projectDir) {
94496
94650
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94497
94651
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94498
94652
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94499
- const statusPath = join42(outputDir, "status.json");
94653
+ const statusPath = join43(outputDir, "status.json");
94500
94654
  if (!existsSync17(statusPath)) {
94501
94655
  return null;
94502
94656
  }
@@ -94508,7 +94662,7 @@ async function loadProjectStatusFile(projectDir) {
94508
94662
  }
94509
94663
  }
94510
94664
  async function getFeatureSummary(featureName, featureDir) {
94511
- const prdPath = join42(featureDir, "prd.json");
94665
+ const prdPath = join43(featureDir, "prd.json");
94512
94666
  if (!existsSync17(prdPath)) {
94513
94667
  return {
94514
94668
  name: featureName,
@@ -94551,7 +94705,7 @@ async function getFeatureSummary(featureName, featureDir) {
94551
94705
  };
94552
94706
  }
94553
94707
  }
94554
- const runsDir = join42(featureDir, "runs");
94708
+ const runsDir = join43(featureDir, "runs");
94555
94709
  if (existsSync17(runsDir)) {
94556
94710
  const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
94557
94711
  if (runs.length > 0) {
@@ -94565,7 +94719,7 @@ async function displayAllFeatures(projectDir) {
94565
94719
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94566
94720
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94567
94721
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94568
- const featuresDir = join42(outputDir, "features");
94722
+ const featuresDir = join43(outputDir, "features");
94569
94723
  if (!existsSync17(featuresDir)) {
94570
94724
  console.log(source_default.dim("No features found."));
94571
94725
  return;
@@ -94606,7 +94760,7 @@ async function displayAllFeatures(projectDir) {
94606
94760
  console.log();
94607
94761
  }
94608
94762
  }
94609
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join42(featuresDir, name))));
94763
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join43(featuresDir, name))));
94610
94764
  console.log(source_default.bold(`\uD83D\uDCCA Features
94611
94765
  `));
94612
94766
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -94632,7 +94786,7 @@ async function displayAllFeatures(projectDir) {
94632
94786
  console.log();
94633
94787
  }
94634
94788
  async function displayFeatureDetails(featureName, featureDir) {
94635
- const prdPath = join42(featureDir, "prd.json");
94789
+ const prdPath = join43(featureDir, "prd.json");
94636
94790
  if (!existsSync17(prdPath)) {
94637
94791
  console.log(source_default.bold(`
94638
94792
  \uD83D\uDCCA ${featureName}
@@ -94778,7 +94932,7 @@ async function displayFeatureStatus(options = {}) {
94778
94932
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94779
94933
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94780
94934
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94781
- featureDir = join42(outputDir, "features", options.feature);
94935
+ featureDir = join43(outputDir, "features", options.feature);
94782
94936
  } else {
94783
94937
  const resolved = resolveProject({ feature: options.feature });
94784
94938
  if (!resolved.featureDir) {
@@ -94798,7 +94952,7 @@ init_errors();
94798
94952
  init_logger2();
94799
94953
  init_runtime();
94800
94954
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
94801
- import { basename as basename8, join as join43 } from "path";
94955
+ import { basename as basename8, join as join44 } from "path";
94802
94956
  async function resolveOutputDir2(workdir, override) {
94803
94957
  if (override)
94804
94958
  return override;
@@ -94822,7 +94976,7 @@ async function runsListCommand(options) {
94822
94976
  const logger = getLogger();
94823
94977
  const { feature, workdir } = options;
94824
94978
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
94825
- const runsDir = join43(outputDir, "features", feature, "runs");
94979
+ const runsDir = join44(outputDir, "features", feature, "runs");
94826
94980
  if (!existsSync18(runsDir)) {
94827
94981
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
94828
94982
  return;
@@ -94834,7 +94988,7 @@ async function runsListCommand(options) {
94834
94988
  }
94835
94989
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
94836
94990
  for (const file3 of files.sort().reverse()) {
94837
- const logPath = join43(runsDir, file3);
94991
+ const logPath = join44(runsDir, file3);
94838
94992
  const entries = await parseRunLog(logPath);
94839
94993
  const startEvent = entries.find((e) => e.message === "run.start");
94840
94994
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -94861,7 +95015,7 @@ async function runsShowCommand(options) {
94861
95015
  const logger = getLogger();
94862
95016
  const { runId, feature, workdir } = options;
94863
95017
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
94864
- const logPath = join43(outputDir, "features", feature, "runs", `${runId}.jsonl`);
95018
+ const logPath = join44(outputDir, "features", feature, "runs", `${runId}.jsonl`);
94865
95019
  if (!existsSync18(logPath)) {
94866
95020
  logger.error("cli", "Run not found", { runId, feature, logPath });
94867
95021
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -94976,7 +95130,7 @@ init_source();
94976
95130
  init_loader();
94977
95131
  init_generator2();
94978
95132
  import { existsSync as existsSync25 } from "fs";
94979
- import { join as join57 } from "path";
95133
+ import { join as join58 } from "path";
94980
95134
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
94981
95135
  async function generateCommand(options) {
94982
95136
  const workdir = options.dir ?? process.cwd();
@@ -95019,7 +95173,7 @@ async function generateCommand(options) {
95019
95173
  return;
95020
95174
  }
95021
95175
  if (options.package) {
95022
- const packageDir = join57(workdir, options.package);
95176
+ const packageDir = join58(workdir, options.package);
95023
95177
  if (dryRun) {
95024
95178
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95025
95179
  }
@@ -95039,8 +95193,8 @@ async function generateCommand(options) {
95039
95193
  process.exit(1);
95040
95194
  return;
95041
95195
  }
95042
- const contextPath = options.context ? join57(workdir, options.context) : join57(workdir, ".nax/context.md");
95043
- const outputDir = options.output ? join57(workdir, options.output) : workdir;
95196
+ const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
95197
+ const outputDir = options.output ? join58(workdir, options.output) : workdir;
95044
95198
  const autoInject = !options.noAutoInject;
95045
95199
  if (!existsSync25(contextPath)) {
95046
95200
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -95146,7 +95300,7 @@ async function generateCommand(options) {
95146
95300
  // src/cli/config-display.ts
95147
95301
  init_loader();
95148
95302
  import { existsSync as existsSync27 } from "fs";
95149
- import { join as join59 } from "path";
95303
+ import { join as join60 } from "path";
95150
95304
 
95151
95305
  // src/cli/config-descriptions.ts
95152
95306
  var FIELD_DESCRIPTIONS = {
@@ -95398,7 +95552,7 @@ function deepEqual(a, b) {
95398
95552
  init_defaults();
95399
95553
  init_loader();
95400
95554
  import { existsSync as existsSync26 } from "fs";
95401
- import { join as join58 } from "path";
95555
+ import { join as join59 } from "path";
95402
95556
  async function loadConfigFile(path18) {
95403
95557
  if (!existsSync26(path18))
95404
95558
  return null;
@@ -95420,7 +95574,7 @@ async function loadProjectConfig() {
95420
95574
  const projectDir = findProjectDir();
95421
95575
  if (!projectDir)
95422
95576
  return null;
95423
- const projectPath = join58(projectDir, "config.json");
95577
+ const projectPath = join59(projectDir, "config.json");
95424
95578
  return await loadConfigFile(projectPath);
95425
95579
  }
95426
95580
 
@@ -95480,7 +95634,7 @@ async function configCommand(config2, options = {}) {
95480
95634
  function determineConfigSources() {
95481
95635
  const globalPath = globalConfigPath();
95482
95636
  const projectDir = findProjectDir();
95483
- const projectPath = projectDir ? join59(projectDir, "config.json") : null;
95637
+ const projectPath = projectDir ? join60(projectDir, "config.json") : null;
95484
95638
  return {
95485
95639
  global: fileExists(globalPath) ? globalPath : null,
95486
95640
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -95629,15 +95783,15 @@ init_paths();
95629
95783
  init_profile();
95630
95784
  import { mkdirSync as mkdirSync5 } from "fs";
95631
95785
  import { readdirSync as readdirSync5 } from "fs";
95632
- import { join as join60 } from "path";
95786
+ import { join as join61 } from "path";
95633
95787
  var _profileCLIDeps = {
95634
95788
  env: process.env
95635
95789
  };
95636
95790
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
95637
95791
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
95638
95792
  async function profileListCommand(startDir) {
95639
- const globalProfilesDir = join60(globalConfigDir(), "profiles");
95640
- const projectProfilesDir = join60(projectConfigDir(startDir), "profiles");
95793
+ const globalProfilesDir = join61(globalConfigDir(), "profiles");
95794
+ const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
95641
95795
  const globalProfiles = scanProfileDir(globalProfilesDir);
95642
95796
  const projectProfiles = scanProfileDir(projectProfilesDir);
95643
95797
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -95696,7 +95850,7 @@ function maskProfileValues(obj) {
95696
95850
  return result;
95697
95851
  }
95698
95852
  async function profileUseCommand(profileName, startDir) {
95699
- const configPath = join60(projectConfigDir(startDir), "config.json");
95853
+ const configPath = join61(projectConfigDir(startDir), "config.json");
95700
95854
  const configFile = Bun.file(configPath);
95701
95855
  let existing = {};
95702
95856
  if (await configFile.exists()) {
@@ -95715,8 +95869,8 @@ async function profileCurrentCommand(startDir) {
95715
95869
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
95716
95870
  }
95717
95871
  async function profileCreateCommand(profileName, startDir) {
95718
- const profilesDir = join60(projectConfigDir(startDir), "profiles");
95719
- const profilePath = join60(profilesDir, `${profileName}.json`);
95872
+ const profilesDir = join61(projectConfigDir(startDir), "profiles");
95873
+ const profilePath = join61(profilesDir, `${profileName}.json`);
95720
95874
  const profileFile = Bun.file(profilePath);
95721
95875
  if (await profileFile.exists()) {
95722
95876
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -95838,7 +95992,7 @@ async function contextInspectCommand(options) {
95838
95992
  init_canonical_loader();
95839
95993
  init_errors();
95840
95994
  import { mkdir as mkdir12 } from "fs/promises";
95841
- import { basename as basename12, join as join61 } from "path";
95995
+ import { basename as basename12, join as join62 } from "path";
95842
95996
  var _rulesCLIDeps = {
95843
95997
  readFile: async (path18) => Bun.file(path18).text(),
95844
95998
  writeFile: async (path18, content) => {
@@ -95847,7 +96001,7 @@ var _rulesCLIDeps = {
95847
96001
  fileExists: async (path18) => Bun.file(path18).exists(),
95848
96002
  globInDir: (dir) => {
95849
96003
  try {
95850
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join61(dir, f));
96004
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
95851
96005
  } catch {
95852
96006
  return [];
95853
96007
  }
@@ -95896,7 +96050,7 @@ ${r.content}`).join(`
95896
96050
  `);
95897
96051
  const shimContent = `${header + body}
95898
96052
  `;
95899
- const shimPath = join61(workdir, shimFileName);
96053
+ const shimPath = join62(workdir, shimFileName);
95900
96054
  if (options.dryRun) {
95901
96055
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
95902
96056
  return;
@@ -95925,14 +96079,14 @@ function neutralizeContent(content) {
95925
96079
  }
95926
96080
  async function collectMigrationSources(workdir) {
95927
96081
  const sources = [];
95928
- const claudeMdPath = join61(workdir, "CLAUDE.md");
96082
+ const claudeMdPath = join62(workdir, "CLAUDE.md");
95929
96083
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
95930
96084
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
95931
96085
  if (content.trim()) {
95932
96086
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
95933
96087
  }
95934
96088
  }
95935
- const rulesDir = join61(workdir, ".claude", "rules");
96089
+ const rulesDir = join62(workdir, ".claude", "rules");
95936
96090
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
95937
96091
  for (const filePath of ruleFiles) {
95938
96092
  try {
@@ -95952,7 +96106,7 @@ async function rulesMigrateCommand(options) {
95952
96106
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
95953
96107
  return;
95954
96108
  }
95955
- const targetDir = join61(workdir, CANONICAL_RULES_DIR);
96109
+ const targetDir = join62(workdir, CANONICAL_RULES_DIR);
95956
96110
  if (!options.dryRun) {
95957
96111
  try {
95958
96112
  await _rulesCLIDeps.mkdir(targetDir);
@@ -95963,7 +96117,7 @@ async function rulesMigrateCommand(options) {
95963
96117
  let written = 0;
95964
96118
  let skipped = 0;
95965
96119
  for (const { sourcePath, targetFileName, content } of sources) {
95966
- const targetPath = join61(targetDir, targetFileName);
96120
+ const targetPath = join62(targetDir, targetFileName);
95967
96121
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
95968
96122
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
95969
96123
  skipped++;
@@ -96002,7 +96156,7 @@ function collectCanonicalRuleRoots(workdir) {
96002
96156
  const packageRel = normalized.slice(0, idx);
96003
96157
  if (!packageRel)
96004
96158
  continue;
96005
- roots.add(join61(workdir, packageRel));
96159
+ roots.add(join62(workdir, packageRel));
96006
96160
  }
96007
96161
  return [...roots].sort();
96008
96162
  }
@@ -96024,7 +96178,7 @@ init_logger2();
96024
96178
  init_detect2();
96025
96179
  init_workspace();
96026
96180
  init_common();
96027
- import { join as join62 } from "path";
96181
+ import { join as join63 } from "path";
96028
96182
  function resolveEffective(detected, configPatterns) {
96029
96183
  if (configPatterns !== undefined)
96030
96184
  return "config";
@@ -96109,7 +96263,7 @@ async function detectCommand(options) {
96109
96263
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96110
96264
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96111
96265
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96112
- const pkgConfigPath = join62(workdir, ".nax", "mono", dir, "config.json");
96266
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96113
96267
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96114
96268
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96115
96269
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -96163,13 +96317,13 @@ async function detectCommand(options) {
96163
96317
  if (rootDetected.confidence === "empty") {
96164
96318
  console.log(source_default.yellow(" root: skipped (empty detection)"));
96165
96319
  } else {
96166
- const rootConfigPath = join62(workdir, ".nax", "config.json");
96320
+ const rootConfigPath = join63(workdir, ".nax", "config.json");
96167
96321
  try {
96168
96322
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
96169
96323
  if (status === "skipped") {
96170
96324
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
96171
96325
  } else {
96172
- console.log(source_default.green(` root: ${status} \u2192 ${join62(".nax", "config.json")}`));
96326
+ console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
96173
96327
  }
96174
96328
  } catch (err) {
96175
96329
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -96182,13 +96336,13 @@ async function detectCommand(options) {
96182
96336
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
96183
96337
  continue;
96184
96338
  }
96185
- const pkgConfigPath = join62(workdir, ".nax", "mono", dir, "config.json");
96339
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96186
96340
  try {
96187
96341
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
96188
96342
  if (status === "skipped") {
96189
96343
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
96190
96344
  } else {
96191
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join62(".nax", "mono", dir, "config.json")}`));
96345
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
96192
96346
  }
96193
96347
  } catch (err) {
96194
96348
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -96206,19 +96360,19 @@ async function detectCommand(options) {
96206
96360
  // src/commands/logs.ts
96207
96361
  init_common();
96208
96362
  import { existsSync as existsSync29 } from "fs";
96209
- import { join as join66 } from "path";
96363
+ import { join as join67 } from "path";
96210
96364
 
96211
96365
  // src/commands/logs-formatter.ts
96212
96366
  init_source();
96213
96367
  init_formatter();
96214
96368
  import { readdirSync as readdirSync7 } from "fs";
96215
- import { join as join65 } from "path";
96369
+ import { join as join66 } from "path";
96216
96370
 
96217
96371
  // src/commands/logs-reader.ts
96218
96372
  init_paths3();
96219
96373
  import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
96220
96374
  import { readdir as readdir4 } from "fs/promises";
96221
- import { join as join64 } from "path";
96375
+ import { join as join65 } from "path";
96222
96376
  var _logsReaderDeps = {
96223
96377
  getRunsDir
96224
96378
  };
@@ -96232,7 +96386,7 @@ async function resolveRunFileFromRegistry(runId) {
96232
96386
  }
96233
96387
  let matched = null;
96234
96388
  for (const entry of entries) {
96235
- const metaPath = join64(runsDir, entry, "meta.json");
96389
+ const metaPath = join65(runsDir, entry, "meta.json");
96236
96390
  try {
96237
96391
  const meta3 = await Bun.file(metaPath).json();
96238
96392
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -96254,14 +96408,14 @@ async function resolveRunFileFromRegistry(runId) {
96254
96408
  return null;
96255
96409
  }
96256
96410
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
96257
- return join64(matched.eventsDir, specificFile ?? files[0]);
96411
+ return join65(matched.eventsDir, specificFile ?? files[0]);
96258
96412
  }
96259
96413
  async function selectRunFile(runsDir) {
96260
96414
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
96261
96415
  if (files.length === 0) {
96262
96416
  return null;
96263
96417
  }
96264
- return join64(runsDir, files[0]);
96418
+ return join65(runsDir, files[0]);
96265
96419
  }
96266
96420
  async function extractRunSummary(filePath) {
96267
96421
  const file3 = Bun.file(filePath);
@@ -96347,7 +96501,7 @@ Runs:
96347
96501
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
96348
96502
  console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
96349
96503
  for (const file3 of files) {
96350
- const filePath = join65(runsDir, file3);
96504
+ const filePath = join66(runsDir, file3);
96351
96505
  const summary = await extractRunSummary(filePath);
96352
96506
  const timestamp = file3.replace(".jsonl", "");
96353
96507
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -96461,7 +96615,7 @@ async function logsCommand(options) {
96461
96615
  return;
96462
96616
  }
96463
96617
  const resolved = resolveProject({ dir: options.dir });
96464
- const naxDir = join66(resolved.projectDir, ".nax");
96618
+ const naxDir = join67(resolved.projectDir, ".nax");
96465
96619
  const configPath = resolved.configPath;
96466
96620
  const configFile = Bun.file(configPath);
96467
96621
  const config2 = await configFile.json();
@@ -96469,8 +96623,8 @@ async function logsCommand(options) {
96469
96623
  if (!featureName) {
96470
96624
  throw new Error("No feature specified in config.json");
96471
96625
  }
96472
- const featureDir = join66(naxDir, "features", featureName);
96473
- const runsDir = join66(featureDir, "runs");
96626
+ const featureDir = join67(naxDir, "features", featureName);
96627
+ const runsDir = join67(featureDir, "runs");
96474
96628
  if (!existsSync29(runsDir)) {
96475
96629
  throw new Error(`No runs directory found for feature: ${featureName}`);
96476
96630
  }
@@ -96496,7 +96650,7 @@ init_prd();
96496
96650
  init_precheck();
96497
96651
  init_common();
96498
96652
  import { existsSync as existsSync30 } from "fs";
96499
- import { join as join67 } from "path";
96653
+ import { join as join68 } from "path";
96500
96654
  async function precheckCommand(options) {
96501
96655
  const resolved = resolveProject({
96502
96656
  dir: options.dir,
@@ -96518,9 +96672,9 @@ async function precheckCommand(options) {
96518
96672
  process.exit(1);
96519
96673
  }
96520
96674
  }
96521
- const naxDir = join67(resolved.projectDir, ".nax");
96522
- const featureDir = join67(naxDir, "features", featureName);
96523
- const prdPath = join67(featureDir, "prd.json");
96675
+ const naxDir = join68(resolved.projectDir, ".nax");
96676
+ const featureDir = join68(naxDir, "features", featureName);
96677
+ const prdPath = join68(featureDir, "prd.json");
96524
96678
  if (!existsSync30(featureDir)) {
96525
96679
  console.error(source_default.red(`Feature not found: ${featureName}`));
96526
96680
  process.exit(1);
@@ -96543,7 +96697,7 @@ async function precheckCommand(options) {
96543
96697
  init_source();
96544
96698
  init_paths3();
96545
96699
  import { readdir as readdir5 } from "fs/promises";
96546
- import { join as join68 } from "path";
96700
+ import { join as join69 } from "path";
96547
96701
  var DEFAULT_LIMIT = 20;
96548
96702
  var _runsCmdDeps = {
96549
96703
  getRunsDir
@@ -96598,7 +96752,7 @@ async function runsCommand(options = {}) {
96598
96752
  }
96599
96753
  const rows = [];
96600
96754
  for (const entry of entries) {
96601
- const metaPath = join68(runsDir, entry, "meta.json");
96755
+ const metaPath = join69(runsDir, entry, "meta.json");
96602
96756
  let meta3;
96603
96757
  try {
96604
96758
  meta3 = await Bun.file(metaPath).json();
@@ -96675,7 +96829,7 @@ async function runsCommand(options = {}) {
96675
96829
 
96676
96830
  // src/commands/unlock.ts
96677
96831
  init_source();
96678
- import { join as join69 } from "path";
96832
+ import { join as join70 } from "path";
96679
96833
  function isProcessAlive2(pid) {
96680
96834
  try {
96681
96835
  process.kill(pid, 0);
@@ -96690,7 +96844,7 @@ function formatLockAge(ageMs) {
96690
96844
  }
96691
96845
  async function unlockCommand(options) {
96692
96846
  const workdir = options.dir ?? process.cwd();
96693
- const lockPath = join69(workdir, "nax.lock");
96847
+ const lockPath = join70(workdir, "nax.lock");
96694
96848
  const lockFile = Bun.file(lockPath);
96695
96849
  const exists = await lockFile.exists();
96696
96850
  if (!exists) {
@@ -96742,6 +96896,7 @@ init_acceptance2();
96742
96896
  init_config();
96743
96897
  init_hooks();
96744
96898
  init_logger2();
96899
+ init_pipeline();
96745
96900
  init_prd();
96746
96901
  init_git();
96747
96902
  init_crash_recovery();
@@ -96775,6 +96930,7 @@ async function runCompletionPhase(options) {
96775
96930
  logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
96776
96931
  } else if (options.config.acceptance.enabled && isComplete(options.prd)) {
96777
96932
  options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
96933
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
96778
96934
  const acceptanceTestPaths = options.featureDir ? await Promise.all(groupStoriesByPackage(options.prd, options.workdir, options.feature, options.config.acceptance.testPath, options.config.project?.language).map(async (g) => {
96779
96935
  const relativeWorkdir = path19.relative(options.workdir, g.packageDir);
96780
96936
  let groupConfig = options.config;
@@ -96821,6 +96977,7 @@ async function runCompletionPhase(options) {
96821
96977
  const lastRunAt = new Date().toISOString();
96822
96978
  if (acceptanceResult.success) {
96823
96979
  options.statusWriter.setPostRunPhase("acceptance", { status: "passed", lastRunAt });
96980
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: true });
96824
96981
  } else {
96825
96982
  acceptancePassed = false;
96826
96983
  options.statusWriter.setPostRunPhase("acceptance", {
@@ -96829,6 +96986,7 @@ async function runCompletionPhase(options) {
96829
96986
  retries: acceptanceResult.retries ?? 0,
96830
96987
  lastRunAt
96831
96988
  });
96989
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: false });
96832
96990
  }
96833
96991
  Object.assign(options, {
96834
96992
  prd: acceptanceResult.prd,
@@ -103725,9 +103883,11 @@ var MAX_ESCALATION_DISPLAY = 5;
103725
103883
  function LiveActivityPanel({
103726
103884
  focused = false,
103727
103885
  activeCalls,
103886
+ storySteps,
103728
103887
  runSummary,
103729
103888
  runErrored,
103730
- escalationLog = []
103889
+ escalationLog = [],
103890
+ currentStage
103731
103891
  }) {
103732
103892
  const borderColor = focused ? "cyan" : "gray";
103733
103893
  const activeCallList = activeCalls ? Array.from(activeCalls.values()) : [];
@@ -103783,7 +103943,9 @@ function LiveActivityPanel({
103783
103943
  paddingX: 1,
103784
103944
  paddingY: 1,
103785
103945
  children: activeCallList.map((call) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ActiveCallRow, {
103786
- call
103946
+ call,
103947
+ step: call.storyId ? storySteps?.[call.storyId] : undefined,
103948
+ currentStage
103787
103949
  }, call.callId, false, undefined, this))
103788
103950
  }, undefined, false, undefined, this),
103789
103951
  hasEscalations && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -103799,10 +103961,49 @@ function LiveActivityPanel({
103799
103961
  }, `${entry.storyId}-${entry.at}`, false, undefined, this))
103800
103962
  ]
103801
103963
  }, undefined, true, undefined, this),
103802
- !hasActiveCalls && !hasSummary && !hasError && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103964
+ !hasActiveCalls && storySteps && Object.keys(storySteps).length > 0 && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103965
+ flexDirection: "column",
103803
103966
  paddingX: 1,
103804
103967
  paddingY: 1,
103805
- children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103968
+ children: Object.entries(storySteps).map(([sid, step]) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103969
+ flexDirection: "row",
103970
+ gap: 1,
103971
+ children: [
103972
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103973
+ children: sid
103974
+ }, undefined, false, undefined, this),
103975
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103976
+ color: "yellow",
103977
+ children: [
103978
+ "[",
103979
+ step,
103980
+ "]"
103981
+ ]
103982
+ }, undefined, true, undefined, this)
103983
+ ]
103984
+ }, sid, true, undefined, this))
103985
+ }, undefined, false, undefined, this),
103986
+ !hasActiveCalls && !hasSummary && !hasError && (!storySteps || Object.keys(storySteps).length === 0) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103987
+ paddingX: 1,
103988
+ paddingY: 1,
103989
+ children: currentStage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103990
+ flexDirection: "row",
103991
+ gap: 1,
103992
+ children: [
103993
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103994
+ color: "yellow",
103995
+ children: [
103996
+ "[",
103997
+ currentStage,
103998
+ "]"
103999
+ ]
104000
+ }, undefined, true, undefined, this),
104001
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104002
+ dimColor: true,
104003
+ children: "preparing..."
104004
+ }, undefined, false, undefined, this)
104005
+ ]
104006
+ }, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103806
104007
  dimColor: true,
103807
104008
  children: "Waiting for agent..."
103808
104009
  }, undefined, false, undefined, this)
@@ -103810,7 +104011,29 @@ function LiveActivityPanel({
103810
104011
  ]
103811
104012
  }, undefined, true, undefined, this);
103812
104013
  }
103813
- function ActiveCallRow({ call }) {
104014
+ function ActiveCallRow({ call, step, currentStage }) {
104015
+ const stageLabel = step ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104016
+ color: "yellow",
104017
+ children: [
104018
+ "[",
104019
+ step,
104020
+ "]"
104021
+ ]
104022
+ }, undefined, true, undefined, this) : call.stage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104023
+ dimColor: true,
104024
+ children: [
104025
+ "[",
104026
+ call.stage,
104027
+ "]"
104028
+ ]
104029
+ }, undefined, true, undefined, this) : currentStage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
104030
+ dimColor: true,
104031
+ children: [
104032
+ "[",
104033
+ currentStage,
104034
+ "]"
104035
+ ]
104036
+ }, undefined, true, undefined, this) : null;
103814
104037
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103815
104038
  flexDirection: "column",
103816
104039
  marginBottom: 1,
@@ -103826,14 +104049,7 @@ function ActiveCallRow({ call }) {
103826
104049
  call.storyId && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103827
104050
  children: call.storyId
103828
104051
  }, undefined, false, undefined, this),
103829
- call.stage && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103830
- dimColor: true,
103831
- children: [
103832
- "[",
103833
- call.stage,
103834
- "]"
103835
- ]
103836
- }, undefined, true, undefined, this)
104052
+ stageLabel
103837
104053
  ]
103838
104054
  }, undefined, true, undefined, this),
103839
104055
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -104040,7 +104256,14 @@ function getStatusIcon(status) {
104040
104256
  return "\u23F8\uFE0F";
104041
104257
  }
104042
104258
  }
104043
- function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight }) {
104259
+ function StoriesPanel({
104260
+ stories,
104261
+ preRunPhases,
104262
+ postRunPhases,
104263
+ width,
104264
+ compact: compact2 = false,
104265
+ maxHeight
104266
+ }) {
104044
104267
  const maxVisible = compact2 ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
104045
104268
  const needsScrolling = stories.length > maxVisible;
104046
104269
  const [scrollOffset, setScrollOffset] = import_react30.useState(0);
@@ -104072,7 +104295,7 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
104072
104295
  children: [
104073
104296
  /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104074
104297
  bold: true,
104075
- children: "Stories"
104298
+ children: "Progress"
104076
104299
  }, undefined, false, undefined, this),
104077
104300
  needsScrolling && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104078
104301
  dimColor: true,
@@ -104084,6 +104307,16 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
104084
104307
  }, undefined, true, undefined, this)
104085
104308
  ]
104086
104309
  }, undefined, true, undefined, this),
104310
+ preRunPhases && Object.keys(preRunPhases).length > 0 && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104311
+ flexDirection: "column",
104312
+ paddingX: 1,
104313
+ paddingTop: 1,
104314
+ children: Object.entries(preRunPhases).map(([name, phase]) => /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PreRunPhaseRow, {
104315
+ label: name,
104316
+ phase,
104317
+ compact: compact2
104318
+ }, name, false, undefined, this))
104319
+ }, undefined, false, undefined, this),
104087
104320
  needsScrolling && canScrollUp && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104088
104321
  paddingX: 1,
104089
104322
  children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
@@ -104156,102 +104389,183 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
104156
104389
  " more below"
104157
104390
  ]
104158
104391
  }, undefined, true, undefined, this)
104159
- }, undefined, false, undefined, this)
104392
+ }, undefined, false, undefined, this),
104393
+ postRunPhases && (postRunPhases.acceptance || postRunPhases.regression || postRunPhases.review) && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104394
+ flexDirection: "column",
104395
+ paddingX: 1,
104396
+ paddingTop: 1,
104397
+ children: [
104398
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104399
+ dimColor: true,
104400
+ children: "Post-Run"
104401
+ }, undefined, false, undefined, this),
104402
+ postRunPhases.acceptance && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
104403
+ label: "acceptance",
104404
+ phase: postRunPhases.acceptance,
104405
+ compact: compact2
104406
+ }, undefined, false, undefined, this),
104407
+ postRunPhases.regression && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
104408
+ label: "regression",
104409
+ phase: postRunPhases.regression,
104410
+ compact: compact2
104411
+ }, undefined, false, undefined, this),
104412
+ postRunPhases.review && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
104413
+ label: "review",
104414
+ phase: postRunPhases.review,
104415
+ compact: compact2
104416
+ }, undefined, false, undefined, this)
104417
+ ]
104418
+ }, undefined, true, undefined, this)
104160
104419
  ]
104161
104420
  }, undefined, true, undefined, this);
104162
104421
  }
104422
+ function PreRunPhaseRow({ label, phase, compact: compact2 }) {
104423
+ const icon = phase.status === "running" ? "\u25CF" : phase.status === "passed" ? "\u2713" : "\u2717";
104424
+ const color = phase.status === "running" ? "yellow" : phase.status === "passed" ? "green" : "red";
104425
+ const displayLabel = compact2 ? label.slice(0, 6) : label;
104426
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104427
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104428
+ color,
104429
+ children: [
104430
+ icon,
104431
+ " ",
104432
+ displayLabel
104433
+ ]
104434
+ }, undefined, true, undefined, this)
104435
+ }, undefined, false, undefined, this);
104436
+ }
104437
+ function PostRunPhaseRow({ label, phase, compact: compact2 }) {
104438
+ const icon = phase.status === "running" ? ">" : phase.status === "passed" ? "[OK]" : "[X]";
104439
+ const color = phase.status === "running" ? "cyan" : phase.status === "passed" ? "green" : "red";
104440
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104441
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104442
+ color,
104443
+ children: [
104444
+ icon,
104445
+ " ",
104446
+ compact2 ? label.slice(0, 3) : label
104447
+ ]
104448
+ }, undefined, true, undefined, this)
104449
+ }, undefined, false, undefined, this);
104450
+ }
104163
104451
 
104164
104452
  // src/tui/hooks/useAgentStreamEvents.ts
104165
104453
  var import_react31 = __toESM(require_react(), 1);
104454
+ var RENDER_INTERVAL_MS = 150;
104166
104455
  function useAgentStreamEvents(bus) {
104456
+ const activeCallsRef = import_react31.useRef(new Map);
104457
+ const inputTokensRef = import_react31.useRef(0);
104458
+ const outputTokensRef = import_react31.useRef(0);
104459
+ const lastTokensRef = import_react31.useRef(new Map);
104460
+ const dirtyRef = import_react31.useRef(false);
104167
104461
  const [activeCalls, setActiveCalls] = import_react31.useState(new Map);
104462
+ const [inputTokens, setInputTokens] = import_react31.useState(0);
104463
+ const [outputTokens, setOutputTokens] = import_react31.useState(0);
104168
104464
  import_react31.useEffect(() => {
104169
104465
  if (!bus)
104170
104466
  return;
104171
104467
  const unsubscribe = bus.onAgentStream((event) => {
104172
- setActiveCalls((prev) => {
104173
- const next = new Map(prev);
104174
- switch (event.kind) {
104175
- case "agent.call_started": {
104176
- const now3 = event.timestamp;
104468
+ const next = new Map(activeCallsRef.current);
104469
+ switch (event.kind) {
104470
+ case "agent.call_started": {
104471
+ next.set(event.callId, {
104472
+ callId: event.callId,
104473
+ agentName: event.agentName,
104474
+ storyId: event.storyId,
104475
+ stage: event.stage,
104476
+ startedAt: event.timestamp,
104477
+ lastActivityAt: event.timestamp,
104478
+ messageUpdates: 0,
104479
+ thinkingUpdates: 0,
104480
+ usageUpdates: 0,
104481
+ toolCallUpdates: 0,
104482
+ status: "active",
104483
+ model: event.model
104484
+ });
104485
+ break;
104486
+ }
104487
+ case "agent.message_update": {
104488
+ const state = next.get(event.callId);
104489
+ if (state) {
104177
104490
  next.set(event.callId, {
104178
- callId: event.callId,
104179
- agentName: event.agentName,
104180
- storyId: event.storyId,
104181
- stage: event.stage,
104182
- startedAt: now3,
104183
- lastActivityAt: now3,
104184
- messageUpdates: 0,
104185
- thinkingUpdates: 0,
104186
- usageUpdates: 0,
104187
- toolCallUpdates: 0,
104188
- status: "active",
104189
- model: event.model
104491
+ ...state,
104492
+ messageUpdates: state.messageUpdates + 1,
104493
+ lastActivityAt: event.timestamp
104190
104494
  });
104191
- break;
104192
- }
104193
- case "agent.message_update": {
104194
- const state = next.get(event.callId);
104195
- if (state) {
104196
- next.set(event.callId, {
104197
- ...state,
104198
- messageUpdates: state.messageUpdates + 1,
104199
- lastActivityAt: event.timestamp
104200
- });
104201
- }
104202
- break;
104203
104495
  }
104204
- case "agent.thinking_update": {
104205
- const state = next.get(event.callId);
104206
- if (state) {
104207
- next.set(event.callId, {
104208
- ...state,
104209
- thinkingUpdates: state.thinkingUpdates + 1,
104210
- lastActivityAt: event.timestamp
104211
- });
104212
- }
104213
- break;
104496
+ break;
104497
+ }
104498
+ case "agent.thinking_update": {
104499
+ const state = next.get(event.callId);
104500
+ if (state) {
104501
+ next.set(event.callId, {
104502
+ ...state,
104503
+ thinkingUpdates: state.thinkingUpdates + 1,
104504
+ lastActivityAt: event.timestamp
104505
+ });
104214
104506
  }
104215
- case "agent.usage_update": {
104216
- const state = next.get(event.callId);
104217
- if (state) {
104218
- next.set(event.callId, {
104219
- ...state,
104220
- usageUpdates: state.usageUpdates + 1,
104221
- lastActivityAt: event.timestamp
104222
- });
104223
- }
104224
- break;
104507
+ break;
104508
+ }
104509
+ case "agent.usage_update": {
104510
+ const state = next.get(event.callId);
104511
+ if (state) {
104512
+ next.set(event.callId, {
104513
+ ...state,
104514
+ usageUpdates: state.usageUpdates + 1,
104515
+ lastActivityAt: event.timestamp
104516
+ });
104225
104517
  }
104226
- case "agent.tool_call_update": {
104227
- const state = next.get(event.callId);
104228
- if (state) {
104229
- next.set(event.callId, {
104230
- ...state,
104231
- toolCallUpdates: state.toolCallUpdates + 1,
104232
- lastActivityAt: event.timestamp,
104233
- lastToolName: event.toolName
104234
- });
104235
- }
104236
- break;
104518
+ {
104519
+ const last2 = lastTokensRef.current.get(event.callId) ?? { input: 0, output: 0 };
104520
+ const newInput = event.inputTokens ?? last2.input;
104521
+ const newOutput = event.outputTokens ?? last2.output;
104522
+ const deltaIn = newInput - last2.input;
104523
+ const deltaOut = newOutput - last2.output;
104524
+ lastTokensRef.current.set(event.callId, { input: newInput, output: newOutput });
104525
+ if (deltaIn > 0)
104526
+ inputTokensRef.current += deltaIn;
104527
+ if (deltaOut > 0)
104528
+ outputTokensRef.current += deltaOut;
104237
104529
  }
104238
- case "agent.call_ended": {
104239
- const state = next.get(event.callId);
104240
- if (state) {
104241
- next.set(event.callId, { ...state, status: "ended" });
104242
- next.delete(event.callId);
104243
- }
104244
- break;
104530
+ break;
104531
+ }
104532
+ case "agent.tool_call_update": {
104533
+ const state = next.get(event.callId);
104534
+ if (state) {
104535
+ next.set(event.callId, {
104536
+ ...state,
104537
+ toolCallUpdates: state.toolCallUpdates + 1,
104538
+ lastActivityAt: event.timestamp,
104539
+ lastToolName: event.toolName
104540
+ });
104245
104541
  }
104246
- default:
104247
- break;
104542
+ break;
104248
104543
  }
104249
- return next;
104250
- });
104544
+ case "agent.call_ended": {
104545
+ next.delete(event.callId);
104546
+ lastTokensRef.current.delete(event.callId);
104547
+ break;
104548
+ }
104549
+ default:
104550
+ break;
104551
+ }
104552
+ activeCallsRef.current = next;
104553
+ dirtyRef.current = true;
104251
104554
  });
104252
104555
  return unsubscribe;
104253
104556
  }, [bus]);
104254
- return { activeCalls };
104557
+ import_react31.useEffect(() => {
104558
+ const interval = setInterval(() => {
104559
+ if (!dirtyRef.current)
104560
+ return;
104561
+ dirtyRef.current = false;
104562
+ setActiveCalls(new Map(activeCallsRef.current));
104563
+ setInputTokens(inputTokensRef.current);
104564
+ setOutputTokens(outputTokensRef.current);
104565
+ }, RENDER_INTERVAL_MS);
104566
+ return () => clearInterval(interval);
104567
+ }, []);
104568
+ return { activeCalls, inputTokens, outputTokens };
104255
104569
  }
104256
104570
 
104257
104571
  // src/tui/hooks/useKeyboard.ts
@@ -104311,20 +104625,13 @@ function usePipelineBusEvents(initialStories) {
104311
104625
  const [state, setState] = import_react32.useState(() => ({
104312
104626
  stories: initialStories,
104313
104627
  totalCost: 0,
104314
- elapsedMs: 0,
104315
104628
  runPaused: false,
104316
104629
  runErrored: false,
104317
- escalationLog: []
104630
+ escalationLog: [],
104631
+ storySteps: {},
104632
+ postRunPhases: {}
104318
104633
  }));
104319
- const startTimeRef = import_react32.useRef(Date.now());
104320
104634
  import_react32.useEffect(() => {
104321
- const startTime = startTimeRef.current;
104322
- const timer = setInterval(() => {
104323
- setState((prev) => ({
104324
- ...prev,
104325
- elapsedMs: Date.now() - startTime
104326
- }));
104327
- }, 1000);
104328
104635
  const unsubStarted = pipelineEventBus.on("story:started", (event) => {
104329
104636
  setState((prev) => ({
104330
104637
  ...prev,
@@ -104347,18 +104654,19 @@ function usePipelineBusEvents(initialStories) {
104347
104654
  return s;
104348
104655
  });
104349
104656
  const totalCost = newStories.reduce((sum2, s) => sum2 + (s.cost ?? 0), 0);
104350
- return { ...prev, stories: newStories, totalCost };
104657
+ const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
104658
+ return { ...prev, stories: newStories, totalCost, storySteps: remainingSteps };
104351
104659
  });
104352
104660
  });
104353
104661
  const unsubFailed = pipelineEventBus.on("story:failed", (event) => {
104354
- setState((prev) => ({
104355
- ...prev,
104356
- stories: prev.stories.map((s) => s.story.id === event.storyId ? {
104357
- ...s,
104358
- status: "failed",
104359
- failureReason: event.reason
104360
- } : s)
104361
- }));
104662
+ setState((prev) => {
104663
+ const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
104664
+ return {
104665
+ ...prev,
104666
+ stories: prev.stories.map((s) => s.story.id === event.storyId ? { ...s, status: "failed", failureReason: event.reason } : s),
104667
+ storySteps: remainingSteps
104668
+ };
104669
+ });
104362
104670
  });
104363
104671
  const unsubSkipped = pipelineEventBus.on("story:skipped", (event) => {
104364
104672
  setState((prev) => ({
@@ -104392,7 +104700,6 @@ function usePipelineBusEvents(initialStories) {
104392
104700
  setState((prev) => ({ ...prev, runPaused: false }));
104393
104701
  });
104394
104702
  const unsubCompleted2 = pipelineEventBus.on("run:completed", (event) => {
104395
- clearInterval(timer);
104396
104703
  const summary = {
104397
104704
  totalStories: event.totalStories,
104398
104705
  passedStories: event.passedStories,
@@ -104404,7 +104711,6 @@ function usePipelineBusEvents(initialStories) {
104404
104711
  };
104405
104712
  setState((prev) => ({
104406
104713
  ...prev,
104407
- elapsedMs: event.durationMs,
104408
104714
  runSummary: summary,
104409
104715
  totalCost: event.totalCost ?? prev.totalCost
104410
104716
  }));
@@ -104412,8 +104718,28 @@ function usePipelineBusEvents(initialStories) {
104412
104718
  const unsubErrored = pipelineEventBus.on("run:errored", (_event) => {
104413
104719
  setState((prev) => ({ ...prev, runErrored: true }));
104414
104720
  });
104721
+ const unsubStep = pipelineEventBus.on("story:step", (event) => {
104722
+ setState((prev) => ({
104723
+ ...prev,
104724
+ storySteps: { ...prev.storySteps, [event.storyId]: event.step }
104725
+ }));
104726
+ });
104727
+ const unsubPostRunStarted = pipelineEventBus.on("postrun:phase:started", (event) => {
104728
+ setState((prev) => ({
104729
+ ...prev,
104730
+ postRunPhases: { ...prev.postRunPhases, [event.phase]: { status: "running" } }
104731
+ }));
104732
+ });
104733
+ const unsubPostRunCompleted = pipelineEventBus.on("postrun:phase:completed", (event) => {
104734
+ setState((prev) => ({
104735
+ ...prev,
104736
+ postRunPhases: {
104737
+ ...prev.postRunPhases,
104738
+ [event.phase]: { status: event.passed ? "passed" : "failed" }
104739
+ }
104740
+ }));
104741
+ });
104415
104742
  return () => {
104416
- clearInterval(timer);
104417
104743
  unsubStarted();
104418
104744
  unsubCompleted();
104419
104745
  unsubFailed();
@@ -104424,6 +104750,9 @@ function usePipelineBusEvents(initialStories) {
104424
104750
  unsubResumed();
104425
104751
  unsubCompleted2();
104426
104752
  unsubErrored();
104753
+ unsubStep();
104754
+ unsubPostRunStarted();
104755
+ unsubPostRunCompleted();
104427
104756
  };
104428
104757
  }, []);
104429
104758
  return state;
@@ -104431,14 +104760,33 @@ function usePipelineBusEvents(initialStories) {
104431
104760
 
104432
104761
  // src/tui/hooks/usePipelineEvents.ts
104433
104762
  var import_react33 = __toESM(require_react(), 1);
104763
+ var PRE_RUN_STAGES = new Set(["acceptance-setup"]);
104434
104764
  function usePipelineEvents(events) {
104435
104765
  const [currentStage, setCurrentStage] = import_react33.useState(undefined);
104766
+ const [preRunPhases, setPreRunPhases] = import_react33.useState({});
104436
104767
  import_react33.useEffect(() => {
104437
- const onStageEnter = (stage) => setCurrentStage(stage);
104768
+ const onStageEnter = (stage) => {
104769
+ setCurrentStage(stage);
104770
+ if (PRE_RUN_STAGES.has(stage)) {
104771
+ setPreRunPhases((prev) => ({ ...prev, [stage]: { status: "running" } }));
104772
+ }
104773
+ };
104774
+ const onStageExit = (stage, result2) => {
104775
+ if (PRE_RUN_STAGES.has(stage)) {
104776
+ setPreRunPhases((prev) => ({
104777
+ ...prev,
104778
+ [stage]: { status: result2.action === "fail" ? "failed" : "passed" }
104779
+ }));
104780
+ }
104781
+ };
104438
104782
  events.on("stage:enter", onStageEnter);
104439
- return () => events.off("stage:enter", onStageEnter);
104783
+ events.on("stage:exit", onStageExit);
104784
+ return () => {
104785
+ events.off("stage:enter", onStageEnter);
104786
+ events.off("stage:exit", onStageExit);
104787
+ };
104440
104788
  }, [events]);
104441
- return { currentStage };
104789
+ return { currentStage, preRunPhases };
104442
104790
  }
104443
104791
 
104444
104792
  // src/tui/hooks/usePty.ts
@@ -104542,6 +104890,8 @@ function usePty(options) {
104542
104890
 
104543
104891
  // src/tui/App.tsx
104544
104892
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
104893
+ var MemoStoriesPanel = import_react35.memo(StoriesPanel);
104894
+ var MemoLiveActivityPanel = import_react35.memo(LiveActivityPanel);
104545
104895
  function formatElapsed(ms) {
104546
104896
  const mins = Math.floor(ms / 60000);
104547
104897
  const secs = Math.floor(ms % 60000 / 1000);
@@ -104550,8 +104900,16 @@ function formatElapsed(ms) {
104550
104900
  function formatCost3(cost) {
104551
104901
  return `$${cost.toFixed(4)}`;
104552
104902
  }
104903
+ function formatTokens(n) {
104904
+ if (n >= 1e6)
104905
+ return `${(n / 1e6).toFixed(1)}M`;
104906
+ if (n >= 1000)
104907
+ return `${(n / 1000).toFixed(1)}k`;
104908
+ return `${n}`;
104909
+ }
104553
104910
  function App2({
104554
104911
  feature,
104912
+ version: version2,
104555
104913
  stories: initialStories,
104556
104914
  events,
104557
104915
  queueFilePath,
@@ -104560,19 +104918,29 @@ function App2({
104560
104918
  }) {
104561
104919
  const layout = useLayout();
104562
104920
  const busState = usePipelineBusEvents(initialStories);
104563
- const { currentStage } = usePipelineEvents(events);
104921
+ const { currentStage, preRunPhases } = usePipelineEvents(events);
104564
104922
  const { exit } = use_app_default();
104923
+ const startTimeRef = import_react35.useRef(Date.now());
104924
+ const [elapsedMs, setElapsedMs] = import_react35.useState(0);
104925
+ import_react35.useEffect(() => {
104926
+ if (busState.runSummary)
104927
+ return;
104928
+ const timer = setInterval(() => setElapsedMs(Date.now() - startTimeRef.current), 1000);
104929
+ return () => clearInterval(timer);
104930
+ }, [busState.runSummary]);
104565
104931
  const [focus, setFocus] = import_react35.useState("stories" /* Stories */);
104566
104932
  const [showHelp, setShowHelp] = import_react35.useState(false);
104567
104933
  const [showCost, setShowCost] = import_react35.useState(false);
104568
104934
  const [showQuitConfirm, setShowQuitConfirm] = import_react35.useState(false);
104569
104935
  const [showAbortConfirm, setShowAbortConfirm] = import_react35.useState(false);
104570
104936
  const { handle: ptyHandle } = usePty(ptyOptions ?? null);
104571
- const { activeCalls } = useAgentStreamEvents(agentStreamEvents);
104937
+ const { activeCalls, inputTokens, outputTokens } = useAgentStreamEvents(agentStreamEvents);
104572
104938
  const isRunComplete = !!busState.runSummary;
104573
104939
  const runningStories = busState.stories.filter((s) => s.status === "running");
104574
104940
  const isParallel = runningStories.length > 1;
104575
104941
  const currentRunningStory = runningStories[0];
104942
+ const runningPostRunPhase = busState.postRunPhases.acceptance?.status === "running" ? "post-run: acceptance" : busState.postRunPhases.regression?.status === "running" ? "post-run: regression" : busState.postRunPhases.review?.status === "running" ? "post-run: review" : undefined;
104943
+ const currentPhaseLabel = runningPostRunPhase ?? currentStage;
104576
104944
  const runErroredForPanel = busState.runErrored ? "Run encountered an error" : undefined;
104577
104945
  const handleKeyboardAction = async (action) => {
104578
104946
  switch (action.type) {
@@ -104655,10 +105023,17 @@ function App2({
104655
105023
  });
104656
105024
  const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
104657
105025
  const activeCount = runningStories.length;
105026
+ const displayElapsed = busState.runSummary ? busState.runSummary.durationMs : elapsedMs;
105027
+ const tokenParts = [
105028
+ inputTokens > 0 ? `${formatTokens(inputTokens)} in` : null,
105029
+ outputTokens > 0 ? `${formatTokens(outputTokens)} out` : null
105030
+ ].filter(Boolean);
105031
+ const tokensStr = tokenParts.length > 0 ? tokenParts.join(" / ") : null;
104658
105032
  const headerRight = [
104659
105033
  activeCount > 0 ? `${activeCount} running` : null,
104660
105034
  formatCost3(busState.totalCost),
104661
- formatElapsed(busState.elapsedMs)
105035
+ tokensStr,
105036
+ formatElapsed(displayElapsed)
104662
105037
  ].filter(Boolean).join(" \xB7 ");
104663
105038
  const maxHeight = layout.mode === "single" ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
104664
105039
  return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
@@ -104677,7 +105052,15 @@ function App2({
104677
105052
  color: "cyan",
104678
105053
  children: [
104679
105054
  "nax run \u2014 ",
104680
- feature
105055
+ feature,
105056
+ version2 ? /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
105057
+ dimColor: true,
105058
+ color: "cyan",
105059
+ children: [
105060
+ " ",
105061
+ version2
105062
+ ]
105063
+ }, undefined, true, undefined, this) : null
104681
105064
  ]
104682
105065
  }, undefined, true, undefined, this),
104683
105066
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
@@ -104704,18 +105087,22 @@ function App2({
104704
105087
  flexDirection: layout.mode === "single" ? "column" : "row",
104705
105088
  flexGrow: 1,
104706
105089
  children: [
104707
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(StoriesPanel, {
105090
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoStoriesPanel, {
104708
105091
  stories: busState.stories,
105092
+ preRunPhases,
105093
+ postRunPhases: busState.postRunPhases,
104709
105094
  width: layout.mode === "single" ? layout.width : layout.storiesPanelWidth,
104710
105095
  compact: layout.mode === "single",
104711
105096
  maxHeight
104712
105097
  }, undefined, false, undefined, this),
104713
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(LiveActivityPanel, {
105098
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoLiveActivityPanel, {
104714
105099
  focused: focus === "agent" /* Agent */,
104715
105100
  activeCalls,
105101
+ storySteps: busState.storySteps,
104716
105102
  runSummary: busState.runSummary,
104717
105103
  runErrored: runErroredForPanel,
104718
- escalationLog: busState.escalationLog
105104
+ escalationLog: busState.escalationLog,
105105
+ currentStage: currentPhaseLabel
104719
105106
  }, undefined, false, undefined, this)
104720
105107
  ]
104721
105108
  }, undefined, true, undefined, this),
@@ -104881,7 +105268,7 @@ Next: nax generate --package ${options.package}`));
104881
105268
  }
104882
105269
  return;
104883
105270
  }
104884
- const naxDir = join83(workdir, ".nax");
105271
+ const naxDir = join84(workdir, ".nax");
104885
105272
  if (existsSync36(naxDir) && !options.force) {
104886
105273
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
104887
105274
  return;
@@ -104910,11 +105297,11 @@ Next: nax generate --package ${options.package}`));
104910
105297
  }
104911
105298
  }
104912
105299
  }
104913
- mkdirSync7(join83(naxDir, "features"), { recursive: true });
104914
- mkdirSync7(join83(naxDir, "hooks"), { recursive: true });
105300
+ mkdirSync7(join84(naxDir, "features"), { recursive: true });
105301
+ mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
104915
105302
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
104916
- await Bun.write(join83(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
104917
- await Bun.write(join83(naxDir, "hooks.json"), JSON.stringify({
105303
+ await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105304
+ await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
104918
105305
  hooks: {
104919
105306
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
104920
105307
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -104922,12 +105309,12 @@ Next: nax generate --package ${options.package}`));
104922
105309
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
104923
105310
  }
104924
105311
  }, null, 2));
104925
- await Bun.write(join83(naxDir, ".gitignore"), `# nax temp files
105312
+ await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
104926
105313
  *.tmp
104927
105314
  .paused.json
104928
105315
  .nax-verifier-verdict.json
104929
105316
  `);
104930
- await Bun.write(join83(naxDir, "context.md"), `# Project Context
105317
+ await Bun.write(join84(naxDir, "context.md"), `# Project Context
104931
105318
 
104932
105319
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
104933
105320
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105057,8 +105444,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
105057
105444
  console.error(source_default.red("nax not initialized. Run: nax init"));
105058
105445
  process.exit(1);
105059
105446
  }
105060
- const featureDir = join83(naxDir, "features", options.feature);
105061
- const prdPath = join83(featureDir, "prd.json");
105447
+ const featureDir = join84(naxDir, "features", options.feature);
105448
+ const prdPath = join84(featureDir, "prd.json");
105062
105449
  if (options.plan && options.from) {
105063
105450
  if (existsSync36(prdPath) && !options.force) {
105064
105451
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -105080,10 +105467,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105080
105467
  }
105081
105468
  }
105082
105469
  try {
105083
- const planLogDir = join83(featureDir, "plan");
105470
+ const planLogDir = join84(featureDir, "plan");
105084
105471
  mkdirSync7(planLogDir, { recursive: true });
105085
105472
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105086
- const planLogPath = join83(planLogDir, `${planLogId}.jsonl`);
105473
+ const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
105087
105474
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105088
105475
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105089
105476
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105129,10 +105516,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105129
105516
  resetLogger();
105130
105517
  const projectKey = config2.name?.trim() || basename16(workdir);
105131
105518
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105132
- const runsDir = join83(outputDir, "features", options.feature, "runs");
105519
+ const runsDir = join84(outputDir, "features", options.feature, "runs");
105133
105520
  mkdirSync7(runsDir, { recursive: true });
105134
105521
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105135
- const logFilePath = join83(runsDir, `${runId}.jsonl`);
105522
+ const logFilePath = join84(runsDir, `${runId}.jsonl`);
105136
105523
  const isTTY = process.stdout.isTTY ?? false;
105137
105524
  const headlessFlag = options.headless ?? false;
105138
105525
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105150,7 +105537,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105150
105537
  config2.agent.default = options.agent;
105151
105538
  }
105152
105539
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105153
- const globalNaxDir = join83(homedir3(), ".nax");
105540
+ const globalNaxDir = join84(homedir3(), ".nax");
105154
105541
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105155
105542
  const eventEmitter = new PipelineEventEmitter;
105156
105543
  const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
@@ -105165,16 +105552,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
105165
105552
  }));
105166
105553
  tuiInstance = renderTui({
105167
105554
  feature: options.feature,
105555
+ version: NAX_BUILD_INFO,
105168
105556
  stories: initialStories,
105169
105557
  events: eventEmitter,
105170
105558
  ptyOptions: null,
105171
105559
  agentStreamEvents,
105172
- queueFilePath: join83(workdir, ".queue.txt")
105560
+ queueFilePath: join84(workdir, ".queue.txt")
105173
105561
  });
105174
105562
  } else {
105175
105563
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105176
105564
  }
105177
- const statusFilePath = join83(outputDir, "status.json");
105565
+ const statusFilePath = join84(outputDir, "status.json");
105178
105566
  let parallel;
105179
105567
  if (options.parallel !== undefined) {
105180
105568
  parallel = Number.parseInt(options.parallel, 10);
@@ -105201,7 +105589,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105201
105589
  skipPrecheck: options.skipPrecheck ?? false,
105202
105590
  agentStreamEvents
105203
105591
  });
105204
- const latestSymlink = join83(runsDir, "latest.jsonl");
105592
+ const latestSymlink = join84(runsDir, "latest.jsonl");
105205
105593
  try {
105206
105594
  if (existsSync36(latestSymlink)) {
105207
105595
  Bun.spawnSync(["rm", latestSymlink]);
@@ -105262,9 +105650,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105262
105650
  console.error(source_default.red("nax not initialized. Run: nax init"));
105263
105651
  process.exit(1);
105264
105652
  }
105265
- const featureDir = join83(naxDir, "features", name);
105653
+ const featureDir = join84(naxDir, "features", name);
105266
105654
  mkdirSync7(featureDir, { recursive: true });
105267
- await Bun.write(join83(featureDir, "spec.md"), `# Feature: ${name}
105655
+ await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
105268
105656
 
105269
105657
  ## Overview
105270
105658
 
@@ -105297,7 +105685,7 @@ features.command("create <name>").description("Create a new feature").option("-d
105297
105685
 
105298
105686
  <!-- What this feature explicitly does NOT cover. -->
105299
105687
  `);
105300
- await Bun.write(join83(featureDir, "progress.txt"), `# Progress: ${name}
105688
+ await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
105301
105689
 
105302
105690
  Created: ${new Date().toISOString()}
105303
105691
 
@@ -105323,7 +105711,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
105323
105711
  console.error(source_default.red("nax not initialized."));
105324
105712
  process.exit(1);
105325
105713
  }
105326
- const featuresDir = join83(naxDir, "features");
105714
+ const featuresDir = join84(naxDir, "features");
105327
105715
  if (!existsSync36(featuresDir)) {
105328
105716
  console.log(source_default.dim("No features yet."));
105329
105717
  return;
@@ -105338,7 +105726,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
105338
105726
  Features:
105339
105727
  `));
105340
105728
  for (const name of entries) {
105341
- const prdPath = join83(featuresDir, name, "prd.json");
105729
+ const prdPath = join84(featuresDir, name, "prd.json");
105342
105730
  if (existsSync36(prdPath)) {
105343
105731
  const prd = await loadPRD(prdPath);
105344
105732
  const c = countStories(prd);
@@ -105373,10 +105761,10 @@ Use: nax plan -f <feature> --from <spec>`));
105373
105761
  cliOverrides.profile = options.profile;
105374
105762
  }
105375
105763
  const config2 = await loadConfig(workdir, cliOverrides);
105376
- const featureLogDir = join83(naxDir, "features", options.feature, "plan");
105764
+ const featureLogDir = join84(naxDir, "features", options.feature, "plan");
105377
105765
  mkdirSync7(featureLogDir, { recursive: true });
105378
105766
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105379
- const planLogPath = join83(featureLogDir, `${planLogId}.jsonl`);
105767
+ const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
105380
105768
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105381
105769
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105382
105770
  try {