@nathapp/nax 0.67.11 → 0.67.13

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 +821 -401
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -32839,6 +32839,122 @@ var init_categorization = __esm(() => {
32839
32839
  MECHANICAL_REVIEW_CHECKS = new Set(ORDERED_MECHANICAL_REVIEW_CHECKS);
32840
32840
  LLM_REVIEW_CHECKS = new Set(ORDERED_LLM_REVIEW_CHECKS);
32841
32841
  });
32842
+
32843
+ // src/review/prepare-inputs.ts
32844
+ import { relative as relative8, sep as sep2 } from "path";
32845
+ function derivePackageDirs(workdir, projectDir) {
32846
+ const repoRoot = projectDir ?? workdir;
32847
+ const packageDir = workdir !== repoRoot ? workdir : undefined;
32848
+ let packageDirRelative;
32849
+ if (projectDir && workdir !== projectDir) {
32850
+ const rel = relative8(projectDir, workdir);
32851
+ if (rel !== ".." && !rel.startsWith(`..${sep2}`)) {
32852
+ packageDirRelative = rel && rel !== "." ? rel : undefined;
32853
+ }
32854
+ }
32855
+ return { repoRoot, packageDir, packageDirRelative };
32856
+ }
32857
+ async function prepareSemanticReviewInput(args) {
32858
+ const { workdir, projectDir, storyId, storyGitRef, config: config2, naxIgnoreIndex, semanticConfig } = args;
32859
+ const effectiveRef = await resolveEffectiveRef(workdir, storyGitRef, storyId);
32860
+ if (!effectiveRef) {
32861
+ return {
32862
+ effectiveRef: undefined,
32863
+ stat: "",
32864
+ diff: undefined,
32865
+ excludePatterns: [],
32866
+ skipReason: "no git ref"
32867
+ };
32868
+ }
32869
+ const { packageDir, packageDirRelative } = derivePackageDirs(workdir, projectDir);
32870
+ const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32871
+ const resolved = await resolveTestFilePatterns(config2 ?? reviewConfigSelector.select(DEFAULT_CONFIG), projectDir ?? workdir, packageDirRelative);
32872
+ const excludePatterns = [...resolveReviewExcludePatterns(semanticConfig.excludePatterns, resolved)];
32873
+ const diffMode = semanticConfig.diffMode ?? "ref";
32874
+ if (diffMode === "ref") {
32875
+ if (!stat) {
32876
+ return { effectiveRef, stat: "", diff: undefined, excludePatterns, skipReason: "no changes detected" };
32877
+ }
32878
+ return { effectiveRef, stat, diff: undefined, excludePatterns };
32879
+ }
32880
+ const rawDiff = await collectDiff(workdir, effectiveRef, excludePatterns, { naxIgnoreIndex, packageDir });
32881
+ const diff = truncateDiff(rawDiff, rawDiff.length > DIFF_CAP_BYTES ? stat : undefined);
32882
+ if (!diff) {
32883
+ return { effectiveRef, stat, diff: undefined, excludePatterns, skipReason: "no production code changes" };
32884
+ }
32885
+ return { effectiveRef, stat, diff, excludePatterns };
32886
+ }
32887
+ async function prepareAdversarialReviewInput(args) {
32888
+ const { workdir, projectDir, storyId, storyGitRef, config: config2, naxIgnoreIndex, adversarialConfig } = args;
32889
+ const effectiveRef = await resolveEffectiveRef(workdir, storyGitRef, storyId);
32890
+ if (!effectiveRef) {
32891
+ return {
32892
+ effectiveRef: undefined,
32893
+ stat: "",
32894
+ diff: undefined,
32895
+ testInventory: undefined,
32896
+ excludePatterns: [],
32897
+ testGlobs: [],
32898
+ refExcludePatterns: [],
32899
+ skipReason: "no git ref"
32900
+ };
32901
+ }
32902
+ const { packageDir, packageDirRelative } = derivePackageDirs(workdir, projectDir);
32903
+ const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32904
+ const effectiveConfig = config2 ?? reviewConfigSelector.select(DEFAULT_CONFIG);
32905
+ const resolved = await resolveTestFilePatterns(effectiveConfig, projectDir ?? workdir, packageDirRelative);
32906
+ const refExcludePatterns = [...resolveReviewExcludePatterns(adversarialConfig.excludePatterns, resolved)];
32907
+ const testGlobs = resolved.globs ?? [];
32908
+ const excludePatterns = [...adversarialConfig.excludePatterns ?? []];
32909
+ const diffMode = adversarialConfig.diffMode ?? "ref";
32910
+ const testFilePatterns = (typeof config2?.execution?.smartTestRunner === "object" ? config2.execution.smartTestRunner?.testFilePatterns : undefined) ?? undefined;
32911
+ if (diffMode === "ref") {
32912
+ if (!stat) {
32913
+ return {
32914
+ effectiveRef,
32915
+ stat: "",
32916
+ diff: undefined,
32917
+ testInventory: undefined,
32918
+ excludePatterns,
32919
+ testGlobs,
32920
+ refExcludePatterns,
32921
+ skipReason: "no changes detected"
32922
+ };
32923
+ }
32924
+ return {
32925
+ effectiveRef,
32926
+ stat,
32927
+ diff: undefined,
32928
+ testInventory: undefined,
32929
+ excludePatterns,
32930
+ testGlobs,
32931
+ refExcludePatterns
32932
+ };
32933
+ }
32934
+ const diff = await collectDiff(workdir, effectiveRef, excludePatterns, { naxIgnoreIndex, packageDir });
32935
+ if (!diff) {
32936
+ return {
32937
+ effectiveRef,
32938
+ stat,
32939
+ diff: undefined,
32940
+ testInventory: undefined,
32941
+ excludePatterns,
32942
+ testGlobs,
32943
+ refExcludePatterns,
32944
+ skipReason: "no code changes"
32945
+ };
32946
+ }
32947
+ const testInventory = await computeTestInventory(workdir, effectiveRef, testFilePatterns, {
32948
+ naxIgnoreIndex,
32949
+ packageDir
32950
+ });
32951
+ return { effectiveRef, stat, diff, testInventory, excludePatterns, testGlobs, refExcludePatterns };
32952
+ }
32953
+ var init_prepare_inputs = __esm(() => {
32954
+ init_config();
32955
+ init_test_runners();
32956
+ init_diff_utils();
32957
+ });
32842
32958
  // src/utils/process-kill.ts
32843
32959
  function killProcessGroup(pid, signal) {
32844
32960
  try {
@@ -33271,7 +33387,7 @@ var init_runners = __esm(() => {
33271
33387
  });
33272
33388
 
33273
33389
  // src/verification/smart-runner.ts
33274
- import { join as join19, relative as relative8 } from "path";
33390
+ import { join as join19, relative as relative9 } from "path";
33275
33391
  function extractPatternSuffix(pattern) {
33276
33392
  const lastStar = pattern.lastIndexOf("*");
33277
33393
  if (lastStar === -1)
@@ -33381,7 +33497,7 @@ async function getChangedNonTestFiles(workdir, baseRef, packagePrefix, testFileR
33381
33497
  let effectivePrefix = packagePrefix;
33382
33498
  if (packagePrefix && repoRoot) {
33383
33499
  const gitRoot = await _gitUtilDeps.getGitRoot(workdir);
33384
- const extraPrefix2 = gitRoot && gitRoot !== repoRoot ? relative8(gitRoot, repoRoot) : "";
33500
+ const extraPrefix2 = gitRoot && gitRoot !== repoRoot ? relative9(gitRoot, repoRoot) : "";
33385
33501
  effectivePrefix = extraPrefix2 ? `${extraPrefix2}/${packagePrefix}` : packagePrefix;
33386
33502
  }
33387
33503
  const scopedRaw = effectivePrefix ? lines.filter((f) => f.startsWith(`${effectivePrefix}/`)) : lines;
@@ -33408,7 +33524,7 @@ async function getChangedTestFiles(workdir, repoRoot, baseRef, packagePrefix, te
33408
33524
  const packageDir = packagePrefix ? join19(repoRoot, packagePrefix) : undefined;
33409
33525
  const ignoreMatchers = naxIgnoreIndex?.getMatchers(packageDir) ?? await resolveNaxIgnorePatterns(repoRoot, packageDir);
33410
33526
  const gitRoot = await _gitUtilDeps.getGitRoot(workdir);
33411
- const extraPrefix = gitRoot && gitRoot !== repoRoot ? relative8(gitRoot, repoRoot) : "";
33527
+ const extraPrefix = gitRoot && gitRoot !== repoRoot ? relative9(gitRoot, repoRoot) : "";
33412
33528
  const effectivePrefix = packagePrefix ? extraPrefix ? `${extraPrefix}/${packagePrefix}` : packagePrefix : undefined;
33413
33529
  const scopedRaw = effectivePrefix ? lines.filter((f) => f.startsWith(`${effectivePrefix}/`)) : lines;
33414
33530
  const scoped2 = filterNaxInternalPaths(scopedRaw, ignoreMatchers);
@@ -33841,12 +33957,12 @@ function acceptanceDiagnoseRawArrayToFindings(raw) {
33841
33957
  }
33842
33958
 
33843
33959
  // src/findings/path-utils.ts
33844
- import { relative as relative9, resolve as resolve11 } from "path";
33960
+ import { relative as relative10, resolve as resolve11 } from "path";
33845
33961
  function rebaseToWorkdir(rawPath, cwd, workdir) {
33846
33962
  if (rawPath.startsWith("/")) {
33847
- return relative9(workdir, rawPath);
33963
+ return relative10(workdir, rawPath);
33848
33964
  }
33849
- return relative9(workdir, resolve11(cwd, rawPath));
33965
+ return relative10(workdir, resolve11(cwd, rawPath));
33850
33966
  }
33851
33967
  var init_path_utils = () => {};
33852
33968
 
@@ -35772,6 +35888,16 @@ function parseTestEditDeclarations(output) {
35772
35888
  }
35773
35889
  return result;
35774
35890
  }
35891
+ function normaliseWs(s) {
35892
+ return s.replace(/\s+/g, " ").replace(/\s*([(),<>])\s*/g, "$1").replace(/\s*:\s*/g, ": ").trim();
35893
+ }
35894
+ function validatePrdQuote(prdQuote, story) {
35895
+ if (!prdQuote.trim())
35896
+ return false;
35897
+ const needle = normaliseWs(prdQuote);
35898
+ const haystack = normaliseWs([story.description, ...story.acceptanceCriteria].join(" "));
35899
+ return haystack.includes(needle);
35900
+ }
35775
35901
  var REASON_RE;
35776
35902
  var init_test_edit_declaration = __esm(() => {
35777
35903
  REASON_RE = /^TEST_EDIT_REASON:\s*(prd_contract|lint_only|sibling_scope|mock_structure)\s*$/m;
@@ -37896,7 +38022,7 @@ var init__finding_to_check = __esm(() => {
37896
38022
  });
37897
38023
 
37898
38024
  // src/operations/autofix-implementer-strategy.ts
37899
- function makeAutofixImplementerStrategy(story, config2) {
38025
+ function makeAutofixImplementerStrategy(story, config2, sink) {
37900
38026
  return {
37901
38027
  name: "autofix-implementer",
37902
38028
  appliesTo: (f) => f.fixTarget === "source" && IMPLEMENTER_SOURCES.has(f.source),
@@ -37905,10 +38031,19 @@ function makeAutofixImplementerStrategy(story, config2) {
37905
38031
  failedChecks: findingsToFailedChecks(findings),
37906
38032
  story
37907
38033
  }),
37908
- extractApplied: (output) => ({
37909
- summary: output.unresolvedReason ?? "",
37910
- unresolved: output.unresolvedReason
37911
- }),
38034
+ extractApplied: (output) => {
38035
+ for (const decl of output.testEditDeclarations) {
38036
+ if (decl.reason === "mock_structure" && decl.files && decl.reasonDetail) {
38037
+ sink.mockHandoffs.push({ files: decl.files, reasonDetail: decl.reasonDetail });
38038
+ } else if (decl.reason !== "mock_structure") {
38039
+ sink.testEdits.push(decl);
38040
+ }
38041
+ }
38042
+ return {
38043
+ summary: output.unresolvedReason ?? "",
38044
+ unresolved: output.unresolvedReason
38045
+ };
38046
+ },
37912
38047
  maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37913
38048
  coRun: "co-run-sequential"
37914
38049
  };
@@ -37921,16 +38056,42 @@ var init_autofix_implementer_strategy = __esm(() => {
37921
38056
  });
37922
38057
 
37923
38058
  // src/operations/autofix-test-writer-strategy.ts
37924
- function makeAutofixTestWriterStrategy(story, config2) {
38059
+ function makeAutofixTestWriterStrategy(story, config2, sink) {
37925
38060
  return {
37926
38061
  name: "autofix-test-writer",
37927
- appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review",
38062
+ appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review" || sink.mockHandoffs.length > 0,
37928
38063
  fixOp: testWriterRectifyOp,
37929
- buildInput: (findings, _prior, _cycleCtx) => ({
37930
- failedChecks: findingsToFailedChecks(findings),
37931
- story,
37932
- blockingThreshold: config2.review?.blockingThreshold
37933
- }),
38064
+ buildInput: (findings, _prior, _cycleCtx) => {
38065
+ if (sink.mockHandoffs.length > 0) {
38066
+ const handoffs = sink.mockHandoffs.splice(0);
38067
+ const seenFiles = new Set;
38068
+ const handoffFiles = [];
38069
+ for (const h of handoffs) {
38070
+ for (const f of h.files) {
38071
+ if (!seenFiles.has(f)) {
38072
+ seenFiles.add(f);
38073
+ handoffFiles.push(f);
38074
+ }
38075
+ }
38076
+ }
38077
+ const handoffReason = handoffs.map((h) => h.reasonDetail).join(`
38078
+ ---
38079
+ `);
38080
+ return {
38081
+ failedChecks: findingsToFailedChecks(findings),
38082
+ story,
38083
+ mode: "mock-restructure",
38084
+ blockingThreshold: config2.review?.blockingThreshold,
38085
+ handoffReason,
38086
+ handoffFiles
38087
+ };
38088
+ }
38089
+ return {
38090
+ failedChecks: findingsToFailedChecks(findings),
38091
+ story,
38092
+ blockingThreshold: config2.review?.blockingThreshold
38093
+ };
38094
+ },
37934
38095
  maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37935
38096
  coRun: "co-run-sequential"
37936
38097
  };
@@ -37940,6 +38101,100 @@ var init_autofix_test_writer_strategy = __esm(() => {
37940
38101
  init_autofix_test_writer();
37941
38102
  });
37942
38103
 
38104
+ // src/operations/apply-test-edit-declarations.ts
38105
+ function applyTestEditDeclarations(findings, declarations, story, invalidMockStructure) {
38106
+ let result = [...findings];
38107
+ const advisories = [];
38108
+ for (const d of declarations) {
38109
+ if (d.reason === "prd_contract") {
38110
+ const prdQuote = d.prdQuote ?? "";
38111
+ const valid = validatePrdQuote(prdQuote, story);
38112
+ if (valid) {
38113
+ result = result.map((f) => {
38114
+ if (f.file === d.file && f.fixTarget === "source") {
38115
+ return {
38116
+ ...f,
38117
+ fixTarget: "test",
38118
+ meta: {
38119
+ ...f.meta,
38120
+ prdContractDeclaration: d
38121
+ }
38122
+ };
38123
+ }
38124
+ return f;
38125
+ });
38126
+ } else {
38127
+ advisories.push({
38128
+ source: "autofix",
38129
+ severity: "warning",
38130
+ category: "prd_quote_mismatch",
38131
+ message: `PRD quote not found verbatim in story text for file: ${d.file}`,
38132
+ file: d.file,
38133
+ fixTarget: "source"
38134
+ });
38135
+ }
38136
+ }
38137
+ }
38138
+ if (invalidMockStructure && invalidMockStructure.length > 0) {
38139
+ for (const d of invalidMockStructure) {
38140
+ const fileList = (d.files ?? [d.file]).join(", ");
38141
+ advisories.push({
38142
+ source: "autofix",
38143
+ severity: "warning",
38144
+ category: "mock_structure_invalid_files",
38145
+ message: `Mock structure handoff references file that does not exist or is not a test file: ${fileList}`,
38146
+ fixTarget: "source"
38147
+ });
38148
+ }
38149
+ }
38150
+ return [...result, ...advisories];
38151
+ }
38152
+ var init_apply_test_edit_declarations = __esm(() => {
38153
+ init_test_edit_declaration();
38154
+ });
38155
+
38156
+ // src/operations/validate-mock-structure-files.ts
38157
+ import { join as join23 } from "path";
38158
+ async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
38159
+ const fileExists = deps?.fileExists ?? defaultFileExists;
38160
+ const valid = [];
38161
+ const invalid = [];
38162
+ for (const d of declarations) {
38163
+ if (d.reason !== "mock_structure") {
38164
+ valid.push(d);
38165
+ continue;
38166
+ }
38167
+ const files = d.files ?? [d.file];
38168
+ let allValid = true;
38169
+ for (const file3 of files) {
38170
+ const absolutePath = join23(packageDir, file3);
38171
+ const exists = await fileExists(absolutePath);
38172
+ if (!exists) {
38173
+ allValid = false;
38174
+ break;
38175
+ }
38176
+ const matchesPattern = resolvedTestPatterns.regex.some((re) => re.test(file3));
38177
+ if (!matchesPattern) {
38178
+ allValid = false;
38179
+ break;
38180
+ }
38181
+ }
38182
+ if (allValid) {
38183
+ valid.push(d);
38184
+ } else {
38185
+ invalid.push(d);
38186
+ }
38187
+ }
38188
+ return { valid, invalid };
38189
+ }
38190
+ var defaultFileExists = (p) => Bun.file(p).exists();
38191
+ var init_validate_mock_structure_files = () => {};
38192
+
38193
+ // src/operations/declaration-sink.ts
38194
+ function makeDeclarationSink() {
38195
+ return { testEdits: [], mockHandoffs: [] };
38196
+ }
38197
+
37943
38198
  // src/operations/mechanical-lintfix-strategy.ts
37944
38199
  function shellQuotePath2(path5) {
37945
38200
  return `'${path5.replaceAll("'", `'\\''`)}'`;
@@ -38420,6 +38675,8 @@ var init_operations = __esm(() => {
38420
38675
  init_full_suite_rectify();
38421
38676
  init_autofix_implementer_strategy();
38422
38677
  init_autofix_test_writer_strategy();
38678
+ init_apply_test_edit_declarations();
38679
+ init_validate_mock_structure_files();
38423
38680
  init__finding_to_check();
38424
38681
  init_mechanical_lintfix_strategy();
38425
38682
  init_mechanical_formatfix_strategy();
@@ -39070,7 +39327,7 @@ var init_lint_parsing = __esm(() => {
39070
39327
  });
39071
39328
 
39072
39329
  // src/review/scoped-lint.ts
39073
- import { join as join23, relative as relative10 } from "path";
39330
+ import { join as join24, relative as relative11 } from "path";
39074
39331
  function shellQuotePath4(path5) {
39075
39332
  return `'${path5.replaceAll("'", "'\\''")}'`;
39076
39333
  }
@@ -39107,7 +39364,7 @@ async function listChangedFiles(workdir, baseRef) {
39107
39364
  function inferActivePackageDir(workdir, projectDir) {
39108
39365
  if (!projectDir)
39109
39366
  return;
39110
- const rel = normalizePath3(relative10(projectDir, workdir));
39367
+ const rel = normalizePath3(relative11(projectDir, workdir));
39111
39368
  if (!rel || rel === "." || rel.startsWith(".."))
39112
39369
  return;
39113
39370
  return rel;
@@ -39118,7 +39375,7 @@ function uniqueFiles(files) {
39118
39375
  async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
39119
39376
  const inScope = [];
39120
39377
  for (const relPath of files) {
39121
- const absPath = join23(workdir, relPath);
39378
+ const absPath = join24(workdir, relPath);
39122
39379
  const exists = await _scopedLintDeps.fileExists(absPath);
39123
39380
  if (!exists)
39124
39381
  continue;
@@ -39592,7 +39849,7 @@ var init_semantic_debate = __esm(() => {
39592
39849
  });
39593
39850
 
39594
39851
  // src/review/semantic.ts
39595
- import { relative as relative11, sep as sep2 } from "path";
39852
+ import { relative as relative12, sep as sep3 } from "path";
39596
39853
  function recordSemanticAudit(opts) {
39597
39854
  opts.runtime?.dispatchEvents.emitReviewDecision({
39598
39855
  kind: "review-decision",
@@ -39658,8 +39915,8 @@ async function runSemanticReview(opts) {
39658
39915
  const packageDir = workdir !== repoRoot ? workdir : undefined;
39659
39916
  const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
39660
39917
  const packageDirRelative = projectDir && workdir !== projectDir ? (() => {
39661
- const rel = relative11(projectDir, workdir);
39662
- if (rel === ".." || rel.startsWith(`..${sep2}`))
39918
+ const rel = relative12(projectDir, workdir);
39919
+ if (rel === ".." || rel.startsWith(`..${sep3}`))
39663
39920
  return;
39664
39921
  return rel && rel !== "." ? rel : undefined;
39665
39922
  })() : undefined;
@@ -40377,6 +40634,7 @@ var init_review = __esm(() => {
40377
40634
  init_semantic_evidence();
40378
40635
  init_categorization();
40379
40636
  init_diff_utils();
40637
+ init_prepare_inputs();
40380
40638
  init_finding_projection();
40381
40639
  init_runner2();
40382
40640
  init_requote_response();
@@ -40384,9 +40642,34 @@ var init_review = __esm(() => {
40384
40642
  });
40385
40643
 
40386
40644
  // src/prompts/builders/rectifier-builder-helpers.ts
40645
+ function buildEscapeHatch(opts) {
40646
+ const exceptions = [EXCEPTION_1_LINT_ONLY, EXCEPTION_2_PRD_CONTRACT, EXCEPTION_3_SIBLING_SCOPE];
40647
+ if (opts.includeMockHandoff)
40648
+ exceptions.push(EXCEPTION_4_MOCK_HANDOFF);
40649
+ const count = exceptions.length;
40650
+ const countWord = ["zero", "one", "two", "three", "four"][count];
40651
+ return `
40652
+ If two findings in this list contradict each other and you cannot satisfy both, do not guess.
40653
+ Emit fixes for defects you can resolve, then output a line in this exact format:
40654
+ UNRESOLVED: <brief explanation of which findings conflicted and why they cannot both be satisfied>
40655
+
40656
+ Before emitting UNRESOLVED, confirm none of Exceptions 1\u2013${count} apply.
40657
+
40658
+ ## Test-file edit exceptions
40659
+
40660
+ The "do not modify test files" rule has ${countWord} narrow escape valves. Each requires a
40661
+ declaration in your output. Outside these ${countWord} cases the rule is absolute.
40662
+
40663
+ ${exceptions.join(`
40664
+
40665
+ `)}`;
40666
+ }
40667
+ function exceptionCountWord(story) {
40668
+ return THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "") ? "four" : "three";
40669
+ }
40387
40670
  function escapeHatchFor(story) {
40388
40671
  const isTdd = THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "");
40389
- return isTdd ? CONTRADICTION_ESCAPE_HATCH : CONTRADICTION_ESCAPE_HATCH.replace(EXCEPTION_4_MOCK_HANDOFF, "");
40672
+ return buildEscapeHatch({ includeMockHandoff: isTdd });
40390
40673
  }
40391
40674
  function noTestIsolationBlock(story) {
40392
40675
  if (story.routing?.testStrategy !== "no-test")
@@ -40456,7 +40739,7 @@ ${errors3}
40456
40739
  2. Only fix findings that are actually valid problems
40457
40740
  3. Do NOT add keys, functions, or imports that already exist \u2014 check first
40458
40741
 
40459
- Do NOT change test files or test behavior \u2014 see the three narrow exceptions appended below.
40742
+ Do NOT change test files or test behavior \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below.
40460
40743
  Do NOT add new features \u2014 only fix valid issues.
40461
40744
  Commit your fixes when done.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
40462
40745
  }
@@ -40518,21 +40801,11 @@ The following quality checks failed after implementation:
40518
40801
 
40519
40802
  ${errors3}
40520
40803
 
40521
- Fix all errors listed above that are within this story's scope \u2014 see the three narrow exceptions appended below for sibling-story spillover. Do NOT change test files or test behavior except via those exceptions.
40804
+ Fix all errors listed above that are within this story's scope \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below for sibling-story spillover. Do NOT change test files or test behavior except via those exceptions.
40522
40805
  Do NOT add new features \u2014 only fix the quality check errors.
40523
40806
  After fixing, re-run the failing check(s) to verify they pass, then commit your changes.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
40524
40807
  }
40525
- var CONTRADICTION_ESCAPE_HATCH = `
40526
- If two findings in this list contradict each other and you cannot satisfy both, do not guess.
40527
- Emit fixes for defects you can resolve, then output a line in this exact format:
40528
- UNRESOLVED: <brief explanation of which findings conflicted and why they cannot both be satisfied>
40529
-
40530
- ## Test-file edit exceptions
40531
-
40532
- The "do not modify test files" rule has three narrow escape valves. Each requires a
40533
- declaration in your output. Outside these three cases the rule is absolute.
40534
-
40535
- ### Exception 1 \u2014 Lint-only edit
40808
+ var EXCEPTION_1_LINT_ONLY = `### Exception 1 \u2014 Lint-only edit
40536
40809
 
40537
40810
  You MAY edit a test file ONLY when ALL of the following hold:
40538
40811
  - The failing check is \`lint\` \u2014 not \`test\`, \`typecheck\`, \`semantic\`, or \`adversarial\`.
@@ -40548,9 +40821,7 @@ TEST_EDIT_REASON: lint_only
40548
40821
  FILE: <test file path>
40549
40822
  FINDING: <lint rule or message verbatim>
40550
40823
  CHANGE: <before line> \u2192 <after line>
40551
- \`\`\`
40552
-
40553
- ### Exception 2 \u2014 PRD-contract mismatch
40824
+ \`\`\``, EXCEPTION_2_PRD_CONTRACT = `### Exception 2 \u2014 PRD-contract mismatch
40554
40825
 
40555
40826
  You MAY correct a test's argument arity, type, or return-handling ONLY when the test's
40556
40827
  call contradicts a literal interface signature stated in this story's description or
@@ -40566,9 +40837,7 @@ TEST_AFTER: <corrected call line>
40566
40837
  \`\`\`
40567
40838
 
40568
40839
  Do NOT use this exception to change test logic, assertions, or mock setup \u2014 only call
40569
- signatures that directly contradict a quoted PRD interface.
40570
-
40571
- ### Exception 3 \u2014 Unrelated sibling spillover
40840
+ signatures that directly contradict a quoted PRD interface.`, EXCEPTION_3_SIBLING_SCOPE = `### Exception 3 \u2014 Unrelated sibling spillover
40572
40841
 
40573
40842
  When a lint or typecheck error is outside this story's intended scope, do NOT edit that
40574
40843
  file. If the smallest package-local fix is required to satisfy this story's acceptance
@@ -40578,46 +40847,37 @@ TEST_EDIT_REASON: sibling_scope
40578
40847
  SIBLING_FILE: <file path>
40579
40848
  FINDING: <error summary>
40580
40849
  \`\`\`
40581
- and continue. Sibling-scope failures do not block your story.
40582
-
40583
- ### Exception 4 \u2014 Mock-structure handoff
40850
+ and continue. Sibling-scope failures do not block your story.`, EXCEPTION_4_MOCK_HANDOFF = `### Exception 4 \u2014 Mock-structure handoff
40584
40851
 
40585
40852
  Use ONLY when the only path to satisfy the ACs requires a structural test rewrite
40586
- that does NOT fit Exception 2. Examples: mocks reference primitives the new code
40587
- bypasses; assertion topology must change to match a new dispatch shape.
40588
-
40589
- Declare with:
40590
- \`\`\`
40591
- TEST_EDIT_REASON: mock_structure
40592
- FILES: <comma-separated test file paths>
40593
- REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses>
40594
- \`\`\`
40853
+ that does NOT fit Exception 2. Two cases qualify:
40595
40854
 
40596
- Rules:
40597
- - Do NOT make any edits yourself; the test-writer will fulfill.
40598
- - Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
40599
- - FILES must list real test files. Each path must exist and be a test file.`, EXCEPTION_4_MOCK_HANDOFF = `
40600
- ### Exception 4 \u2014 Mock-structure handoff
40855
+ (a) Existing mocks are wrong \u2014 mocks reference primitives the new code bypasses,
40856
+ or assertion topology must change to match a new dispatch shape.
40601
40857
 
40602
- Use ONLY when the only path to satisfy the ACs requires a structural test rewrite
40603
- that does NOT fit Exception 2. Examples: mocks reference primitives the new code
40604
- bypasses; assertion topology must change to match a new dispatch shape.
40858
+ (b) Required test-infrastructure does not yet exist and must be introduced \u2014
40859
+ e.g. in-process fake servers, network-level request interception, hermetic
40860
+ fixture-backed HTTP, or equivalent. Applies whenever the AC describes a
40861
+ hermetic/fixture-backed test surface that the current test setup cannot
40862
+ satisfy without new infrastructure.
40605
40863
 
40606
40864
  Declare with:
40607
40865
  \`\`\`
40608
40866
  TEST_EDIT_REASON: mock_structure
40609
40867
  FILES: <comma-separated test file paths>
40610
- REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses>
40868
+ REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses,
40869
+ or what infrastructure must be introduced>
40611
40870
  \`\`\`
40612
40871
 
40613
40872
  Rules:
40614
40873
  - Do NOT make any edits yourself; the test-writer will fulfill.
40615
40874
  - Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
40616
- - FILES must list real test files. Each path must exist and be a test file.`, THREE_SESSION_STRATEGIES, MAX_STRUCTURED_FINDINGS = 10, RAW_WITH_FINDINGS_LIMIT = 1000, RAW_FALLBACK_LIMIT = 4000;
40875
+ - FILES must list real test files. Each path must exist and be a test file.`, THREE_SESSION_STRATEGIES, CONTRADICTION_ESCAPE_HATCH, MAX_STRUCTURED_FINDINGS = 10, RAW_WITH_FINDINGS_LIMIT = 1000, RAW_FALLBACK_LIMIT = 4000;
40617
40876
  var init_rectifier_builder_helpers = __esm(() => {
40618
40877
  init_review();
40619
40878
  init_sections2();
40620
40879
  THREE_SESSION_STRATEGIES = new Set(["three-session-tdd", "three-session-tdd-lite"]);
40880
+ CONTRADICTION_ESCAPE_HATCH = buildEscapeHatch({ includeMockHandoff: false });
40621
40881
  });
40622
40882
 
40623
40883
  // src/prompts/builders/rectifier-builder.ts
@@ -40698,15 +40958,16 @@ function renderPrioritizedFailures(failedChecks, opts) {
40698
40958
  }
40699
40959
 
40700
40960
  class RectifierPromptBuilder {
40701
- static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel) {
40961
+ static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel, story) {
40702
40962
  const parts = [];
40703
40963
  const attemptWord = maxAttempts === 1 ? "1 attempt" : `${maxAttempts} attempts`;
40964
+ const exCount = story ? exceptionCountWord(story) : "three";
40704
40965
  parts.push(`Review failed after your implementation. Fix the following issues (${attemptWord} available before escalation):
40705
40966
  `);
40706
40967
  parts.push(renderPrioritizedFailures(failedChecks));
40707
40968
  parts.push(`
40708
- Fix in priority order. After fixing each priority, re-run the failing check(s) at that level to verify they pass before moving on. Do NOT change test files or test behavior \u2014 see the three narrow exceptions appended below. Commit your changes when all checks pass.`);
40709
- parts.push(CONTRADICTION_ESCAPE_HATCH);
40969
+ Fix in priority order. After fixing each priority, re-run the failing check(s) at that level to verify they pass before moving on. Do NOT change test files or test behavior \u2014 see the ${exCount} narrow exceptions appended below. Commit your changes when all checks pass.`);
40970
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40710
40971
  const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
40711
40972
  if (guardrails) {
40712
40973
  parts.push(`
@@ -40716,7 +40977,7 @@ ${guardrails}`);
40716
40977
  return parts.join(`
40717
40978
  `);
40718
40979
  }
40719
- static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel) {
40980
+ static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel, story) {
40720
40981
  const parts = [];
40721
40982
  parts.push(`Your previous fix attempt did not resolve all issues. Here are the remaining failures:
40722
40983
  `);
@@ -40729,7 +40990,7 @@ ${guardrails}`);
40729
40990
  if (attempt >= urgencyAtAttempt) {
40730
40991
  parts.push("\n**URGENT: This is your final attempt.** If you cannot fix all issues, emit `UNRESOLVED: <reason>` to escalate.\n");
40731
40992
  }
40732
- parts.push(CONTRADICTION_ESCAPE_HATCH);
40993
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40733
40994
  const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
40734
40995
  if (guardrails) {
40735
40996
  parts.push(`
@@ -40851,7 +41112,7 @@ ${importantNote}
40851
41112
 
40852
41113
  Commit your fixes when done.${scopeConstraint}`;
40853
41114
  }
40854
- static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts) {
41115
+ static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts, story) {
40855
41116
  const parts = [];
40856
41117
  parts.push(`**Your previous turn produced no committed file changes.**
40857
41118
 
@@ -40892,7 +41153,7 @@ ${output}
40892
41153
  `);
40893
41154
  }
40894
41155
  }
40895
- parts.push(CONTRADICTION_ESCAPE_HATCH);
41156
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40896
41157
  return parts.join("");
40897
41158
  }
40898
41159
  static escalated(failures, story, priorAttempts, originalTier, targetTier, config2, testCommand, testScopedTemplate) {
@@ -40968,11 +41229,11 @@ ${testCommands}
40968
41229
  6. Ensure ALL tests pass before completing.
40969
41230
 
40970
41231
  **IMPORTANT:**
40971
- - Do NOT modify test files \u2014 see the three narrow exceptions in the escape valve section if you believe a test has a lint error, a PRD-contract mismatch, or belongs to a sibling story.
41232
+ - Do NOT modify test files \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below if you believe a test has a lint error, a PRD-contract mismatch, or belongs to a sibling story.
40972
41233
  - Do NOT loosen assertions to mask implementation bugs.
40973
41234
  - Focus on fixing the source code to meet the test requirements.
40974
41235
  - When running tests, run ONLY the failing test files shown above${cmd ? ` \u2014 NEVER run \`${cmd}\` without a file filter` : " \u2014 never run the full test suite without a file filter"}.
40975
- `;
41236
+ ${escapeHatchFor(story)}`;
40976
41237
  }
40977
41238
  static reviewRectification(failedChecks, story, opts) {
40978
41239
  const scopeConstraint = story.workdir ? `
@@ -41029,7 +41290,7 @@ ${llmSection}
41029
41290
  **Important:** LLM reviewers may flag false positives. Before making changes for LLM review findings, read the relevant files to verify each finding is a real issue. Do NOT add keys, functions, or imports that already exist.
41030
41291
 
41031
41292
  Do NOT add new features \u2014 only fix the identified issues.
41032
- Commit your fixes when done.${scopeConstraint}${CONTRADICTION_ESCAPE_HATCH}`;
41293
+ Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
41033
41294
  }
41034
41295
  static dialogueAwareRectification(failedChecks, story, opts) {
41035
41296
  const scopeConstraint = story.workdir ? `
@@ -41068,9 +41329,9 @@ ${errors3}${reasoningSection}${historySection}
41068
41329
  2. Only fix findings that are actually valid problems
41069
41330
  3. Do NOT add keys, functions, or imports that already exist \u2014 check first
41070
41331
 
41071
- Do NOT change test files or test behavior \u2014 see the three narrow exceptions appended below.
41332
+ Do NOT change test files or test behavior \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below.
41072
41333
  Do NOT add new features \u2014 only fix valid issues.
41073
- Commit your fixes when done.${scopeConstraint}${CONTRADICTION_ESCAPE_HATCH}`;
41334
+ Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
41074
41335
  }
41075
41336
  static swapHandoff(basePrompt, pushMarkdown) {
41076
41337
  const trimmed = pushMarkdown?.trim();
@@ -41162,9 +41423,10 @@ Tests are failing. Fix the source so all tests pass \u2014 not just the ones lis
41162
41423
  4. Do not declare done until step 3 shows 0 failures.
41163
41424
 
41164
41425
  **IMPORTANT:**
41165
- - Do NOT modify test files \u2014 see the three narrow exceptions in the escape valve section if you believe a test has a lint error, a PRD-contract mismatch, or belongs to a sibling story.
41426
+ - Do NOT modify test files \u2014 see the ${exceptionCountWord(opts.story)} narrow exceptions appended below if you believe a test has a lint error, a PRD-contract mismatch, or belongs to a sibling story.
41166
41427
  - Do NOT loosen assertions to mask implementation bugs.
41167
41428
  - Focus on fixing the source code to meet the test requirements.`);
41429
+ parts.push(escapeHatchFor(opts.story));
41168
41430
  return parts.join("");
41169
41431
  }
41170
41432
  static failingTestContext(findings) {
@@ -42498,7 +42760,7 @@ var init_call = __esm(() => {
42498
42760
 
42499
42761
  // src/runtime/cost-aggregator.ts
42500
42762
  import { mkdirSync as mkdirSync2 } from "fs";
42501
- import { join as join24 } from "path";
42763
+ import { join as join25 } from "path";
42502
42764
  function makeCorrelationId() {
42503
42765
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
42504
42766
  }
@@ -42689,7 +42951,7 @@ class CostAggregator {
42689
42951
  if (events.length === 0 && errors3.length === 0)
42690
42952
  return;
42691
42953
  mkdirSync2(this._drainDir, { recursive: true });
42692
- const path5 = join24(this._drainDir, `${this._runId}.jsonl`);
42954
+ const path5 = join25(this._drainDir, `${this._runId}.jsonl`);
42693
42955
  const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
42694
42956
  await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
42695
42957
  `)}
@@ -42729,7 +42991,7 @@ var init_cost_aggregator = __esm(() => {
42729
42991
  // src/runtime/prompt-auditor.ts
42730
42992
  import { appendFileSync } from "fs";
42731
42993
  import { mkdir as mkdir4 } from "fs/promises";
42732
- import { join as join25 } from "path";
42994
+ import { join as join26 } from "path";
42733
42995
  function createNoOpPromptAuditor() {
42734
42996
  return {
42735
42997
  record() {},
@@ -42795,8 +43057,8 @@ class PromptAuditor {
42795
43057
  _jsonlPath;
42796
43058
  _featureDir;
42797
43059
  constructor(runId, flushDir, featureName) {
42798
- this._featureDir = join25(flushDir, featureName);
42799
- this._jsonlPath = join25(this._featureDir, `${runId}.jsonl`);
43060
+ this._featureDir = join26(flushDir, featureName);
43061
+ this._jsonlPath = join26(this._featureDir, `${runId}.jsonl`);
42800
43062
  }
42801
43063
  record(entry) {
42802
43064
  this._enqueue(entry);
@@ -42845,7 +43107,7 @@ class PromptAuditor {
42845
43107
  const auditEntry = entry;
42846
43108
  const filename = deriveTxtFilename(auditEntry);
42847
43109
  try {
42848
- await _promptAuditorDeps.write(join25(this._featureDir, filename), buildTxtContent(auditEntry));
43110
+ await _promptAuditorDeps.write(join26(this._featureDir, filename), buildTxtContent(auditEntry));
42849
43111
  } catch (err) {
42850
43112
  throw tagAuditError(err, "txt");
42851
43113
  }
@@ -43933,9 +44195,9 @@ var init_pid_registry = __esm(() => {
43933
44195
  // src/session/manager-deps.ts
43934
44196
  import { randomUUID as randomUUID3 } from "crypto";
43935
44197
  import { mkdir as mkdir5 } from "fs/promises";
43936
- import { isAbsolute as isAbsolute9, join as join26, relative as relative12, sep as sep3 } from "path";
44198
+ import { isAbsolute as isAbsolute9, join as join27, relative as relative13, sep as sep4 } from "path";
43937
44199
  function resolveProjectDirFromScratchDir(scratchDir) {
43938
- const marker = `${sep3}.nax${sep3}features${sep3}`;
44200
+ const marker = `${sep4}.nax${sep4}features${sep4}`;
43939
44201
  const markerIdx = scratchDir.lastIndexOf(marker);
43940
44202
  if (markerIdx > 0)
43941
44203
  return scratchDir.slice(0, markerIdx);
@@ -43945,7 +44207,7 @@ function resolveProjectDirFromScratchDir(scratchDir) {
43945
44207
  return;
43946
44208
  }
43947
44209
  function toProjectRelativePath(projectDir, pathValue) {
43948
- const relativePath = isAbsolute9(pathValue) ? relative12(projectDir, pathValue) : pathValue;
44210
+ const relativePath = isAbsolute9(pathValue) ? relative13(projectDir, pathValue) : pathValue;
43949
44211
  return relativePath === "" ? "." : relativePath;
43950
44212
  }
43951
44213
  var _sessionManagerDeps;
@@ -43954,7 +44216,7 @@ var init_manager_deps = __esm(() => {
43954
44216
  now: () => new Date().toISOString(),
43955
44217
  nowMs: () => Date.now(),
43956
44218
  uuid: () => randomUUID3(),
43957
- sessionScratchDir: (projectDir, featureName, sessionId) => join26(projectDir, ".nax", "features", featureName, "sessions", sessionId),
44219
+ sessionScratchDir: (projectDir, featureName, sessionId) => join27(projectDir, ".nax", "features", featureName, "sessions", sessionId),
43958
44220
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
43959
44221
  await mkdir5(scratchDir, { recursive: true });
43960
44222
  const { handle: _handle, ...persistable } = descriptor;
@@ -43965,7 +44227,7 @@ var init_manager_deps = __esm(() => {
43965
44227
  persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
43966
44228
  }
43967
44229
  }
43968
- await Bun.write(join26(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
44230
+ await Bun.write(join27(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
43969
44231
  }
43970
44232
  };
43971
44233
  });
@@ -44716,7 +44978,7 @@ __export(exports_runtime, {
44716
44978
  CostAggregator: () => CostAggregator,
44717
44979
  AgentStreamEventBus: () => AgentStreamEventBus
44718
44980
  });
44719
- import { basename as basename5, join as join27 } from "path";
44981
+ import { basename as basename5, join as join28 } from "path";
44720
44982
  function createRuntime(config2, workdir, opts) {
44721
44983
  const runId = crypto.randomUUID();
44722
44984
  const controller = new AbortController;
@@ -44732,10 +44994,10 @@ function createRuntime(config2, workdir, opts) {
44732
44994
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
44733
44995
  const globalDir = globalOutputDir();
44734
44996
  const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
44735
- const costDir = join27(outputDir, "cost");
44997
+ const costDir = join28(outputDir, "cost");
44736
44998
  const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
44737
44999
  const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
44738
- const auditDir = config2.agent?.promptAudit?.dir ?? join27(outputDir, "prompt-audit");
45000
+ const auditDir = config2.agent?.promptAudit?.dir ?? join28(outputDir, "prompt-audit");
44739
45001
  let promptAuditor;
44740
45002
  if (opts?.promptAuditor) {
44741
45003
  promptAuditor = opts.promptAuditor;
@@ -44886,9 +45148,9 @@ async function allSettledBounded(tasks, limit) {
44886
45148
 
44887
45149
  // src/context/injector.ts
44888
45150
  import { existsSync as existsSync8 } from "fs";
44889
- import { join as join28 } from "path";
45151
+ import { join as join29 } from "path";
44890
45152
  async function detectNode(workdir) {
44891
- const pkgPath = join28(workdir, "package.json");
45153
+ const pkgPath = join29(workdir, "package.json");
44892
45154
  if (!existsSync8(pkgPath))
44893
45155
  return null;
44894
45156
  try {
@@ -44905,7 +45167,7 @@ async function detectNode(workdir) {
44905
45167
  }
44906
45168
  }
44907
45169
  async function detectGo(workdir) {
44908
- const goMod = join28(workdir, "go.mod");
45170
+ const goMod = join29(workdir, "go.mod");
44909
45171
  if (!existsSync8(goMod))
44910
45172
  return null;
44911
45173
  try {
@@ -44929,7 +45191,7 @@ async function detectGo(workdir) {
44929
45191
  }
44930
45192
  }
44931
45193
  async function detectRust(workdir) {
44932
- const cargoPath = join28(workdir, "Cargo.toml");
45194
+ const cargoPath = join29(workdir, "Cargo.toml");
44933
45195
  if (!existsSync8(cargoPath))
44934
45196
  return null;
44935
45197
  try {
@@ -44945,8 +45207,8 @@ async function detectRust(workdir) {
44945
45207
  }
44946
45208
  }
44947
45209
  async function detectPython(workdir) {
44948
- const pyproject = join28(workdir, "pyproject.toml");
44949
- const requirements = join28(workdir, "requirements.txt");
45210
+ const pyproject = join29(workdir, "pyproject.toml");
45211
+ const requirements = join29(workdir, "requirements.txt");
44950
45212
  if (!existsSync8(pyproject) && !existsSync8(requirements))
44951
45213
  return null;
44952
45214
  try {
@@ -44965,7 +45227,7 @@ async function detectPython(workdir) {
44965
45227
  }
44966
45228
  }
44967
45229
  async function detectPhp(workdir) {
44968
- const composerPath = join28(workdir, "composer.json");
45230
+ const composerPath = join29(workdir, "composer.json");
44969
45231
  if (!existsSync8(composerPath))
44970
45232
  return null;
44971
45233
  try {
@@ -44978,7 +45240,7 @@ async function detectPhp(workdir) {
44978
45240
  }
44979
45241
  }
44980
45242
  async function detectRuby(workdir) {
44981
- const gemfile = join28(workdir, "Gemfile");
45243
+ const gemfile = join29(workdir, "Gemfile");
44982
45244
  if (!existsSync8(gemfile))
44983
45245
  return null;
44984
45246
  try {
@@ -44990,9 +45252,9 @@ async function detectRuby(workdir) {
44990
45252
  }
44991
45253
  }
44992
45254
  async function detectJvm(workdir) {
44993
- const pom = join28(workdir, "pom.xml");
44994
- const gradle = join28(workdir, "build.gradle");
44995
- const gradleKts = join28(workdir, "build.gradle.kts");
45255
+ const pom = join29(workdir, "pom.xml");
45256
+ const gradle = join29(workdir, "build.gradle");
45257
+ const gradleKts = join29(workdir, "build.gradle.kts");
44996
45258
  if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
44997
45259
  return null;
44998
45260
  try {
@@ -45000,7 +45262,7 @@ async function detectJvm(workdir) {
45000
45262
  const content2 = await Bun.file(pom).text();
45001
45263
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
45002
45264
  const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
45003
- const lang2 = existsSync8(join28(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
45265
+ const lang2 = existsSync8(join29(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
45004
45266
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
45005
45267
  }
45006
45268
  const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
@@ -45254,7 +45516,7 @@ var init_windsurf = __esm(() => {
45254
45516
 
45255
45517
  // src/context/generator.ts
45256
45518
  import { existsSync as existsSync9 } from "fs";
45257
- import { join as join29, relative as relative13 } from "path";
45519
+ import { join as join30, relative as relative14 } from "path";
45258
45520
  async function loadContextContent(options, config2) {
45259
45521
  if (!_generatorDeps.existsSync(options.contextPath)) {
45260
45522
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -45272,7 +45534,7 @@ async function generateFor(agent, options, config2) {
45272
45534
  try {
45273
45535
  const context = await loadContextContent(options, config2);
45274
45536
  const content = generator.generate(context);
45275
- const outputPath = join29(options.outputDir, generator.outputFile);
45537
+ const outputPath = join30(options.outputDir, generator.outputFile);
45276
45538
  validateFilePath(outputPath, options.outputDir);
45277
45539
  if (!options.dryRun) {
45278
45540
  await _generatorDeps.writeFile(outputPath, content);
@@ -45290,7 +45552,7 @@ async function generateAll(options, config2, agentFilter) {
45290
45552
  for (const [agentKey, generator] of entries) {
45291
45553
  try {
45292
45554
  const content = generator.generate(context);
45293
- const outputPath = join29(options.outputDir, generator.outputFile);
45555
+ const outputPath = join30(options.outputDir, generator.outputFile);
45294
45556
  validateFilePath(outputPath, options.outputDir);
45295
45557
  if (!options.dryRun) {
45296
45558
  await _generatorDeps.writeFile(outputPath, content);
@@ -45310,7 +45572,7 @@ async function discoverPackages(repoRoot) {
45310
45572
  const glob = new Bun.Glob(pattern);
45311
45573
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
45312
45574
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
45313
- const pkgAbsolute = join29(repoRoot, pkgRelative);
45575
+ const pkgAbsolute = join30(repoRoot, pkgRelative);
45314
45576
  if (!seen.has(pkgAbsolute)) {
45315
45577
  seen.add(pkgAbsolute);
45316
45578
  packages.push(pkgAbsolute);
@@ -45342,14 +45604,14 @@ async function discoverWorkspacePackages2(repoRoot) {
45342
45604
  }
45343
45605
  }
45344
45606
  }
45345
- const turboPath = join29(repoRoot, "turbo.json");
45607
+ const turboPath = join30(repoRoot, "turbo.json");
45346
45608
  try {
45347
45609
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
45348
45610
  if (Array.isArray(turbo.packages)) {
45349
45611
  await resolveGlobs(turbo.packages);
45350
45612
  }
45351
45613
  } catch {}
45352
- const pkgPath = join29(repoRoot, "package.json");
45614
+ const pkgPath = join30(repoRoot, "package.json");
45353
45615
  try {
45354
45616
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
45355
45617
  const ws = pkg.workspaces;
@@ -45357,7 +45619,7 @@ async function discoverWorkspacePackages2(repoRoot) {
45357
45619
  if (patterns.length > 0)
45358
45620
  await resolveGlobs(patterns);
45359
45621
  } catch {}
45360
- const pnpmPath = join29(repoRoot, "pnpm-workspace.yaml");
45622
+ const pnpmPath = join30(repoRoot, "pnpm-workspace.yaml");
45361
45623
  try {
45362
45624
  const raw = await _generatorDeps.readTextFile(pnpmPath);
45363
45625
  const lines = raw.split(`
@@ -45382,8 +45644,8 @@ async function discoverWorkspacePackages2(repoRoot) {
45382
45644
  }
45383
45645
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
45384
45646
  const resolvedRepoRoot = repoRoot ?? packageDir;
45385
- const relativePkgPath = relative13(resolvedRepoRoot, packageDir);
45386
- const contextPath = join29(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
45647
+ const relativePkgPath = relative14(resolvedRepoRoot, packageDir);
45648
+ const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
45387
45649
  if (!_generatorDeps.existsSync(contextPath)) {
45388
45650
  return [
45389
45651
  {
@@ -45451,7 +45713,7 @@ var init_generator2 = __esm(() => {
45451
45713
  });
45452
45714
 
45453
45715
  // src/analyze/scanner.ts
45454
- import { join as join30 } from "path";
45716
+ import { join as join31 } from "path";
45455
45717
  function resolveFrameworkAndRunner(language, pkg) {
45456
45718
  if (language === "go")
45457
45719
  return { framework: "", testRunner: "go-test" };
@@ -45473,7 +45735,7 @@ async function scanSourceRoots(workdir) {
45473
45735
  });
45474
45736
  try {
45475
45737
  const language = await deps.detectLanguage(workdir);
45476
- const pkg = await deps.readPackageJson(join30(workdir, "package.json"));
45738
+ const pkg = await deps.readPackageJson(join31(workdir, "package.json"));
45477
45739
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
45478
45740
  return [{ path: ".", language, framework, testRunner }];
45479
45741
  } catch {
@@ -45491,9 +45753,9 @@ async function scanSourceRoots(workdir) {
45491
45753
  packages = packages.slice(0, MAX_SOURCE_ROOTS);
45492
45754
  }
45493
45755
  return Promise.all(packages.map(async (pkgPath) => {
45494
- const pkgDir = pkgPath === "." ? workdir : join30(workdir, pkgPath);
45756
+ const pkgDir = pkgPath === "." ? workdir : join31(workdir, pkgPath);
45495
45757
  const language = await deps.detectLanguage(pkgDir);
45496
- const pkg = await deps.readPackageJson(join30(pkgDir, "package.json"));
45758
+ const pkg = await deps.readPackageJson(join31(pkgDir, "package.json"));
45497
45759
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
45498
45760
  return { path: pkgPath, language, framework, testRunner };
45499
45761
  }));
@@ -45526,7 +45788,7 @@ var init_analyze = __esm(() => {
45526
45788
  });
45527
45789
 
45528
45790
  // src/debate/pre-phase/grounder.ts
45529
- import { join as join31 } from "path";
45791
+ import { join as join32 } from "path";
45530
45792
  async function buildCodebaseContext(workdir) {
45531
45793
  const roots = await _grounderDeps.scanSourceRoots(workdir);
45532
45794
  return buildSourceRootsSection(normalizeRoots(workdir, roots));
@@ -45538,7 +45800,7 @@ function normalizeRoots(workdir, roots) {
45538
45800
  }));
45539
45801
  }
45540
45802
  async function writeManifestArtifact(ctx, manifest) {
45541
- const manifestPath = join31(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
45803
+ const manifestPath = join32(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
45542
45804
  await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
45543
45805
  }
45544
45806
  var _grounderDeps, grounderStrategy = async (ctx) => {
@@ -45899,7 +46161,7 @@ function formatSpecDeltas(blockers, manifest) {
45899
46161
 
45900
46162
  // src/debate/verifiers/checks.ts
45901
46163
  import { existsSync as defaultExistsSync } from "fs";
45902
- import { join as join32 } from "path";
46164
+ import { join as join33 } from "path";
45903
46165
  function checkFilesExist(prd, workdir, deps) {
45904
46166
  const existsSync10 = deps?.existsSync ?? defaultExistsSync;
45905
46167
  const findings = [];
@@ -45909,7 +46171,7 @@ function checkFilesExist(prd, workdir, deps) {
45909
46171
  for (const entry of story.contextFiles) {
45910
46172
  const filePath = typeof entry === "string" ? entry : entry.path;
45911
46173
  const factId = typeof entry === "string" ? undefined : entry.factId;
45912
- const absPath = join32(workdir, filePath);
46174
+ const absPath = join33(workdir, filePath);
45913
46175
  if (existsSync10(absPath))
45914
46176
  continue;
45915
46177
  if (factId) {
@@ -46020,7 +46282,7 @@ var init_checks3 = () => {};
46020
46282
 
46021
46283
  // src/debate/verifiers/plan-checklist.ts
46022
46284
  import { existsSync as existsSync10 } from "fs";
46023
- import { join as join33 } from "path";
46285
+ import { join as join34 } from "path";
46024
46286
  function parsePrd(output) {
46025
46287
  if (!output)
46026
46288
  return null;
@@ -46031,7 +46293,7 @@ function parsePrd(output) {
46031
46293
  }
46032
46294
  }
46033
46295
  async function loadManifest(ctx) {
46034
- const manifestPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46296
+ const manifestPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46035
46297
  const raw = await _planChecklistDeps.readFile(manifestPath);
46036
46298
  if (!raw)
46037
46299
  return null;
@@ -46044,7 +46306,7 @@ async function loadManifest(ctx) {
46044
46306
  }
46045
46307
  }
46046
46308
  async function emitSpecDeltas(ctx, blockers, manifest) {
46047
- const artifactPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
46309
+ const artifactPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
46048
46310
  const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
46049
46311
  await _planChecklistDeps.write(artifactPath, content);
46050
46312
  return artifactPath;
@@ -46395,7 +46657,7 @@ var init_runner_plan_helpers = __esm(() => {
46395
46657
  });
46396
46658
 
46397
46659
  // src/debate/runner-plan.ts
46398
- import { join as join34 } from "path";
46660
+ import { join as join35 } from "path";
46399
46661
  async function runPlan(ctx, taskContext, outputFormat, opts) {
46400
46662
  const logger = _debateSessionDeps.getSafeLogger();
46401
46663
  const config2 = ctx.stageConfig;
@@ -46454,7 +46716,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
46454
46716
  sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
46455
46717
  proposers: ctx.stageConfig.proposers
46456
46718
  });
46457
- const outputPaths = resolved.map((_, i) => join34(opts.outputDir, `prd-debate-${i}.json`));
46719
+ const outputPaths = resolved.map((_, i) => join35(opts.outputDir, `prd-debate-${i}.json`));
46458
46720
  const successful = [];
46459
46721
  let rebuttalList;
46460
46722
  if (selectorKind === "verifier-pick") {
@@ -48702,9 +48964,9 @@ function validateFeatureName(feature) {
48702
48964
 
48703
48965
  // src/plan/critic.ts
48704
48966
  import { mkdir as mkdir6 } from "fs/promises";
48705
- import { dirname as dirname7, join as join37 } from "path";
48967
+ import { dirname as dirname7, join as join38 } from "path";
48706
48968
  async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
48707
- const path7 = join37(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
48969
+ const path7 = join38(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
48708
48970
  await mkdir6(dirname7(path7), { recursive: true });
48709
48971
  await Bun.write(path7, formatSpecDeltas(findings, manifest));
48710
48972
  return path7;
@@ -49917,9 +50179,9 @@ __export(exports_plan_decompose, {
49917
50179
  runReplanLoop: () => runReplanLoop,
49918
50180
  planDecomposeCommand: () => planDecomposeCommand
49919
50181
  });
49920
- import { join as join38 } from "path";
50182
+ import { join as join39 } from "path";
49921
50183
  async function planDecomposeCommand(workdir, config2, options) {
49922
- const prdPath = join38(workdir, ".nax", "features", options.feature, "prd.json");
50184
+ const prdPath = join39(workdir, ".nax", "features", options.feature, "prd.json");
49923
50185
  if (!_planDeps.existsSync(prdPath)) {
49924
50186
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
49925
50187
  stage: "decompose",
@@ -50093,7 +50355,7 @@ var init_plan_decompose = __esm(() => {
50093
50355
 
50094
50356
  // src/cli/plan-runtime.ts
50095
50357
  import { existsSync as existsSync15 } from "fs";
50096
- import { join as join39 } from "path";
50358
+ import { join as join40 } from "path";
50097
50359
  function isRuntimeWithAgentManager(value) {
50098
50360
  return typeof value === "object" && value !== null && "agentManager" in value;
50099
50361
  }
@@ -50145,7 +50407,7 @@ var init_plan_runtime = __esm(() => {
50145
50407
  writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
50146
50408
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
50147
50409
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
50148
- readPackageJson: (workdir) => Bun.file(join39(workdir, "package.json")).json().catch(() => null),
50410
+ readPackageJson: (workdir) => Bun.file(join40(workdir, "package.json")).json().catch(() => null),
50149
50411
  spawnSync: (cmd, opts) => {
50150
50412
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
50151
50413
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -50530,7 +50792,7 @@ var init_metrics = __esm(() => {
50530
50792
 
50531
50793
  // src/commands/common.ts
50532
50794
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
50533
- import { join as join40, resolve as resolve13 } from "path";
50795
+ import { join as join41, resolve as resolve13 } from "path";
50534
50796
  function resolveProject(options = {}) {
50535
50797
  const { dir, feature } = options;
50536
50798
  let projectRoot;
@@ -50538,12 +50800,12 @@ function resolveProject(options = {}) {
50538
50800
  let configPath;
50539
50801
  if (dir) {
50540
50802
  projectRoot = realpathSync3(resolve13(dir));
50541
- naxDir = join40(projectRoot, ".nax");
50803
+ naxDir = join41(projectRoot, ".nax");
50542
50804
  if (!existsSync16(naxDir)) {
50543
50805
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
50544
50806
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
50545
50807
  }
50546
- configPath = join40(naxDir, "config.json");
50808
+ configPath = join41(naxDir, "config.json");
50547
50809
  if (!existsSync16(configPath)) {
50548
50810
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
50549
50811
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -50551,17 +50813,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
50551
50813
  } else {
50552
50814
  const found = findProjectRoot(process.cwd());
50553
50815
  if (!found) {
50554
- const cwdNaxDir = join40(process.cwd(), ".nax");
50816
+ const cwdNaxDir = join41(process.cwd(), ".nax");
50555
50817
  if (existsSync16(cwdNaxDir)) {
50556
- const cwdConfigPath = join40(cwdNaxDir, "config.json");
50818
+ const cwdConfigPath = join41(cwdNaxDir, "config.json");
50557
50819
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
50558
50820
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
50559
50821
  }
50560
50822
  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() });
50561
50823
  }
50562
50824
  projectRoot = found;
50563
- naxDir = join40(projectRoot, ".nax");
50564
- configPath = join40(naxDir, "config.json");
50825
+ naxDir = join41(projectRoot, ".nax");
50826
+ configPath = join41(naxDir, "config.json");
50565
50827
  }
50566
50828
  let featureDir;
50567
50829
  if (feature) {
@@ -50570,8 +50832,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
50570
50832
  } catch (error48) {
50571
50833
  throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
50572
50834
  }
50573
- const featuresDir = join40(naxDir, "features");
50574
- featureDir = join40(featuresDir, feature);
50835
+ const featuresDir = join41(naxDir, "features");
50836
+ featureDir = join41(featuresDir, feature);
50575
50837
  if (!existsSync16(featureDir)) {
50576
50838
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
50577
50839
  const availableMsg = availableFeatures.length > 0 ? `
@@ -50604,7 +50866,7 @@ async function resolveProjectAsync(options = {}) {
50604
50866
  }
50605
50867
  const isPlainName = !dir.includes("/") && !dir.includes("\\");
50606
50868
  if (isPlainName) {
50607
- const registryIdentityPath = join40(globalConfigDir(), dir, ".identity");
50869
+ const registryIdentityPath = join41(globalConfigDir(), dir, ".identity");
50608
50870
  const identityFile = Bun.file(registryIdentityPath);
50609
50871
  if (await identityFile.exists()) {
50610
50872
  try {
@@ -50636,12 +50898,12 @@ function findProjectRoot(startDir) {
50636
50898
  let current = resolve13(startDir);
50637
50899
  let depth = 0;
50638
50900
  while (depth < MAX_DIRECTORY_DEPTH) {
50639
- const naxDir = join40(current, ".nax");
50640
- const configPath = join40(naxDir, "config.json");
50901
+ const naxDir = join41(current, ".nax");
50902
+ const configPath = join41(naxDir, "config.json");
50641
50903
  if (existsSync16(configPath)) {
50642
50904
  return realpathSync3(current);
50643
50905
  }
50644
- const parent = join40(current, "..");
50906
+ const parent = join41(current, "..");
50645
50907
  if (parent === current) {
50646
50908
  break;
50647
50909
  }
@@ -51800,10 +52062,10 @@ var init_effectiveness = __esm(() => {
51800
52062
 
51801
52063
  // src/execution/progress.ts
51802
52064
  import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
51803
- import { join as join43 } from "path";
52065
+ import { join as join44 } from "path";
51804
52066
  async function appendProgress(featureDir, storyId, status, message) {
51805
52067
  await mkdir7(featureDir, { recursive: true });
51806
- const progressPath = join43(featureDir, "progress.txt");
52068
+ const progressPath = join44(featureDir, "progress.txt");
51807
52069
  const timestamp = new Date().toISOString();
51808
52070
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
51809
52071
  `;
@@ -51992,7 +52254,7 @@ var init_completion = __esm(() => {
51992
52254
 
51993
52255
  // src/constitution/loader.ts
51994
52256
  import { existsSync as existsSync19 } from "fs";
51995
- import { join as join44 } from "path";
52257
+ import { join as join45 } from "path";
51996
52258
  function truncateToTokens(text, maxTokens) {
51997
52259
  const maxChars = maxTokens * 3;
51998
52260
  if (text.length <= maxChars) {
@@ -52014,7 +52276,7 @@ async function loadConstitution(projectDir, config2) {
52014
52276
  }
52015
52277
  let combinedContent = "";
52016
52278
  if (!config2.skipGlobal) {
52017
- const globalPath = join44(globalConfigDir(), config2.path);
52279
+ const globalPath = join45(globalConfigDir(), config2.path);
52018
52280
  if (existsSync19(globalPath)) {
52019
52281
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
52020
52282
  const globalFile = Bun.file(validatedPath);
@@ -52024,7 +52286,7 @@ async function loadConstitution(projectDir, config2) {
52024
52286
  }
52025
52287
  }
52026
52288
  }
52027
- const projectPath = join44(projectDir, config2.path);
52289
+ const projectPath = join45(projectDir, config2.path);
52028
52290
  if (existsSync19(projectPath)) {
52029
52291
  const validatedPath = validateFilePath(projectPath, projectDir);
52030
52292
  const projectFile = Bun.file(validatedPath);
@@ -52538,26 +52800,34 @@ function phaseExplicitlyPassed(output) {
52538
52800
  const r = output;
52539
52801
  return r.success === true || r.passed === true;
52540
52802
  }
52541
- function phasePassed(opName, output) {
52803
+ function phasePassed(opName, output, storyId) {
52804
+ const strictVerdictPhase = STRICT_VERDICT_PHASE_NAMES.has(opName);
52542
52805
  if (output === null || output === undefined) {
52543
- getSafeLogger()?.warn("story-orchestrator", "Phase produced no output \u2014 treating as pass", {
52544
- storyId: undefined,
52806
+ getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase produced no output \u2014 treating as fail" : "Phase produced no output \u2014 treating as pass", {
52807
+ storyId,
52545
52808
  phase: opName
52546
52809
  });
52547
- return true;
52810
+ return !strictVerdictPhase;
52811
+ }
52812
+ if (typeof output !== "object") {
52813
+ if (!strictVerdictPhase)
52814
+ return true;
52815
+ getSafeLogger()?.warn("story-orchestrator", "Strict phase produced non-object output \u2014 treating as fail", {
52816
+ storyId,
52817
+ phase: opName
52818
+ });
52819
+ return false;
52548
52820
  }
52549
- if (typeof output !== "object")
52550
- return true;
52551
52821
  const r = output;
52552
52822
  if ("success" in r)
52553
52823
  return r.success !== false;
52554
52824
  if ("passed" in r)
52555
52825
  return r.passed !== false;
52556
- getSafeLogger()?.warn("story-orchestrator", "Phase output has neither 'success' nor 'passed' \u2014 treating as pass", {
52557
- storyId: undefined,
52826
+ getSafeLogger()?.warn("story-orchestrator", strictVerdictPhase ? "Strict phase output has neither 'success' nor 'passed' \u2014 treating as fail" : "Phase output has neither 'success' nor 'passed' \u2014 treating as pass", {
52827
+ storyId,
52558
52828
  phase: opName
52559
52829
  });
52560
- return true;
52830
+ return !strictVerdictPhase;
52561
52831
  }
52562
52832
  function isFinding(value) {
52563
52833
  return typeof value === "object" && value !== null && typeof value.source === "string" && value.source.length > 0;
@@ -52680,6 +52950,29 @@ function logUnifiedReviewPhaseStart(storyId, opName) {
52680
52950
  logger?.info("review", "Running adversarial check", { storyId });
52681
52951
  }
52682
52952
  }
52953
+ function logDeterministicPhaseOutcome(storyId, opName, output, durationMs, isTddPhase) {
52954
+ if (isTddPhase)
52955
+ return;
52956
+ if (opName === "semantic-review" || opName === "adversarial-review")
52957
+ return;
52958
+ if (output === null || output === undefined || typeof output !== "object")
52959
+ return;
52960
+ const logger = getSafeLogger();
52961
+ const r = output;
52962
+ const success2 = r.success === true || r.passed === true;
52963
+ const findingsCount = Array.isArray(r.findings) ? r.findings.length : undefined;
52964
+ const status = typeof r.status === "string" ? r.status : undefined;
52965
+ const data = { storyId, phase: opName, durationMs };
52966
+ if (findingsCount !== undefined)
52967
+ data.findingsCount = findingsCount;
52968
+ if (status !== undefined)
52969
+ data.status = status;
52970
+ if (success2) {
52971
+ logger?.info("story-orchestrator", `Phase passed: ${opName}`, data);
52972
+ } else {
52973
+ logger?.warn("story-orchestrator", `Phase failed: ${opName}`, data);
52974
+ }
52975
+ }
52683
52976
  function logUnifiedReviewPhaseResult(storyId, opName, output) {
52684
52977
  const logger = getSafeLogger();
52685
52978
  const payload = toReviewDecisionPayload(opName, output);
@@ -52749,6 +53042,7 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
52749
53042
  phaseOutputs[opName] = output;
52750
53043
  emitReviewDecision(ctx, opName, output);
52751
53044
  logUnifiedReviewPhaseResult(ctx.storyId, opName, output);
53045
+ logDeterministicPhaseOutcome(ctx.storyId, opName, output, Date.now() - phaseStartedAt, isTddPhase);
52752
53046
  if (isTddPhase) {
52753
53047
  const durationMs = Date.now() - phaseStartedAt;
52754
53048
  logger?.info("tdd", `Session complete: ${opName}`, {
@@ -52847,13 +53141,27 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52847
53141
  continue;
52848
53142
  findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
52849
53143
  }
52850
- return findings;
53144
+ return rectification2.postValidate ? await rectification2.postValidate(findings, _validateCtx) : findings;
52851
53145
  }
52852
53146
  };
52853
53147
  const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
52854
53148
  phaseOutputs.rectification = { iterationCount: cycleResult.iterations.length };
53149
+ const rectLogger = getSafeLogger();
53150
+ const rectSummary = {
53151
+ storyId: ctx.storyId,
53152
+ initialFindingsCount: initialFindings.length,
53153
+ iterationCount: cycleResult.iterations.length,
53154
+ finalFindingsCount: cycleResult.finalFindings.length,
53155
+ exitReason: cycleResult.exitReason,
53156
+ costUsd: cycleResult.costUsd
53157
+ };
53158
+ if (cycleResult.exitReason === "resolved") {
53159
+ rectLogger?.info("story-orchestrator", "Rectification resolved all findings", rectSummary);
53160
+ } else {
53161
+ rectLogger?.warn("story-orchestrator", `Rectification exited: ${cycleResult.exitReason}`, rectSummary);
53162
+ }
52855
53163
  if (cycleResult.exitReason === "validator-error") {
52856
- getSafeLogger()?.warn("story-orchestrator", "rectification cycle aborted \u2014 validator infrastructure error", {
53164
+ rectLogger?.warn("story-orchestrator", "rectification cycle aborted \u2014 validator infrastructure error", {
52857
53165
  storyId: ctx.storyId
52858
53166
  });
52859
53167
  }
@@ -52861,7 +53169,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52861
53169
  "max-attempts-total",
52862
53170
  "max-attempts-per-strategy",
52863
53171
  "bail-when",
52864
- "no-strategy"
53172
+ "no-strategy",
53173
+ "agent-gave-up"
52865
53174
  ]);
52866
53175
  if (exhaustedReasons.has(cycleResult.exitReason) && cycleResult.finalFindings.length > 0) {
52867
53176
  return { rectificationExhausted: true, unfixedFindings: cycleResult.finalFindings };
@@ -52908,8 +53217,12 @@ class ExecutionPlan {
52908
53217
  });
52909
53218
  throw error48;
52910
53219
  }
52911
- if (!phasePassed(phase.slot.op.name, phaseOutputs[phase.slot.op.name])) {
53220
+ if (!phasePassed(phase.slot.op.name, phaseOutputs[phase.slot.op.name], this.ctx.storyId)) {
52912
53221
  if (!shortCircuitExempt.has(phase.slot.op.name)) {
53222
+ logger?.warn("story-orchestrator", "Short-circuiting on phase failure", {
53223
+ storyId: this.ctx.storyId,
53224
+ phase: phase.slot.op.name
53225
+ });
52913
53226
  break;
52914
53227
  }
52915
53228
  }
@@ -52918,20 +53231,43 @@ class ExecutionPlan {
52918
53231
  const verifierName = this.state.verifier?.slot.op.name;
52919
53232
  const gateName = this.state.fullSuiteGate?.slot.op.name;
52920
53233
  const verifierPassedSsot = verifierName !== undefined && phaseExplicitlyPassed(phaseOutputs[verifierName]);
52921
- if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName])) {
53234
+ if (verifierPassedSsot && gateName !== undefined && !phasePassed(gateName, phaseOutputs[gateName], this.ctx.storyId)) {
52922
53235
  logger?.warn("story-orchestrator", "Full-suite gate failed but verifier judged story OK \u2014 treating gate failures as unrelated regressions", { storyId: this.ctx.storyId, packageDir: this.ctx.packageDir });
52923
53236
  }
52924
53237
  const success2 = Object.entries(phaseOutputs).every(([name, output]) => {
52925
53238
  if (verifierPassedSsot && name === gateName)
52926
53239
  return true;
52927
- return phasePassed(name, output);
53240
+ return phasePassed(name, output, this.ctx.storyId);
52928
53241
  });
52929
53242
  const totalCostUsd = Object.values(phaseCosts).reduce((sum, cost) => sum + cost, 0);
53243
+ const durationMs = Date.now() - startedAt;
53244
+ const failedPhases = Object.entries(phaseOutputs).filter(([name, output]) => {
53245
+ if (verifierPassedSsot && name === gateName)
53246
+ return false;
53247
+ return !phasePassed(name, output, this.ctx.storyId);
53248
+ }).map(([name]) => name);
53249
+ const summary = {
53250
+ storyId: this.ctx.storyId,
53251
+ success: success2,
53252
+ totalCostUsd,
53253
+ durationMs,
53254
+ phaseCount: Object.keys(phaseOutputs).length,
53255
+ failedPhases: failedPhases.length > 0 ? failedPhases : undefined
53256
+ };
53257
+ if (rectResult.rectificationExhausted)
53258
+ summary.rectificationExhausted = true;
53259
+ if (rectResult.unfixedFindings)
53260
+ summary.unfixedFindingsCount = rectResult.unfixedFindings.length;
53261
+ if (success2) {
53262
+ logger?.info("story-orchestrator", "Story orchestration complete", summary);
53263
+ } else {
53264
+ logger?.warn("story-orchestrator", "Story orchestration failed", summary);
53265
+ }
52930
53266
  return {
52931
53267
  success: success2,
52932
53268
  phaseCosts,
52933
53269
  totalCostUsd,
52934
- durationMs: Date.now() - startedAt,
53270
+ durationMs,
52935
53271
  phaseOutputs,
52936
53272
  ...rectResult
52937
53273
  };
@@ -52991,7 +53327,7 @@ class StoryOrchestratorBuilder {
52991
53327
  return new ExecutionPlan(ctx, { ...this.state }, opts.isThreeSession ?? false);
52992
53328
  }
52993
53329
  }
52994
- var _storyOrchestratorDeps, TDD_OP_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES;
53330
+ var _storyOrchestratorDeps, TDD_OP_NAMES, STRICT_VERDICT_PHASE_NAMES, CANONICAL_ORDER, PHASE_KIND_TO_STATE_KEY, STRATEGY_TO_REVALIDATION_PHASES;
52995
53331
  var init_story_orchestrator = __esm(() => {
52996
53332
  init_errors();
52997
53333
  init_findings();
@@ -53005,6 +53341,13 @@ var init_story_orchestrator = __esm(() => {
53005
53341
  captureGitRef
53006
53342
  };
53007
53343
  TDD_OP_NAMES = new Set(["test-writer", "implementer", "verifier"]);
53344
+ STRICT_VERDICT_PHASE_NAMES = new Set([
53345
+ fullSuiteGateOp.name,
53346
+ verifyScopedOp.name,
53347
+ lintCheckOp.name,
53348
+ typecheckCheckOp.name,
53349
+ verifierOp.name
53350
+ ]);
53008
53351
  CANONICAL_ORDER = [
53009
53352
  "test-writer",
53010
53353
  "greenfield-gate",
@@ -53046,6 +53389,7 @@ var init_story_orchestrator = __esm(() => {
53046
53389
  });
53047
53390
 
53048
53391
  // src/execution/build-plan-for-strategy.ts
53392
+ import { join as join46 } from "path";
53049
53393
  function isThreeSessionStrategy(strategy) {
53050
53394
  return THREE_SESSION_STRATEGIES2.has(strategy);
53051
53395
  }
@@ -53057,7 +53401,7 @@ function isFreshRun(story) {
53057
53401
  const hasReviewEscalation = (story.priorFailures ?? []).some((f) => f.stage === "review");
53058
53402
  return !hasAttempts && !hasReviewEscalation;
53059
53403
  }
53060
- function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53404
+ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53061
53405
  const isThreeSession = isThreeSessionStrategy(testStrategy);
53062
53406
  const freshRun = isFreshRun(story);
53063
53407
  const builder = new StoryOrchestratorBuilder;
@@ -53092,6 +53436,9 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53092
53436
  builder.addAdversarialReview(inputs.adversarialReview);
53093
53437
  }
53094
53438
  if (shouldRunRectification(config2) && inputs.rectification) {
53439
+ const sink = makeDeclarationSink();
53440
+ const packageDir = join46(ctx.packageDir, story.workdir ?? "");
53441
+ const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
53095
53442
  const strategies = [];
53096
53443
  if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
53097
53444
  strategies.push(makeMechanicalLintFixStrategy());
@@ -53103,12 +53450,28 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53103
53450
  strategies.push(makeFullSuiteRectifyStrategy(story, config2));
53104
53451
  }
53105
53452
  if (config2.quality.autofix?.enabled !== false) {
53106
- strategies.push(makeAutofixImplementerStrategy(story, config2));
53107
- strategies.push(makeAutofixTestWriterStrategy(story, config2));
53108
- }
53453
+ strategies.push(makeAutofixImplementerStrategy(story, config2, sink));
53454
+ strategies.push(makeAutofixTestWriterStrategy(story, config2, sink));
53455
+ }
53456
+ const postValidate = async (findings, _validateCtx) => {
53457
+ if (sink.testEdits.length === 0 && sink.mockHandoffs.length === 0)
53458
+ return findings;
53459
+ const pendingMock = sink.mockHandoffs.map((h) => ({
53460
+ reason: "mock_structure",
53461
+ file: h.files[0] ?? "",
53462
+ files: h.files,
53463
+ reasonDetail: h.reasonDetail
53464
+ }));
53465
+ const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir);
53466
+ sink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
53467
+ const allDeclarations = [...sink.testEdits, ...valid];
53468
+ sink.testEdits = [];
53469
+ return applyTestEditDeclarations(findings, allDeclarations, story, invalid);
53470
+ };
53109
53471
  const rectOpts = {
53110
53472
  ...inputs.rectification,
53111
- strategies: [...strategies, ...inputs.rectification.strategies]
53473
+ strategies: [...strategies, ...inputs.rectification.strategies],
53474
+ postValidate
53112
53475
  };
53113
53476
  builder.addRectification(rectOpts);
53114
53477
  }
@@ -53119,6 +53482,7 @@ var init_build_plan_for_strategy = __esm(() => {
53119
53482
  init_operations();
53120
53483
  init_execution_gates();
53121
53484
  init_full_suite_rectify();
53485
+ init_test_runners();
53122
53486
  init_story_orchestrator();
53123
53487
  THREE_SESSION_STRATEGIES2 = new Set(["three-session-tdd", "three-session-tdd-lite"]);
53124
53488
  });
@@ -53225,24 +53589,63 @@ async function assemblePlanInputsFromCtx(ctx) {
53225
53589
  const verifyScopedInput = !_isTdd ? { workdir: ctx.workdir, storyId: story.id } : undefined;
53226
53590
  const lintCheckInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("lint") && ctx.config.quality.commands.lint ? { workdir: ctx.workdir, storyId: story.id } : undefined;
53227
53591
  const typecheckCheckInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("typecheck") && ctx.config.quality.commands.typecheck ? { workdir: ctx.workdir, storyId: story.id } : undefined;
53228
- const semanticReviewInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("semantic") && ctx.config.review.semantic ? {
53229
- workdir: ctx.workdir,
53230
- story,
53231
- semanticConfig: ctx.config.review.semantic,
53232
- mode: ctx.config.review.semantic.diffMode,
53233
- storyGitRef: ctx.storyGitRef,
53234
- featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
53235
- blockingThreshold: ctx.config.review.blockingThreshold
53236
- } : undefined;
53237
- const adversarialReviewInput = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("adversarial") && ctx.config.review.adversarial ? {
53238
- workdir: ctx.workdir,
53239
- story,
53240
- adversarialConfig: ctx.config.review.adversarial,
53241
- mode: ctx.config.review.adversarial.diffMode,
53242
- storyGitRef: ctx.storyGitRef,
53243
- featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
53244
- blockingThreshold: ctx.config.review.blockingThreshold
53245
- } : undefined;
53592
+ const semanticEnabled = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("semantic") && !!ctx.config.review.semantic;
53593
+ const semanticReviewInput = semanticEnabled ? await (async () => {
53594
+ const prepared = await prepareSemanticReviewInput({
53595
+ workdir: ctx.workdir,
53596
+ projectDir: ctx.projectDir,
53597
+ storyId: story.id,
53598
+ storyGitRef: ctx.storyGitRef,
53599
+ config: ctx.config,
53600
+ naxIgnoreIndex: ctx.naxIgnoreIndex,
53601
+ semanticConfig: ctx.config.review.semantic
53602
+ });
53603
+ if (prepared.skipReason)
53604
+ return;
53605
+ return {
53606
+ workdir: ctx.workdir,
53607
+ story,
53608
+ semanticConfig: ctx.config.review.semantic,
53609
+ mode: ctx.config.review.semantic.diffMode,
53610
+ storyGitRef: prepared.effectiveRef,
53611
+ stat: prepared.stat,
53612
+ diff: prepared.diff,
53613
+ excludePatterns: prepared.excludePatterns,
53614
+ featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-semantic"),
53615
+ priorSemanticIterations: ctx.priorSemanticIterations,
53616
+ blockingThreshold: ctx.config.review.blockingThreshold
53617
+ };
53618
+ })() : undefined;
53619
+ const adversarialEnabled = ctx.config.review?.enabled === true && ctx.config.review.checks?.includes("adversarial") && !!ctx.config.review.adversarial;
53620
+ const adversarialReviewInput = adversarialEnabled ? await (async () => {
53621
+ const prepared = await prepareAdversarialReviewInput({
53622
+ workdir: ctx.workdir,
53623
+ projectDir: ctx.projectDir,
53624
+ storyId: story.id,
53625
+ storyGitRef: ctx.storyGitRef,
53626
+ config: ctx.config,
53627
+ naxIgnoreIndex: ctx.naxIgnoreIndex,
53628
+ adversarialConfig: ctx.config.review.adversarial
53629
+ });
53630
+ if (prepared.skipReason)
53631
+ return;
53632
+ return {
53633
+ workdir: ctx.workdir,
53634
+ story,
53635
+ adversarialConfig: ctx.config.review.adversarial,
53636
+ mode: ctx.config.review.adversarial.diffMode,
53637
+ storyGitRef: prepared.effectiveRef,
53638
+ stat: prepared.stat,
53639
+ diff: prepared.diff,
53640
+ testInventory: prepared.testInventory,
53641
+ excludePatterns: prepared.excludePatterns,
53642
+ testGlobs: prepared.testGlobs,
53643
+ refExcludePatterns: prepared.refExcludePatterns,
53644
+ featureCtxBlock: buildFeatureCtxBlock(ctx, "reviewer-adversarial"),
53645
+ priorAdversarialIterations: ctx.priorAdversarialIterations,
53646
+ blockingThreshold: ctx.config.review.blockingThreshold
53647
+ };
53648
+ })() : undefined;
53246
53649
  const rectificationInput = ctx.config.execution?.rectification?.enabled === true ? {
53247
53650
  maxAttempts: ctx.config.execution.rectification.maxAttemptsTotal,
53248
53651
  strategies: [],
@@ -53268,23 +53671,28 @@ var init_plan_inputs = __esm(() => {
53268
53671
  init_context();
53269
53672
  init_errors();
53270
53673
  init_prompts();
53674
+ init_review();
53271
53675
  init_resolver();
53272
53676
  init_build_plan_for_strategy();
53273
53677
  });
53274
53678
 
53275
53679
  // src/pipeline/stages/execution-helpers.ts
53276
- function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
53680
+ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason, failureDetail) {
53681
+ const buildReason = (category) => {
53682
+ const trimmedDetail = failureDetail?.trim();
53683
+ return trimmedDetail ? `TDD ${category}: ${trimmedDetail}` : `TDD ${category}`;
53684
+ };
53277
53685
  if (failureCategory === "isolation-violation") {
53278
53686
  if (!isLiteMode) {
53279
53687
  ctx.retryAsLite = true;
53280
53688
  }
53281
- return { action: "escalate" };
53689
+ return { action: "escalate", reason: buildReason("isolation-violation") };
53282
53690
  }
53283
53691
  if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "full-suite-gate-exhausted" || failureCategory === "verifier-rejected") {
53284
- return { action: "escalate" };
53692
+ return { action: "escalate", reason: buildReason(failureCategory) };
53285
53693
  }
53286
53694
  if (failureCategory === "greenfield-no-tests") {
53287
- return { action: "escalate" };
53695
+ return { action: "escalate", reason: buildReason("greenfield-no-tests") };
53288
53696
  }
53289
53697
  return {
53290
53698
  action: "pause",
@@ -53738,7 +54146,16 @@ Category: ${failureCategory ?? "unknown"}`,
53738
54146
  logger.warn("execution", "Rate limited \u2014 will retry", { storyId: ctx.story.id });
53739
54147
  }
53740
54148
  await cleanupSessionOnFailure(ctx);
53741
- return { action: "escalate" };
54149
+ const failedPhaseNames = Object.keys(failedPhases);
54150
+ const reasonParts = [];
54151
+ reasonParts.push(`agent session failed (exit ${agentResult.exitCode ?? "?"})`);
54152
+ if (failureCategory)
54153
+ reasonParts.push(`category=${failureCategory}`);
54154
+ if (agentResult.rateLimited)
54155
+ reasonParts.push("rate-limited");
54156
+ if (failedPhaseNames.length > 0)
54157
+ reasonParts.push(`phases=${failedPhaseNames.join(",")}`);
54158
+ return { action: "escalate", reason: reasonParts.join("; ") };
53742
54159
  }
53743
54160
  if (!isTdd) {
53744
54161
  await _postRunDeps.autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
@@ -53837,7 +54254,7 @@ var init_execution = __esm(() => {
53837
54254
  } : null;
53838
54255
  const initialRef = tddMode ? await _executionDeps.captureGitRef(ctx.workdir) ?? "HEAD" : null;
53839
54256
  const inputs = await _executionDeps.assemblePlanInputsFromCtx(ctx);
53840
- const plan = buildPlanForStrategy(callCtx, ctx.story, ctx.config, ctx.routing.testStrategy, inputs);
54257
+ const plan = await buildPlanForStrategy(callCtx, ctx.story, ctx.config, ctx.routing.testStrategy, inputs);
53841
54258
  let planResult;
53842
54259
  try {
53843
54260
  planResult = await plan.run();
@@ -54784,7 +55201,7 @@ function buildFrontmatter(story, ctx, role) {
54784
55201
  }
54785
55202
 
54786
55203
  // src/cli/prompts-tdd.ts
54787
- import { join as join45 } from "path";
55204
+ import { join as join47 } from "path";
54788
55205
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
54789
55206
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
54790
55207
  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(),
@@ -54803,7 +55220,7 @@ ${frontmatter}---
54803
55220
 
54804
55221
  ${session.prompt}`;
54805
55222
  if (outputDir) {
54806
- const promptFile = join45(outputDir, `${story.id}.${session.role}.md`);
55223
+ const promptFile = join47(outputDir, `${story.id}.${session.role}.md`);
54807
55224
  await Bun.write(promptFile, fullOutput);
54808
55225
  logger.info("cli", "Written TDD prompt file", {
54809
55226
  storyId: story.id,
@@ -54819,7 +55236,7 @@ ${"=".repeat(80)}`);
54819
55236
  }
54820
55237
  }
54821
55238
  if (outputDir && ctx.contextMarkdown) {
54822
- const contextFile = join45(outputDir, `${story.id}.context.md`);
55239
+ const contextFile = join47(outputDir, `${story.id}.context.md`);
54823
55240
  const frontmatter = buildFrontmatter(story, ctx);
54824
55241
  const contextOutput = `---
54825
55242
  ${frontmatter}---
@@ -54834,16 +55251,16 @@ var init_prompts_tdd = __esm(() => {
54834
55251
 
54835
55252
  // src/cli/prompts-main.ts
54836
55253
  import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
54837
- import { join as join46 } from "path";
55254
+ import { join as join48 } from "path";
54838
55255
  async function promptsCommand(options) {
54839
55256
  const logger = getLogger();
54840
55257
  const { feature, workdir, config: config2, storyId, outputDir } = options;
54841
- const naxDir = join46(workdir, ".nax");
55258
+ const naxDir = join48(workdir, ".nax");
54842
55259
  if (!existsSync20(naxDir)) {
54843
55260
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
54844
55261
  }
54845
- const featureDir = join46(naxDir, "features", feature);
54846
- const prdPath = join46(featureDir, "prd.json");
55262
+ const featureDir = join48(naxDir, "features", feature);
55263
+ const prdPath = join48(featureDir, "prd.json");
54847
55264
  if (!existsSync20(prdPath)) {
54848
55265
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
54849
55266
  }
@@ -54910,10 +55327,10 @@ ${frontmatter}---
54910
55327
 
54911
55328
  ${ctx.prompt}`;
54912
55329
  if (outputDir) {
54913
- const promptFile = join46(outputDir, `${story.id}.prompt.md`);
55330
+ const promptFile = join48(outputDir, `${story.id}.prompt.md`);
54914
55331
  await Bun.write(promptFile, fullOutput);
54915
55332
  if (ctx.contextMarkdown) {
54916
- const contextFile = join46(outputDir, `${story.id}.context.md`);
55333
+ const contextFile = join48(outputDir, `${story.id}.context.md`);
54917
55334
  const contextOutput = `---
54918
55335
  ${frontmatter}---
54919
55336
 
@@ -54949,12 +55366,12 @@ var init_prompts_main = __esm(() => {
54949
55366
 
54950
55367
  // src/cli/prompts-init.ts
54951
55368
  import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
54952
- import { join as join47 } from "path";
55369
+ import { join as join49 } from "path";
54953
55370
  async function promptsInitCommand(options) {
54954
55371
  const { workdir, force = false, autoWireConfig = true } = options;
54955
- const templatesDir = join47(workdir, ".nax", "templates");
55372
+ const templatesDir = join49(workdir, ".nax", "templates");
54956
55373
  mkdirSync4(templatesDir, { recursive: true });
54957
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join47(templatesDir, f)));
55374
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join49(templatesDir, f)));
54958
55375
  if (existingFiles.length > 0 && !force) {
54959
55376
  _promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
54960
55377
  Pass --force to overwrite existing templates.`);
@@ -54962,7 +55379,7 @@ async function promptsInitCommand(options) {
54962
55379
  }
54963
55380
  const written = [];
54964
55381
  for (const template of TEMPLATE_ROLES) {
54965
- const filePath = join47(templatesDir, template.file);
55382
+ const filePath = join49(templatesDir, template.file);
54966
55383
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
54967
55384
  const content = TEMPLATE_HEADER + roleBody;
54968
55385
  await Bun.write(filePath, content);
@@ -54978,7 +55395,7 @@ async function promptsInitCommand(options) {
54978
55395
  return written;
54979
55396
  }
54980
55397
  async function autoWirePromptsConfig(workdir) {
54981
- const configPath = join47(workdir, "nax.config.json");
55398
+ const configPath = join49(workdir, "nax.config.json");
54982
55399
  if (!existsSync21(configPath)) {
54983
55400
  const exampleConfig = JSON.stringify({
54984
55401
  prompts: {
@@ -55148,7 +55565,7 @@ __export(exports_init_context, {
55148
55565
  });
55149
55566
  import { existsSync as existsSync22 } from "fs";
55150
55567
  import { mkdir as mkdir8 } from "fs/promises";
55151
- import { basename as basename9, join as join48 } from "path";
55568
+ import { basename as basename9, join as join50 } from "path";
55152
55569
  async function findFiles(dir, maxFiles = 200) {
55153
55570
  try {
55154
55571
  const proc = Bun.spawnSync([
@@ -55176,7 +55593,7 @@ async function findFiles(dir, maxFiles = 200) {
55176
55593
  return [];
55177
55594
  }
55178
55595
  async function readPackageManifest(projectRoot) {
55179
- const packageJsonPath = join48(projectRoot, "package.json");
55596
+ const packageJsonPath = join50(projectRoot, "package.json");
55180
55597
  if (!existsSync22(packageJsonPath)) {
55181
55598
  return null;
55182
55599
  }
@@ -55194,7 +55611,7 @@ async function readPackageManifest(projectRoot) {
55194
55611
  }
55195
55612
  }
55196
55613
  async function readReadmeSnippet(projectRoot) {
55197
- const readmePath = join48(projectRoot, "README.md");
55614
+ const readmePath = join50(projectRoot, "README.md");
55198
55615
  if (!existsSync22(readmePath)) {
55199
55616
  return null;
55200
55617
  }
@@ -55212,7 +55629,7 @@ async function detectEntryPoints(projectRoot) {
55212
55629
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
55213
55630
  const found = [];
55214
55631
  for (const candidate of candidates) {
55215
- const path14 = join48(projectRoot, candidate);
55632
+ const path14 = join50(projectRoot, candidate);
55216
55633
  if (existsSync22(path14)) {
55217
55634
  found.push(candidate);
55218
55635
  }
@@ -55223,7 +55640,7 @@ async function detectConfigFiles(projectRoot) {
55223
55640
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
55224
55641
  const found = [];
55225
55642
  for (const candidate of candidates) {
55226
- const path14 = join48(projectRoot, candidate);
55643
+ const path14 = join50(projectRoot, candidate);
55227
55644
  if (existsSync22(path14)) {
55228
55645
  found.push(candidate);
55229
55646
  }
@@ -55384,8 +55801,8 @@ function generatePackageContextTemplate(packagePath) {
55384
55801
  }
55385
55802
  async function initPackage(repoRoot, packagePath, force = false) {
55386
55803
  const logger = getLogger();
55387
- const naxDir = join48(repoRoot, ".nax", "mono", packagePath);
55388
- const contextPath = join48(naxDir, "context.md");
55804
+ const naxDir = join50(repoRoot, ".nax", "mono", packagePath);
55805
+ const contextPath = join50(naxDir, "context.md");
55389
55806
  if (existsSync22(contextPath) && !force) {
55390
55807
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
55391
55808
  return;
@@ -55399,8 +55816,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
55399
55816
  }
55400
55817
  async function initContext(projectRoot, options = {}) {
55401
55818
  const logger = getLogger();
55402
- const naxDir = join48(projectRoot, ".nax");
55403
- const contextPath = join48(naxDir, "context.md");
55819
+ const naxDir = join50(projectRoot, ".nax");
55820
+ const contextPath = join50(naxDir, "context.md");
55404
55821
  if (existsSync22(contextPath) && !options.force) {
55405
55822
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
55406
55823
  return;
@@ -55430,9 +55847,9 @@ var init_init_context = __esm(() => {
55430
55847
 
55431
55848
  // src/cli/init-detect.ts
55432
55849
  import { existsSync as existsSync23, readFileSync } from "fs";
55433
- import { join as join49 } from "path";
55850
+ import { join as join51 } from "path";
55434
55851
  function readPackageJson(projectRoot) {
55435
- const pkgPath = join49(projectRoot, "package.json");
55852
+ const pkgPath = join51(projectRoot, "package.json");
55436
55853
  if (!existsSync23(pkgPath))
55437
55854
  return;
55438
55855
  try {
@@ -55475,41 +55892,41 @@ function detectStack(projectRoot) {
55475
55892
  };
55476
55893
  }
55477
55894
  function detectRuntime(projectRoot) {
55478
- if (existsSync23(join49(projectRoot, "bun.lockb")) || existsSync23(join49(projectRoot, "bunfig.toml"))) {
55895
+ if (existsSync23(join51(projectRoot, "bun.lockb")) || existsSync23(join51(projectRoot, "bunfig.toml"))) {
55479
55896
  return "bun";
55480
55897
  }
55481
- if (existsSync23(join49(projectRoot, "package-lock.json")) || existsSync23(join49(projectRoot, "yarn.lock")) || existsSync23(join49(projectRoot, "pnpm-lock.yaml"))) {
55898
+ if (existsSync23(join51(projectRoot, "package-lock.json")) || existsSync23(join51(projectRoot, "yarn.lock")) || existsSync23(join51(projectRoot, "pnpm-lock.yaml"))) {
55482
55899
  return "node";
55483
55900
  }
55484
55901
  return "unknown";
55485
55902
  }
55486
55903
  function detectLanguage2(projectRoot) {
55487
- if (existsSync23(join49(projectRoot, "tsconfig.json")))
55904
+ if (existsSync23(join51(projectRoot, "tsconfig.json")))
55488
55905
  return "typescript";
55489
- if (existsSync23(join49(projectRoot, "pyproject.toml")) || existsSync23(join49(projectRoot, "setup.py"))) {
55906
+ if (existsSync23(join51(projectRoot, "pyproject.toml")) || existsSync23(join51(projectRoot, "setup.py"))) {
55490
55907
  return "python";
55491
55908
  }
55492
- if (existsSync23(join49(projectRoot, "Cargo.toml")))
55909
+ if (existsSync23(join51(projectRoot, "Cargo.toml")))
55493
55910
  return "rust";
55494
- if (existsSync23(join49(projectRoot, "go.mod")))
55911
+ if (existsSync23(join51(projectRoot, "go.mod")))
55495
55912
  return "go";
55496
55913
  return "unknown";
55497
55914
  }
55498
55915
  function detectLinter(projectRoot) {
55499
- if (existsSync23(join49(projectRoot, "biome.json")) || existsSync23(join49(projectRoot, "biome.jsonc"))) {
55916
+ if (existsSync23(join51(projectRoot, "biome.json")) || existsSync23(join51(projectRoot, "biome.jsonc"))) {
55500
55917
  return "biome";
55501
55918
  }
55502
- if (existsSync23(join49(projectRoot, ".eslintrc.json")) || existsSync23(join49(projectRoot, ".eslintrc.js")) || existsSync23(join49(projectRoot, "eslint.config.js"))) {
55919
+ if (existsSync23(join51(projectRoot, ".eslintrc.json")) || existsSync23(join51(projectRoot, ".eslintrc.js")) || existsSync23(join51(projectRoot, "eslint.config.js"))) {
55503
55920
  return "eslint";
55504
55921
  }
55505
55922
  return "unknown";
55506
55923
  }
55507
55924
  function detectMonorepo(projectRoot) {
55508
- if (existsSync23(join49(projectRoot, "turbo.json")))
55925
+ if (existsSync23(join51(projectRoot, "turbo.json")))
55509
55926
  return "turborepo";
55510
- if (existsSync23(join49(projectRoot, "nx.json")))
55927
+ if (existsSync23(join51(projectRoot, "nx.json")))
55511
55928
  return "nx";
55512
- if (existsSync23(join49(projectRoot, "pnpm-workspace.yaml")))
55929
+ if (existsSync23(join51(projectRoot, "pnpm-workspace.yaml")))
55513
55930
  return "pnpm-workspaces";
55514
55931
  const pkg = readPackageJson(projectRoot);
55515
55932
  if (pkg?.workspaces)
@@ -55653,7 +56070,7 @@ __export(exports_init, {
55653
56070
  });
55654
56071
  import { existsSync as existsSync24 } from "fs";
55655
56072
  import { mkdir as mkdir9 } from "fs/promises";
55656
- import { join as join50 } from "path";
56073
+ import { join as join52 } from "path";
55657
56074
  function validateProjectName(name) {
55658
56075
  if (!name)
55659
56076
  return { valid: false, error: "name must be non-empty" };
@@ -55689,7 +56106,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
55689
56106
  }
55690
56107
  async function updateGitignore(projectRoot) {
55691
56108
  const logger = getLogger();
55692
- const gitignorePath = join50(projectRoot, ".gitignore");
56109
+ const gitignorePath = join52(projectRoot, ".gitignore");
55693
56110
  let existing = "";
55694
56111
  if (existsSync24(gitignorePath)) {
55695
56112
  existing = await Bun.file(gitignorePath).text();
@@ -55775,7 +56192,7 @@ async function initGlobal() {
55775
56192
  await mkdir9(globalDir, { recursive: true });
55776
56193
  logger.info("init", "Created global config directory", { path: globalDir });
55777
56194
  }
55778
- const configPath = join50(globalDir, "config.json");
56195
+ const configPath = join52(globalDir, "config.json");
55779
56196
  if (!existsSync24(configPath)) {
55780
56197
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
55781
56198
  `);
@@ -55783,14 +56200,14 @@ async function initGlobal() {
55783
56200
  } else {
55784
56201
  logger.info("init", "Global config already exists", { path: configPath });
55785
56202
  }
55786
- const constitutionPath = join50(globalDir, "constitution.md");
56203
+ const constitutionPath = join52(globalDir, "constitution.md");
55787
56204
  if (!existsSync24(constitutionPath)) {
55788
56205
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
55789
56206
  logger.info("init", "Created global constitution", { path: constitutionPath });
55790
56207
  } else {
55791
56208
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
55792
56209
  }
55793
- const hooksDir = join50(globalDir, "hooks");
56210
+ const hooksDir = join52(globalDir, "hooks");
55794
56211
  if (!existsSync24(hooksDir)) {
55795
56212
  await mkdir9(hooksDir, { recursive: true });
55796
56213
  logger.info("init", "Created global hooks directory", { path: hooksDir });
@@ -55823,7 +56240,7 @@ async function initProject(projectRoot, options) {
55823
56240
  if (detectedName && !options?.force) {
55824
56241
  const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
55825
56242
  if (collision.collision && collision.existing) {
55826
- const configPath2 = join50(projectDir, "config.json");
56243
+ const configPath2 = join52(projectDir, "config.json");
55827
56244
  throw new NaxError([
55828
56245
  `Project name collision: "${detectedName}"`,
55829
56246
  ` This project: ${projectRoot}`,
@@ -55851,7 +56268,7 @@ async function initProject(projectRoot, options) {
55851
56268
  linter: stack.linter,
55852
56269
  monorepo: stack.monorepo
55853
56270
  });
55854
- const configPath = join50(projectDir, "config.json");
56271
+ const configPath = join52(projectDir, "config.json");
55855
56272
  if (!existsSync24(configPath)) {
55856
56273
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
55857
56274
  `);
@@ -55860,14 +56277,14 @@ async function initProject(projectRoot, options) {
55860
56277
  logger.info("init", "Project config already exists", { path: configPath });
55861
56278
  }
55862
56279
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
55863
- const constitutionPath = join50(projectDir, "constitution.md");
56280
+ const constitutionPath = join52(projectDir, "constitution.md");
55864
56281
  if (!existsSync24(constitutionPath) || options?.force) {
55865
56282
  await Bun.write(constitutionPath, buildConstitution(stack));
55866
56283
  logger.info("init", "Created project constitution", { path: constitutionPath });
55867
56284
  } else {
55868
56285
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
55869
56286
  }
55870
- const hooksDir = join50(projectDir, "hooks");
56287
+ const hooksDir = join52(projectDir, "hooks");
55871
56288
  if (!existsSync24(hooksDir)) {
55872
56289
  await mkdir9(hooksDir, { recursive: true });
55873
56290
  logger.info("init", "Created project hooks directory", { path: hooksDir });
@@ -57302,12 +57719,12 @@ var init_loader4 = __esm(() => {
57302
57719
  });
57303
57720
 
57304
57721
  // src/utils/paths.ts
57305
- import { join as join62 } from "path";
57722
+ import { join as join64 } from "path";
57306
57723
  function getRunsDir() {
57307
- return process.env.NAX_RUNS_DIR ?? join62(globalConfigDir(), "runs");
57724
+ return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
57308
57725
  }
57309
57726
  function getEventsRootDir() {
57310
- return join62(globalConfigDir(), "events");
57727
+ return join64(globalConfigDir(), "events");
57311
57728
  }
57312
57729
  var init_paths3 = __esm(() => {
57313
57730
  init_paths();
@@ -57367,7 +57784,7 @@ var init_command_argv = __esm(() => {
57367
57784
  });
57368
57785
 
57369
57786
  // src/hooks/runner.ts
57370
- import { join as join69 } from "path";
57787
+ import { join as join71 } from "path";
57371
57788
  function createDrainDeadline2(deadlineMs) {
57372
57789
  let timeoutId;
57373
57790
  const promise2 = new Promise((resolve16) => {
@@ -57386,14 +57803,14 @@ async function loadHooksConfig(projectDir, globalDir) {
57386
57803
  let globalHooks = { hooks: {} };
57387
57804
  let projectHooks = { hooks: {} };
57388
57805
  let skipGlobal = false;
57389
- const projectPath = join69(projectDir, "hooks.json");
57806
+ const projectPath = join71(projectDir, "hooks.json");
57390
57807
  const projectData = await loadJsonFile(projectPath, "hooks");
57391
57808
  if (projectData) {
57392
57809
  projectHooks = projectData;
57393
57810
  skipGlobal = projectData.skipGlobal ?? false;
57394
57811
  }
57395
57812
  if (!skipGlobal && globalDir) {
57396
- const globalPath = join69(globalDir, "hooks.json");
57813
+ const globalPath = join71(globalDir, "hooks.json");
57397
57814
  const globalData = await loadJsonFile(globalPath, "hooks");
57398
57815
  if (globalData) {
57399
57816
  globalHooks = globalData;
@@ -57563,7 +57980,7 @@ var package_default;
57563
57980
  var init_package = __esm(() => {
57564
57981
  package_default = {
57565
57982
  name: "@nathapp/nax",
57566
- version: "0.67.11",
57983
+ version: "0.67.13",
57567
57984
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
57568
57985
  type: "module",
57569
57986
  bin: {
@@ -57658,8 +58075,8 @@ var init_version = __esm(() => {
57658
58075
  NAX_VERSION = package_default.version;
57659
58076
  NAX_COMMIT = (() => {
57660
58077
  try {
57661
- if (/^[0-9a-f]{6,10}$/.test("0db5c72e"))
57662
- return "0db5c72e";
58078
+ if (/^[0-9a-f]{6,10}$/.test("45ff95d3"))
58079
+ return "45ff95d3";
57663
58080
  } catch {}
57664
58081
  try {
57665
58082
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -58528,15 +58945,15 @@ var init_acceptance_loop = __esm(() => {
58528
58945
 
58529
58946
  // src/session/scratch-purge.ts
58530
58947
  import { mkdir as mkdir13, rename, rm } from "fs/promises";
58531
- import { dirname as dirname12, join as join70 } from "path";
58948
+ import { dirname as dirname12, join as join72 } from "path";
58532
58949
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
58533
- const sessionsDir = join70(projectDir, ".nax", "features", featureName, "sessions");
58950
+ const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
58534
58951
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
58535
58952
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
58536
58953
  let purged = 0;
58537
58954
  for (const sessionId of sessionIds) {
58538
- const sessionDir = join70(sessionsDir, sessionId);
58539
- const descriptorPath = join70(sessionDir, "descriptor.json");
58955
+ const sessionDir = join72(sessionsDir, sessionId);
58956
+ const descriptorPath = join72(sessionDir, "descriptor.json");
58540
58957
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
58541
58958
  continue;
58542
58959
  let lastActivityAt;
@@ -58552,7 +58969,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
58552
58969
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
58553
58970
  continue;
58554
58971
  if (archiveInsteadOfDelete) {
58555
- const archiveDest = join70(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
58972
+ const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
58556
58973
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
58557
58974
  } else {
58558
58975
  await _scratchPurgeDeps.remove(sessionDir);
@@ -59261,12 +59678,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
59261
59678
 
59262
59679
  // src/pipeline/subscribers/events-writer.ts
59263
59680
  import { appendFile as appendFile5, mkdir as mkdir14 } from "fs/promises";
59264
- import { basename as basename14, join as join71 } from "path";
59681
+ import { basename as basename14, join as join73 } from "path";
59265
59682
  function wireEventsWriter(bus, feature, runId, workdir) {
59266
59683
  const logger = getSafeLogger();
59267
59684
  const project = basename14(workdir);
59268
- const eventsDir = join71(getEventsRootDir(), project);
59269
- const eventsFile = join71(eventsDir, "events.jsonl");
59685
+ const eventsDir = join73(getEventsRootDir(), project);
59686
+ const eventsFile = join73(eventsDir, "events.jsonl");
59270
59687
  let dirReady = false;
59271
59688
  const write = (line) => {
59272
59689
  return (async () => {
@@ -59447,12 +59864,12 @@ var init_interaction2 = __esm(() => {
59447
59864
 
59448
59865
  // src/pipeline/subscribers/registry.ts
59449
59866
  import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
59450
- import { basename as basename15, join as join72 } from "path";
59867
+ import { basename as basename15, join as join74 } from "path";
59451
59868
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
59452
59869
  const logger = getSafeLogger();
59453
59870
  const project = basename15(workdir);
59454
- const runDir = join72(getRunsDir(), `${project}-${feature}-${runId}`);
59455
- const metaFile = join72(runDir, "meta.json");
59871
+ const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
59872
+ const metaFile = join74(runDir, "meta.json");
59456
59873
  const unsub = bus.on("run:started", (_ev) => {
59457
59874
  return (async () => {
59458
59875
  try {
@@ -59462,8 +59879,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
59462
59879
  project,
59463
59880
  feature,
59464
59881
  workdir,
59465
- statusPath: join72(outputDir, "features", feature, "status.json"),
59466
- eventsDir: join72(outputDir, "features", feature, "runs"),
59882
+ statusPath: join74(outputDir, "features", feature, "status.json"),
59883
+ eventsDir: join74(outputDir, "features", feature, "runs"),
59467
59884
  registeredAt: new Date().toISOString()
59468
59885
  };
59469
59886
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -59709,7 +60126,7 @@ var init_types9 = __esm(() => {
59709
60126
 
59710
60127
  // src/worktree/dependencies.ts
59711
60128
  import { existsSync as existsSync32 } from "fs";
59712
- import { join as join73 } from "path";
60129
+ import { join as join75 } from "path";
59713
60130
  async function prepareWorktreeDependencies(options) {
59714
60131
  const mode = options.config.execution.worktreeDependencies.mode;
59715
60132
  const resolvedCwd = resolveDependencyCwd(options);
@@ -59723,7 +60140,7 @@ async function prepareWorktreeDependencies(options) {
59723
60140
  }
59724
60141
  }
59725
60142
  function resolveDependencyCwd(options) {
59726
- return options.storyWorkdir ? join73(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
60143
+ return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
59727
60144
  }
59728
60145
  function resolveInheritedDependencies(options, resolvedCwd) {
59729
60146
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -59733,7 +60150,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
59733
60150
  }
59734
60151
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
59735
60152
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
59736
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join73(directory, filename))));
60153
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
59737
60154
  }
59738
60155
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
59739
60156
  const setupCommand = config2.execution.worktreeDependencies.setupCommand;
@@ -59797,13 +60214,13 @@ __export(exports_manager, {
59797
60214
  });
59798
60215
  import { existsSync as existsSync33, symlinkSync } from "fs";
59799
60216
  import { mkdir as mkdir16 } from "fs/promises";
59800
- import { join as join74 } from "path";
60217
+ import { join as join76 } from "path";
59801
60218
 
59802
60219
  class WorktreeManager {
59803
60220
  async ensureGitExcludes(projectRoot) {
59804
60221
  const logger = getSafeLogger();
59805
- const infoDir = join74(projectRoot, ".git", "info");
59806
- const excludePath = join74(infoDir, "exclude");
60222
+ const infoDir = join76(projectRoot, ".git", "info");
60223
+ const excludePath = join76(infoDir, "exclude");
59807
60224
  try {
59808
60225
  await mkdir16(infoDir, { recursive: true });
59809
60226
  let existing = "";
@@ -59830,7 +60247,7 @@ ${missing.join(`
59830
60247
  }
59831
60248
  async create(projectRoot, storyId) {
59832
60249
  validateStoryId(storyId);
59833
- const worktreePath = join74(projectRoot, ".nax-wt", storyId);
60250
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
59834
60251
  const branchName = `nax/${storyId}`;
59835
60252
  try {
59836
60253
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -59871,9 +60288,9 @@ ${missing.join(`
59871
60288
  }
59872
60289
  throw new Error(`Failed to create worktree: ${String(error48)}`);
59873
60290
  }
59874
- const envSource = join74(projectRoot, ".env");
60291
+ const envSource = join76(projectRoot, ".env");
59875
60292
  if (existsSync33(envSource)) {
59876
- const envTarget = join74(worktreePath, ".env");
60293
+ const envTarget = join76(worktreePath, ".env");
59877
60294
  try {
59878
60295
  symlinkSync(envSource, envTarget, "file");
59879
60296
  } catch (error48) {
@@ -59884,7 +60301,7 @@ ${missing.join(`
59884
60301
  }
59885
60302
  async remove(projectRoot, storyId) {
59886
60303
  validateStoryId(storyId);
59887
- const worktreePath = join74(projectRoot, ".nax-wt", storyId);
60304
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
59888
60305
  const branchName = `nax/${storyId}`;
59889
60306
  try {
59890
60307
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -60426,13 +60843,16 @@ var init_tier_outcome = __esm(() => {
60426
60843
  });
60427
60844
 
60428
60845
  // src/execution/escalation/tier-escalation.ts
60429
- function buildEscalationFailure(story, currentTier, reviewFindings, cost) {
60846
+ function buildEscalationFailure(story, currentTier, reviewFindings, cost, pipelineReason, failureCategory) {
60430
60847
  const stage = reviewFindings && reviewFindings.length > 0 ? "review" : "escalation";
60848
+ const trimmedReason = pipelineReason?.trim();
60849
+ const categoryPart = failureCategory ? ` [${failureCategory}]` : "";
60850
+ const summary = trimmedReason ? `Tier ${currentTier}${categoryPart}: ${trimmedReason}` : `Tier ${currentTier}${categoryPart} failed \u2014 no pipeline reason recorded`;
60431
60851
  return {
60432
60852
  attempt: (story.attempts ?? 0) + 1,
60433
60853
  modelTier: currentTier,
60434
60854
  stage,
60435
- summary: `Failed with tier ${currentTier}, escalating to next tier`,
60855
+ summary,
60436
60856
  reviewFindings: reviewFindings && reviewFindings.length > 0 ? reviewFindings : undefined,
60437
60857
  cost: cost ?? 0,
60438
60858
  timestamp: new Date().toISOString()
@@ -60536,7 +60956,7 @@ async function handleTierEscalation(ctx) {
60536
60956
  const isChangingTier = currentStoryTier !== escalatedTier;
60537
60957
  const shouldResetAttempts = isChangingTier || shouldSwitchToTestAfter;
60538
60958
  const escalationRecord = isChangingTier || shouldSwitchToTestAfter ? buildEscalationRecord(currentStoryTier, shouldSwitchToTestAfter ? currentStoryTier : escalatedTier, ctx.pipelineResult.reason ?? "Escalated to next retry path") : undefined;
60539
- const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost);
60959
+ const escalationFailure = buildEscalationFailure(s, currentStoryTier, escalateReviewFindings, ctx.attemptCost, verifiedPipelineReason, escalateFailureCategory);
60540
60960
  return {
60541
60961
  ...s,
60542
60962
  attempts: shouldResetAttempts ? 0 : (s.attempts ?? 0) + 1,
@@ -60684,10 +61104,10 @@ var init_merge_conflict_rectify = __esm(() => {
60684
61104
  });
60685
61105
 
60686
61106
  // src/execution/pipeline-result-handler.ts
60687
- import { join as join75 } from "path";
61107
+ import { join as join77 } from "path";
60688
61108
  async function removeWorktreeDirectory(projectRoot, storyId) {
60689
61109
  const logger = getSafeLogger();
60690
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
61110
+ const worktreePath = join77(projectRoot, ".nax-wt", storyId);
60691
61111
  try {
60692
61112
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
60693
61113
  cwd: projectRoot,
@@ -60898,7 +61318,7 @@ var init_pipeline_result_handler = __esm(() => {
60898
61318
 
60899
61319
  // src/execution/iteration-runner.ts
60900
61320
  import { existsSync as existsSync34 } from "fs";
60901
- import { join as join76 } from "path";
61321
+ import { join as join78 } from "path";
60902
61322
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
60903
61323
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
60904
61324
  if (ctx.dryRun) {
@@ -60923,7 +61343,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60923
61343
  const storyStartTime = Date.now();
60924
61344
  let effectiveWorkdir = ctx.workdir;
60925
61345
  if (ctx.config.execution.storyIsolation === "worktree") {
60926
- const worktreePath = join76(ctx.workdir, ".nax-wt", story.id);
61346
+ const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
60927
61347
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
60928
61348
  if (!worktreeExists) {
60929
61349
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -60943,7 +61363,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60943
61363
  }
60944
61364
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
60945
61365
  const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
60946
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join76(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
61366
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
60947
61367
  let dependencyContext;
60948
61368
  if (ctx.config.execution.storyIsolation === "worktree") {
60949
61369
  try {
@@ -60970,7 +61390,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60970
61390
  };
60971
61391
  }
60972
61392
  }
60973
- const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join76(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join76(ctx.workdir, story.workdir) : ctx.workdir;
61393
+ 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;
60974
61394
  const pipelineContext = {
60975
61395
  config: effectiveConfig,
60976
61396
  rootConfig: ctx.config,
@@ -61171,7 +61591,7 @@ __export(exports_parallel_worker, {
61171
61591
  executeParallelBatch: () => executeParallelBatch,
61172
61592
  _parallelWorkerDeps: () => _parallelWorkerDeps
61173
61593
  });
61174
- import { join as join77 } from "path";
61594
+ import { join as join79 } from "path";
61175
61595
  async function executeStoryInWorktree(story, worktreePath, dependencyContext, context, routing, eventEmitter) {
61176
61596
  const logger = getSafeLogger();
61177
61597
  try {
@@ -61191,7 +61611,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
61191
61611
  story,
61192
61612
  stories: [story],
61193
61613
  projectDir: context.projectDir,
61194
- workdir: dependencyContext.cwd ?? (story.workdir ? join77(worktreePath, story.workdir) : worktreePath),
61614
+ workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
61195
61615
  worktreeDependencyContext: dependencyContext,
61196
61616
  routing,
61197
61617
  storyGitRef: storyGitRef ?? undefined
@@ -62020,7 +62440,7 @@ async function writeStatusFile(filePath, status) {
62020
62440
  var init_status_file = () => {};
62021
62441
 
62022
62442
  // src/execution/status-writer.ts
62023
- import { join as join78 } from "path";
62443
+ import { join as join80 } from "path";
62024
62444
 
62025
62445
  class StatusWriter {
62026
62446
  statusFile;
@@ -62139,7 +62559,7 @@ class StatusWriter {
62139
62559
  if (!this._prd)
62140
62560
  return;
62141
62561
  const safeLogger = getSafeLogger();
62142
- const featureStatusPath = join78(featureDir, "status.json");
62562
+ const featureStatusPath = join80(featureDir, "status.json");
62143
62563
  const write = async () => {
62144
62564
  try {
62145
62565
  const base = this.getSnapshot(totalCost, iterations);
@@ -62573,7 +62993,7 @@ __export(exports_run_initialization, {
62573
62993
  initializeRun: () => initializeRun,
62574
62994
  _reconcileDeps: () => _reconcileDeps
62575
62995
  });
62576
- import { join as join79 } from "path";
62996
+ import { join as join81 } from "path";
62577
62997
  async function reconcileState(prd, prdPath, workdir, config2) {
62578
62998
  const logger = getSafeLogger();
62579
62999
  let reconciledCount = 0;
@@ -62590,7 +63010,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
62590
63010
  });
62591
63011
  continue;
62592
63012
  }
62593
- const effectiveWorkdir = story.workdir ? join79(workdir, story.workdir) : workdir;
63013
+ const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
62594
63014
  try {
62595
63015
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
62596
63016
  if (!reviewResult.success) {
@@ -94022,7 +94442,7 @@ __export(exports_curator, {
94022
94442
  });
94023
94443
  import { readdirSync as readdirSync9 } from "fs";
94024
94444
  import { unlink as unlink4 } from "fs/promises";
94025
- import { basename as basename16, join as join81 } from "path";
94445
+ import { basename as basename16, join as join83 } from "path";
94026
94446
  function getProjectKey(config2, projectDir) {
94027
94447
  return config2.name?.trim() || basename16(projectDir);
94028
94448
  }
@@ -94105,7 +94525,7 @@ async function curatorStatus(options) {
94105
94525
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
94106
94526
  const projectKey = getProjectKey(config2, resolved.projectDir);
94107
94527
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94108
- const runsDir = join81(outputDir, "runs");
94528
+ const runsDir = join83(outputDir, "runs");
94109
94529
  const runIds = listRunIds(runsDir);
94110
94530
  let runId;
94111
94531
  if (options.run) {
@@ -94122,8 +94542,8 @@ async function curatorStatus(options) {
94122
94542
  runId = runIds[runIds.length - 1];
94123
94543
  }
94124
94544
  console.log(`Run: ${runId}`);
94125
- const runDir = join81(runsDir, runId);
94126
- const observationsPath = join81(runDir, "observations.jsonl");
94545
+ const runDir = join83(runsDir, runId);
94546
+ const observationsPath = join83(runDir, "observations.jsonl");
94127
94547
  const observations = await parseObservations(observationsPath);
94128
94548
  const counts = new Map;
94129
94549
  for (const obs of observations) {
@@ -94133,7 +94553,7 @@ async function curatorStatus(options) {
94133
94553
  for (const [kind, count] of counts.entries()) {
94134
94554
  console.log(` ${kind}: ${count}`);
94135
94555
  }
94136
- const proposalsPath = join81(runDir, "curator-proposals.md");
94556
+ const proposalsPath = join83(runDir, "curator-proposals.md");
94137
94557
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
94138
94558
  if (proposalText !== null) {
94139
94559
  console.log("");
@@ -94147,8 +94567,8 @@ async function curatorCommit(options) {
94147
94567
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
94148
94568
  const projectKey = getProjectKey(config2, resolved.projectDir);
94149
94569
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94150
- const runDir = join81(outputDir, "runs", options.runId);
94151
- const proposalsPath = join81(runDir, "curator-proposals.md");
94570
+ const runDir = join83(outputDir, "runs", options.runId);
94571
+ const proposalsPath = join83(runDir, "curator-proposals.md");
94152
94572
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
94153
94573
  if (proposalText === null) {
94154
94574
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -94164,7 +94584,7 @@ async function curatorCommit(options) {
94164
94584
  const dropFileState = new Map;
94165
94585
  const skippedDrops = new Set;
94166
94586
  for (const drop2 of drops) {
94167
- const targetPath = join81(resolved.projectDir, drop2.canonicalFile);
94587
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94168
94588
  if (!dropFileState.has(targetPath)) {
94169
94589
  const fileExists2 = await Bun.file(targetPath).exists();
94170
94590
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -94198,7 +94618,7 @@ async function curatorCommit(options) {
94198
94618
  if (skippedDrops.has(drop2)) {
94199
94619
  continue;
94200
94620
  }
94201
- const targetPath = join81(resolved.projectDir, drop2.canonicalFile);
94621
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94202
94622
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
94203
94623
  const filtered = filterDropContent(existing, drop2.description);
94204
94624
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -94207,7 +94627,7 @@ async function curatorCommit(options) {
94207
94627
  }
94208
94628
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
94209
94629
  for (const add2 of adds) {
94210
- const targetPath = join81(resolved.projectDir, add2.canonicalFile);
94630
+ const targetPath = join83(resolved.projectDir, add2.canonicalFile);
94211
94631
  const content = buildAddContent(add2);
94212
94632
  await _curatorCmdDeps.appendFile(targetPath, content);
94213
94633
  modifiedFiles.add(targetPath);
@@ -94244,7 +94664,7 @@ async function curatorDryrun(options) {
94244
94664
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
94245
94665
  const projectKey = getProjectKey(config2, resolved.projectDir);
94246
94666
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94247
- const runsDir = join81(outputDir, "runs");
94667
+ const runsDir = join83(outputDir, "runs");
94248
94668
  const runIds = listRunIds(runsDir);
94249
94669
  if (runIds.length === 0) {
94250
94670
  console.log("No runs found.");
@@ -94255,7 +94675,7 @@ async function curatorDryrun(options) {
94255
94675
  console.log(`Run ${options.run} not found in ${runsDir}.`);
94256
94676
  return;
94257
94677
  }
94258
- const observationsPath = join81(runsDir, runId, "observations.jsonl");
94678
+ const observationsPath = join83(runsDir, runId, "observations.jsonl");
94259
94679
  const observations = await parseObservations(observationsPath);
94260
94680
  const thresholds = getThresholds(config2);
94261
94681
  const proposals = runHeuristics(observations, thresholds);
@@ -94296,12 +94716,12 @@ async function curatorGc(options) {
94296
94716
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
94297
94717
  const projectKey = getProjectKey(config2, resolved.projectDir);
94298
94718
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94299
- const perRunsDir = join81(outputDir, "runs");
94719
+ const perRunsDir = join83(outputDir, "runs");
94300
94720
  for (const runId of uniqueRunIds) {
94301
94721
  if (!keepSet.has(runId)) {
94302
- const runDir = join81(perRunsDir, runId);
94303
- await _curatorCmdDeps.removeFile(join81(runDir, "observations.jsonl"));
94304
- await _curatorCmdDeps.removeFile(join81(runDir, "curator-proposals.md"));
94722
+ const runDir = join83(perRunsDir, runId);
94723
+ await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
94724
+ await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
94305
94725
  }
94306
94726
  }
94307
94727
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -94346,7 +94766,7 @@ var init_curator2 = __esm(() => {
94346
94766
  init_source();
94347
94767
  import { existsSync as existsSync37, mkdirSync as mkdirSync7 } from "fs";
94348
94768
  import { homedir as homedir3 } from "os";
94349
- import { basename as basename17, join as join82 } from "path";
94769
+ import { basename as basename17, join as join84 } from "path";
94350
94770
 
94351
94771
  // node_modules/commander/esm.mjs
94352
94772
  var import__ = __toESM(require_commander(), 1);
@@ -94370,12 +94790,12 @@ init_errors();
94370
94790
  init_operations();
94371
94791
 
94372
94792
  // src/plan/strategies/context-builder.ts
94373
- import { join as join36 } from "path";
94793
+ import { join as join37 } from "path";
94374
94794
  init_config();
94375
94795
  init_errors();
94376
94796
  init_interaction();
94377
94797
  async function buildPlanModeContext(workdir, fullConfig, options, deps) {
94378
- const naxDir = join36(workdir, ".nax");
94798
+ const naxDir = join37(workdir, ".nax");
94379
94799
  if (!deps.existsSync(naxDir)) {
94380
94800
  throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
94381
94801
  stage: "plan",
@@ -94383,8 +94803,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
94383
94803
  });
94384
94804
  }
94385
94805
  validateFeatureName(options.feature);
94386
- const outputDir = join36(naxDir, "features", options.feature);
94387
- const outputPath = join36(outputDir, "prd.json");
94806
+ const outputDir = join37(naxDir, "features", options.feature);
94807
+ const outputPath = join37(outputDir, "prd.json");
94388
94808
  const [specContent, sourceRoots, pkg] = await Promise.all([
94389
94809
  deps.readFile(options.from),
94390
94810
  deps.scanSourceRoots(workdir),
@@ -94399,7 +94819,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
94399
94819
  ...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
94400
94820
  ];
94401
94821
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
94402
- const packageJson = await deps.readPackageJsonAt(join36(workdir, relativePath, "package.json"));
94822
+ const packageJson = await deps.readPackageJsonAt(join37(workdir, relativePath, "package.json"));
94403
94823
  return buildPackageSummary(relativePath, packageJson);
94404
94824
  }));
94405
94825
  const projectName = detectProjectName(workdir, pkg);
@@ -95000,7 +95420,7 @@ init_interaction();
95000
95420
  init_prd();
95001
95421
  init_runtime();
95002
95422
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
95003
- import { basename as basename7, join as join41, resolve as resolve14 } from "path";
95423
+ import { basename as basename7, join as join42, resolve as resolve14 } from "path";
95004
95424
  var _statusFeaturesDeps = {
95005
95425
  projectOutputDir,
95006
95426
  loadConfig
@@ -95014,7 +95434,7 @@ function isPidAlive(pid) {
95014
95434
  }
95015
95435
  }
95016
95436
  async function loadStatusFile(featureDir) {
95017
- const statusPath = join41(featureDir, "status.json");
95437
+ const statusPath = join42(featureDir, "status.json");
95018
95438
  if (!existsSync17(statusPath)) {
95019
95439
  return null;
95020
95440
  }
@@ -95029,7 +95449,7 @@ async function loadProjectStatusFile(projectDir) {
95029
95449
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
95030
95450
  const projectKey = config2?.name?.trim() || basename7(projectDir);
95031
95451
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
95032
- const statusPath = join41(outputDir, "status.json");
95452
+ const statusPath = join42(outputDir, "status.json");
95033
95453
  if (!existsSync17(statusPath)) {
95034
95454
  return null;
95035
95455
  }
@@ -95041,7 +95461,7 @@ async function loadProjectStatusFile(projectDir) {
95041
95461
  }
95042
95462
  }
95043
95463
  async function getFeatureSummary(featureName, featureDir) {
95044
- const prdPath = join41(featureDir, "prd.json");
95464
+ const prdPath = join42(featureDir, "prd.json");
95045
95465
  if (!existsSync17(prdPath)) {
95046
95466
  return {
95047
95467
  name: featureName,
@@ -95084,7 +95504,7 @@ async function getFeatureSummary(featureName, featureDir) {
95084
95504
  };
95085
95505
  }
95086
95506
  }
95087
- const runsDir = join41(featureDir, "runs");
95507
+ const runsDir = join42(featureDir, "runs");
95088
95508
  if (existsSync17(runsDir)) {
95089
95509
  const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
95090
95510
  if (runs.length > 0) {
@@ -95098,7 +95518,7 @@ async function displayAllFeatures(projectDir) {
95098
95518
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
95099
95519
  const projectKey = config2?.name?.trim() || basename7(projectDir);
95100
95520
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
95101
- const featuresDir = join41(outputDir, "features");
95521
+ const featuresDir = join42(outputDir, "features");
95102
95522
  if (!existsSync17(featuresDir)) {
95103
95523
  console.log(source_default.dim("No features found."));
95104
95524
  return;
@@ -95139,7 +95559,7 @@ async function displayAllFeatures(projectDir) {
95139
95559
  console.log();
95140
95560
  }
95141
95561
  }
95142
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join41(featuresDir, name))));
95562
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join42(featuresDir, name))));
95143
95563
  console.log(source_default.bold(`\uD83D\uDCCA Features
95144
95564
  `));
95145
95565
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -95165,7 +95585,7 @@ async function displayAllFeatures(projectDir) {
95165
95585
  console.log();
95166
95586
  }
95167
95587
  async function displayFeatureDetails(featureName, featureDir) {
95168
- const prdPath = join41(featureDir, "prd.json");
95588
+ const prdPath = join42(featureDir, "prd.json");
95169
95589
  if (!existsSync17(prdPath)) {
95170
95590
  console.log(source_default.bold(`
95171
95591
  \uD83D\uDCCA ${featureName}
@@ -95311,7 +95731,7 @@ async function displayFeatureStatus(options = {}) {
95311
95731
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
95312
95732
  const projectKey = config2?.name?.trim() || basename7(projectDir);
95313
95733
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
95314
- featureDir = join41(outputDir, "features", options.feature);
95734
+ featureDir = join42(outputDir, "features", options.feature);
95315
95735
  } else {
95316
95736
  const resolved = resolveProject({ feature: options.feature });
95317
95737
  if (!resolved.featureDir) {
@@ -95331,7 +95751,7 @@ init_errors();
95331
95751
  init_logger2();
95332
95752
  init_runtime();
95333
95753
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
95334
- import { basename as basename8, join as join42 } from "path";
95754
+ import { basename as basename8, join as join43 } from "path";
95335
95755
  async function resolveOutputDir2(workdir, override) {
95336
95756
  if (override)
95337
95757
  return override;
@@ -95355,7 +95775,7 @@ async function runsListCommand(options) {
95355
95775
  const logger = getLogger();
95356
95776
  const { feature, workdir } = options;
95357
95777
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
95358
- const runsDir = join42(outputDir, "features", feature, "runs");
95778
+ const runsDir = join43(outputDir, "features", feature, "runs");
95359
95779
  if (!existsSync18(runsDir)) {
95360
95780
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
95361
95781
  return;
@@ -95367,7 +95787,7 @@ async function runsListCommand(options) {
95367
95787
  }
95368
95788
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
95369
95789
  for (const file3 of files.sort().reverse()) {
95370
- const logPath = join42(runsDir, file3);
95790
+ const logPath = join43(runsDir, file3);
95371
95791
  const entries = await parseRunLog(logPath);
95372
95792
  const startEvent = entries.find((e) => e.message === "run.start");
95373
95793
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -95394,7 +95814,7 @@ async function runsShowCommand(options) {
95394
95814
  const logger = getLogger();
95395
95815
  const { runId, feature, workdir } = options;
95396
95816
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
95397
- const logPath = join42(outputDir, "features", feature, "runs", `${runId}.jsonl`);
95817
+ const logPath = join43(outputDir, "features", feature, "runs", `${runId}.jsonl`);
95398
95818
  if (!existsSync18(logPath)) {
95399
95819
  logger.error("cli", "Run not found", { runId, feature, logPath });
95400
95820
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -95507,7 +95927,7 @@ init_logger2();
95507
95927
  init_prd();
95508
95928
  init_runtime();
95509
95929
  import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
95510
- import { basename as basename12, join as join55 } from "path";
95930
+ import { basename as basename12, join as join57 } from "path";
95511
95931
 
95512
95932
  // src/cli/diagnose-analysis.ts
95513
95933
  function detectFailurePattern(story, _prd, status) {
@@ -95710,7 +96130,7 @@ function isProcessAlive2(pid) {
95710
96130
  }
95711
96131
  }
95712
96132
  async function loadStatusFile2(outputDir) {
95713
- const statusPath = join55(outputDir, "status.json");
96133
+ const statusPath = join57(outputDir, "status.json");
95714
96134
  if (!existsSync25(statusPath))
95715
96135
  return null;
95716
96136
  try {
@@ -95738,7 +96158,7 @@ async function countCommitsSince(workdir, since) {
95738
96158
  }
95739
96159
  }
95740
96160
  async function checkLock(workdir) {
95741
- const lockFile = Bun.file(join55(workdir, "nax.lock"));
96161
+ const lockFile = Bun.file(join57(workdir, "nax.lock"));
95742
96162
  if (!await lockFile.exists())
95743
96163
  return { lockPresent: false };
95744
96164
  try {
@@ -95756,8 +96176,8 @@ async function diagnoseCommand(options = {}) {
95756
96176
  const logger = getLogger();
95757
96177
  const workdir = options.workdir ?? process.cwd();
95758
96178
  const naxSubdir = findProjectDir(workdir);
95759
- let projectDir = naxSubdir ? join55(naxSubdir, "..") : null;
95760
- if (!projectDir && existsSync25(join55(workdir, ".nax"))) {
96179
+ let projectDir = naxSubdir ? join57(naxSubdir, "..") : null;
96180
+ if (!projectDir && existsSync25(join57(workdir, ".nax"))) {
95761
96181
  projectDir = workdir;
95762
96182
  }
95763
96183
  if (!projectDir)
@@ -95771,7 +96191,7 @@ async function diagnoseCommand(options = {}) {
95771
96191
  if (status2) {
95772
96192
  feature = status2.run.feature;
95773
96193
  } else {
95774
- const featuresDir = join55(outputDir, "features");
96194
+ const featuresDir = join57(outputDir, "features");
95775
96195
  if (!existsSync25(featuresDir))
95776
96196
  throw new Error("No features found in project");
95777
96197
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -95781,8 +96201,8 @@ async function diagnoseCommand(options = {}) {
95781
96201
  logger.info("diagnose", "No feature specified, using first found", { feature });
95782
96202
  }
95783
96203
  }
95784
- const featureDir = join55(outputDir, "features", feature);
95785
- const prdPath = join55(featureDir, "prd.json");
96204
+ const featureDir = join57(outputDir, "features", feature);
96205
+ const prdPath = join57(featureDir, "prd.json");
95786
96206
  if (!existsSync25(prdPath))
95787
96207
  throw new Error(`Feature not found: ${feature}`);
95788
96208
  const prd = await loadPRD(prdPath);
@@ -95827,7 +96247,7 @@ init_source();
95827
96247
  init_loader();
95828
96248
  init_generator2();
95829
96249
  import { existsSync as existsSync26 } from "fs";
95830
- import { join as join56 } from "path";
96250
+ import { join as join58 } from "path";
95831
96251
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
95832
96252
  async function generateCommand(options) {
95833
96253
  const workdir = options.dir ?? process.cwd();
@@ -95870,7 +96290,7 @@ async function generateCommand(options) {
95870
96290
  return;
95871
96291
  }
95872
96292
  if (options.package) {
95873
- const packageDir = join56(workdir, options.package);
96293
+ const packageDir = join58(workdir, options.package);
95874
96294
  if (dryRun) {
95875
96295
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95876
96296
  }
@@ -95890,8 +96310,8 @@ async function generateCommand(options) {
95890
96310
  process.exit(1);
95891
96311
  return;
95892
96312
  }
95893
- const contextPath = options.context ? join56(workdir, options.context) : join56(workdir, ".nax/context.md");
95894
- const outputDir = options.output ? join56(workdir, options.output) : workdir;
96313
+ const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
96314
+ const outputDir = options.output ? join58(workdir, options.output) : workdir;
95895
96315
  const autoInject = !options.noAutoInject;
95896
96316
  if (!existsSync26(contextPath)) {
95897
96317
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -95997,7 +96417,7 @@ async function generateCommand(options) {
95997
96417
  // src/cli/config-display.ts
95998
96418
  init_loader();
95999
96419
  import { existsSync as existsSync28 } from "fs";
96000
- import { join as join58 } from "path";
96420
+ import { join as join60 } from "path";
96001
96421
 
96002
96422
  // src/cli/config-descriptions.ts
96003
96423
  var FIELD_DESCRIPTIONS = {
@@ -96248,7 +96668,7 @@ function deepEqual(a, b) {
96248
96668
  init_defaults();
96249
96669
  init_loader();
96250
96670
  import { existsSync as existsSync27 } from "fs";
96251
- import { join as join57 } from "path";
96671
+ import { join as join59 } from "path";
96252
96672
  async function loadConfigFile(path19) {
96253
96673
  if (!existsSync27(path19))
96254
96674
  return null;
@@ -96270,7 +96690,7 @@ async function loadProjectConfig() {
96270
96690
  const projectDir = findProjectDir();
96271
96691
  if (!projectDir)
96272
96692
  return null;
96273
- const projectPath = join57(projectDir, "config.json");
96693
+ const projectPath = join59(projectDir, "config.json");
96274
96694
  return await loadConfigFile(projectPath);
96275
96695
  }
96276
96696
 
@@ -96330,7 +96750,7 @@ async function configCommand(config2, options = {}) {
96330
96750
  function determineConfigSources() {
96331
96751
  const globalPath = globalConfigPath();
96332
96752
  const projectDir = findProjectDir();
96333
- const projectPath = projectDir ? join58(projectDir, "config.json") : null;
96753
+ const projectPath = projectDir ? join60(projectDir, "config.json") : null;
96334
96754
  return {
96335
96755
  global: fileExists(globalPath) ? globalPath : null,
96336
96756
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -96479,15 +96899,15 @@ init_paths();
96479
96899
  init_profile();
96480
96900
  import { mkdirSync as mkdirSync5 } from "fs";
96481
96901
  import { readdirSync as readdirSync6 } from "fs";
96482
- import { join as join59 } from "path";
96902
+ import { join as join61 } from "path";
96483
96903
  var _profileCLIDeps = {
96484
96904
  env: process.env
96485
96905
  };
96486
96906
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
96487
96907
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
96488
96908
  async function profileListCommand(startDir) {
96489
- const globalProfilesDir = join59(globalConfigDir(), "profiles");
96490
- const projectProfilesDir = join59(projectConfigDir(startDir), "profiles");
96909
+ const globalProfilesDir = join61(globalConfigDir(), "profiles");
96910
+ const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
96491
96911
  const globalProfiles = scanProfileDir(globalProfilesDir);
96492
96912
  const projectProfiles = scanProfileDir(projectProfilesDir);
96493
96913
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -96546,7 +96966,7 @@ function maskProfileValues(obj) {
96546
96966
  return result;
96547
96967
  }
96548
96968
  async function profileUseCommand(profileName, startDir) {
96549
- const configPath = join59(projectConfigDir(startDir), "config.json");
96969
+ const configPath = join61(projectConfigDir(startDir), "config.json");
96550
96970
  const configFile = Bun.file(configPath);
96551
96971
  let existing = {};
96552
96972
  if (await configFile.exists()) {
@@ -96565,8 +96985,8 @@ async function profileCurrentCommand(startDir) {
96565
96985
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
96566
96986
  }
96567
96987
  async function profileCreateCommand(profileName, startDir) {
96568
- const profilesDir = join59(projectConfigDir(startDir), "profiles");
96569
- const profilePath = join59(profilesDir, `${profileName}.json`);
96988
+ const profilesDir = join61(projectConfigDir(startDir), "profiles");
96989
+ const profilePath = join61(profilesDir, `${profileName}.json`);
96570
96990
  const profileFile = Bun.file(profilePath);
96571
96991
  if (await profileFile.exists()) {
96572
96992
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -96688,7 +97108,7 @@ async function contextInspectCommand(options) {
96688
97108
  init_canonical_loader();
96689
97109
  init_errors();
96690
97110
  import { mkdir as mkdir12 } from "fs/promises";
96691
- import { basename as basename13, join as join60 } from "path";
97111
+ import { basename as basename13, join as join62 } from "path";
96692
97112
  var _rulesCLIDeps = {
96693
97113
  readFile: async (path19) => Bun.file(path19).text(),
96694
97114
  writeFile: async (path19, content) => {
@@ -96697,7 +97117,7 @@ var _rulesCLIDeps = {
96697
97117
  fileExists: async (path19) => Bun.file(path19).exists(),
96698
97118
  globInDir: (dir) => {
96699
97119
  try {
96700
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join60(dir, f));
97120
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
96701
97121
  } catch {
96702
97122
  return [];
96703
97123
  }
@@ -96746,7 +97166,7 @@ ${r.content}`).join(`
96746
97166
  `);
96747
97167
  const shimContent = `${header + body}
96748
97168
  `;
96749
- const shimPath = join60(workdir, shimFileName);
97169
+ const shimPath = join62(workdir, shimFileName);
96750
97170
  if (options.dryRun) {
96751
97171
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
96752
97172
  return;
@@ -96775,14 +97195,14 @@ function neutralizeContent(content) {
96775
97195
  }
96776
97196
  async function collectMigrationSources(workdir) {
96777
97197
  const sources = [];
96778
- const claudeMdPath = join60(workdir, "CLAUDE.md");
97198
+ const claudeMdPath = join62(workdir, "CLAUDE.md");
96779
97199
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
96780
97200
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
96781
97201
  if (content.trim()) {
96782
97202
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
96783
97203
  }
96784
97204
  }
96785
- const rulesDir = join60(workdir, ".claude", "rules");
97205
+ const rulesDir = join62(workdir, ".claude", "rules");
96786
97206
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
96787
97207
  for (const filePath of ruleFiles) {
96788
97208
  try {
@@ -96802,7 +97222,7 @@ async function rulesMigrateCommand(options) {
96802
97222
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
96803
97223
  return;
96804
97224
  }
96805
- const targetDir = join60(workdir, CANONICAL_RULES_DIR);
97225
+ const targetDir = join62(workdir, CANONICAL_RULES_DIR);
96806
97226
  if (!options.dryRun) {
96807
97227
  try {
96808
97228
  await _rulesCLIDeps.mkdir(targetDir);
@@ -96813,7 +97233,7 @@ async function rulesMigrateCommand(options) {
96813
97233
  let written = 0;
96814
97234
  let skipped = 0;
96815
97235
  for (const { sourcePath, targetFileName, content } of sources) {
96816
- const targetPath = join60(targetDir, targetFileName);
97236
+ const targetPath = join62(targetDir, targetFileName);
96817
97237
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
96818
97238
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
96819
97239
  skipped++;
@@ -96852,7 +97272,7 @@ function collectCanonicalRuleRoots(workdir) {
96852
97272
  const packageRel = normalized.slice(0, idx);
96853
97273
  if (!packageRel)
96854
97274
  continue;
96855
- roots.add(join60(workdir, packageRel));
97275
+ roots.add(join62(workdir, packageRel));
96856
97276
  }
96857
97277
  return [...roots].sort();
96858
97278
  }
@@ -96874,7 +97294,7 @@ init_logger2();
96874
97294
  init_detect2();
96875
97295
  init_workspace();
96876
97296
  init_common();
96877
- import { join as join61 } from "path";
97297
+ import { join as join63 } from "path";
96878
97298
  function resolveEffective(detected, configPatterns) {
96879
97299
  if (configPatterns !== undefined)
96880
97300
  return "config";
@@ -96959,7 +97379,7 @@ async function detectCommand(options) {
96959
97379
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96960
97380
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96961
97381
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96962
- const pkgConfigPath = join61(workdir, ".nax", "mono", dir, "config.json");
97382
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96963
97383
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96964
97384
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96965
97385
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -97013,13 +97433,13 @@ async function detectCommand(options) {
97013
97433
  if (rootDetected.confidence === "empty") {
97014
97434
  console.log(source_default.yellow(" root: skipped (empty detection)"));
97015
97435
  } else {
97016
- const rootConfigPath = join61(workdir, ".nax", "config.json");
97436
+ const rootConfigPath = join63(workdir, ".nax", "config.json");
97017
97437
  try {
97018
97438
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
97019
97439
  if (status === "skipped") {
97020
97440
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
97021
97441
  } else {
97022
- console.log(source_default.green(` root: ${status} \u2192 ${join61(".nax", "config.json")}`));
97442
+ console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
97023
97443
  }
97024
97444
  } catch (err) {
97025
97445
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -97032,13 +97452,13 @@ async function detectCommand(options) {
97032
97452
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
97033
97453
  continue;
97034
97454
  }
97035
- const pkgConfigPath = join61(workdir, ".nax", "mono", dir, "config.json");
97455
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
97036
97456
  try {
97037
97457
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
97038
97458
  if (status === "skipped") {
97039
97459
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
97040
97460
  } else {
97041
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join61(".nax", "mono", dir, "config.json")}`));
97461
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
97042
97462
  }
97043
97463
  } catch (err) {
97044
97464
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -97061,19 +97481,19 @@ async function diagnose(options) {
97061
97481
  // src/commands/logs.ts
97062
97482
  init_common();
97063
97483
  import { existsSync as existsSync30 } from "fs";
97064
- import { join as join65 } from "path";
97484
+ import { join as join67 } from "path";
97065
97485
 
97066
97486
  // src/commands/logs-formatter.ts
97067
97487
  init_source();
97068
97488
  init_formatter();
97069
97489
  import { readdirSync as readdirSync8 } from "fs";
97070
- import { join as join64 } from "path";
97490
+ import { join as join66 } from "path";
97071
97491
 
97072
97492
  // src/commands/logs-reader.ts
97073
97493
  init_paths3();
97074
97494
  import { existsSync as existsSync29, readdirSync as readdirSync7 } from "fs";
97075
97495
  import { readdir as readdir4 } from "fs/promises";
97076
- import { join as join63 } from "path";
97496
+ import { join as join65 } from "path";
97077
97497
  var _logsReaderDeps = {
97078
97498
  getRunsDir
97079
97499
  };
@@ -97087,7 +97507,7 @@ async function resolveRunFileFromRegistry(runId) {
97087
97507
  }
97088
97508
  let matched = null;
97089
97509
  for (const entry of entries) {
97090
- const metaPath = join63(runsDir, entry, "meta.json");
97510
+ const metaPath = join65(runsDir, entry, "meta.json");
97091
97511
  try {
97092
97512
  const meta3 = await Bun.file(metaPath).json();
97093
97513
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -97109,14 +97529,14 @@ async function resolveRunFileFromRegistry(runId) {
97109
97529
  return null;
97110
97530
  }
97111
97531
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
97112
- return join63(matched.eventsDir, specificFile ?? files[0]);
97532
+ return join65(matched.eventsDir, specificFile ?? files[0]);
97113
97533
  }
97114
97534
  async function selectRunFile(runsDir) {
97115
97535
  const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
97116
97536
  if (files.length === 0) {
97117
97537
  return null;
97118
97538
  }
97119
- return join63(runsDir, files[0]);
97539
+ return join65(runsDir, files[0]);
97120
97540
  }
97121
97541
  async function extractRunSummary(filePath) {
97122
97542
  const file3 = Bun.file(filePath);
@@ -97202,7 +97622,7 @@ Runs:
97202
97622
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
97203
97623
  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"));
97204
97624
  for (const file3 of files) {
97205
- const filePath = join64(runsDir, file3);
97625
+ const filePath = join66(runsDir, file3);
97206
97626
  const summary = await extractRunSummary(filePath);
97207
97627
  const timestamp = file3.replace(".jsonl", "");
97208
97628
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -97316,7 +97736,7 @@ async function logsCommand(options) {
97316
97736
  return;
97317
97737
  }
97318
97738
  const resolved = resolveProject({ dir: options.dir });
97319
- const naxDir = join65(resolved.projectDir, ".nax");
97739
+ const naxDir = join67(resolved.projectDir, ".nax");
97320
97740
  const configPath = resolved.configPath;
97321
97741
  const configFile = Bun.file(configPath);
97322
97742
  const config2 = await configFile.json();
@@ -97324,8 +97744,8 @@ async function logsCommand(options) {
97324
97744
  if (!featureName) {
97325
97745
  throw new Error("No feature specified in config.json");
97326
97746
  }
97327
- const featureDir = join65(naxDir, "features", featureName);
97328
- const runsDir = join65(featureDir, "runs");
97747
+ const featureDir = join67(naxDir, "features", featureName);
97748
+ const runsDir = join67(featureDir, "runs");
97329
97749
  if (!existsSync30(runsDir)) {
97330
97750
  throw new Error(`No runs directory found for feature: ${featureName}`);
97331
97751
  }
@@ -97351,7 +97771,7 @@ init_prd();
97351
97771
  init_precheck();
97352
97772
  init_common();
97353
97773
  import { existsSync as existsSync31 } from "fs";
97354
- import { join as join66 } from "path";
97774
+ import { join as join68 } from "path";
97355
97775
  async function precheckCommand(options) {
97356
97776
  const resolved = resolveProject({
97357
97777
  dir: options.dir,
@@ -97373,9 +97793,9 @@ async function precheckCommand(options) {
97373
97793
  process.exit(1);
97374
97794
  }
97375
97795
  }
97376
- const naxDir = join66(resolved.projectDir, ".nax");
97377
- const featureDir = join66(naxDir, "features", featureName);
97378
- const prdPath = join66(featureDir, "prd.json");
97796
+ const naxDir = join68(resolved.projectDir, ".nax");
97797
+ const featureDir = join68(naxDir, "features", featureName);
97798
+ const prdPath = join68(featureDir, "prd.json");
97379
97799
  if (!existsSync31(featureDir)) {
97380
97800
  console.error(source_default.red(`Feature not found: ${featureName}`));
97381
97801
  process.exit(1);
@@ -97398,7 +97818,7 @@ async function precheckCommand(options) {
97398
97818
  init_source();
97399
97819
  init_paths3();
97400
97820
  import { readdir as readdir5 } from "fs/promises";
97401
- import { join as join67 } from "path";
97821
+ import { join as join69 } from "path";
97402
97822
  var DEFAULT_LIMIT = 20;
97403
97823
  var _runsCmdDeps = {
97404
97824
  getRunsDir
@@ -97453,7 +97873,7 @@ async function runsCommand(options = {}) {
97453
97873
  }
97454
97874
  const rows = [];
97455
97875
  for (const entry of entries) {
97456
- const metaPath = join67(runsDir, entry, "meta.json");
97876
+ const metaPath = join69(runsDir, entry, "meta.json");
97457
97877
  let meta3;
97458
97878
  try {
97459
97879
  meta3 = await Bun.file(metaPath).json();
@@ -97530,7 +97950,7 @@ async function runsCommand(options = {}) {
97530
97950
 
97531
97951
  // src/commands/unlock.ts
97532
97952
  init_source();
97533
- import { join as join68 } from "path";
97953
+ import { join as join70 } from "path";
97534
97954
  function isProcessAlive3(pid) {
97535
97955
  try {
97536
97956
  process.kill(pid, 0);
@@ -97545,7 +97965,7 @@ function formatLockAge(ageMs) {
97545
97965
  }
97546
97966
  async function unlockCommand(options) {
97547
97967
  const workdir = options.dir ?? process.cwd();
97548
- const lockPath = join68(workdir, "nax.lock");
97968
+ const lockPath = join70(workdir, "nax.lock");
97549
97969
  const lockFile = Bun.file(lockPath);
97550
97970
  const exists = await lockFile.exists();
97551
97971
  if (!exists) {
@@ -105618,7 +106038,7 @@ Next: nax generate --package ${options.package}`));
105618
106038
  }
105619
106039
  return;
105620
106040
  }
105621
- const naxDir = join82(workdir, ".nax");
106041
+ const naxDir = join84(workdir, ".nax");
105622
106042
  if (existsSync37(naxDir) && !options.force) {
105623
106043
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
105624
106044
  return;
@@ -105647,11 +106067,11 @@ Next: nax generate --package ${options.package}`));
105647
106067
  }
105648
106068
  }
105649
106069
  }
105650
- mkdirSync7(join82(naxDir, "features"), { recursive: true });
105651
- mkdirSync7(join82(naxDir, "hooks"), { recursive: true });
106070
+ mkdirSync7(join84(naxDir, "features"), { recursive: true });
106071
+ mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
105652
106072
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
105653
- await Bun.write(join82(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105654
- await Bun.write(join82(naxDir, "hooks.json"), JSON.stringify({
106073
+ await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
106074
+ await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
105655
106075
  hooks: {
105656
106076
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
105657
106077
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -105659,12 +106079,12 @@ Next: nax generate --package ${options.package}`));
105659
106079
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
105660
106080
  }
105661
106081
  }, null, 2));
105662
- await Bun.write(join82(naxDir, ".gitignore"), `# nax temp files
106082
+ await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
105663
106083
  *.tmp
105664
106084
  .paused.json
105665
106085
  .nax-verifier-verdict.json
105666
106086
  `);
105667
- await Bun.write(join82(naxDir, "context.md"), `# Project Context
106087
+ await Bun.write(join84(naxDir, "context.md"), `# Project Context
105668
106088
 
105669
106089
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
105670
106090
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105794,8 +106214,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
105794
106214
  console.error(source_default.red("nax not initialized. Run: nax init"));
105795
106215
  process.exit(1);
105796
106216
  }
105797
- const featureDir = join82(naxDir, "features", options.feature);
105798
- const prdPath = join82(featureDir, "prd.json");
106217
+ const featureDir = join84(naxDir, "features", options.feature);
106218
+ const prdPath = join84(featureDir, "prd.json");
105799
106219
  if (options.plan && options.from) {
105800
106220
  if (existsSync37(prdPath) && !options.force) {
105801
106221
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -105817,10 +106237,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105817
106237
  }
105818
106238
  }
105819
106239
  try {
105820
- const planLogDir = join82(featureDir, "plan");
106240
+ const planLogDir = join84(featureDir, "plan");
105821
106241
  mkdirSync7(planLogDir, { recursive: true });
105822
106242
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105823
- const planLogPath = join82(planLogDir, `${planLogId}.jsonl`);
106243
+ const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
105824
106244
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105825
106245
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105826
106246
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105866,10 +106286,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105866
106286
  resetLogger();
105867
106287
  const projectKey = config2.name?.trim() || basename17(workdir);
105868
106288
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105869
- const runsDir = join82(outputDir, "features", options.feature, "runs");
106289
+ const runsDir = join84(outputDir, "features", options.feature, "runs");
105870
106290
  mkdirSync7(runsDir, { recursive: true });
105871
106291
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105872
- const logFilePath = join82(runsDir, `${runId}.jsonl`);
106292
+ const logFilePath = join84(runsDir, `${runId}.jsonl`);
105873
106293
  const isTTY = process.stdout.isTTY ?? false;
105874
106294
  const headlessFlag = options.headless ?? false;
105875
106295
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105886,7 +106306,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105886
106306
  config2.agent.default = options.agent;
105887
106307
  }
105888
106308
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105889
- const globalNaxDir = join82(homedir3(), ".nax");
106309
+ const globalNaxDir = join84(homedir3(), ".nax");
105890
106310
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105891
106311
  const eventEmitter = new PipelineEventEmitter;
105892
106312
  let tuiInstance;
@@ -105909,7 +106329,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105909
106329
  } else {
105910
106330
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105911
106331
  }
105912
- const statusFilePath = join82(outputDir, "status.json");
106332
+ const statusFilePath = join84(outputDir, "status.json");
105913
106333
  let parallel;
105914
106334
  if (options.parallel !== undefined) {
105915
106335
  parallel = Number.parseInt(options.parallel, 10);
@@ -105935,7 +106355,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105935
106355
  headless: useHeadless,
105936
106356
  skipPrecheck: options.skipPrecheck ?? false
105937
106357
  });
105938
- const latestSymlink = join82(runsDir, "latest.jsonl");
106358
+ const latestSymlink = join84(runsDir, "latest.jsonl");
105939
106359
  try {
105940
106360
  if (existsSync37(latestSymlink)) {
105941
106361
  Bun.spawnSync(["rm", latestSymlink]);
@@ -105996,9 +106416,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105996
106416
  console.error(source_default.red("nax not initialized. Run: nax init"));
105997
106417
  process.exit(1);
105998
106418
  }
105999
- const featureDir = join82(naxDir, "features", name);
106419
+ const featureDir = join84(naxDir, "features", name);
106000
106420
  mkdirSync7(featureDir, { recursive: true });
106001
- await Bun.write(join82(featureDir, "spec.md"), `# Feature: ${name}
106421
+ await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
106002
106422
 
106003
106423
  ## Overview
106004
106424
 
@@ -106031,7 +106451,7 @@ features.command("create <name>").description("Create a new feature").option("-d
106031
106451
 
106032
106452
  <!-- What this feature explicitly does NOT cover. -->
106033
106453
  `);
106034
- await Bun.write(join82(featureDir, "progress.txt"), `# Progress: ${name}
106454
+ await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
106035
106455
 
106036
106456
  Created: ${new Date().toISOString()}
106037
106457
 
@@ -106057,7 +106477,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
106057
106477
  console.error(source_default.red("nax not initialized."));
106058
106478
  process.exit(1);
106059
106479
  }
106060
- const featuresDir = join82(naxDir, "features");
106480
+ const featuresDir = join84(naxDir, "features");
106061
106481
  if (!existsSync37(featuresDir)) {
106062
106482
  console.log(source_default.dim("No features yet."));
106063
106483
  return;
@@ -106072,7 +106492,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
106072
106492
  Features:
106073
106493
  `));
106074
106494
  for (const name of entries) {
106075
- const prdPath = join82(featuresDir, name, "prd.json");
106495
+ const prdPath = join84(featuresDir, name, "prd.json");
106076
106496
  if (existsSync37(prdPath)) {
106077
106497
  const prd = await loadPRD(prdPath);
106078
106498
  const c = countStories(prd);
@@ -106107,10 +106527,10 @@ Use: nax plan -f <feature> --from <spec>`));
106107
106527
  cliOverrides.profile = options.profile;
106108
106528
  }
106109
106529
  const config2 = await loadConfig(workdir, cliOverrides);
106110
- const featureLogDir = join82(naxDir, "features", options.feature, "plan");
106530
+ const featureLogDir = join84(naxDir, "features", options.feature, "plan");
106111
106531
  mkdirSync7(featureLogDir, { recursive: true });
106112
106532
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
106113
- const planLogPath = join82(featureLogDir, `${planLogId}.jsonl`);
106533
+ const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
106114
106534
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
106115
106535
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
106116
106536
  try {