@nathapp/nax 0.68.7 → 0.68.8

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 +588 -327
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -27497,6 +27497,9 @@ function getContextFiles(story) {
27497
27497
  const files = story.contextFiles ?? story.relevantFiles ?? [];
27498
27498
  return files.map((f) => typeof f === "string" ? f : f.path);
27499
27499
  }
27500
+ function getExpectedFiles(story) {
27501
+ return story.expectedFiles ?? [];
27502
+ }
27500
27503
  function isStalled(prd) {
27501
27504
  const remaining = prd.userStories.filter((s) => s.status !== "passed" && s.status !== "skipped");
27502
27505
  if (remaining.length === 0)
@@ -27772,6 +27775,24 @@ function validateStory(raw, index, allIds) {
27772
27775
  }
27773
27776
  }
27774
27777
  }
27778
+ const rawExpectedFiles = s.expectedFiles;
27779
+ const expectedFiles = [];
27780
+ if (Array.isArray(rawExpectedFiles)) {
27781
+ for (const f of rawExpectedFiles) {
27782
+ if (typeof f !== "string")
27783
+ continue;
27784
+ const trimmed = f.trim();
27785
+ if (trimmed === "")
27786
+ continue;
27787
+ if (trimmed.startsWith("/")) {
27788
+ throw new Error(`[schema] story[${index}].expectedFiles entry must be relative (no absolute paths): "${trimmed}"`);
27789
+ }
27790
+ if (trimmed.includes("..")) {
27791
+ throw new Error(`[schema] story[${index}].expectedFiles entry must not contain '..': "${trimmed}"`);
27792
+ }
27793
+ expectedFiles.push(trimmed);
27794
+ }
27795
+ }
27775
27796
  const VALID_VERIFIED_BY_KINDS = ["test", "symbol", "file"];
27776
27797
  let verifiedBy;
27777
27798
  if (s.verifiedBy !== undefined && s.verifiedBy !== null) {
@@ -27805,6 +27826,7 @@ function validateStory(raw, index, allIds) {
27805
27826
  },
27806
27827
  ...workdir !== undefined ? { workdir } : {},
27807
27828
  ...contextFiles.length > 0 ? { contextFiles } : {},
27829
+ ...expectedFiles.length > 0 ? { expectedFiles } : {},
27808
27830
  ...suggestedCriteria !== undefined ? { suggestedCriteria } : {},
27809
27831
  ...verifiedBy !== undefined ? { verifiedBy } : {},
27810
27832
  ...intent !== undefined ? { intent } : {}
@@ -29366,6 +29388,12 @@ function renderErrorSection(sections, byType) {
29366
29388
 
29367
29389
  // src/context/builder.ts
29368
29390
  import path2 from "path";
29391
+ function readContextMessage(relativeFilePath) {
29392
+ return `_Path: \`${relativeFilePath}\` \u2014 read this file before implementing._`;
29393
+ }
29394
+ function createIntentMessage(relativeFilePath) {
29395
+ return `_Path: \`${relativeFilePath}\` \u2014 this file does not exist yet; you will CREATE it as part of this story._`;
29396
+ }
29369
29397
  function sortContextElements(elements) {
29370
29398
  return [...elements].sort((a, b) => {
29371
29399
  if (a.priority !== b.priority)
@@ -29493,7 +29521,6 @@ async function addTestCoverageElement(elements, storyContext, story) {
29493
29521
  }
29494
29522
  }
29495
29523
  async function addFileElements(elements, storyContext, story) {
29496
- const MAX_FILES3 = 5;
29497
29524
  const fileInjection = storyContext.config?.context?.fileInjection;
29498
29525
  let contextFiles = getContextFiles(story);
29499
29526
  const parentFiles = getParentOutputFiles(story, storyContext.prd?.userStories ?? []);
@@ -29530,26 +29557,51 @@ async function addFileElements(elements, storyContext, story) {
29530
29557
  });
29531
29558
  }
29532
29559
  }
29533
- if (contextFiles.length === 0)
29560
+ const expectedFiles = getExpectedFiles(story);
29561
+ if (contextFiles.length === 0 && expectedFiles.length === 0)
29534
29562
  return;
29535
- const filesToLoad = contextFiles.slice(0, MAX_FILES3);
29536
29563
  const { workdir } = storyContext;
29537
29564
  if (!workdir) {
29538
29565
  getLogger().warn("context", "workdir not set \u2014 cannot load context files", { storyId: story.id });
29539
29566
  return;
29540
29567
  }
29568
+ const expectedSet = new Set(expectedFiles);
29569
+ const surfaced = new Set;
29570
+ const filesToLoad = contextFiles.slice(0, FILE_INJECTION_MAX_FILES);
29541
29571
  for (let i = 0;i < filesToLoad.length; i++) {
29542
29572
  const relativeFilePath = filesToLoad[i];
29573
+ surfaced.add(relativeFilePath);
29543
29574
  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 });
29575
+ if (await Bun.file(absolutePath).exists()) {
29576
+ elements.push(createFileContext(relativeFilePath, readContextMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
29547
29577
  continue;
29548
29578
  }
29549
- elements.push(createFileContext(relativeFilePath, `_Path: \`${relativeFilePath}\` \u2014 read this file before implementing._`, 60 - i));
29579
+ if (expectedSet.has(relativeFilePath)) {
29580
+ elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
29581
+ getLogger().debug("context", "Context file does not exist yet \u2014 treated as to-be-created", {
29582
+ storyId: story.id,
29583
+ filePath: relativeFilePath
29584
+ });
29585
+ } else {
29586
+ getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
29587
+ }
29550
29588
  }
29589
+ await addCreateIntentElements(elements, workdir, expectedFiles, surfaced);
29551
29590
  }
29552
- var _contextBuilderDeps;
29591
+ async function addCreateIntentElements(elements, workdir, expectedFiles, surfaced) {
29592
+ let idx = 0;
29593
+ for (const relativeFilePath of expectedFiles.slice(0, FILE_INJECTION_MAX_FILES)) {
29594
+ if (surfaced.has(relativeFilePath))
29595
+ continue;
29596
+ const absolutePath = path2.resolve(workdir, relativeFilePath);
29597
+ if (await Bun.file(absolutePath).exists())
29598
+ continue;
29599
+ elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - FILE_INJECTION_MAX_FILES - idx));
29600
+ surfaced.add(relativeFilePath);
29601
+ idx++;
29602
+ }
29603
+ }
29604
+ var _contextBuilderDeps, FILE_INJECTION_MAX_FILES = 5, FILE_CONTEXT_PRIORITY_BASE = 60;
29553
29605
  var init_builder = __esm(() => {
29554
29606
  init_logger2();
29555
29607
  init_prd();
@@ -34222,6 +34274,7 @@ ${outputFormat}`, overridable: false }
34222
34274
  });
34223
34275
 
34224
34276
  // src/operations/plan-refine.ts
34277
+ import { join as join20 } from "path";
34225
34278
  function hasToken(text, tokens) {
34226
34279
  const lower = text.toLowerCase();
34227
34280
  return tokens.some((token) => lower.includes(token));
@@ -34284,6 +34337,53 @@ async function readSpecDriftViolations(input) {
34284
34337
  return [];
34285
34338
  }
34286
34339
  }
34340
+ async function normalizeStoryFiles(story, workdir, fileExists) {
34341
+ const contextFiles = story.contextFiles ?? [];
34342
+ if (contextFiles.length === 0)
34343
+ return { story, changed: false };
34344
+ const logger = getSafeLogger();
34345
+ const expected = new Set(getExpectedFiles(story));
34346
+ const kept = [];
34347
+ const moved = [];
34348
+ for (const entry of contextFiles) {
34349
+ const filePath = typeof entry === "string" ? entry : entry.path;
34350
+ const factId = typeof entry === "string" ? undefined : entry.factId;
34351
+ if (expected.has(filePath) || await fileExists(join20(workdir, filePath))) {
34352
+ kept.push(entry);
34353
+ continue;
34354
+ }
34355
+ if (factId) {
34356
+ logger?.warn("plan", "Context file cites a manifest fact but is absent on disk", {
34357
+ storyId: story.id,
34358
+ filePath,
34359
+ factId
34360
+ });
34361
+ kept.push(entry);
34362
+ continue;
34363
+ }
34364
+ moved.push(filePath);
34365
+ }
34366
+ if (moved.length === 0)
34367
+ return { story, changed: false };
34368
+ const newExpected = [...getExpectedFiles(story)];
34369
+ for (const filePath of moved) {
34370
+ if (!newExpected.includes(filePath))
34371
+ newExpected.push(filePath);
34372
+ }
34373
+ logger?.info("plan", "Moved absent contextFiles entries to expectedFiles (story creates them)", {
34374
+ storyId: story.id,
34375
+ moved
34376
+ });
34377
+ return { story: { ...story, contextFiles: kept, expectedFiles: newExpected }, changed: true };
34378
+ }
34379
+ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
34380
+ if (!workdir)
34381
+ return prd;
34382
+ const results = await Promise.all(prd.userStories.map((story) => normalizeStoryFiles(story, workdir, fileExists)));
34383
+ if (!results.some((r) => r.changed))
34384
+ return prd;
34385
+ return { ...prd, userStories: results.map((r) => r.story) };
34386
+ }
34287
34387
  var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
34288
34388
  var init_plan_refine = __esm(() => {
34289
34389
  init_retry();
@@ -34405,7 +34505,7 @@ ${outputFormat}`,
34405
34505
  if (ctx.config.plan.specGuard) {
34406
34506
  warnOnSpecDrift(validated, input.featureName);
34407
34507
  }
34408
- return validated;
34508
+ return await normalizeCreatedContextFiles(validated, input.workdir, ctx.fileExists);
34409
34509
  },
34410
34510
  recover: async (input, ctx) => {
34411
34511
  const content = await ctx.readFile(input.outputPath);
@@ -35391,11 +35491,11 @@ function extractTestCode(output) {
35391
35491
 
35392
35492
  // src/acceptance/generator.ts
35393
35493
  import { existsSync as existsSync5 } from "fs";
35394
- import { join as join20 } from "path";
35494
+ import { join as join21 } from "path";
35395
35495
  function resolvePytestBin(packageDir) {
35396
35496
  if (packageDir) {
35397
35497
  for (const venvDir of [".venv", "venv", "env"]) {
35398
- const candidate = join20(packageDir, venvDir, "bin", "pytest");
35498
+ const candidate = join21(packageDir, venvDir, "bin", "pytest");
35399
35499
  if (existsSync5(candidate))
35400
35500
  return candidate;
35401
35501
  }
@@ -38004,14 +38104,14 @@ var init_plan_critic_llm = __esm(() => {
38004
38104
 
38005
38105
  // src/context/greenfield.ts
38006
38106
  import { readdir as readdir2 } from "fs/promises";
38007
- import { join as join21 } from "path";
38107
+ import { join as join22 } from "path";
38008
38108
  async function scanForTestFiles(dir, testPatterns, isRootCall = true) {
38009
38109
  const results = [];
38010
38110
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
38011
38111
  try {
38012
38112
  const entries = await readdir2(dir, { withFileTypes: true });
38013
38113
  for (const entry of entries) {
38014
- const fullPath = join21(dir, entry.name);
38114
+ const fullPath = join22(dir, entry.name);
38015
38115
  if (entry.isDirectory()) {
38016
38116
  if (ignoreDirs.has(entry.name))
38017
38117
  continue;
@@ -38216,13 +38316,13 @@ __export(exports_runners, {
38216
38316
  _regressionRunnerDeps: () => _regressionRunnerDeps
38217
38317
  });
38218
38318
  import { existsSync as existsSync6 } from "fs";
38219
- import { join as join22 } from "path";
38319
+ import { join as join23 } from "path";
38220
38320
  async function verifyAssets(workingDirectory, expectedFiles) {
38221
38321
  if (!expectedFiles || expectedFiles.length === 0)
38222
38322
  return { success: true, missingFiles: [] };
38223
38323
  const missingFiles = [];
38224
38324
  for (const file3 of expectedFiles) {
38225
- if (!existsSync6(join22(workingDirectory, file3)))
38325
+ if (!existsSync6(join23(workingDirectory, file3)))
38226
38326
  missingFiles.push(file3);
38227
38327
  }
38228
38328
  if (missingFiles.length > 0) {
@@ -38668,7 +38768,7 @@ var init_apply_test_edit_declarations = __esm(() => {
38668
38768
  });
38669
38769
 
38670
38770
  // src/operations/validate-mock-structure-files.ts
38671
- import { join as join23 } from "path";
38771
+ import { join as join24 } from "path";
38672
38772
  async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
38673
38773
  const fileExists = deps?.fileExists ?? defaultFileExists;
38674
38774
  const valid = [];
@@ -38681,7 +38781,7 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
38681
38781
  const files = d.files ?? [d.file];
38682
38782
  let allValid = true;
38683
38783
  for (const file3 of files) {
38684
- const absolutePath = join23(packageDir, file3);
38784
+ const absolutePath = join24(packageDir, file3);
38685
38785
  const exists = await fileExists(absolutePath);
38686
38786
  if (!exists) {
38687
38787
  allValid = false;
@@ -39975,7 +40075,7 @@ var init_lint_parsing = __esm(() => {
39975
40075
  });
39976
40076
 
39977
40077
  // src/review/scoped-lint.ts
39978
- import { join as join24, relative as relative10 } from "path";
40078
+ import { join as join25, relative as relative10 } from "path";
39979
40079
  function shellQuotePath4(path5) {
39980
40080
  return `'${path5.replaceAll("'", "'\\''")}'`;
39981
40081
  }
@@ -40023,7 +40123,7 @@ function uniqueFiles(files) {
40023
40123
  async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
40024
40124
  const inScope = [];
40025
40125
  for (const relPath of files) {
40026
- const absPath = join24(workdir, relPath);
40126
+ const absPath = join25(workdir, relPath);
40027
40127
  const exists = await _scopedLintDeps.fileExists(absPath);
40028
40128
  if (!exists)
40029
40129
  continue;
@@ -42177,7 +42277,7 @@ Output ONLY the JSON object. Do not include markdown fences or explanation.`;
42177
42277
  buildRefineContinuation(outputFilePath, specGuard = false) {
42178
42278
  const specGuardItems = specGuard ? `
42179
42279
  #### 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.
42280
+ 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
42281
 
42182
42282
  #### no-behavior-degradation
42183
42283
  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 +42408,9 @@ Based on your Step 2 analysis, create stories that produce CODE CHANGES.
42308
42408
 
42309
42409
  ${buildSharedQualityRules(specContent, projectProfile)}
42310
42410
 
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.
42411
+ 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
42412
 
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.`;
42413
+ ${CONTEXT_VS_EXPECTED_FILES_RULE}`;
42314
42414
  const suggestedCriteriaField = specContent.trim() ? `
42315
42415
  "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
42416
  const outputDirective = outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
@@ -42332,7 +42432,8 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
42332
42432
  "title": "string \u2014 concise story title",
42333
42433
  "description": "string \u2014 detailed description of the story",
42334
42434
  "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)"],
42435
+ "contextFiles": ["string \u2014 EXISTING source files the agent should read (max 5, relative paths)"],
42436
+ ${EXPECTED_FILES_SCHEMA_FIELD}
42336
42437
  "tags": ["string \u2014 routing tags, e.g. feature, security, api"],
42337
42438
  "dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
42338
42439
  "status": "pending",
@@ -42405,9 +42506,9 @@ Every concrete claim referencing existing code must cite [F-NNN] or [S-NNN] from
42405
42506
 
42406
42507
  ${buildSharedQualityRules(input.specContent, input.projectProfile)}
42407
42508
 
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.
42509
+ 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
42510
 
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.
42511
+ ${CONTEXT_VS_EXPECTED_FILES_RULE}
42411
42512
 
42412
42513
  ## Output Schema
42413
42514
 
@@ -42423,7 +42524,8 @@ Produce a JSON object with this exact structure. Field names are mandatory \u201
42423
42524
  "title": "string \u2014 concise story title",
42424
42525
  "description": "string \u2014 detailed description of what to implement",
42425
42526
  "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)"],
42527
+ "contextFiles": ["string \u2014 EXISTING relative paths the implementer should read (max 5)"],
42528
+ ${EXPECTED_FILES_SCHEMA_FIELD}
42427
42529
  "tags": ["string"],
42428
42530
  "dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
42429
42531
  "routing": {
@@ -42464,6 +42566,9 @@ ${rows.join(`
42464
42566
  `)}
42465
42567
  `;
42466
42568
  }
42569
+ var CONTEXT_VS_EXPECTED_FILES_RULE = `**\`contextFiles\` rule \u2014 existing files only.** Only list paths that already exist in the repo today. The pipeline verifies every \`contextFiles\` entry against the filesystem; a path that does not exist is treated as a missing-context warning.
42570
+
42571
+ **\`expectedFiles\` rule \u2014 files this story CREATES.** List every NEW file the story authors (relative paths). A file the story will create belongs here, NEVER in \`contextFiles\` \u2014 these are the story's outputs, not files to read first. 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
42572
  var init_plan_builder = __esm(() => {
42468
42573
  init_config();
42469
42574
  });
@@ -43392,7 +43497,7 @@ var init_call = __esm(() => {
43392
43497
 
43393
43498
  // src/runtime/cost-aggregator.ts
43394
43499
  import { mkdirSync as mkdirSync2 } from "fs";
43395
- import { join as join25 } from "path";
43500
+ import { join as join26 } from "path";
43396
43501
  function makeCorrelationId() {
43397
43502
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
43398
43503
  }
@@ -43583,7 +43688,7 @@ class CostAggregator {
43583
43688
  if (events.length === 0 && errors3.length === 0)
43584
43689
  return;
43585
43690
  mkdirSync2(this._drainDir, { recursive: true });
43586
- const path5 = join25(this._drainDir, `${this._runId}.jsonl`);
43691
+ const path5 = join26(this._drainDir, `${this._runId}.jsonl`);
43587
43692
  const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
43588
43693
  await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
43589
43694
  `)}
@@ -43623,7 +43728,7 @@ var init_cost_aggregator = __esm(() => {
43623
43728
  // src/runtime/prompt-auditor.ts
43624
43729
  import { appendFileSync } from "fs";
43625
43730
  import { mkdir as mkdir4 } from "fs/promises";
43626
- import { join as join26 } from "path";
43731
+ import { join as join27 } from "path";
43627
43732
  function createNoOpPromptAuditor() {
43628
43733
  return {
43629
43734
  record() {},
@@ -43689,8 +43794,8 @@ class PromptAuditor {
43689
43794
  _jsonlPath;
43690
43795
  _featureDir;
43691
43796
  constructor(runId, flushDir, featureName) {
43692
- this._featureDir = join26(flushDir, featureName);
43693
- this._jsonlPath = join26(this._featureDir, `${runId}.jsonl`);
43797
+ this._featureDir = join27(flushDir, featureName);
43798
+ this._jsonlPath = join27(this._featureDir, `${runId}.jsonl`);
43694
43799
  }
43695
43800
  record(entry) {
43696
43801
  this._enqueue(entry);
@@ -43739,7 +43844,7 @@ class PromptAuditor {
43739
43844
  const auditEntry = entry;
43740
43845
  const filename = deriveTxtFilename(auditEntry);
43741
43846
  try {
43742
- await _promptAuditorDeps.write(join26(this._featureDir, filename), buildTxtContent(auditEntry));
43847
+ await _promptAuditorDeps.write(join27(this._featureDir, filename), buildTxtContent(auditEntry));
43743
43848
  } catch (err) {
43744
43849
  throw tagAuditError(err, "txt");
43745
43850
  }
@@ -44851,7 +44956,7 @@ var init_pid_registry = __esm(() => {
44851
44956
  // src/session/manager-deps.ts
44852
44957
  import { randomUUID as randomUUID3 } from "crypto";
44853
44958
  import { mkdir as mkdir5 } from "fs/promises";
44854
- import { isAbsolute as isAbsolute9, join as join27, relative as relative11, sep as sep2 } from "path";
44959
+ import { isAbsolute as isAbsolute9, join as join28, relative as relative11, sep as sep2 } from "path";
44855
44960
  function resolveProjectDirFromScratchDir(scratchDir) {
44856
44961
  const marker = `${sep2}.nax${sep2}features${sep2}`;
44857
44962
  const markerIdx = scratchDir.lastIndexOf(marker);
@@ -44872,7 +44977,7 @@ var init_manager_deps = __esm(() => {
44872
44977
  now: () => new Date().toISOString(),
44873
44978
  nowMs: () => Date.now(),
44874
44979
  uuid: () => randomUUID3(),
44875
- sessionScratchDir: (projectDir, featureName, sessionId) => join27(projectDir, ".nax", "features", featureName, "sessions", sessionId),
44980
+ sessionScratchDir: (projectDir, featureName, sessionId) => join28(projectDir, ".nax", "features", featureName, "sessions", sessionId),
44876
44981
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
44877
44982
  await mkdir5(scratchDir, { recursive: true });
44878
44983
  const { handle: _handle, ...persistable } = descriptor;
@@ -44883,7 +44988,7 @@ var init_manager_deps = __esm(() => {
44883
44988
  persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
44884
44989
  }
44885
44990
  }
44886
- await Bun.write(join27(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
44991
+ await Bun.write(join28(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
44887
44992
  }
44888
44993
  };
44889
44994
  });
@@ -45634,7 +45739,7 @@ __export(exports_runtime, {
45634
45739
  CostAggregator: () => CostAggregator,
45635
45740
  AgentStreamEventBus: () => AgentStreamEventBus
45636
45741
  });
45637
- import { basename as basename5, join as join28 } from "path";
45742
+ import { basename as basename5, join as join29 } from "path";
45638
45743
  function createRuntime(config2, workdir, opts) {
45639
45744
  const runId = crypto.randomUUID();
45640
45745
  const controller = new AbortController;
@@ -45650,10 +45755,10 @@ function createRuntime(config2, workdir, opts) {
45650
45755
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
45651
45756
  const globalDir = globalOutputDir();
45652
45757
  const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
45653
- const costDir = join28(outputDir, "cost");
45758
+ const costDir = join29(outputDir, "cost");
45654
45759
  const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
45655
45760
  const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
45656
- const auditDir = config2.agent?.promptAudit?.dir ?? join28(outputDir, "prompt-audit");
45761
+ const auditDir = config2.agent?.promptAudit?.dir ?? join29(outputDir, "prompt-audit");
45657
45762
  let promptAuditor;
45658
45763
  if (opts?.promptAuditor) {
45659
45764
  promptAuditor = opts.promptAuditor;
@@ -45804,9 +45909,9 @@ async function allSettledBounded(tasks, limit) {
45804
45909
 
45805
45910
  // src/context/injector.ts
45806
45911
  import { existsSync as existsSync8 } from "fs";
45807
- import { join as join29 } from "path";
45912
+ import { join as join30 } from "path";
45808
45913
  async function detectNode(workdir) {
45809
- const pkgPath = join29(workdir, "package.json");
45914
+ const pkgPath = join30(workdir, "package.json");
45810
45915
  if (!existsSync8(pkgPath))
45811
45916
  return null;
45812
45917
  try {
@@ -45823,7 +45928,7 @@ async function detectNode(workdir) {
45823
45928
  }
45824
45929
  }
45825
45930
  async function detectGo(workdir) {
45826
- const goMod = join29(workdir, "go.mod");
45931
+ const goMod = join30(workdir, "go.mod");
45827
45932
  if (!existsSync8(goMod))
45828
45933
  return null;
45829
45934
  try {
@@ -45847,7 +45952,7 @@ async function detectGo(workdir) {
45847
45952
  }
45848
45953
  }
45849
45954
  async function detectRust(workdir) {
45850
- const cargoPath = join29(workdir, "Cargo.toml");
45955
+ const cargoPath = join30(workdir, "Cargo.toml");
45851
45956
  if (!existsSync8(cargoPath))
45852
45957
  return null;
45853
45958
  try {
@@ -45863,8 +45968,8 @@ async function detectRust(workdir) {
45863
45968
  }
45864
45969
  }
45865
45970
  async function detectPython(workdir) {
45866
- const pyproject = join29(workdir, "pyproject.toml");
45867
- const requirements = join29(workdir, "requirements.txt");
45971
+ const pyproject = join30(workdir, "pyproject.toml");
45972
+ const requirements = join30(workdir, "requirements.txt");
45868
45973
  if (!existsSync8(pyproject) && !existsSync8(requirements))
45869
45974
  return null;
45870
45975
  try {
@@ -45883,7 +45988,7 @@ async function detectPython(workdir) {
45883
45988
  }
45884
45989
  }
45885
45990
  async function detectPhp(workdir) {
45886
- const composerPath = join29(workdir, "composer.json");
45991
+ const composerPath = join30(workdir, "composer.json");
45887
45992
  if (!existsSync8(composerPath))
45888
45993
  return null;
45889
45994
  try {
@@ -45896,7 +46001,7 @@ async function detectPhp(workdir) {
45896
46001
  }
45897
46002
  }
45898
46003
  async function detectRuby(workdir) {
45899
- const gemfile = join29(workdir, "Gemfile");
46004
+ const gemfile = join30(workdir, "Gemfile");
45900
46005
  if (!existsSync8(gemfile))
45901
46006
  return null;
45902
46007
  try {
@@ -45908,9 +46013,9 @@ async function detectRuby(workdir) {
45908
46013
  }
45909
46014
  }
45910
46015
  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");
46016
+ const pom = join30(workdir, "pom.xml");
46017
+ const gradle = join30(workdir, "build.gradle");
46018
+ const gradleKts = join30(workdir, "build.gradle.kts");
45914
46019
  if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
45915
46020
  return null;
45916
46021
  try {
@@ -45918,7 +46023,7 @@ async function detectJvm(workdir) {
45918
46023
  const content2 = await Bun.file(pom).text();
45919
46024
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
45920
46025
  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";
46026
+ const lang2 = existsSync8(join30(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
45922
46027
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
45923
46028
  }
45924
46029
  const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
@@ -46172,7 +46277,7 @@ var init_windsurf = __esm(() => {
46172
46277
 
46173
46278
  // src/context/generator.ts
46174
46279
  import { existsSync as existsSync9 } from "fs";
46175
- import { join as join30, relative as relative12 } from "path";
46280
+ import { join as join31, relative as relative12 } from "path";
46176
46281
  async function loadContextContent(options, config2) {
46177
46282
  if (!_generatorDeps.existsSync(options.contextPath)) {
46178
46283
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -46190,7 +46295,7 @@ async function generateFor(agent, options, config2) {
46190
46295
  try {
46191
46296
  const context = await loadContextContent(options, config2);
46192
46297
  const content = generator.generate(context);
46193
- const outputPath = join30(options.outputDir, generator.outputFile);
46298
+ const outputPath = join31(options.outputDir, generator.outputFile);
46194
46299
  validateFilePath(outputPath, options.outputDir);
46195
46300
  if (!options.dryRun) {
46196
46301
  await _generatorDeps.writeFile(outputPath, content);
@@ -46208,7 +46313,7 @@ async function generateAll(options, config2, agentFilter) {
46208
46313
  for (const [agentKey, generator] of entries) {
46209
46314
  try {
46210
46315
  const content = generator.generate(context);
46211
- const outputPath = join30(options.outputDir, generator.outputFile);
46316
+ const outputPath = join31(options.outputDir, generator.outputFile);
46212
46317
  validateFilePath(outputPath, options.outputDir);
46213
46318
  if (!options.dryRun) {
46214
46319
  await _generatorDeps.writeFile(outputPath, content);
@@ -46228,7 +46333,7 @@ async function discoverPackages(repoRoot) {
46228
46333
  const glob = new Bun.Glob(pattern);
46229
46334
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
46230
46335
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
46231
- const pkgAbsolute = join30(repoRoot, pkgRelative);
46336
+ const pkgAbsolute = join31(repoRoot, pkgRelative);
46232
46337
  if (!seen.has(pkgAbsolute)) {
46233
46338
  seen.add(pkgAbsolute);
46234
46339
  packages.push(pkgAbsolute);
@@ -46260,14 +46365,14 @@ async function discoverWorkspacePackages2(repoRoot) {
46260
46365
  }
46261
46366
  }
46262
46367
  }
46263
- const turboPath = join30(repoRoot, "turbo.json");
46368
+ const turboPath = join31(repoRoot, "turbo.json");
46264
46369
  try {
46265
46370
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
46266
46371
  if (Array.isArray(turbo.packages)) {
46267
46372
  await resolveGlobs(turbo.packages);
46268
46373
  }
46269
46374
  } catch {}
46270
- const pkgPath = join30(repoRoot, "package.json");
46375
+ const pkgPath = join31(repoRoot, "package.json");
46271
46376
  try {
46272
46377
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
46273
46378
  const ws = pkg.workspaces;
@@ -46275,7 +46380,7 @@ async function discoverWorkspacePackages2(repoRoot) {
46275
46380
  if (patterns.length > 0)
46276
46381
  await resolveGlobs(patterns);
46277
46382
  } catch {}
46278
- const pnpmPath = join30(repoRoot, "pnpm-workspace.yaml");
46383
+ const pnpmPath = join31(repoRoot, "pnpm-workspace.yaml");
46279
46384
  try {
46280
46385
  const raw = await _generatorDeps.readTextFile(pnpmPath);
46281
46386
  const lines = raw.split(`
@@ -46301,7 +46406,7 @@ async function discoverWorkspacePackages2(repoRoot) {
46301
46406
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
46302
46407
  const resolvedRepoRoot = repoRoot ?? packageDir;
46303
46408
  const relativePkgPath = relative12(resolvedRepoRoot, packageDir);
46304
- const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
46409
+ const contextPath = join31(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
46305
46410
  if (!_generatorDeps.existsSync(contextPath)) {
46306
46411
  return [
46307
46412
  {
@@ -46369,7 +46474,7 @@ var init_generator2 = __esm(() => {
46369
46474
  });
46370
46475
 
46371
46476
  // src/analyze/scanner.ts
46372
- import { join as join31 } from "path";
46477
+ import { join as join32 } from "path";
46373
46478
  function resolveFrameworkAndRunner(language, pkg) {
46374
46479
  if (language === "go")
46375
46480
  return { framework: "", testRunner: "go-test" };
@@ -46391,7 +46496,7 @@ async function scanSourceRoots(workdir) {
46391
46496
  });
46392
46497
  try {
46393
46498
  const language = await deps.detectLanguage(workdir);
46394
- const pkg = await deps.readPackageJson(join31(workdir, "package.json"));
46499
+ const pkg = await deps.readPackageJson(join32(workdir, "package.json"));
46395
46500
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
46396
46501
  return [{ path: ".", language, framework, testRunner }];
46397
46502
  } catch {
@@ -46409,9 +46514,9 @@ async function scanSourceRoots(workdir) {
46409
46514
  packages = packages.slice(0, MAX_SOURCE_ROOTS);
46410
46515
  }
46411
46516
  return Promise.all(packages.map(async (pkgPath) => {
46412
- const pkgDir = pkgPath === "." ? workdir : join31(workdir, pkgPath);
46517
+ const pkgDir = pkgPath === "." ? workdir : join32(workdir, pkgPath);
46413
46518
  const language = await deps.detectLanguage(pkgDir);
46414
- const pkg = await deps.readPackageJson(join31(pkgDir, "package.json"));
46519
+ const pkg = await deps.readPackageJson(join32(pkgDir, "package.json"));
46415
46520
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
46416
46521
  return { path: pkgPath, language, framework, testRunner };
46417
46522
  }));
@@ -46444,7 +46549,7 @@ var init_analyze = __esm(() => {
46444
46549
  });
46445
46550
 
46446
46551
  // src/debate/pre-phase/grounder.ts
46447
- import { join as join32 } from "path";
46552
+ import { join as join33 } from "path";
46448
46553
  async function buildCodebaseContext(workdir) {
46449
46554
  const roots = await _grounderDeps.scanSourceRoots(workdir);
46450
46555
  return buildSourceRootsSection(normalizeRoots(workdir, roots));
@@ -46456,7 +46561,7 @@ function normalizeRoots(workdir, roots) {
46456
46561
  }));
46457
46562
  }
46458
46563
  async function writeManifestArtifact(ctx, manifest) {
46459
- const manifestPath = join32(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46564
+ const manifestPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46460
46565
  await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
46461
46566
  }
46462
46567
  var _grounderDeps, grounderStrategy = async (ctx) => {
@@ -46799,7 +46904,7 @@ function formatSpecDeltas(blockers, manifest) {
46799
46904
 
46800
46905
  // src/debate/verifiers/checks.ts
46801
46906
  import { existsSync as defaultExistsSync } from "fs";
46802
- import { join as join33 } from "path";
46907
+ import { join as join34 } from "path";
46803
46908
  function checkFilesExist(prd, workdir, deps) {
46804
46909
  const existsSync10 = deps?.existsSync ?? defaultExistsSync;
46805
46910
  const findings = [];
@@ -46809,7 +46914,7 @@ function checkFilesExist(prd, workdir, deps) {
46809
46914
  for (const entry of story.contextFiles) {
46810
46915
  const filePath = typeof entry === "string" ? entry : entry.path;
46811
46916
  const factId = typeof entry === "string" ? undefined : entry.factId;
46812
- const absPath = join33(workdir, filePath);
46917
+ const absPath = join34(workdir, filePath);
46813
46918
  if (existsSync10(absPath))
46814
46919
  continue;
46815
46920
  if (factId) {
@@ -46920,7 +47025,7 @@ var init_checks3 = () => {};
46920
47025
 
46921
47026
  // src/debate/verifiers/plan-checklist.ts
46922
47027
  import { existsSync as existsSync10 } from "fs";
46923
- import { join as join34 } from "path";
47028
+ import { join as join35 } from "path";
46924
47029
  function parsePrd(output) {
46925
47030
  if (!output)
46926
47031
  return null;
@@ -46931,7 +47036,7 @@ function parsePrd(output) {
46931
47036
  }
46932
47037
  }
46933
47038
  async function loadManifest(ctx) {
46934
- const manifestPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
47039
+ const manifestPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46935
47040
  const raw = await _planChecklistDeps.readFile(manifestPath);
46936
47041
  if (!raw)
46937
47042
  return null;
@@ -46944,7 +47049,7 @@ async function loadManifest(ctx) {
46944
47049
  }
46945
47050
  }
46946
47051
  async function emitSpecDeltas(ctx, blockers, manifest) {
46947
- const artifactPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
47052
+ const artifactPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
46948
47053
  const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
46949
47054
  await _planChecklistDeps.write(artifactPath, content);
46950
47055
  return artifactPath;
@@ -47290,7 +47395,7 @@ var init_runner_plan_helpers = __esm(() => {
47290
47395
  });
47291
47396
 
47292
47397
  // src/debate/runner-plan.ts
47293
- import { join as join35 } from "path";
47398
+ import { join as join36 } from "path";
47294
47399
  async function runPlan(ctx, taskContext, outputFormat, opts) {
47295
47400
  const logger = _debateSessionDeps.getSafeLogger();
47296
47401
  const config2 = ctx.stageConfig;
@@ -47349,7 +47454,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
47349
47454
  sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
47350
47455
  proposers: ctx.stageConfig.proposers
47351
47456
  });
47352
- const outputPaths = resolved.map((_, i) => join35(opts.outputDir, `prd-debate-${i}.json`));
47457
+ const outputPaths = resolved.map((_, i) => join36(opts.outputDir, `prd-debate-${i}.json`));
47353
47458
  const successful = [];
47354
47459
  let rebuttalList;
47355
47460
  if (selectorKind === "verifier-pick") {
@@ -49592,9 +49697,9 @@ function validateFeatureName(feature) {
49592
49697
 
49593
49698
  // src/plan/critic.ts
49594
49699
  import { mkdir as mkdir6 } from "fs/promises";
49595
- import { dirname as dirname7, join as join38 } from "path";
49700
+ import { dirname as dirname7, join as join39 } from "path";
49596
49701
  async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
49597
- const path7 = join38(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
49702
+ const path7 = join39(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
49598
49703
  await mkdir6(dirname7(path7), { recursive: true });
49599
49704
  await Bun.write(path7, formatSpecDeltas(findings, manifest));
49600
49705
  return path7;
@@ -50807,9 +50912,9 @@ __export(exports_plan_decompose, {
50807
50912
  runReplanLoop: () => runReplanLoop,
50808
50913
  planDecomposeCommand: () => planDecomposeCommand
50809
50914
  });
50810
- import { join as join39 } from "path";
50915
+ import { join as join40 } from "path";
50811
50916
  async function planDecomposeCommand(workdir, config2, options) {
50812
- const prdPath = join39(workdir, ".nax", "features", options.feature, "prd.json");
50917
+ const prdPath = join40(workdir, ".nax", "features", options.feature, "prd.json");
50813
50918
  if (!_planDeps.existsSync(prdPath)) {
50814
50919
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
50815
50920
  stage: "decompose",
@@ -50983,7 +51088,7 @@ var init_plan_decompose = __esm(() => {
50983
51088
 
50984
51089
  // src/cli/plan-runtime.ts
50985
51090
  import { existsSync as existsSync15 } from "fs";
50986
- import { join as join40 } from "path";
51091
+ import { join as join41 } from "path";
50987
51092
  function isRuntimeWithAgentManager(value) {
50988
51093
  return typeof value === "object" && value !== null && "agentManager" in value;
50989
51094
  }
@@ -51035,7 +51140,7 @@ var init_plan_runtime = __esm(() => {
51035
51140
  writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
51036
51141
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
51037
51142
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
51038
- readPackageJson: (workdir) => Bun.file(join40(workdir, "package.json")).json().catch(() => null),
51143
+ readPackageJson: (workdir) => Bun.file(join41(workdir, "package.json")).json().catch(() => null),
51039
51144
  spawnSync: (cmd, opts) => {
51040
51145
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
51041
51146
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -51420,7 +51525,7 @@ var init_metrics = __esm(() => {
51420
51525
 
51421
51526
  // src/commands/common.ts
51422
51527
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
51423
- import { join as join41, resolve as resolve13 } from "path";
51528
+ import { join as join42, resolve as resolve13 } from "path";
51424
51529
  function resolveProject(options = {}) {
51425
51530
  const { dir, feature } = options;
51426
51531
  let projectRoot;
@@ -51428,12 +51533,12 @@ function resolveProject(options = {}) {
51428
51533
  let configPath;
51429
51534
  if (dir) {
51430
51535
  projectRoot = realpathSync3(resolve13(dir));
51431
- naxDir = join41(projectRoot, ".nax");
51536
+ naxDir = join42(projectRoot, ".nax");
51432
51537
  if (!existsSync16(naxDir)) {
51433
51538
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
51434
51539
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
51435
51540
  }
51436
- configPath = join41(naxDir, "config.json");
51541
+ configPath = join42(naxDir, "config.json");
51437
51542
  if (!existsSync16(configPath)) {
51438
51543
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
51439
51544
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -51441,17 +51546,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
51441
51546
  } else {
51442
51547
  const found = findProjectRoot(process.cwd());
51443
51548
  if (!found) {
51444
- const cwdNaxDir = join41(process.cwd(), ".nax");
51549
+ const cwdNaxDir = join42(process.cwd(), ".nax");
51445
51550
  if (existsSync16(cwdNaxDir)) {
51446
- const cwdConfigPath = join41(cwdNaxDir, "config.json");
51551
+ const cwdConfigPath = join42(cwdNaxDir, "config.json");
51447
51552
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
51448
51553
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
51449
51554
  }
51450
51555
  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
51556
  }
51452
51557
  projectRoot = found;
51453
- naxDir = join41(projectRoot, ".nax");
51454
- configPath = join41(naxDir, "config.json");
51558
+ naxDir = join42(projectRoot, ".nax");
51559
+ configPath = join42(naxDir, "config.json");
51455
51560
  }
51456
51561
  let featureDir;
51457
51562
  if (feature) {
@@ -51460,8 +51565,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
51460
51565
  } catch (error48) {
51461
51566
  throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
51462
51567
  }
51463
- const featuresDir = join41(naxDir, "features");
51464
- featureDir = join41(featuresDir, feature);
51568
+ const featuresDir = join42(naxDir, "features");
51569
+ featureDir = join42(featuresDir, feature);
51465
51570
  if (!existsSync16(featureDir)) {
51466
51571
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
51467
51572
  const availableMsg = availableFeatures.length > 0 ? `
@@ -51494,7 +51599,7 @@ async function resolveProjectAsync(options = {}) {
51494
51599
  }
51495
51600
  const isPlainName = !dir.includes("/") && !dir.includes("\\");
51496
51601
  if (isPlainName) {
51497
- const registryIdentityPath = join41(globalConfigDir(), dir, ".identity");
51602
+ const registryIdentityPath = join42(globalConfigDir(), dir, ".identity");
51498
51603
  const identityFile = Bun.file(registryIdentityPath);
51499
51604
  if (await identityFile.exists()) {
51500
51605
  try {
@@ -51526,12 +51631,12 @@ function findProjectRoot(startDir) {
51526
51631
  let current = resolve13(startDir);
51527
51632
  let depth = 0;
51528
51633
  while (depth < MAX_DIRECTORY_DEPTH) {
51529
- const naxDir = join41(current, ".nax");
51530
- const configPath = join41(naxDir, "config.json");
51634
+ const naxDir = join42(current, ".nax");
51635
+ const configPath = join42(naxDir, "config.json");
51531
51636
  if (existsSync16(configPath)) {
51532
51637
  return realpathSync3(current);
51533
51638
  }
51534
- const parent = join41(current, "..");
51639
+ const parent = join42(current, "..");
51535
51640
  if (parent === current) {
51536
51641
  break;
51537
51642
  }
@@ -52690,10 +52795,10 @@ var init_effectiveness = __esm(() => {
52690
52795
 
52691
52796
  // src/execution/progress.ts
52692
52797
  import { appendFile as appendFile2, mkdir as mkdir7 } from "fs/promises";
52693
- import { join as join44 } from "path";
52798
+ import { join as join45 } from "path";
52694
52799
  async function appendProgress(featureDir, storyId, status, message) {
52695
52800
  await mkdir7(featureDir, { recursive: true });
52696
- const progressPath = join44(featureDir, "progress.txt");
52801
+ const progressPath = join45(featureDir, "progress.txt");
52697
52802
  const timestamp = new Date().toISOString();
52698
52803
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
52699
52804
  `;
@@ -52887,7 +52992,7 @@ var init_completion = __esm(() => {
52887
52992
 
52888
52993
  // src/constitution/loader.ts
52889
52994
  import { existsSync as existsSync19 } from "fs";
52890
- import { join as join45 } from "path";
52995
+ import { join as join46 } from "path";
52891
52996
  function truncateToTokens(text, maxTokens) {
52892
52997
  const maxChars = maxTokens * 3;
52893
52998
  if (text.length <= maxChars) {
@@ -52909,7 +53014,7 @@ async function loadConstitution(projectDir, config2) {
52909
53014
  }
52910
53015
  let combinedContent = "";
52911
53016
  if (!config2.skipGlobal) {
52912
- const globalPath = join45(globalConfigDir(), config2.path);
53017
+ const globalPath = join46(globalConfigDir(), config2.path);
52913
53018
  if (existsSync19(globalPath)) {
52914
53019
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
52915
53020
  const globalFile = Bun.file(validatedPath);
@@ -52919,7 +53024,7 @@ async function loadConstitution(projectDir, config2) {
52919
53024
  }
52920
53025
  }
52921
53026
  }
52922
- const projectPath = join45(projectDir, config2.path);
53027
+ const projectPath = join46(projectDir, config2.path);
52923
53028
  if (existsSync19(projectPath)) {
52924
53029
  const validatedPath = validateFilePath(projectPath, projectDir);
52925
53030
  const projectFile = Bun.file(validatedPath);
@@ -53762,6 +53867,9 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
53762
53867
  });
53763
53868
  }
53764
53869
  logUnifiedReviewPhaseStart(ctx.storyId, opName);
53870
+ if (ctx.storyId) {
53871
+ pipelineEventBus.emit({ type: "story:step", storyId: ctx.storyId, step: opName });
53872
+ }
53765
53873
  const phaseStartedAt = Date.now();
53766
53874
  const scope = ctx.runtime.costAggregator.openScope();
53767
53875
  try {
@@ -54116,6 +54224,7 @@ var init_story_orchestrator = __esm(() => {
54116
54224
  init_logger2();
54117
54225
  init_operations();
54118
54226
  init_call();
54227
+ init_event_bus();
54119
54228
  init_prepare_inputs();
54120
54229
  init_git();
54121
54230
  _storyOrchestratorDeps = {
@@ -54182,7 +54291,7 @@ var init_story_orchestrator = __esm(() => {
54182
54291
  });
54183
54292
 
54184
54293
  // src/execution/build-plan-for-strategy.ts
54185
- import { join as join46 } from "path";
54294
+ import { join as join47 } from "path";
54186
54295
  function requiresInitialRefCapture(strategy) {
54187
54296
  return isThreeSessionStrategy(strategy);
54188
54297
  }
@@ -54228,7 +54337,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
54228
54337
  }
54229
54338
  if (shouldRunRectification(config2) && inputs.rectification) {
54230
54339
  const sink = makeDeclarationSink();
54231
- const packageDir = join46(ctx.packageDir, story.workdir ?? "");
54340
+ const packageDir = join47(ctx.packageDir, story.workdir ?? "");
54232
54341
  const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
54233
54342
  const strategies = [];
54234
54343
  if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
@@ -55780,7 +55889,7 @@ function buildFrontmatter(story, ctx, role) {
55780
55889
  }
55781
55890
 
55782
55891
  // src/cli/prompts-tdd.ts
55783
- import { join as join47 } from "path";
55892
+ import { join as join48 } from "path";
55784
55893
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
55785
55894
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
55786
55895
  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 +55908,7 @@ ${frontmatter}---
55799
55908
 
55800
55909
  ${session.prompt}`;
55801
55910
  if (outputDir) {
55802
- const promptFile = join47(outputDir, `${story.id}.${session.role}.md`);
55911
+ const promptFile = join48(outputDir, `${story.id}.${session.role}.md`);
55803
55912
  await Bun.write(promptFile, fullOutput);
55804
55913
  logger.info("cli", "Written TDD prompt file", {
55805
55914
  storyId: story.id,
@@ -55815,7 +55924,7 @@ ${"=".repeat(80)}`);
55815
55924
  }
55816
55925
  }
55817
55926
  if (outputDir && ctx.contextMarkdown) {
55818
- const contextFile = join47(outputDir, `${story.id}.context.md`);
55927
+ const contextFile = join48(outputDir, `${story.id}.context.md`);
55819
55928
  const frontmatter = buildFrontmatter(story, ctx);
55820
55929
  const contextOutput = `---
55821
55930
  ${frontmatter}---
@@ -55830,16 +55939,16 @@ var init_prompts_tdd = __esm(() => {
55830
55939
 
55831
55940
  // src/cli/prompts-main.ts
55832
55941
  import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
55833
- import { join as join48 } from "path";
55942
+ import { join as join49 } from "path";
55834
55943
  async function promptsCommand(options) {
55835
55944
  const logger = getLogger();
55836
55945
  const { feature, workdir, config: config2, storyId, outputDir } = options;
55837
- const naxDir = join48(workdir, ".nax");
55946
+ const naxDir = join49(workdir, ".nax");
55838
55947
  if (!existsSync20(naxDir)) {
55839
55948
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
55840
55949
  }
55841
- const featureDir = join48(naxDir, "features", feature);
55842
- const prdPath = join48(featureDir, "prd.json");
55950
+ const featureDir = join49(naxDir, "features", feature);
55951
+ const prdPath = join49(featureDir, "prd.json");
55843
55952
  if (!existsSync20(prdPath)) {
55844
55953
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
55845
55954
  }
@@ -55906,10 +56015,10 @@ ${frontmatter}---
55906
56015
 
55907
56016
  ${ctx.prompt}`;
55908
56017
  if (outputDir) {
55909
- const promptFile = join48(outputDir, `${story.id}.prompt.md`);
56018
+ const promptFile = join49(outputDir, `${story.id}.prompt.md`);
55910
56019
  await Bun.write(promptFile, fullOutput);
55911
56020
  if (ctx.contextMarkdown) {
55912
- const contextFile = join48(outputDir, `${story.id}.context.md`);
56021
+ const contextFile = join49(outputDir, `${story.id}.context.md`);
55913
56022
  const contextOutput = `---
55914
56023
  ${frontmatter}---
55915
56024
 
@@ -55945,12 +56054,12 @@ var init_prompts_main = __esm(() => {
55945
56054
 
55946
56055
  // src/cli/prompts-init.ts
55947
56056
  import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
55948
- import { join as join49 } from "path";
56057
+ import { join as join50 } from "path";
55949
56058
  async function promptsInitCommand(options) {
55950
56059
  const { workdir, force = false, autoWireConfig = true } = options;
55951
- const templatesDir = join49(workdir, ".nax", "templates");
56060
+ const templatesDir = join50(workdir, ".nax", "templates");
55952
56061
  mkdirSync4(templatesDir, { recursive: true });
55953
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join49(templatesDir, f)));
56062
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join50(templatesDir, f)));
55954
56063
  if (existingFiles.length > 0 && !force) {
55955
56064
  _promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
55956
56065
  Pass --force to overwrite existing templates.`);
@@ -55958,7 +56067,7 @@ async function promptsInitCommand(options) {
55958
56067
  }
55959
56068
  const written = [];
55960
56069
  for (const template of TEMPLATE_ROLES) {
55961
- const filePath = join49(templatesDir, template.file);
56070
+ const filePath = join50(templatesDir, template.file);
55962
56071
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
55963
56072
  const content = TEMPLATE_HEADER + roleBody;
55964
56073
  await Bun.write(filePath, content);
@@ -55974,7 +56083,7 @@ async function promptsInitCommand(options) {
55974
56083
  return written;
55975
56084
  }
55976
56085
  async function autoWirePromptsConfig(workdir) {
55977
- const configPath = join49(workdir, "nax.config.json");
56086
+ const configPath = join50(workdir, "nax.config.json");
55978
56087
  if (!existsSync21(configPath)) {
55979
56088
  const exampleConfig = JSON.stringify({
55980
56089
  prompts: {
@@ -56144,7 +56253,7 @@ __export(exports_init_context, {
56144
56253
  });
56145
56254
  import { existsSync as existsSync22 } from "fs";
56146
56255
  import { mkdir as mkdir8 } from "fs/promises";
56147
- import { basename as basename9, join as join50 } from "path";
56256
+ import { basename as basename9, join as join51 } from "path";
56148
56257
  async function findFiles(dir, maxFiles = 200) {
56149
56258
  try {
56150
56259
  const proc = Bun.spawnSync([
@@ -56172,7 +56281,7 @@ async function findFiles(dir, maxFiles = 200) {
56172
56281
  return [];
56173
56282
  }
56174
56283
  async function readPackageManifest(projectRoot) {
56175
- const packageJsonPath = join50(projectRoot, "package.json");
56284
+ const packageJsonPath = join51(projectRoot, "package.json");
56176
56285
  if (!existsSync22(packageJsonPath)) {
56177
56286
  return null;
56178
56287
  }
@@ -56190,7 +56299,7 @@ async function readPackageManifest(projectRoot) {
56190
56299
  }
56191
56300
  }
56192
56301
  async function readReadmeSnippet(projectRoot) {
56193
- const readmePath = join50(projectRoot, "README.md");
56302
+ const readmePath = join51(projectRoot, "README.md");
56194
56303
  if (!existsSync22(readmePath)) {
56195
56304
  return null;
56196
56305
  }
@@ -56208,7 +56317,7 @@ async function detectEntryPoints(projectRoot) {
56208
56317
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
56209
56318
  const found = [];
56210
56319
  for (const candidate of candidates) {
56211
- const path13 = join50(projectRoot, candidate);
56320
+ const path13 = join51(projectRoot, candidate);
56212
56321
  if (existsSync22(path13)) {
56213
56322
  found.push(candidate);
56214
56323
  }
@@ -56219,7 +56328,7 @@ async function detectConfigFiles(projectRoot) {
56219
56328
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
56220
56329
  const found = [];
56221
56330
  for (const candidate of candidates) {
56222
- const path13 = join50(projectRoot, candidate);
56331
+ const path13 = join51(projectRoot, candidate);
56223
56332
  if (existsSync22(path13)) {
56224
56333
  found.push(candidate);
56225
56334
  }
@@ -56380,8 +56489,8 @@ function generatePackageContextTemplate(packagePath) {
56380
56489
  }
56381
56490
  async function initPackage(repoRoot, packagePath, force = false) {
56382
56491
  const logger = getLogger();
56383
- const naxDir = join50(repoRoot, ".nax", "mono", packagePath);
56384
- const contextPath = join50(naxDir, "context.md");
56492
+ const naxDir = join51(repoRoot, ".nax", "mono", packagePath);
56493
+ const contextPath = join51(naxDir, "context.md");
56385
56494
  if (existsSync22(contextPath) && !force) {
56386
56495
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
56387
56496
  return;
@@ -56395,8 +56504,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
56395
56504
  }
56396
56505
  async function initContext(projectRoot, options = {}) {
56397
56506
  const logger = getLogger();
56398
- const naxDir = join50(projectRoot, ".nax");
56399
- const contextPath = join50(naxDir, "context.md");
56507
+ const naxDir = join51(projectRoot, ".nax");
56508
+ const contextPath = join51(naxDir, "context.md");
56400
56509
  if (existsSync22(contextPath) && !options.force) {
56401
56510
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
56402
56511
  return;
@@ -56426,9 +56535,9 @@ var init_init_context = __esm(() => {
56426
56535
 
56427
56536
  // src/cli/init-detect.ts
56428
56537
  import { existsSync as existsSync23, readFileSync } from "fs";
56429
- import { join as join51 } from "path";
56538
+ import { join as join52 } from "path";
56430
56539
  function readPackageJson(projectRoot) {
56431
- const pkgPath = join51(projectRoot, "package.json");
56540
+ const pkgPath = join52(projectRoot, "package.json");
56432
56541
  if (!existsSync23(pkgPath))
56433
56542
  return;
56434
56543
  try {
@@ -56471,41 +56580,41 @@ function detectStack(projectRoot) {
56471
56580
  };
56472
56581
  }
56473
56582
  function detectRuntime(projectRoot) {
56474
- if (existsSync23(join51(projectRoot, "bun.lockb")) || existsSync23(join51(projectRoot, "bunfig.toml"))) {
56583
+ if (existsSync23(join52(projectRoot, "bun.lockb")) || existsSync23(join52(projectRoot, "bunfig.toml"))) {
56475
56584
  return "bun";
56476
56585
  }
56477
- if (existsSync23(join51(projectRoot, "package-lock.json")) || existsSync23(join51(projectRoot, "yarn.lock")) || existsSync23(join51(projectRoot, "pnpm-lock.yaml"))) {
56586
+ if (existsSync23(join52(projectRoot, "package-lock.json")) || existsSync23(join52(projectRoot, "yarn.lock")) || existsSync23(join52(projectRoot, "pnpm-lock.yaml"))) {
56478
56587
  return "node";
56479
56588
  }
56480
56589
  return "unknown";
56481
56590
  }
56482
56591
  function detectLanguage2(projectRoot) {
56483
- if (existsSync23(join51(projectRoot, "tsconfig.json")))
56592
+ if (existsSync23(join52(projectRoot, "tsconfig.json")))
56484
56593
  return "typescript";
56485
- if (existsSync23(join51(projectRoot, "pyproject.toml")) || existsSync23(join51(projectRoot, "setup.py"))) {
56594
+ if (existsSync23(join52(projectRoot, "pyproject.toml")) || existsSync23(join52(projectRoot, "setup.py"))) {
56486
56595
  return "python";
56487
56596
  }
56488
- if (existsSync23(join51(projectRoot, "Cargo.toml")))
56597
+ if (existsSync23(join52(projectRoot, "Cargo.toml")))
56489
56598
  return "rust";
56490
- if (existsSync23(join51(projectRoot, "go.mod")))
56599
+ if (existsSync23(join52(projectRoot, "go.mod")))
56491
56600
  return "go";
56492
56601
  return "unknown";
56493
56602
  }
56494
56603
  function detectLinter(projectRoot) {
56495
- if (existsSync23(join51(projectRoot, "biome.json")) || existsSync23(join51(projectRoot, "biome.jsonc"))) {
56604
+ if (existsSync23(join52(projectRoot, "biome.json")) || existsSync23(join52(projectRoot, "biome.jsonc"))) {
56496
56605
  return "biome";
56497
56606
  }
56498
- if (existsSync23(join51(projectRoot, ".eslintrc.json")) || existsSync23(join51(projectRoot, ".eslintrc.js")) || existsSync23(join51(projectRoot, "eslint.config.js"))) {
56607
+ if (existsSync23(join52(projectRoot, ".eslintrc.json")) || existsSync23(join52(projectRoot, ".eslintrc.js")) || existsSync23(join52(projectRoot, "eslint.config.js"))) {
56499
56608
  return "eslint";
56500
56609
  }
56501
56610
  return "unknown";
56502
56611
  }
56503
56612
  function detectMonorepo(projectRoot) {
56504
- if (existsSync23(join51(projectRoot, "turbo.json")))
56613
+ if (existsSync23(join52(projectRoot, "turbo.json")))
56505
56614
  return "turborepo";
56506
- if (existsSync23(join51(projectRoot, "nx.json")))
56615
+ if (existsSync23(join52(projectRoot, "nx.json")))
56507
56616
  return "nx";
56508
- if (existsSync23(join51(projectRoot, "pnpm-workspace.yaml")))
56617
+ if (existsSync23(join52(projectRoot, "pnpm-workspace.yaml")))
56509
56618
  return "pnpm-workspaces";
56510
56619
  const pkg = readPackageJson(projectRoot);
56511
56620
  if (pkg?.workspaces)
@@ -56649,7 +56758,7 @@ __export(exports_init, {
56649
56758
  });
56650
56759
  import { existsSync as existsSync24 } from "fs";
56651
56760
  import { mkdir as mkdir9 } from "fs/promises";
56652
- import { join as join52 } from "path";
56761
+ import { join as join53 } from "path";
56653
56762
  function validateProjectName(name) {
56654
56763
  if (!name)
56655
56764
  return { valid: false, error: "name must be non-empty" };
@@ -56685,7 +56794,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
56685
56794
  }
56686
56795
  async function updateGitignore(projectRoot) {
56687
56796
  const logger = getLogger();
56688
- const gitignorePath = join52(projectRoot, ".gitignore");
56797
+ const gitignorePath = join53(projectRoot, ".gitignore");
56689
56798
  let existing = "";
56690
56799
  if (existsSync24(gitignorePath)) {
56691
56800
  existing = await Bun.file(gitignorePath).text();
@@ -56771,7 +56880,7 @@ async function initGlobal() {
56771
56880
  await mkdir9(globalDir, { recursive: true });
56772
56881
  logger.info("init", "Created global config directory", { path: globalDir });
56773
56882
  }
56774
- const configPath = join52(globalDir, "config.json");
56883
+ const configPath = join53(globalDir, "config.json");
56775
56884
  if (!existsSync24(configPath)) {
56776
56885
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
56777
56886
  `);
@@ -56779,14 +56888,14 @@ async function initGlobal() {
56779
56888
  } else {
56780
56889
  logger.info("init", "Global config already exists", { path: configPath });
56781
56890
  }
56782
- const constitutionPath = join52(globalDir, "constitution.md");
56891
+ const constitutionPath = join53(globalDir, "constitution.md");
56783
56892
  if (!existsSync24(constitutionPath)) {
56784
56893
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
56785
56894
  logger.info("init", "Created global constitution", { path: constitutionPath });
56786
56895
  } else {
56787
56896
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
56788
56897
  }
56789
- const hooksDir = join52(globalDir, "hooks");
56898
+ const hooksDir = join53(globalDir, "hooks");
56790
56899
  if (!existsSync24(hooksDir)) {
56791
56900
  await mkdir9(hooksDir, { recursive: true });
56792
56901
  logger.info("init", "Created global hooks directory", { path: hooksDir });
@@ -56819,7 +56928,7 @@ async function initProject(projectRoot, options) {
56819
56928
  if (detectedName && !options?.force) {
56820
56929
  const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
56821
56930
  if (collision.collision && collision.existing) {
56822
- const configPath2 = join52(projectDir, "config.json");
56931
+ const configPath2 = join53(projectDir, "config.json");
56823
56932
  throw new NaxError([
56824
56933
  `Project name collision: "${detectedName}"`,
56825
56934
  ` This project: ${projectRoot}`,
@@ -56847,7 +56956,7 @@ async function initProject(projectRoot, options) {
56847
56956
  linter: stack.linter,
56848
56957
  monorepo: stack.monorepo
56849
56958
  });
56850
- const configPath = join52(projectDir, "config.json");
56959
+ const configPath = join53(projectDir, "config.json");
56851
56960
  if (!existsSync24(configPath)) {
56852
56961
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
56853
56962
  `);
@@ -56856,14 +56965,14 @@ async function initProject(projectRoot, options) {
56856
56965
  logger.info("init", "Project config already exists", { path: configPath });
56857
56966
  }
56858
56967
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
56859
- const constitutionPath = join52(projectDir, "constitution.md");
56968
+ const constitutionPath = join53(projectDir, "constitution.md");
56860
56969
  if (!existsSync24(constitutionPath) || options?.force) {
56861
56970
  await Bun.write(constitutionPath, buildConstitution(stack));
56862
56971
  logger.info("init", "Created project constitution", { path: constitutionPath });
56863
56972
  } else {
56864
56973
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
56865
56974
  }
56866
- const hooksDir = join52(projectDir, "hooks");
56975
+ const hooksDir = join53(projectDir, "hooks");
56867
56976
  if (!existsSync24(hooksDir)) {
56868
56977
  await mkdir9(hooksDir, { recursive: true });
56869
56978
  logger.info("init", "Created project hooks directory", { path: hooksDir });
@@ -58298,12 +58407,12 @@ var init_loader4 = __esm(() => {
58298
58407
  });
58299
58408
 
58300
58409
  // src/utils/paths.ts
58301
- import { join as join63 } from "path";
58410
+ import { join as join64 } from "path";
58302
58411
  function getRunsDir() {
58303
- return process.env.NAX_RUNS_DIR ?? join63(globalConfigDir(), "runs");
58412
+ return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
58304
58413
  }
58305
58414
  function getEventsRootDir() {
58306
- return join63(globalConfigDir(), "events");
58415
+ return join64(globalConfigDir(), "events");
58307
58416
  }
58308
58417
  var init_paths3 = __esm(() => {
58309
58418
  init_paths();
@@ -58363,7 +58472,7 @@ var init_command_argv = __esm(() => {
58363
58472
  });
58364
58473
 
58365
58474
  // src/hooks/runner.ts
58366
- import { join as join70 } from "path";
58475
+ import { join as join71 } from "path";
58367
58476
  function createDrainDeadline2(deadlineMs) {
58368
58477
  let timeoutId;
58369
58478
  const promise2 = new Promise((resolve16) => {
@@ -58382,14 +58491,14 @@ async function loadHooksConfig(projectDir, globalDir) {
58382
58491
  let globalHooks = { hooks: {} };
58383
58492
  let projectHooks = { hooks: {} };
58384
58493
  let skipGlobal = false;
58385
- const projectPath = join70(projectDir, "hooks.json");
58494
+ const projectPath = join71(projectDir, "hooks.json");
58386
58495
  const projectData = await loadJsonFile(projectPath, "hooks");
58387
58496
  if (projectData) {
58388
58497
  projectHooks = projectData;
58389
58498
  skipGlobal = projectData.skipGlobal ?? false;
58390
58499
  }
58391
58500
  if (!skipGlobal && globalDir) {
58392
- const globalPath = join70(globalDir, "hooks.json");
58501
+ const globalPath = join71(globalDir, "hooks.json");
58393
58502
  const globalData = await loadJsonFile(globalPath, "hooks");
58394
58503
  if (globalData) {
58395
58504
  globalHooks = globalData;
@@ -58559,7 +58668,7 @@ var package_default;
58559
58668
  var init_package = __esm(() => {
58560
58669
  package_default = {
58561
58670
  name: "@nathapp/nax",
58562
- version: "0.68.7",
58671
+ version: "0.68.8",
58563
58672
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
58564
58673
  type: "module",
58565
58674
  bin: {
@@ -58654,8 +58763,8 @@ var init_version = __esm(() => {
58654
58763
  NAX_VERSION = package_default.version;
58655
58764
  NAX_COMMIT = (() => {
58656
58765
  try {
58657
- if (/^[0-9a-f]{6,10}$/.test("0ba99b3b"))
58658
- return "0ba99b3b";
58766
+ if (/^[0-9a-f]{6,10}$/.test("00515ea8"))
58767
+ return "00515ea8";
58659
58768
  } catch {}
58660
58769
  try {
58661
58770
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -59532,15 +59641,15 @@ var init_acceptance_loop = __esm(() => {
59532
59641
 
59533
59642
  // src/session/scratch-purge.ts
59534
59643
  import { mkdir as mkdir13, rename, rm } from "fs/promises";
59535
- import { dirname as dirname12, join as join71 } from "path";
59644
+ import { dirname as dirname12, join as join72 } from "path";
59536
59645
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
59537
- const sessionsDir = join71(projectDir, ".nax", "features", featureName, "sessions");
59646
+ const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
59538
59647
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
59539
59648
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
59540
59649
  let purged = 0;
59541
59650
  for (const sessionId of sessionIds) {
59542
- const sessionDir = join71(sessionsDir, sessionId);
59543
- const descriptorPath = join71(sessionDir, "descriptor.json");
59651
+ const sessionDir = join72(sessionsDir, sessionId);
59652
+ const descriptorPath = join72(sessionDir, "descriptor.json");
59544
59653
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
59545
59654
  continue;
59546
59655
  let lastActivityAt;
@@ -59556,7 +59665,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
59556
59665
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
59557
59666
  continue;
59558
59667
  if (archiveInsteadOfDelete) {
59559
- const archiveDest = join71(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
59668
+ const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
59560
59669
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
59561
59670
  } else {
59562
59671
  await _scratchPurgeDeps.remove(sessionDir);
@@ -59936,6 +60045,7 @@ async function handleRunCompletion(options) {
59936
60045
  const regressionMode = config2.execution.regressionGate?.mode;
59937
60046
  if (options.skipRegression) {} else if (regressionMode === "deferred" && config2.quality.commands.test) {
59938
60047
  statusWriter.setPostRunPhase("regression", { status: "running" });
60048
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "regression" });
59939
60049
  const regressionResult = await _runCompletionDeps.runDeferredRegression({
59940
60050
  config: config2,
59941
60051
  prd,
@@ -59950,6 +60060,7 @@ async function handleRunCompletion(options) {
59950
60060
  });
59951
60061
  if (regressionResult.success) {
59952
60062
  statusWriter.setPostRunPhase("regression", { status: "passed", lastRunAt });
60063
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: true });
59953
60064
  } else {
59954
60065
  statusWriter.setPostRunPhase("regression", {
59955
60066
  status: "failed",
@@ -59957,6 +60068,7 @@ async function handleRunCompletion(options) {
59957
60068
  affectedStories: regressionResult.affectedStories,
59958
60069
  lastRunAt
59959
60070
  });
60071
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: false });
59960
60072
  for (const storyId of regressionResult.affectedStories) {
59961
60073
  const story = prd.userStories.find((s) => s.id === storyId);
59962
60074
  if (story) {
@@ -60021,6 +60133,9 @@ async function handleRunCompletion(options) {
60021
60133
  }
60022
60134
  let pluginGateFailed = false;
60023
60135
  const deferredReview = options.deferredReview;
60136
+ if (deferredReview !== undefined) {
60137
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "review", passed: !deferredReview.anyFailed });
60138
+ }
60024
60139
  if (deferredReview?.anyFailed) {
60025
60140
  const failedReviewers = deferredReview.reviewerResults.filter((r) => !r.passed).map((r) => r.name);
60026
60141
  pluginGateFailed = config2.review.pluginMode === "gating";
@@ -60288,12 +60403,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
60288
60403
 
60289
60404
  // src/pipeline/subscribers/events-writer.ts
60290
60405
  import { appendFile as appendFile4, mkdir as mkdir14 } from "fs/promises";
60291
- import { basename as basename13, join as join72 } from "path";
60406
+ import { basename as basename13, join as join73 } from "path";
60292
60407
  function wireEventsWriter(bus, feature, runId, workdir) {
60293
60408
  const logger = getSafeLogger();
60294
60409
  const project = basename13(workdir);
60295
- const eventsDir = join72(getEventsRootDir(), project);
60296
- const eventsFile = join72(eventsDir, "events.jsonl");
60410
+ const eventsDir = join73(getEventsRootDir(), project);
60411
+ const eventsFile = join73(eventsDir, "events.jsonl");
60297
60412
  let dirReady = false;
60298
60413
  const write = (line) => {
60299
60414
  return (async () => {
@@ -60474,12 +60589,12 @@ var init_interaction2 = __esm(() => {
60474
60589
 
60475
60590
  // src/pipeline/subscribers/registry.ts
60476
60591
  import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
60477
- import { basename as basename14, join as join73 } from "path";
60592
+ import { basename as basename14, join as join74 } from "path";
60478
60593
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
60479
60594
  const logger = getSafeLogger();
60480
60595
  const project = basename14(workdir);
60481
- const runDir = join73(getRunsDir(), `${project}-${feature}-${runId}`);
60482
- const metaFile = join73(runDir, "meta.json");
60596
+ const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
60597
+ const metaFile = join74(runDir, "meta.json");
60483
60598
  const unsub = bus.on("run:started", (_ev) => {
60484
60599
  return (async () => {
60485
60600
  try {
@@ -60489,8 +60604,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
60489
60604
  project,
60490
60605
  feature,
60491
60606
  workdir,
60492
- statusPath: join73(outputDir, "features", feature, "status.json"),
60493
- eventsDir: join73(outputDir, "features", feature, "runs"),
60607
+ statusPath: join74(outputDir, "features", feature, "status.json"),
60608
+ eventsDir: join74(outputDir, "features", feature, "runs"),
60494
60609
  registeredAt: new Date().toISOString()
60495
60610
  };
60496
60611
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -60736,7 +60851,7 @@ var init_types9 = __esm(() => {
60736
60851
 
60737
60852
  // src/worktree/dependencies.ts
60738
60853
  import { existsSync as existsSync31 } from "fs";
60739
- import { join as join74 } from "path";
60854
+ import { join as join75 } from "path";
60740
60855
  async function prepareWorktreeDependencies(options) {
60741
60856
  const mode = options.config.execution.worktreeDependencies.mode;
60742
60857
  const resolvedCwd = resolveDependencyCwd(options);
@@ -60750,7 +60865,7 @@ async function prepareWorktreeDependencies(options) {
60750
60865
  }
60751
60866
  }
60752
60867
  function resolveDependencyCwd(options) {
60753
- return options.storyWorkdir ? join74(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60868
+ return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60754
60869
  }
60755
60870
  function resolveInheritedDependencies(options, resolvedCwd) {
60756
60871
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -60760,7 +60875,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
60760
60875
  }
60761
60876
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
60762
60877
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
60763
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join74(directory, filename))));
60878
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
60764
60879
  }
60765
60880
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
60766
60881
  const setupCommand = config2.execution.worktreeDependencies.setupCommand;
@@ -60824,13 +60939,13 @@ __export(exports_manager, {
60824
60939
  });
60825
60940
  import { existsSync as existsSync32, symlinkSync } from "fs";
60826
60941
  import { mkdir as mkdir16 } from "fs/promises";
60827
- import { join as join75 } from "path";
60942
+ import { join as join76 } from "path";
60828
60943
 
60829
60944
  class WorktreeManager {
60830
60945
  async ensureGitExcludes(projectRoot) {
60831
60946
  const logger = getSafeLogger();
60832
- const infoDir = join75(projectRoot, ".git", "info");
60833
- const excludePath = join75(infoDir, "exclude");
60947
+ const infoDir = join76(projectRoot, ".git", "info");
60948
+ const excludePath = join76(infoDir, "exclude");
60834
60949
  try {
60835
60950
  await mkdir16(infoDir, { recursive: true });
60836
60951
  let existing = "";
@@ -60857,7 +60972,7 @@ ${missing.join(`
60857
60972
  }
60858
60973
  async create(projectRoot, storyId) {
60859
60974
  validateStoryId(storyId);
60860
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
60975
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
60861
60976
  const branchName = `nax/${storyId}`;
60862
60977
  try {
60863
60978
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -60898,9 +61013,9 @@ ${missing.join(`
60898
61013
  }
60899
61014
  throw new Error(`Failed to create worktree: ${String(error48)}`);
60900
61015
  }
60901
- const envSource = join75(projectRoot, ".env");
61016
+ const envSource = join76(projectRoot, ".env");
60902
61017
  if (existsSync32(envSource)) {
60903
- const envTarget = join75(worktreePath, ".env");
61018
+ const envTarget = join76(worktreePath, ".env");
60904
61019
  try {
60905
61020
  symlinkSync(envSource, envTarget, "file");
60906
61021
  } catch (error48) {
@@ -60911,7 +61026,7 @@ ${missing.join(`
60911
61026
  }
60912
61027
  async remove(projectRoot, storyId) {
60913
61028
  validateStoryId(storyId);
60914
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
61029
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
60915
61030
  const branchName = `nax/${storyId}`;
60916
61031
  try {
60917
61032
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -61723,10 +61838,10 @@ var init_merge_conflict_rectify = __esm(() => {
61723
61838
  });
61724
61839
 
61725
61840
  // src/execution/pipeline-result-handler.ts
61726
- import { join as join76 } from "path";
61841
+ import { join as join77 } from "path";
61727
61842
  async function removeWorktreeDirectory(projectRoot, storyId) {
61728
61843
  const logger = getSafeLogger();
61729
- const worktreePath = join76(projectRoot, ".nax-wt", storyId);
61844
+ const worktreePath = join77(projectRoot, ".nax-wt", storyId);
61730
61845
  try {
61731
61846
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
61732
61847
  cwd: projectRoot,
@@ -61943,7 +62058,7 @@ var init_pipeline_result_handler = __esm(() => {
61943
62058
 
61944
62059
  // src/execution/iteration-runner.ts
61945
62060
  import { existsSync as existsSync33 } from "fs";
61946
- import { join as join77 } from "path";
62061
+ import { join as join78 } from "path";
61947
62062
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
61948
62063
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
61949
62064
  if (ctx.dryRun) {
@@ -61968,7 +62083,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
61968
62083
  const storyStartTime = Date.now();
61969
62084
  let effectiveWorkdir = ctx.workdir;
61970
62085
  if (ctx.config.execution.storyIsolation === "worktree") {
61971
- const worktreePath = join77(ctx.workdir, ".nax-wt", story.id);
62086
+ const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
61972
62087
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
61973
62088
  if (!worktreeExists) {
61974
62089
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -61988,7 +62103,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
61988
62103
  }
61989
62104
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
61990
62105
  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;
62106
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
61992
62107
  let dependencyContext;
61993
62108
  if (ctx.config.execution.storyIsolation === "worktree") {
61994
62109
  try {
@@ -62015,7 +62130,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
62015
62130
  };
62016
62131
  }
62017
62132
  }
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;
62133
+ 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
62134
  const pipelineContext = {
62020
62135
  config: effectiveConfig,
62021
62136
  rootConfig: ctx.config,
@@ -62217,7 +62332,7 @@ __export(exports_parallel_worker, {
62217
62332
  buildWorktreePipelineContext: () => buildWorktreePipelineContext,
62218
62333
  _parallelWorkerDeps: () => _parallelWorkerDeps
62219
62334
  });
62220
- import { join as join78 } from "path";
62335
+ import { join as join79 } from "path";
62221
62336
  function buildWorktreePipelineContext(base, _story) {
62222
62337
  return { ...base, prd: structuredClone(base.prd) };
62223
62338
  }
@@ -62240,7 +62355,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
62240
62355
  story,
62241
62356
  stories: [story],
62242
62357
  projectDir: context.projectDir,
62243
- workdir: dependencyContext.cwd ?? (story.workdir ? join78(worktreePath, story.workdir) : worktreePath),
62358
+ workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
62244
62359
  worktreeDependencyContext: dependencyContext,
62245
62360
  routing,
62246
62361
  storyGitRef: storyGitRef ?? undefined
@@ -62617,6 +62732,7 @@ async function executeUnified(ctx, initialPrd) {
62617
62732
  if (isComplete(prd)) {
62618
62733
  logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
62619
62734
  const naxIgnoreIndex = await getRunNaxIgnoreIndex(prd);
62735
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
62620
62736
  deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
62621
62737
  return buildResult2("completed");
62622
62738
  }
@@ -62670,6 +62786,7 @@ async function executeUnified(ctx, initialPrd) {
62670
62786
  return buildResult2("pre-merge-aborted");
62671
62787
  }
62672
62788
  logger?.debug("execution", "Running deferred review");
62789
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
62673
62790
  deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
62674
62791
  logger?.debug("execution", "Deferred review done \u2014 returning completed");
62675
62792
  return buildResult2("completed");
@@ -63125,7 +63242,7 @@ async function writeStatusFile(filePath, status) {
63125
63242
  var init_status_file = () => {};
63126
63243
 
63127
63244
  // src/execution/status-writer.ts
63128
- import { join as join79 } from "path";
63245
+ import { join as join80 } from "path";
63129
63246
 
63130
63247
  class StatusWriter {
63131
63248
  statusFile;
@@ -63244,7 +63361,7 @@ class StatusWriter {
63244
63361
  if (!this._prd)
63245
63362
  return;
63246
63363
  const safeLogger = getSafeLogger();
63247
- const featureStatusPath = join79(featureDir, "status.json");
63364
+ const featureStatusPath = join80(featureDir, "status.json");
63248
63365
  const write = async () => {
63249
63366
  try {
63250
63367
  const base = this.getSnapshot(totalCost, iterations);
@@ -63678,7 +63795,7 @@ __export(exports_run_initialization, {
63678
63795
  initializeRun: () => initializeRun,
63679
63796
  _reconcileDeps: () => _reconcileDeps
63680
63797
  });
63681
- import { join as join80 } from "path";
63798
+ import { join as join81 } from "path";
63682
63799
  async function reconcileState(prd, prdPath, workdir, config2) {
63683
63800
  const logger = getSafeLogger();
63684
63801
  let reconciledCount = 0;
@@ -63695,7 +63812,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
63695
63812
  });
63696
63813
  continue;
63697
63814
  }
63698
- const effectiveWorkdir = story.workdir ? join80(workdir, story.workdir) : workdir;
63815
+ const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
63699
63816
  try {
63700
63817
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
63701
63818
  if (!reviewResult.success) {
@@ -93488,7 +93605,7 @@ __export(exports_curator, {
93488
93605
  });
93489
93606
  import { readdirSync as readdirSync8 } from "fs";
93490
93607
  import { unlink as unlink4 } from "fs/promises";
93491
- import { basename as basename15, join as join82 } from "path";
93608
+ import { basename as basename15, join as join83 } from "path";
93492
93609
  function getProjectKey(config2, projectDir) {
93493
93610
  return config2.name?.trim() || basename15(projectDir);
93494
93611
  }
@@ -93571,7 +93688,7 @@ async function curatorStatus(options) {
93571
93688
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93572
93689
  const projectKey = getProjectKey(config2, resolved.projectDir);
93573
93690
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93574
- const runsDir = join82(outputDir, "runs");
93691
+ const runsDir = join83(outputDir, "runs");
93575
93692
  const runIds = listRunIds(runsDir);
93576
93693
  let runId;
93577
93694
  if (options.run) {
@@ -93588,8 +93705,8 @@ async function curatorStatus(options) {
93588
93705
  runId = runIds[runIds.length - 1];
93589
93706
  }
93590
93707
  console.log(`Run: ${runId}`);
93591
- const runDir = join82(runsDir, runId);
93592
- const observationsPath = join82(runDir, "observations.jsonl");
93708
+ const runDir = join83(runsDir, runId);
93709
+ const observationsPath = join83(runDir, "observations.jsonl");
93593
93710
  const observations = await parseObservations(observationsPath);
93594
93711
  const counts = new Map;
93595
93712
  for (const obs of observations) {
@@ -93599,7 +93716,7 @@ async function curatorStatus(options) {
93599
93716
  for (const [kind, count] of counts.entries()) {
93600
93717
  console.log(` ${kind}: ${count}`);
93601
93718
  }
93602
- const proposalsPath = join82(runDir, "curator-proposals.md");
93719
+ const proposalsPath = join83(runDir, "curator-proposals.md");
93603
93720
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93604
93721
  if (proposalText !== null) {
93605
93722
  console.log("");
@@ -93613,8 +93730,8 @@ async function curatorCommit(options) {
93613
93730
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93614
93731
  const projectKey = getProjectKey(config2, resolved.projectDir);
93615
93732
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93616
- const runDir = join82(outputDir, "runs", options.runId);
93617
- const proposalsPath = join82(runDir, "curator-proposals.md");
93733
+ const runDir = join83(outputDir, "runs", options.runId);
93734
+ const proposalsPath = join83(runDir, "curator-proposals.md");
93618
93735
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93619
93736
  if (proposalText === null) {
93620
93737
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -93630,7 +93747,7 @@ async function curatorCommit(options) {
93630
93747
  const dropFileState = new Map;
93631
93748
  const skippedDrops = new Set;
93632
93749
  for (const drop2 of drops) {
93633
- const targetPath = join82(resolved.projectDir, drop2.canonicalFile);
93750
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
93634
93751
  if (!dropFileState.has(targetPath)) {
93635
93752
  const fileExists2 = await Bun.file(targetPath).exists();
93636
93753
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -93664,7 +93781,7 @@ async function curatorCommit(options) {
93664
93781
  if (skippedDrops.has(drop2)) {
93665
93782
  continue;
93666
93783
  }
93667
- const targetPath = join82(resolved.projectDir, drop2.canonicalFile);
93784
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
93668
93785
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
93669
93786
  const filtered = filterDropContent(existing, drop2.description);
93670
93787
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -93673,7 +93790,7 @@ async function curatorCommit(options) {
93673
93790
  }
93674
93791
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
93675
93792
  for (const add2 of adds) {
93676
- const targetPath = join82(resolved.projectDir, add2.canonicalFile);
93793
+ const targetPath = join83(resolved.projectDir, add2.canonicalFile);
93677
93794
  const content = buildAddContent(add2);
93678
93795
  await _curatorCmdDeps.appendFile(targetPath, content);
93679
93796
  modifiedFiles.add(targetPath);
@@ -93710,7 +93827,7 @@ async function curatorDryrun(options) {
93710
93827
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93711
93828
  const projectKey = getProjectKey(config2, resolved.projectDir);
93712
93829
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93713
- const runsDir = join82(outputDir, "runs");
93830
+ const runsDir = join83(outputDir, "runs");
93714
93831
  const runIds = listRunIds(runsDir);
93715
93832
  if (runIds.length === 0) {
93716
93833
  console.log("No runs found.");
@@ -93721,7 +93838,7 @@ async function curatorDryrun(options) {
93721
93838
  console.log(`Run ${options.run} not found in ${runsDir}.`);
93722
93839
  return;
93723
93840
  }
93724
- const observationsPath = join82(runsDir, runId, "observations.jsonl");
93841
+ const observationsPath = join83(runsDir, runId, "observations.jsonl");
93725
93842
  const observations = await parseObservations(observationsPath);
93726
93843
  const thresholds = getThresholds(config2);
93727
93844
  const proposals = runHeuristics(observations, thresholds);
@@ -93762,12 +93879,12 @@ async function curatorGc(options) {
93762
93879
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
93763
93880
  const projectKey = getProjectKey(config2, resolved.projectDir);
93764
93881
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93765
- const perRunsDir = join82(outputDir, "runs");
93882
+ const perRunsDir = join83(outputDir, "runs");
93766
93883
  for (const runId of uniqueRunIds) {
93767
93884
  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"));
93885
+ const runDir = join83(perRunsDir, runId);
93886
+ await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
93887
+ await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
93771
93888
  }
93772
93889
  }
93773
93890
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -93812,7 +93929,7 @@ var init_curator2 = __esm(() => {
93812
93929
  init_source();
93813
93930
  import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
93814
93931
  import { homedir as homedir3 } from "os";
93815
- import { basename as basename16, join as join83 } from "path";
93932
+ import { basename as basename16, join as join84 } from "path";
93816
93933
 
93817
93934
  // node_modules/commander/esm.mjs
93818
93935
  var import__ = __toESM(require_commander(), 1);
@@ -93836,12 +93953,12 @@ init_errors();
93836
93953
  init_operations();
93837
93954
 
93838
93955
  // src/plan/strategies/context-builder.ts
93839
- import { join as join37 } from "path";
93956
+ import { join as join38 } from "path";
93840
93957
  init_config();
93841
93958
  init_errors();
93842
93959
  init_interaction();
93843
93960
  async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93844
- const naxDir = join37(workdir, ".nax");
93961
+ const naxDir = join38(workdir, ".nax");
93845
93962
  if (!deps.existsSync(naxDir)) {
93846
93963
  throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
93847
93964
  stage: "plan",
@@ -93849,8 +93966,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93849
93966
  });
93850
93967
  }
93851
93968
  validateFeatureName(options.feature);
93852
- const outputDir = join37(naxDir, "features", options.feature);
93853
- const outputPath = join37(outputDir, "prd.json");
93969
+ const outputDir = join38(naxDir, "features", options.feature);
93970
+ const outputPath = join38(outputDir, "prd.json");
93854
93971
  const [specContent, sourceRoots, pkg] = await Promise.all([
93855
93972
  deps.readFile(options.from),
93856
93973
  deps.scanSourceRoots(workdir),
@@ -93865,7 +93982,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93865
93982
  ...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
93866
93983
  ];
93867
93984
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
93868
- const packageJson = await deps.readPackageJsonAt(join37(workdir, relativePath, "package.json"));
93985
+ const packageJson = await deps.readPackageJsonAt(join38(workdir, relativePath, "package.json"));
93869
93986
  return buildPackageSummary(relativePath, packageJson);
93870
93987
  }));
93871
93988
  const projectName = detectProjectName(workdir, pkg);
@@ -94182,7 +94299,8 @@ class RefinePlanStrategy {
94182
94299
  packages: ctx.relativePackages,
94183
94300
  packageDetails: ctx.packageDetails,
94184
94301
  projectProfile: ctx.config.project,
94185
- specGuard: ctx.config.plan.specGuard ?? false
94302
+ specGuard: ctx.config.plan.specGuard ?? false,
94303
+ workdir: ctx.workdir
94186
94304
  });
94187
94305
  return writeOrRecoverPrd(ctx, prd);
94188
94306
  } catch (err) {
@@ -94467,7 +94585,7 @@ init_interaction();
94467
94585
  init_prd();
94468
94586
  init_runtime();
94469
94587
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
94470
- import { basename as basename7, join as join42, resolve as resolve14 } from "path";
94588
+ import { basename as basename7, join as join43, resolve as resolve14 } from "path";
94471
94589
  var _statusFeaturesDeps = {
94472
94590
  projectOutputDir,
94473
94591
  loadConfig
@@ -94481,7 +94599,7 @@ function isPidAlive(pid) {
94481
94599
  }
94482
94600
  }
94483
94601
  async function loadStatusFile(featureDir) {
94484
- const statusPath = join42(featureDir, "status.json");
94602
+ const statusPath = join43(featureDir, "status.json");
94485
94603
  if (!existsSync17(statusPath)) {
94486
94604
  return null;
94487
94605
  }
@@ -94496,7 +94614,7 @@ async function loadProjectStatusFile(projectDir) {
94496
94614
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94497
94615
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94498
94616
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94499
- const statusPath = join42(outputDir, "status.json");
94617
+ const statusPath = join43(outputDir, "status.json");
94500
94618
  if (!existsSync17(statusPath)) {
94501
94619
  return null;
94502
94620
  }
@@ -94508,7 +94626,7 @@ async function loadProjectStatusFile(projectDir) {
94508
94626
  }
94509
94627
  }
94510
94628
  async function getFeatureSummary(featureName, featureDir) {
94511
- const prdPath = join42(featureDir, "prd.json");
94629
+ const prdPath = join43(featureDir, "prd.json");
94512
94630
  if (!existsSync17(prdPath)) {
94513
94631
  return {
94514
94632
  name: featureName,
@@ -94551,7 +94669,7 @@ async function getFeatureSummary(featureName, featureDir) {
94551
94669
  };
94552
94670
  }
94553
94671
  }
94554
- const runsDir = join42(featureDir, "runs");
94672
+ const runsDir = join43(featureDir, "runs");
94555
94673
  if (existsSync17(runsDir)) {
94556
94674
  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
94675
  if (runs.length > 0) {
@@ -94565,7 +94683,7 @@ async function displayAllFeatures(projectDir) {
94565
94683
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94566
94684
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94567
94685
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94568
- const featuresDir = join42(outputDir, "features");
94686
+ const featuresDir = join43(outputDir, "features");
94569
94687
  if (!existsSync17(featuresDir)) {
94570
94688
  console.log(source_default.dim("No features found."));
94571
94689
  return;
@@ -94606,7 +94724,7 @@ async function displayAllFeatures(projectDir) {
94606
94724
  console.log();
94607
94725
  }
94608
94726
  }
94609
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join42(featuresDir, name))));
94727
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join43(featuresDir, name))));
94610
94728
  console.log(source_default.bold(`\uD83D\uDCCA Features
94611
94729
  `));
94612
94730
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -94632,7 +94750,7 @@ async function displayAllFeatures(projectDir) {
94632
94750
  console.log();
94633
94751
  }
94634
94752
  async function displayFeatureDetails(featureName, featureDir) {
94635
- const prdPath = join42(featureDir, "prd.json");
94753
+ const prdPath = join43(featureDir, "prd.json");
94636
94754
  if (!existsSync17(prdPath)) {
94637
94755
  console.log(source_default.bold(`
94638
94756
  \uD83D\uDCCA ${featureName}
@@ -94778,7 +94896,7 @@ async function displayFeatureStatus(options = {}) {
94778
94896
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94779
94897
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94780
94898
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94781
- featureDir = join42(outputDir, "features", options.feature);
94899
+ featureDir = join43(outputDir, "features", options.feature);
94782
94900
  } else {
94783
94901
  const resolved = resolveProject({ feature: options.feature });
94784
94902
  if (!resolved.featureDir) {
@@ -94798,7 +94916,7 @@ init_errors();
94798
94916
  init_logger2();
94799
94917
  init_runtime();
94800
94918
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
94801
- import { basename as basename8, join as join43 } from "path";
94919
+ import { basename as basename8, join as join44 } from "path";
94802
94920
  async function resolveOutputDir2(workdir, override) {
94803
94921
  if (override)
94804
94922
  return override;
@@ -94822,7 +94940,7 @@ async function runsListCommand(options) {
94822
94940
  const logger = getLogger();
94823
94941
  const { feature, workdir } = options;
94824
94942
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
94825
- const runsDir = join43(outputDir, "features", feature, "runs");
94943
+ const runsDir = join44(outputDir, "features", feature, "runs");
94826
94944
  if (!existsSync18(runsDir)) {
94827
94945
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
94828
94946
  return;
@@ -94834,7 +94952,7 @@ async function runsListCommand(options) {
94834
94952
  }
94835
94953
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
94836
94954
  for (const file3 of files.sort().reverse()) {
94837
- const logPath = join43(runsDir, file3);
94955
+ const logPath = join44(runsDir, file3);
94838
94956
  const entries = await parseRunLog(logPath);
94839
94957
  const startEvent = entries.find((e) => e.message === "run.start");
94840
94958
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -94861,7 +94979,7 @@ async function runsShowCommand(options) {
94861
94979
  const logger = getLogger();
94862
94980
  const { runId, feature, workdir } = options;
94863
94981
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
94864
- const logPath = join43(outputDir, "features", feature, "runs", `${runId}.jsonl`);
94982
+ const logPath = join44(outputDir, "features", feature, "runs", `${runId}.jsonl`);
94865
94983
  if (!existsSync18(logPath)) {
94866
94984
  logger.error("cli", "Run not found", { runId, feature, logPath });
94867
94985
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -94976,7 +95094,7 @@ init_source();
94976
95094
  init_loader();
94977
95095
  init_generator2();
94978
95096
  import { existsSync as existsSync25 } from "fs";
94979
- import { join as join57 } from "path";
95097
+ import { join as join58 } from "path";
94980
95098
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
94981
95099
  async function generateCommand(options) {
94982
95100
  const workdir = options.dir ?? process.cwd();
@@ -95019,7 +95137,7 @@ async function generateCommand(options) {
95019
95137
  return;
95020
95138
  }
95021
95139
  if (options.package) {
95022
- const packageDir = join57(workdir, options.package);
95140
+ const packageDir = join58(workdir, options.package);
95023
95141
  if (dryRun) {
95024
95142
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95025
95143
  }
@@ -95039,8 +95157,8 @@ async function generateCommand(options) {
95039
95157
  process.exit(1);
95040
95158
  return;
95041
95159
  }
95042
- const contextPath = options.context ? join57(workdir, options.context) : join57(workdir, ".nax/context.md");
95043
- const outputDir = options.output ? join57(workdir, options.output) : workdir;
95160
+ const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
95161
+ const outputDir = options.output ? join58(workdir, options.output) : workdir;
95044
95162
  const autoInject = !options.noAutoInject;
95045
95163
  if (!existsSync25(contextPath)) {
95046
95164
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -95146,7 +95264,7 @@ async function generateCommand(options) {
95146
95264
  // src/cli/config-display.ts
95147
95265
  init_loader();
95148
95266
  import { existsSync as existsSync27 } from "fs";
95149
- import { join as join59 } from "path";
95267
+ import { join as join60 } from "path";
95150
95268
 
95151
95269
  // src/cli/config-descriptions.ts
95152
95270
  var FIELD_DESCRIPTIONS = {
@@ -95398,7 +95516,7 @@ function deepEqual(a, b) {
95398
95516
  init_defaults();
95399
95517
  init_loader();
95400
95518
  import { existsSync as existsSync26 } from "fs";
95401
- import { join as join58 } from "path";
95519
+ import { join as join59 } from "path";
95402
95520
  async function loadConfigFile(path18) {
95403
95521
  if (!existsSync26(path18))
95404
95522
  return null;
@@ -95420,7 +95538,7 @@ async function loadProjectConfig() {
95420
95538
  const projectDir = findProjectDir();
95421
95539
  if (!projectDir)
95422
95540
  return null;
95423
- const projectPath = join58(projectDir, "config.json");
95541
+ const projectPath = join59(projectDir, "config.json");
95424
95542
  return await loadConfigFile(projectPath);
95425
95543
  }
95426
95544
 
@@ -95480,7 +95598,7 @@ async function configCommand(config2, options = {}) {
95480
95598
  function determineConfigSources() {
95481
95599
  const globalPath = globalConfigPath();
95482
95600
  const projectDir = findProjectDir();
95483
- const projectPath = projectDir ? join59(projectDir, "config.json") : null;
95601
+ const projectPath = projectDir ? join60(projectDir, "config.json") : null;
95484
95602
  return {
95485
95603
  global: fileExists(globalPath) ? globalPath : null,
95486
95604
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -95629,15 +95747,15 @@ init_paths();
95629
95747
  init_profile();
95630
95748
  import { mkdirSync as mkdirSync5 } from "fs";
95631
95749
  import { readdirSync as readdirSync5 } from "fs";
95632
- import { join as join60 } from "path";
95750
+ import { join as join61 } from "path";
95633
95751
  var _profileCLIDeps = {
95634
95752
  env: process.env
95635
95753
  };
95636
95754
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
95637
95755
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
95638
95756
  async function profileListCommand(startDir) {
95639
- const globalProfilesDir = join60(globalConfigDir(), "profiles");
95640
- const projectProfilesDir = join60(projectConfigDir(startDir), "profiles");
95757
+ const globalProfilesDir = join61(globalConfigDir(), "profiles");
95758
+ const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
95641
95759
  const globalProfiles = scanProfileDir(globalProfilesDir);
95642
95760
  const projectProfiles = scanProfileDir(projectProfilesDir);
95643
95761
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -95696,7 +95814,7 @@ function maskProfileValues(obj) {
95696
95814
  return result;
95697
95815
  }
95698
95816
  async function profileUseCommand(profileName, startDir) {
95699
- const configPath = join60(projectConfigDir(startDir), "config.json");
95817
+ const configPath = join61(projectConfigDir(startDir), "config.json");
95700
95818
  const configFile = Bun.file(configPath);
95701
95819
  let existing = {};
95702
95820
  if (await configFile.exists()) {
@@ -95715,8 +95833,8 @@ async function profileCurrentCommand(startDir) {
95715
95833
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
95716
95834
  }
95717
95835
  async function profileCreateCommand(profileName, startDir) {
95718
- const profilesDir = join60(projectConfigDir(startDir), "profiles");
95719
- const profilePath = join60(profilesDir, `${profileName}.json`);
95836
+ const profilesDir = join61(projectConfigDir(startDir), "profiles");
95837
+ const profilePath = join61(profilesDir, `${profileName}.json`);
95720
95838
  const profileFile = Bun.file(profilePath);
95721
95839
  if (await profileFile.exists()) {
95722
95840
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -95838,7 +95956,7 @@ async function contextInspectCommand(options) {
95838
95956
  init_canonical_loader();
95839
95957
  init_errors();
95840
95958
  import { mkdir as mkdir12 } from "fs/promises";
95841
- import { basename as basename12, join as join61 } from "path";
95959
+ import { basename as basename12, join as join62 } from "path";
95842
95960
  var _rulesCLIDeps = {
95843
95961
  readFile: async (path18) => Bun.file(path18).text(),
95844
95962
  writeFile: async (path18, content) => {
@@ -95847,7 +95965,7 @@ var _rulesCLIDeps = {
95847
95965
  fileExists: async (path18) => Bun.file(path18).exists(),
95848
95966
  globInDir: (dir) => {
95849
95967
  try {
95850
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join61(dir, f));
95968
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
95851
95969
  } catch {
95852
95970
  return [];
95853
95971
  }
@@ -95896,7 +96014,7 @@ ${r.content}`).join(`
95896
96014
  `);
95897
96015
  const shimContent = `${header + body}
95898
96016
  `;
95899
- const shimPath = join61(workdir, shimFileName);
96017
+ const shimPath = join62(workdir, shimFileName);
95900
96018
  if (options.dryRun) {
95901
96019
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
95902
96020
  return;
@@ -95925,14 +96043,14 @@ function neutralizeContent(content) {
95925
96043
  }
95926
96044
  async function collectMigrationSources(workdir) {
95927
96045
  const sources = [];
95928
- const claudeMdPath = join61(workdir, "CLAUDE.md");
96046
+ const claudeMdPath = join62(workdir, "CLAUDE.md");
95929
96047
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
95930
96048
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
95931
96049
  if (content.trim()) {
95932
96050
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
95933
96051
  }
95934
96052
  }
95935
- const rulesDir = join61(workdir, ".claude", "rules");
96053
+ const rulesDir = join62(workdir, ".claude", "rules");
95936
96054
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
95937
96055
  for (const filePath of ruleFiles) {
95938
96056
  try {
@@ -95952,7 +96070,7 @@ async function rulesMigrateCommand(options) {
95952
96070
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
95953
96071
  return;
95954
96072
  }
95955
- const targetDir = join61(workdir, CANONICAL_RULES_DIR);
96073
+ const targetDir = join62(workdir, CANONICAL_RULES_DIR);
95956
96074
  if (!options.dryRun) {
95957
96075
  try {
95958
96076
  await _rulesCLIDeps.mkdir(targetDir);
@@ -95963,7 +96081,7 @@ async function rulesMigrateCommand(options) {
95963
96081
  let written = 0;
95964
96082
  let skipped = 0;
95965
96083
  for (const { sourcePath, targetFileName, content } of sources) {
95966
- const targetPath = join61(targetDir, targetFileName);
96084
+ const targetPath = join62(targetDir, targetFileName);
95967
96085
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
95968
96086
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
95969
96087
  skipped++;
@@ -96002,7 +96120,7 @@ function collectCanonicalRuleRoots(workdir) {
96002
96120
  const packageRel = normalized.slice(0, idx);
96003
96121
  if (!packageRel)
96004
96122
  continue;
96005
- roots.add(join61(workdir, packageRel));
96123
+ roots.add(join62(workdir, packageRel));
96006
96124
  }
96007
96125
  return [...roots].sort();
96008
96126
  }
@@ -96024,7 +96142,7 @@ init_logger2();
96024
96142
  init_detect2();
96025
96143
  init_workspace();
96026
96144
  init_common();
96027
- import { join as join62 } from "path";
96145
+ import { join as join63 } from "path";
96028
96146
  function resolveEffective(detected, configPatterns) {
96029
96147
  if (configPatterns !== undefined)
96030
96148
  return "config";
@@ -96109,7 +96227,7 @@ async function detectCommand(options) {
96109
96227
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96110
96228
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96111
96229
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96112
- const pkgConfigPath = join62(workdir, ".nax", "mono", dir, "config.json");
96230
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96113
96231
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96114
96232
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96115
96233
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -96163,13 +96281,13 @@ async function detectCommand(options) {
96163
96281
  if (rootDetected.confidence === "empty") {
96164
96282
  console.log(source_default.yellow(" root: skipped (empty detection)"));
96165
96283
  } else {
96166
- const rootConfigPath = join62(workdir, ".nax", "config.json");
96284
+ const rootConfigPath = join63(workdir, ".nax", "config.json");
96167
96285
  try {
96168
96286
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
96169
96287
  if (status === "skipped") {
96170
96288
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
96171
96289
  } else {
96172
- console.log(source_default.green(` root: ${status} \u2192 ${join62(".nax", "config.json")}`));
96290
+ console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
96173
96291
  }
96174
96292
  } catch (err) {
96175
96293
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -96182,13 +96300,13 @@ async function detectCommand(options) {
96182
96300
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
96183
96301
  continue;
96184
96302
  }
96185
- const pkgConfigPath = join62(workdir, ".nax", "mono", dir, "config.json");
96303
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96186
96304
  try {
96187
96305
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
96188
96306
  if (status === "skipped") {
96189
96307
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
96190
96308
  } else {
96191
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join62(".nax", "mono", dir, "config.json")}`));
96309
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
96192
96310
  }
96193
96311
  } catch (err) {
96194
96312
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -96206,19 +96324,19 @@ async function detectCommand(options) {
96206
96324
  // src/commands/logs.ts
96207
96325
  init_common();
96208
96326
  import { existsSync as existsSync29 } from "fs";
96209
- import { join as join66 } from "path";
96327
+ import { join as join67 } from "path";
96210
96328
 
96211
96329
  // src/commands/logs-formatter.ts
96212
96330
  init_source();
96213
96331
  init_formatter();
96214
96332
  import { readdirSync as readdirSync7 } from "fs";
96215
- import { join as join65 } from "path";
96333
+ import { join as join66 } from "path";
96216
96334
 
96217
96335
  // src/commands/logs-reader.ts
96218
96336
  init_paths3();
96219
96337
  import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
96220
96338
  import { readdir as readdir4 } from "fs/promises";
96221
- import { join as join64 } from "path";
96339
+ import { join as join65 } from "path";
96222
96340
  var _logsReaderDeps = {
96223
96341
  getRunsDir
96224
96342
  };
@@ -96232,7 +96350,7 @@ async function resolveRunFileFromRegistry(runId) {
96232
96350
  }
96233
96351
  let matched = null;
96234
96352
  for (const entry of entries) {
96235
- const metaPath = join64(runsDir, entry, "meta.json");
96353
+ const metaPath = join65(runsDir, entry, "meta.json");
96236
96354
  try {
96237
96355
  const meta3 = await Bun.file(metaPath).json();
96238
96356
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -96254,14 +96372,14 @@ async function resolveRunFileFromRegistry(runId) {
96254
96372
  return null;
96255
96373
  }
96256
96374
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
96257
- return join64(matched.eventsDir, specificFile ?? files[0]);
96375
+ return join65(matched.eventsDir, specificFile ?? files[0]);
96258
96376
  }
96259
96377
  async function selectRunFile(runsDir) {
96260
96378
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
96261
96379
  if (files.length === 0) {
96262
96380
  return null;
96263
96381
  }
96264
- return join64(runsDir, files[0]);
96382
+ return join65(runsDir, files[0]);
96265
96383
  }
96266
96384
  async function extractRunSummary(filePath) {
96267
96385
  const file3 = Bun.file(filePath);
@@ -96347,7 +96465,7 @@ Runs:
96347
96465
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
96348
96466
  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
96467
  for (const file3 of files) {
96350
- const filePath = join65(runsDir, file3);
96468
+ const filePath = join66(runsDir, file3);
96351
96469
  const summary = await extractRunSummary(filePath);
96352
96470
  const timestamp = file3.replace(".jsonl", "");
96353
96471
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -96461,7 +96579,7 @@ async function logsCommand(options) {
96461
96579
  return;
96462
96580
  }
96463
96581
  const resolved = resolveProject({ dir: options.dir });
96464
- const naxDir = join66(resolved.projectDir, ".nax");
96582
+ const naxDir = join67(resolved.projectDir, ".nax");
96465
96583
  const configPath = resolved.configPath;
96466
96584
  const configFile = Bun.file(configPath);
96467
96585
  const config2 = await configFile.json();
@@ -96469,8 +96587,8 @@ async function logsCommand(options) {
96469
96587
  if (!featureName) {
96470
96588
  throw new Error("No feature specified in config.json");
96471
96589
  }
96472
- const featureDir = join66(naxDir, "features", featureName);
96473
- const runsDir = join66(featureDir, "runs");
96590
+ const featureDir = join67(naxDir, "features", featureName);
96591
+ const runsDir = join67(featureDir, "runs");
96474
96592
  if (!existsSync29(runsDir)) {
96475
96593
  throw new Error(`No runs directory found for feature: ${featureName}`);
96476
96594
  }
@@ -96496,7 +96614,7 @@ init_prd();
96496
96614
  init_precheck();
96497
96615
  init_common();
96498
96616
  import { existsSync as existsSync30 } from "fs";
96499
- import { join as join67 } from "path";
96617
+ import { join as join68 } from "path";
96500
96618
  async function precheckCommand(options) {
96501
96619
  const resolved = resolveProject({
96502
96620
  dir: options.dir,
@@ -96518,9 +96636,9 @@ async function precheckCommand(options) {
96518
96636
  process.exit(1);
96519
96637
  }
96520
96638
  }
96521
- const naxDir = join67(resolved.projectDir, ".nax");
96522
- const featureDir = join67(naxDir, "features", featureName);
96523
- const prdPath = join67(featureDir, "prd.json");
96639
+ const naxDir = join68(resolved.projectDir, ".nax");
96640
+ const featureDir = join68(naxDir, "features", featureName);
96641
+ const prdPath = join68(featureDir, "prd.json");
96524
96642
  if (!existsSync30(featureDir)) {
96525
96643
  console.error(source_default.red(`Feature not found: ${featureName}`));
96526
96644
  process.exit(1);
@@ -96543,7 +96661,7 @@ async function precheckCommand(options) {
96543
96661
  init_source();
96544
96662
  init_paths3();
96545
96663
  import { readdir as readdir5 } from "fs/promises";
96546
- import { join as join68 } from "path";
96664
+ import { join as join69 } from "path";
96547
96665
  var DEFAULT_LIMIT = 20;
96548
96666
  var _runsCmdDeps = {
96549
96667
  getRunsDir
@@ -96598,7 +96716,7 @@ async function runsCommand(options = {}) {
96598
96716
  }
96599
96717
  const rows = [];
96600
96718
  for (const entry of entries) {
96601
- const metaPath = join68(runsDir, entry, "meta.json");
96719
+ const metaPath = join69(runsDir, entry, "meta.json");
96602
96720
  let meta3;
96603
96721
  try {
96604
96722
  meta3 = await Bun.file(metaPath).json();
@@ -96675,7 +96793,7 @@ async function runsCommand(options = {}) {
96675
96793
 
96676
96794
  // src/commands/unlock.ts
96677
96795
  init_source();
96678
- import { join as join69 } from "path";
96796
+ import { join as join70 } from "path";
96679
96797
  function isProcessAlive2(pid) {
96680
96798
  try {
96681
96799
  process.kill(pid, 0);
@@ -96690,7 +96808,7 @@ function formatLockAge(ageMs) {
96690
96808
  }
96691
96809
  async function unlockCommand(options) {
96692
96810
  const workdir = options.dir ?? process.cwd();
96693
- const lockPath = join69(workdir, "nax.lock");
96811
+ const lockPath = join70(workdir, "nax.lock");
96694
96812
  const lockFile = Bun.file(lockPath);
96695
96813
  const exists = await lockFile.exists();
96696
96814
  if (!exists) {
@@ -96742,6 +96860,7 @@ init_acceptance2();
96742
96860
  init_config();
96743
96861
  init_hooks();
96744
96862
  init_logger2();
96863
+ init_pipeline();
96745
96864
  init_prd();
96746
96865
  init_git();
96747
96866
  init_crash_recovery();
@@ -96775,6 +96894,7 @@ async function runCompletionPhase(options) {
96775
96894
  logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
96776
96895
  } else if (options.config.acceptance.enabled && isComplete(options.prd)) {
96777
96896
  options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
96897
+ pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
96778
96898
  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
96899
  const relativeWorkdir = path19.relative(options.workdir, g.packageDir);
96780
96900
  let groupConfig = options.config;
@@ -96821,6 +96941,7 @@ async function runCompletionPhase(options) {
96821
96941
  const lastRunAt = new Date().toISOString();
96822
96942
  if (acceptanceResult.success) {
96823
96943
  options.statusWriter.setPostRunPhase("acceptance", { status: "passed", lastRunAt });
96944
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: true });
96824
96945
  } else {
96825
96946
  acceptancePassed = false;
96826
96947
  options.statusWriter.setPostRunPhase("acceptance", {
@@ -96829,6 +96950,7 @@ async function runCompletionPhase(options) {
96829
96950
  retries: acceptanceResult.retries ?? 0,
96830
96951
  lastRunAt
96831
96952
  });
96953
+ pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: false });
96832
96954
  }
96833
96955
  Object.assign(options, {
96834
96956
  prd: acceptanceResult.prd,
@@ -103725,6 +103847,7 @@ var MAX_ESCALATION_DISPLAY = 5;
103725
103847
  function LiveActivityPanel({
103726
103848
  focused = false,
103727
103849
  activeCalls,
103850
+ storySteps,
103728
103851
  runSummary,
103729
103852
  runErrored,
103730
103853
  escalationLog = []
@@ -103783,7 +103906,8 @@ function LiveActivityPanel({
103783
103906
  paddingX: 1,
103784
103907
  paddingY: 1,
103785
103908
  children: activeCallList.map((call) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ActiveCallRow, {
103786
- call
103909
+ call,
103910
+ step: call.storyId ? storySteps?.[call.storyId] : undefined
103787
103911
  }, call.callId, false, undefined, this))
103788
103912
  }, undefined, false, undefined, this),
103789
103913
  hasEscalations && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -103799,7 +103923,29 @@ function LiveActivityPanel({
103799
103923
  }, `${entry.storyId}-${entry.at}`, false, undefined, this))
103800
103924
  ]
103801
103925
  }, undefined, true, undefined, this),
103802
- !hasActiveCalls && !hasSummary && !hasError && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103926
+ !hasActiveCalls && storySteps && Object.keys(storySteps).length > 0 && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103927
+ flexDirection: "column",
103928
+ paddingX: 1,
103929
+ paddingY: 1,
103930
+ children: Object.entries(storySteps).map(([sid, step]) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103931
+ flexDirection: "row",
103932
+ gap: 1,
103933
+ children: [
103934
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103935
+ children: sid
103936
+ }, undefined, false, undefined, this),
103937
+ /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103938
+ color: "yellow",
103939
+ children: [
103940
+ "[",
103941
+ step,
103942
+ "]"
103943
+ ]
103944
+ }, undefined, true, undefined, this)
103945
+ ]
103946
+ }, sid, true, undefined, this))
103947
+ }, undefined, false, undefined, this),
103948
+ !hasActiveCalls && !hasSummary && !hasError && (!storySteps || Object.keys(storySteps).length === 0) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103803
103949
  paddingX: 1,
103804
103950
  paddingY: 1,
103805
103951
  children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
@@ -103810,7 +103956,7 @@ function LiveActivityPanel({
103810
103956
  ]
103811
103957
  }, undefined, true, undefined, this);
103812
103958
  }
103813
- function ActiveCallRow({ call }) {
103959
+ function ActiveCallRow({ call, step }) {
103814
103960
  return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
103815
103961
  flexDirection: "column",
103816
103962
  marginBottom: 1,
@@ -103826,14 +103972,21 @@ function ActiveCallRow({ call }) {
103826
103972
  call.storyId && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103827
103973
  children: call.storyId
103828
103974
  }, undefined, false, undefined, this),
103829
- call.stage && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103975
+ step ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103976
+ color: "yellow",
103977
+ children: [
103978
+ "[",
103979
+ step,
103980
+ "]"
103981
+ ]
103982
+ }, undefined, true, undefined, this) : call.stage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
103830
103983
  dimColor: true,
103831
103984
  children: [
103832
103985
  "[",
103833
103986
  call.stage,
103834
103987
  "]"
103835
103988
  ]
103836
- }, undefined, true, undefined, this)
103989
+ }, undefined, true, undefined, this) : null
103837
103990
  ]
103838
103991
  }, undefined, true, undefined, this),
103839
103992
  /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
@@ -104040,7 +104193,7 @@ function getStatusIcon(status) {
104040
104193
  return "\u23F8\uFE0F";
104041
104194
  }
104042
104195
  }
104043
- function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight }) {
104196
+ function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false, maxHeight }) {
104044
104197
  const maxVisible = compact2 ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
104045
104198
  const needsScrolling = stories.length > maxVisible;
104046
104199
  const [scrollOffset, setScrollOffset] = import_react30.useState(0);
@@ -104156,15 +104309,63 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
104156
104309
  " more below"
104157
104310
  ]
104158
104311
  }, undefined, true, undefined, this)
104159
- }, undefined, false, undefined, this)
104312
+ }, undefined, false, undefined, this),
104313
+ postRunPhases && (postRunPhases.acceptance || postRunPhases.regression || postRunPhases.review) && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104314
+ flexDirection: "column",
104315
+ paddingX: 1,
104316
+ paddingTop: 1,
104317
+ children: [
104318
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104319
+ borderStyle: "single",
104320
+ borderTop: true,
104321
+ borderColor: "gray"
104322
+ }, undefined, false, undefined, this),
104323
+ /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104324
+ dimColor: true,
104325
+ children: "Post-Run"
104326
+ }, undefined, false, undefined, this),
104327
+ postRunPhases.acceptance && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
104328
+ label: "acceptance",
104329
+ phase: postRunPhases.acceptance,
104330
+ compact: compact2
104331
+ }, undefined, false, undefined, this),
104332
+ postRunPhases.regression && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
104333
+ label: "regression",
104334
+ phase: postRunPhases.regression,
104335
+ compact: compact2
104336
+ }, undefined, false, undefined, this),
104337
+ postRunPhases.review && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
104338
+ label: "review",
104339
+ phase: postRunPhases.review,
104340
+ compact: compact2
104341
+ }, undefined, false, undefined, this)
104342
+ ]
104343
+ }, undefined, true, undefined, this)
104160
104344
  ]
104161
104345
  }, undefined, true, undefined, this);
104162
104346
  }
104347
+ function PostRunPhaseRow({ label, phase, compact: compact2 }) {
104348
+ const icon = phase.status === "running" ? ">" : phase.status === "passed" ? "[OK]" : "[X]";
104349
+ const color = phase.status === "running" ? "cyan" : phase.status === "passed" ? "green" : "red";
104350
+ return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
104351
+ children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
104352
+ color,
104353
+ children: [
104354
+ icon,
104355
+ " ",
104356
+ compact2 ? label.slice(0, 3) : label
104357
+ ]
104358
+ }, undefined, true, undefined, this)
104359
+ }, undefined, false, undefined, this);
104360
+ }
104163
104361
 
104164
104362
  // src/tui/hooks/useAgentStreamEvents.ts
104165
104363
  var import_react31 = __toESM(require_react(), 1);
104166
104364
  function useAgentStreamEvents(bus) {
104167
104365
  const [activeCalls, setActiveCalls] = import_react31.useState(new Map);
104366
+ const [inputTokens, setInputTokens] = import_react31.useState(0);
104367
+ const [outputTokens, setOutputTokens] = import_react31.useState(0);
104368
+ const lastTokensRef = import_react31.useRef(new Map);
104168
104369
  import_react31.useEffect(() => {
104169
104370
  if (!bus)
104170
104371
  return;
@@ -104221,6 +104422,18 @@ function useAgentStreamEvents(bus) {
104221
104422
  lastActivityAt: event.timestamp
104222
104423
  });
104223
104424
  }
104425
+ {
104426
+ const last2 = lastTokensRef.current.get(event.callId) ?? { input: 0, output: 0 };
104427
+ const newInput = event.inputTokens ?? last2.input;
104428
+ const newOutput = event.outputTokens ?? last2.output;
104429
+ const deltaIn = newInput - last2.input;
104430
+ const deltaOut = newOutput - last2.output;
104431
+ lastTokensRef.current.set(event.callId, { input: newInput, output: newOutput });
104432
+ if (deltaIn > 0)
104433
+ setInputTokens((prev2) => prev2 + deltaIn);
104434
+ if (deltaOut > 0)
104435
+ setOutputTokens((prev2) => prev2 + deltaOut);
104436
+ }
104224
104437
  break;
104225
104438
  }
104226
104439
  case "agent.tool_call_update": {
@@ -104241,6 +104454,7 @@ function useAgentStreamEvents(bus) {
104241
104454
  next.set(event.callId, { ...state, status: "ended" });
104242
104455
  next.delete(event.callId);
104243
104456
  }
104457
+ lastTokensRef.current.delete(event.callId);
104244
104458
  break;
104245
104459
  }
104246
104460
  default:
@@ -104251,7 +104465,7 @@ function useAgentStreamEvents(bus) {
104251
104465
  });
104252
104466
  return unsubscribe;
104253
104467
  }, [bus]);
104254
- return { activeCalls };
104468
+ return { activeCalls, inputTokens, outputTokens };
104255
104469
  }
104256
104470
 
104257
104471
  // src/tui/hooks/useKeyboard.ts
@@ -104311,20 +104525,13 @@ function usePipelineBusEvents(initialStories) {
104311
104525
  const [state, setState] = import_react32.useState(() => ({
104312
104526
  stories: initialStories,
104313
104527
  totalCost: 0,
104314
- elapsedMs: 0,
104315
104528
  runPaused: false,
104316
104529
  runErrored: false,
104317
- escalationLog: []
104530
+ escalationLog: [],
104531
+ storySteps: {},
104532
+ postRunPhases: {}
104318
104533
  }));
104319
- const startTimeRef = import_react32.useRef(Date.now());
104320
104534
  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
104535
  const unsubStarted = pipelineEventBus.on("story:started", (event) => {
104329
104536
  setState((prev) => ({
104330
104537
  ...prev,
@@ -104347,18 +104554,19 @@ function usePipelineBusEvents(initialStories) {
104347
104554
  return s;
104348
104555
  });
104349
104556
  const totalCost = newStories.reduce((sum2, s) => sum2 + (s.cost ?? 0), 0);
104350
- return { ...prev, stories: newStories, totalCost };
104557
+ const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
104558
+ return { ...prev, stories: newStories, totalCost, storySteps: remainingSteps };
104351
104559
  });
104352
104560
  });
104353
104561
  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
- }));
104562
+ setState((prev) => {
104563
+ const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
104564
+ return {
104565
+ ...prev,
104566
+ stories: prev.stories.map((s) => s.story.id === event.storyId ? { ...s, status: "failed", failureReason: event.reason } : s),
104567
+ storySteps: remainingSteps
104568
+ };
104569
+ });
104362
104570
  });
104363
104571
  const unsubSkipped = pipelineEventBus.on("story:skipped", (event) => {
104364
104572
  setState((prev) => ({
@@ -104392,7 +104600,6 @@ function usePipelineBusEvents(initialStories) {
104392
104600
  setState((prev) => ({ ...prev, runPaused: false }));
104393
104601
  });
104394
104602
  const unsubCompleted2 = pipelineEventBus.on("run:completed", (event) => {
104395
- clearInterval(timer);
104396
104603
  const summary = {
104397
104604
  totalStories: event.totalStories,
104398
104605
  passedStories: event.passedStories,
@@ -104404,7 +104611,6 @@ function usePipelineBusEvents(initialStories) {
104404
104611
  };
104405
104612
  setState((prev) => ({
104406
104613
  ...prev,
104407
- elapsedMs: event.durationMs,
104408
104614
  runSummary: summary,
104409
104615
  totalCost: event.totalCost ?? prev.totalCost
104410
104616
  }));
@@ -104412,8 +104618,28 @@ function usePipelineBusEvents(initialStories) {
104412
104618
  const unsubErrored = pipelineEventBus.on("run:errored", (_event) => {
104413
104619
  setState((prev) => ({ ...prev, runErrored: true }));
104414
104620
  });
104621
+ const unsubStep = pipelineEventBus.on("story:step", (event) => {
104622
+ setState((prev) => ({
104623
+ ...prev,
104624
+ storySteps: { ...prev.storySteps, [event.storyId]: event.step }
104625
+ }));
104626
+ });
104627
+ const unsubPostRunStarted = pipelineEventBus.on("postrun:phase:started", (event) => {
104628
+ setState((prev) => ({
104629
+ ...prev,
104630
+ postRunPhases: { ...prev.postRunPhases, [event.phase]: { status: "running" } }
104631
+ }));
104632
+ });
104633
+ const unsubPostRunCompleted = pipelineEventBus.on("postrun:phase:completed", (event) => {
104634
+ setState((prev) => ({
104635
+ ...prev,
104636
+ postRunPhases: {
104637
+ ...prev.postRunPhases,
104638
+ [event.phase]: { status: event.passed ? "passed" : "failed" }
104639
+ }
104640
+ }));
104641
+ });
104415
104642
  return () => {
104416
- clearInterval(timer);
104417
104643
  unsubStarted();
104418
104644
  unsubCompleted();
104419
104645
  unsubFailed();
@@ -104424,6 +104650,9 @@ function usePipelineBusEvents(initialStories) {
104424
104650
  unsubResumed();
104425
104651
  unsubCompleted2();
104426
104652
  unsubErrored();
104653
+ unsubStep();
104654
+ unsubPostRunStarted();
104655
+ unsubPostRunCompleted();
104427
104656
  };
104428
104657
  }, []);
104429
104658
  return state;
@@ -104542,6 +104771,8 @@ function usePty(options) {
104542
104771
 
104543
104772
  // src/tui/App.tsx
104544
104773
  var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
104774
+ var MemoStoriesPanel = import_react35.memo(StoriesPanel);
104775
+ var MemoLiveActivityPanel = import_react35.memo(LiveActivityPanel);
104545
104776
  function formatElapsed(ms) {
104546
104777
  const mins = Math.floor(ms / 60000);
104547
104778
  const secs = Math.floor(ms % 60000 / 1000);
@@ -104550,8 +104781,16 @@ function formatElapsed(ms) {
104550
104781
  function formatCost3(cost) {
104551
104782
  return `$${cost.toFixed(4)}`;
104552
104783
  }
104784
+ function formatTokens(n) {
104785
+ if (n >= 1e6)
104786
+ return `${(n / 1e6).toFixed(1)}M`;
104787
+ if (n >= 1000)
104788
+ return `${(n / 1000).toFixed(1)}k`;
104789
+ return `${n}`;
104790
+ }
104553
104791
  function App2({
104554
104792
  feature,
104793
+ version: version2,
104555
104794
  stories: initialStories,
104556
104795
  events,
104557
104796
  queueFilePath,
@@ -104562,13 +104801,21 @@ function App2({
104562
104801
  const busState = usePipelineBusEvents(initialStories);
104563
104802
  const { currentStage } = usePipelineEvents(events);
104564
104803
  const { exit } = use_app_default();
104804
+ const startTimeRef = import_react35.useRef(Date.now());
104805
+ const [elapsedMs, setElapsedMs] = import_react35.useState(0);
104806
+ import_react35.useEffect(() => {
104807
+ if (busState.runSummary)
104808
+ return;
104809
+ const timer = setInterval(() => setElapsedMs(Date.now() - startTimeRef.current), 1000);
104810
+ return () => clearInterval(timer);
104811
+ }, [busState.runSummary]);
104565
104812
  const [focus, setFocus] = import_react35.useState("stories" /* Stories */);
104566
104813
  const [showHelp, setShowHelp] = import_react35.useState(false);
104567
104814
  const [showCost, setShowCost] = import_react35.useState(false);
104568
104815
  const [showQuitConfirm, setShowQuitConfirm] = import_react35.useState(false);
104569
104816
  const [showAbortConfirm, setShowAbortConfirm] = import_react35.useState(false);
104570
104817
  const { handle: ptyHandle } = usePty(ptyOptions ?? null);
104571
- const { activeCalls } = useAgentStreamEvents(agentStreamEvents);
104818
+ const { activeCalls, inputTokens, outputTokens } = useAgentStreamEvents(agentStreamEvents);
104572
104819
  const isRunComplete = !!busState.runSummary;
104573
104820
  const runningStories = busState.stories.filter((s) => s.status === "running");
104574
104821
  const isParallel = runningStories.length > 1;
@@ -104655,10 +104902,13 @@ function App2({
104655
104902
  });
104656
104903
  const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
104657
104904
  const activeCount = runningStories.length;
104905
+ const displayElapsed = busState.runSummary ? busState.runSummary.durationMs : elapsedMs;
104906
+ const tokensStr = inputTokens > 0 || outputTokens > 0 ? `${formatTokens(inputTokens)} in / ${formatTokens(outputTokens)} out` : null;
104658
104907
  const headerRight = [
104659
104908
  activeCount > 0 ? `${activeCount} running` : null,
104660
104909
  formatCost3(busState.totalCost),
104661
- formatElapsed(busState.elapsedMs)
104910
+ tokensStr,
104911
+ formatElapsed(displayElapsed)
104662
104912
  ].filter(Boolean).join(" \xB7 ");
104663
104913
  const maxHeight = layout.mode === "single" ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
104664
104914
  return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
@@ -104677,7 +104927,15 @@ function App2({
104677
104927
  color: "cyan",
104678
104928
  children: [
104679
104929
  "nax run \u2014 ",
104680
- feature
104930
+ feature,
104931
+ version2 ? /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
104932
+ dimColor: true,
104933
+ color: "cyan",
104934
+ children: [
104935
+ " ",
104936
+ version2
104937
+ ]
104938
+ }, undefined, true, undefined, this) : null
104681
104939
  ]
104682
104940
  }, undefined, true, undefined, this),
104683
104941
  /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
@@ -104704,15 +104962,17 @@ function App2({
104704
104962
  flexDirection: layout.mode === "single" ? "column" : "row",
104705
104963
  flexGrow: 1,
104706
104964
  children: [
104707
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(StoriesPanel, {
104965
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoStoriesPanel, {
104708
104966
  stories: busState.stories,
104967
+ postRunPhases: busState.postRunPhases,
104709
104968
  width: layout.mode === "single" ? layout.width : layout.storiesPanelWidth,
104710
104969
  compact: layout.mode === "single",
104711
104970
  maxHeight
104712
104971
  }, undefined, false, undefined, this),
104713
- /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(LiveActivityPanel, {
104972
+ /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoLiveActivityPanel, {
104714
104973
  focused: focus === "agent" /* Agent */,
104715
104974
  activeCalls,
104975
+ storySteps: busState.storySteps,
104716
104976
  runSummary: busState.runSummary,
104717
104977
  runErrored: runErroredForPanel,
104718
104978
  escalationLog: busState.escalationLog
@@ -104881,7 +105141,7 @@ Next: nax generate --package ${options.package}`));
104881
105141
  }
104882
105142
  return;
104883
105143
  }
104884
- const naxDir = join83(workdir, ".nax");
105144
+ const naxDir = join84(workdir, ".nax");
104885
105145
  if (existsSync36(naxDir) && !options.force) {
104886
105146
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
104887
105147
  return;
@@ -104910,11 +105170,11 @@ Next: nax generate --package ${options.package}`));
104910
105170
  }
104911
105171
  }
104912
105172
  }
104913
- mkdirSync7(join83(naxDir, "features"), { recursive: true });
104914
- mkdirSync7(join83(naxDir, "hooks"), { recursive: true });
105173
+ mkdirSync7(join84(naxDir, "features"), { recursive: true });
105174
+ mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
104915
105175
  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({
105176
+ await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105177
+ await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
104918
105178
  hooks: {
104919
105179
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
104920
105180
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -104922,12 +105182,12 @@ Next: nax generate --package ${options.package}`));
104922
105182
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
104923
105183
  }
104924
105184
  }, null, 2));
104925
- await Bun.write(join83(naxDir, ".gitignore"), `# nax temp files
105185
+ await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
104926
105186
  *.tmp
104927
105187
  .paused.json
104928
105188
  .nax-verifier-verdict.json
104929
105189
  `);
104930
- await Bun.write(join83(naxDir, "context.md"), `# Project Context
105190
+ await Bun.write(join84(naxDir, "context.md"), `# Project Context
104931
105191
 
104932
105192
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
104933
105193
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105057,8 +105317,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
105057
105317
  console.error(source_default.red("nax not initialized. Run: nax init"));
105058
105318
  process.exit(1);
105059
105319
  }
105060
- const featureDir = join83(naxDir, "features", options.feature);
105061
- const prdPath = join83(featureDir, "prd.json");
105320
+ const featureDir = join84(naxDir, "features", options.feature);
105321
+ const prdPath = join84(featureDir, "prd.json");
105062
105322
  if (options.plan && options.from) {
105063
105323
  if (existsSync36(prdPath) && !options.force) {
105064
105324
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -105080,10 +105340,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105080
105340
  }
105081
105341
  }
105082
105342
  try {
105083
- const planLogDir = join83(featureDir, "plan");
105343
+ const planLogDir = join84(featureDir, "plan");
105084
105344
  mkdirSync7(planLogDir, { recursive: true });
105085
105345
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105086
- const planLogPath = join83(planLogDir, `${planLogId}.jsonl`);
105346
+ const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
105087
105347
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105088
105348
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105089
105349
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105129,10 +105389,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105129
105389
  resetLogger();
105130
105390
  const projectKey = config2.name?.trim() || basename16(workdir);
105131
105391
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105132
- const runsDir = join83(outputDir, "features", options.feature, "runs");
105392
+ const runsDir = join84(outputDir, "features", options.feature, "runs");
105133
105393
  mkdirSync7(runsDir, { recursive: true });
105134
105394
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105135
- const logFilePath = join83(runsDir, `${runId}.jsonl`);
105395
+ const logFilePath = join84(runsDir, `${runId}.jsonl`);
105136
105396
  const isTTY = process.stdout.isTTY ?? false;
105137
105397
  const headlessFlag = options.headless ?? false;
105138
105398
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105150,7 +105410,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105150
105410
  config2.agent.default = options.agent;
105151
105411
  }
105152
105412
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105153
- const globalNaxDir = join83(homedir3(), ".nax");
105413
+ const globalNaxDir = join84(homedir3(), ".nax");
105154
105414
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105155
105415
  const eventEmitter = new PipelineEventEmitter;
105156
105416
  const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
@@ -105165,16 +105425,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
105165
105425
  }));
105166
105426
  tuiInstance = renderTui({
105167
105427
  feature: options.feature,
105428
+ version: NAX_BUILD_INFO,
105168
105429
  stories: initialStories,
105169
105430
  events: eventEmitter,
105170
105431
  ptyOptions: null,
105171
105432
  agentStreamEvents,
105172
- queueFilePath: join83(workdir, ".queue.txt")
105433
+ queueFilePath: join84(workdir, ".queue.txt")
105173
105434
  });
105174
105435
  } else {
105175
105436
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105176
105437
  }
105177
- const statusFilePath = join83(outputDir, "status.json");
105438
+ const statusFilePath = join84(outputDir, "status.json");
105178
105439
  let parallel;
105179
105440
  if (options.parallel !== undefined) {
105180
105441
  parallel = Number.parseInt(options.parallel, 10);
@@ -105201,7 +105462,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105201
105462
  skipPrecheck: options.skipPrecheck ?? false,
105202
105463
  agentStreamEvents
105203
105464
  });
105204
- const latestSymlink = join83(runsDir, "latest.jsonl");
105465
+ const latestSymlink = join84(runsDir, "latest.jsonl");
105205
105466
  try {
105206
105467
  if (existsSync36(latestSymlink)) {
105207
105468
  Bun.spawnSync(["rm", latestSymlink]);
@@ -105262,9 +105523,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105262
105523
  console.error(source_default.red("nax not initialized. Run: nax init"));
105263
105524
  process.exit(1);
105264
105525
  }
105265
- const featureDir = join83(naxDir, "features", name);
105526
+ const featureDir = join84(naxDir, "features", name);
105266
105527
  mkdirSync7(featureDir, { recursive: true });
105267
- await Bun.write(join83(featureDir, "spec.md"), `# Feature: ${name}
105528
+ await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
105268
105529
 
105269
105530
  ## Overview
105270
105531
 
@@ -105297,7 +105558,7 @@ features.command("create <name>").description("Create a new feature").option("-d
105297
105558
 
105298
105559
  <!-- What this feature explicitly does NOT cover. -->
105299
105560
  `);
105300
- await Bun.write(join83(featureDir, "progress.txt"), `# Progress: ${name}
105561
+ await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
105301
105562
 
105302
105563
  Created: ${new Date().toISOString()}
105303
105564
 
@@ -105323,7 +105584,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
105323
105584
  console.error(source_default.red("nax not initialized."));
105324
105585
  process.exit(1);
105325
105586
  }
105326
- const featuresDir = join83(naxDir, "features");
105587
+ const featuresDir = join84(naxDir, "features");
105327
105588
  if (!existsSync36(featuresDir)) {
105328
105589
  console.log(source_default.dim("No features yet."));
105329
105590
  return;
@@ -105338,7 +105599,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
105338
105599
  Features:
105339
105600
  `));
105340
105601
  for (const name of entries) {
105341
- const prdPath = join83(featuresDir, name, "prd.json");
105602
+ const prdPath = join84(featuresDir, name, "prd.json");
105342
105603
  if (existsSync36(prdPath)) {
105343
105604
  const prd = await loadPRD(prdPath);
105344
105605
  const c = countStories(prd);
@@ -105373,10 +105634,10 @@ Use: nax plan -f <feature> --from <spec>`));
105373
105634
  cliOverrides.profile = options.profile;
105374
105635
  }
105375
105636
  const config2 = await loadConfig(workdir, cliOverrides);
105376
- const featureLogDir = join83(naxDir, "features", options.feature, "plan");
105637
+ const featureLogDir = join84(naxDir, "features", options.feature, "plan");
105377
105638
  mkdirSync7(featureLogDir, { recursive: true });
105378
105639
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105379
- const planLogPath = join83(featureLogDir, `${planLogId}.jsonl`);
105640
+ const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
105380
105641
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105381
105642
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105382
105643
  try {