@nathapp/nax 0.67.11 → 0.67.12

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 +514 -347
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -35772,6 +35772,16 @@ function parseTestEditDeclarations(output) {
35772
35772
  }
35773
35773
  return result;
35774
35774
  }
35775
+ function normaliseWs(s) {
35776
+ return s.replace(/\s+/g, " ").replace(/\s*([(),<>])\s*/g, "$1").replace(/\s*:\s*/g, ": ").trim();
35777
+ }
35778
+ function validatePrdQuote(prdQuote, story) {
35779
+ if (!prdQuote.trim())
35780
+ return false;
35781
+ const needle = normaliseWs(prdQuote);
35782
+ const haystack = normaliseWs([story.description, ...story.acceptanceCriteria].join(" "));
35783
+ return haystack.includes(needle);
35784
+ }
35775
35785
  var REASON_RE;
35776
35786
  var init_test_edit_declaration = __esm(() => {
35777
35787
  REASON_RE = /^TEST_EDIT_REASON:\s*(prd_contract|lint_only|sibling_scope|mock_structure)\s*$/m;
@@ -37896,7 +37906,7 @@ var init__finding_to_check = __esm(() => {
37896
37906
  });
37897
37907
 
37898
37908
  // src/operations/autofix-implementer-strategy.ts
37899
- function makeAutofixImplementerStrategy(story, config2) {
37909
+ function makeAutofixImplementerStrategy(story, config2, sink) {
37900
37910
  return {
37901
37911
  name: "autofix-implementer",
37902
37912
  appliesTo: (f) => f.fixTarget === "source" && IMPLEMENTER_SOURCES.has(f.source),
@@ -37905,10 +37915,19 @@ function makeAutofixImplementerStrategy(story, config2) {
37905
37915
  failedChecks: findingsToFailedChecks(findings),
37906
37916
  story
37907
37917
  }),
37908
- extractApplied: (output) => ({
37909
- summary: output.unresolvedReason ?? "",
37910
- unresolved: output.unresolvedReason
37911
- }),
37918
+ extractApplied: (output) => {
37919
+ for (const decl of output.testEditDeclarations) {
37920
+ if (decl.reason === "mock_structure" && decl.files && decl.reasonDetail) {
37921
+ sink.mockHandoffs.push({ files: decl.files, reasonDetail: decl.reasonDetail });
37922
+ } else if (decl.reason !== "mock_structure") {
37923
+ sink.testEdits.push(decl);
37924
+ }
37925
+ }
37926
+ return {
37927
+ summary: output.unresolvedReason ?? "",
37928
+ unresolved: output.unresolvedReason
37929
+ };
37930
+ },
37912
37931
  maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37913
37932
  coRun: "co-run-sequential"
37914
37933
  };
@@ -37921,16 +37940,42 @@ var init_autofix_implementer_strategy = __esm(() => {
37921
37940
  });
37922
37941
 
37923
37942
  // src/operations/autofix-test-writer-strategy.ts
37924
- function makeAutofixTestWriterStrategy(story, config2) {
37943
+ function makeAutofixTestWriterStrategy(story, config2, sink) {
37925
37944
  return {
37926
37945
  name: "autofix-test-writer",
37927
- appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review",
37946
+ appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review" || sink.mockHandoffs.length > 0,
37928
37947
  fixOp: testWriterRectifyOp,
37929
- buildInput: (findings, _prior, _cycleCtx) => ({
37930
- failedChecks: findingsToFailedChecks(findings),
37931
- story,
37932
- blockingThreshold: config2.review?.blockingThreshold
37933
- }),
37948
+ buildInput: (findings, _prior, _cycleCtx) => {
37949
+ if (sink.mockHandoffs.length > 0) {
37950
+ const handoffs = sink.mockHandoffs.splice(0);
37951
+ const seenFiles = new Set;
37952
+ const handoffFiles = [];
37953
+ for (const h of handoffs) {
37954
+ for (const f of h.files) {
37955
+ if (!seenFiles.has(f)) {
37956
+ seenFiles.add(f);
37957
+ handoffFiles.push(f);
37958
+ }
37959
+ }
37960
+ }
37961
+ const handoffReason = handoffs.map((h) => h.reasonDetail).join(`
37962
+ ---
37963
+ `);
37964
+ return {
37965
+ failedChecks: findingsToFailedChecks(findings),
37966
+ story,
37967
+ mode: "mock-restructure",
37968
+ blockingThreshold: config2.review?.blockingThreshold,
37969
+ handoffReason,
37970
+ handoffFiles
37971
+ };
37972
+ }
37973
+ return {
37974
+ failedChecks: findingsToFailedChecks(findings),
37975
+ story,
37976
+ blockingThreshold: config2.review?.blockingThreshold
37977
+ };
37978
+ },
37934
37979
  maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37935
37980
  coRun: "co-run-sequential"
37936
37981
  };
@@ -37940,6 +37985,100 @@ var init_autofix_test_writer_strategy = __esm(() => {
37940
37985
  init_autofix_test_writer();
37941
37986
  });
37942
37987
 
37988
+ // src/operations/apply-test-edit-declarations.ts
37989
+ function applyTestEditDeclarations(findings, declarations, story, invalidMockStructure) {
37990
+ let result = [...findings];
37991
+ const advisories = [];
37992
+ for (const d of declarations) {
37993
+ if (d.reason === "prd_contract") {
37994
+ const prdQuote = d.prdQuote ?? "";
37995
+ const valid = validatePrdQuote(prdQuote, story);
37996
+ if (valid) {
37997
+ result = result.map((f) => {
37998
+ if (f.file === d.file && f.fixTarget === "source") {
37999
+ return {
38000
+ ...f,
38001
+ fixTarget: "test",
38002
+ meta: {
38003
+ ...f.meta,
38004
+ prdContractDeclaration: d
38005
+ }
38006
+ };
38007
+ }
38008
+ return f;
38009
+ });
38010
+ } else {
38011
+ advisories.push({
38012
+ source: "autofix",
38013
+ severity: "warning",
38014
+ category: "prd_quote_mismatch",
38015
+ message: `PRD quote not found verbatim in story text for file: ${d.file}`,
38016
+ file: d.file,
38017
+ fixTarget: "source"
38018
+ });
38019
+ }
38020
+ }
38021
+ }
38022
+ if (invalidMockStructure && invalidMockStructure.length > 0) {
38023
+ for (const d of invalidMockStructure) {
38024
+ const fileList = (d.files ?? [d.file]).join(", ");
38025
+ advisories.push({
38026
+ source: "autofix",
38027
+ severity: "warning",
38028
+ category: "mock_structure_invalid_files",
38029
+ message: `Mock structure handoff references file that does not exist or is not a test file: ${fileList}`,
38030
+ fixTarget: "source"
38031
+ });
38032
+ }
38033
+ }
38034
+ return [...result, ...advisories];
38035
+ }
38036
+ var init_apply_test_edit_declarations = __esm(() => {
38037
+ init_test_edit_declaration();
38038
+ });
38039
+
38040
+ // src/operations/validate-mock-structure-files.ts
38041
+ import { join as join23 } from "path";
38042
+ async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
38043
+ const fileExists = deps?.fileExists ?? defaultFileExists;
38044
+ const valid = [];
38045
+ const invalid = [];
38046
+ for (const d of declarations) {
38047
+ if (d.reason !== "mock_structure") {
38048
+ valid.push(d);
38049
+ continue;
38050
+ }
38051
+ const files = d.files ?? [d.file];
38052
+ let allValid = true;
38053
+ for (const file3 of files) {
38054
+ const absolutePath = join23(packageDir, file3);
38055
+ const exists = await fileExists(absolutePath);
38056
+ if (!exists) {
38057
+ allValid = false;
38058
+ break;
38059
+ }
38060
+ const matchesPattern = resolvedTestPatterns.regex.some((re) => re.test(file3));
38061
+ if (!matchesPattern) {
38062
+ allValid = false;
38063
+ break;
38064
+ }
38065
+ }
38066
+ if (allValid) {
38067
+ valid.push(d);
38068
+ } else {
38069
+ invalid.push(d);
38070
+ }
38071
+ }
38072
+ return { valid, invalid };
38073
+ }
38074
+ var defaultFileExists = (p) => Bun.file(p).exists();
38075
+ var init_validate_mock_structure_files = () => {};
38076
+
38077
+ // src/operations/declaration-sink.ts
38078
+ function makeDeclarationSink() {
38079
+ return { testEdits: [], mockHandoffs: [] };
38080
+ }
38081
+
37943
38082
  // src/operations/mechanical-lintfix-strategy.ts
37944
38083
  function shellQuotePath2(path5) {
37945
38084
  return `'${path5.replaceAll("'", `'\\''`)}'`;
@@ -38420,6 +38559,8 @@ var init_operations = __esm(() => {
38420
38559
  init_full_suite_rectify();
38421
38560
  init_autofix_implementer_strategy();
38422
38561
  init_autofix_test_writer_strategy();
38562
+ init_apply_test_edit_declarations();
38563
+ init_validate_mock_structure_files();
38423
38564
  init__finding_to_check();
38424
38565
  init_mechanical_lintfix_strategy();
38425
38566
  init_mechanical_formatfix_strategy();
@@ -39070,7 +39211,7 @@ var init_lint_parsing = __esm(() => {
39070
39211
  });
39071
39212
 
39072
39213
  // src/review/scoped-lint.ts
39073
- import { join as join23, relative as relative10 } from "path";
39214
+ import { join as join24, relative as relative10 } from "path";
39074
39215
  function shellQuotePath4(path5) {
39075
39216
  return `'${path5.replaceAll("'", "'\\''")}'`;
39076
39217
  }
@@ -39118,7 +39259,7 @@ function uniqueFiles(files) {
39118
39259
  async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
39119
39260
  const inScope = [];
39120
39261
  for (const relPath of files) {
39121
- const absPath = join23(workdir, relPath);
39262
+ const absPath = join24(workdir, relPath);
39122
39263
  const exists = await _scopedLintDeps.fileExists(absPath);
39123
39264
  if (!exists)
39124
39265
  continue;
@@ -40384,9 +40525,34 @@ var init_review = __esm(() => {
40384
40525
  });
40385
40526
 
40386
40527
  // src/prompts/builders/rectifier-builder-helpers.ts
40528
+ function buildEscapeHatch(opts) {
40529
+ const exceptions = [EXCEPTION_1_LINT_ONLY, EXCEPTION_2_PRD_CONTRACT, EXCEPTION_3_SIBLING_SCOPE];
40530
+ if (opts.includeMockHandoff)
40531
+ exceptions.push(EXCEPTION_4_MOCK_HANDOFF);
40532
+ const count = exceptions.length;
40533
+ const countWord = ["zero", "one", "two", "three", "four"][count];
40534
+ return `
40535
+ If two findings in this list contradict each other and you cannot satisfy both, do not guess.
40536
+ Emit fixes for defects you can resolve, then output a line in this exact format:
40537
+ UNRESOLVED: <brief explanation of which findings conflicted and why they cannot both be satisfied>
40538
+
40539
+ Before emitting UNRESOLVED, confirm none of Exceptions 1\u2013${count} apply.
40540
+
40541
+ ## Test-file edit exceptions
40542
+
40543
+ The "do not modify test files" rule has ${countWord} narrow escape valves. Each requires a
40544
+ declaration in your output. Outside these ${countWord} cases the rule is absolute.
40545
+
40546
+ ${exceptions.join(`
40547
+
40548
+ `)}`;
40549
+ }
40550
+ function exceptionCountWord(story) {
40551
+ return THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "") ? "four" : "three";
40552
+ }
40387
40553
  function escapeHatchFor(story) {
40388
40554
  const isTdd = THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "");
40389
- return isTdd ? CONTRADICTION_ESCAPE_HATCH : CONTRADICTION_ESCAPE_HATCH.replace(EXCEPTION_4_MOCK_HANDOFF, "");
40555
+ return buildEscapeHatch({ includeMockHandoff: isTdd });
40390
40556
  }
40391
40557
  function noTestIsolationBlock(story) {
40392
40558
  if (story.routing?.testStrategy !== "no-test")
@@ -40456,7 +40622,7 @@ ${errors3}
40456
40622
  2. Only fix findings that are actually valid problems
40457
40623
  3. Do NOT add keys, functions, or imports that already exist \u2014 check first
40458
40624
 
40459
- Do NOT change test files or test behavior \u2014 see the three narrow exceptions appended below.
40625
+ Do NOT change test files or test behavior \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below.
40460
40626
  Do NOT add new features \u2014 only fix valid issues.
40461
40627
  Commit your fixes when done.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
40462
40628
  }
@@ -40518,21 +40684,11 @@ The following quality checks failed after implementation:
40518
40684
 
40519
40685
  ${errors3}
40520
40686
 
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.
40687
+ 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
40688
  Do NOT add new features \u2014 only fix the quality check errors.
40523
40689
  After fixing, re-run the failing check(s) to verify they pass, then commit your changes.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
40524
40690
  }
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
40691
+ var EXCEPTION_1_LINT_ONLY = `### Exception 1 \u2014 Lint-only edit
40536
40692
 
40537
40693
  You MAY edit a test file ONLY when ALL of the following hold:
40538
40694
  - The failing check is \`lint\` \u2014 not \`test\`, \`typecheck\`, \`semantic\`, or \`adversarial\`.
@@ -40548,9 +40704,7 @@ TEST_EDIT_REASON: lint_only
40548
40704
  FILE: <test file path>
40549
40705
  FINDING: <lint rule or message verbatim>
40550
40706
  CHANGE: <before line> \u2192 <after line>
40551
- \`\`\`
40552
-
40553
- ### Exception 2 \u2014 PRD-contract mismatch
40707
+ \`\`\``, EXCEPTION_2_PRD_CONTRACT = `### Exception 2 \u2014 PRD-contract mismatch
40554
40708
 
40555
40709
  You MAY correct a test's argument arity, type, or return-handling ONLY when the test's
40556
40710
  call contradicts a literal interface signature stated in this story's description or
@@ -40566,9 +40720,7 @@ TEST_AFTER: <corrected call line>
40566
40720
  \`\`\`
40567
40721
 
40568
40722
  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
40723
+ signatures that directly contradict a quoted PRD interface.`, EXCEPTION_3_SIBLING_SCOPE = `### Exception 3 \u2014 Unrelated sibling spillover
40572
40724
 
40573
40725
  When a lint or typecheck error is outside this story's intended scope, do NOT edit that
40574
40726
  file. If the smallest package-local fix is required to satisfy this story's acceptance
@@ -40578,46 +40730,37 @@ TEST_EDIT_REASON: sibling_scope
40578
40730
  SIBLING_FILE: <file path>
40579
40731
  FINDING: <error summary>
40580
40732
  \`\`\`
40581
- and continue. Sibling-scope failures do not block your story.
40582
-
40583
- ### Exception 4 \u2014 Mock-structure handoff
40733
+ and continue. Sibling-scope failures do not block your story.`, EXCEPTION_4_MOCK_HANDOFF = `### Exception 4 \u2014 Mock-structure handoff
40584
40734
 
40585
40735
  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.
40736
+ that does NOT fit Exception 2. Two cases qualify:
40588
40737
 
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
- \`\`\`
40738
+ (a) Existing mocks are wrong \u2014 mocks reference primitives the new code bypasses,
40739
+ or assertion topology must change to match a new dispatch shape.
40595
40740
 
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
40601
-
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.
40741
+ (b) Required test-infrastructure does not yet exist and must be introduced \u2014
40742
+ e.g. in-process fake servers, network-level request interception, hermetic
40743
+ fixture-backed HTTP, or equivalent. Applies whenever the AC describes a
40744
+ hermetic/fixture-backed test surface that the current test setup cannot
40745
+ satisfy without new infrastructure.
40605
40746
 
40606
40747
  Declare with:
40607
40748
  \`\`\`
40608
40749
  TEST_EDIT_REASON: mock_structure
40609
40750
  FILES: <comma-separated test file paths>
40610
- REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses>
40751
+ REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses,
40752
+ or what infrastructure must be introduced>
40611
40753
  \`\`\`
40612
40754
 
40613
40755
  Rules:
40614
40756
  - Do NOT make any edits yourself; the test-writer will fulfill.
40615
40757
  - 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;
40758
+ - 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
40759
  var init_rectifier_builder_helpers = __esm(() => {
40618
40760
  init_review();
40619
40761
  init_sections2();
40620
40762
  THREE_SESSION_STRATEGIES = new Set(["three-session-tdd", "three-session-tdd-lite"]);
40763
+ CONTRADICTION_ESCAPE_HATCH = buildEscapeHatch({ includeMockHandoff: false });
40621
40764
  });
40622
40765
 
40623
40766
  // src/prompts/builders/rectifier-builder.ts
@@ -40698,15 +40841,16 @@ function renderPrioritizedFailures(failedChecks, opts) {
40698
40841
  }
40699
40842
 
40700
40843
  class RectifierPromptBuilder {
40701
- static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel) {
40844
+ static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel, story) {
40702
40845
  const parts = [];
40703
40846
  const attemptWord = maxAttempts === 1 ? "1 attempt" : `${maxAttempts} attempts`;
40847
+ const exCount = story ? exceptionCountWord(story) : "three";
40704
40848
  parts.push(`Review failed after your implementation. Fix the following issues (${attemptWord} available before escalation):
40705
40849
  `);
40706
40850
  parts.push(renderPrioritizedFailures(failedChecks));
40707
40851
  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);
40852
+ 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.`);
40853
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40710
40854
  const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
40711
40855
  if (guardrails) {
40712
40856
  parts.push(`
@@ -40716,7 +40860,7 @@ ${guardrails}`);
40716
40860
  return parts.join(`
40717
40861
  `);
40718
40862
  }
40719
- static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel) {
40863
+ static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel, story) {
40720
40864
  const parts = [];
40721
40865
  parts.push(`Your previous fix attempt did not resolve all issues. Here are the remaining failures:
40722
40866
  `);
@@ -40729,7 +40873,7 @@ ${guardrails}`);
40729
40873
  if (attempt >= urgencyAtAttempt) {
40730
40874
  parts.push("\n**URGENT: This is your final attempt.** If you cannot fix all issues, emit `UNRESOLVED: <reason>` to escalate.\n");
40731
40875
  }
40732
- parts.push(CONTRADICTION_ESCAPE_HATCH);
40876
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40733
40877
  const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
40734
40878
  if (guardrails) {
40735
40879
  parts.push(`
@@ -40851,7 +40995,7 @@ ${importantNote}
40851
40995
 
40852
40996
  Commit your fixes when done.${scopeConstraint}`;
40853
40997
  }
40854
- static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts) {
40998
+ static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts, story) {
40855
40999
  const parts = [];
40856
41000
  parts.push(`**Your previous turn produced no committed file changes.**
40857
41001
 
@@ -40892,7 +41036,7 @@ ${output}
40892
41036
  `);
40893
41037
  }
40894
41038
  }
40895
- parts.push(CONTRADICTION_ESCAPE_HATCH);
41039
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40896
41040
  return parts.join("");
40897
41041
  }
40898
41042
  static escalated(failures, story, priorAttempts, originalTier, targetTier, config2, testCommand, testScopedTemplate) {
@@ -40968,11 +41112,11 @@ ${testCommands}
40968
41112
  6. Ensure ALL tests pass before completing.
40969
41113
 
40970
41114
  **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.
41115
+ - 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
41116
  - Do NOT loosen assertions to mask implementation bugs.
40973
41117
  - Focus on fixing the source code to meet the test requirements.
40974
41118
  - 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
- `;
41119
+ ${escapeHatchFor(story)}`;
40976
41120
  }
40977
41121
  static reviewRectification(failedChecks, story, opts) {
40978
41122
  const scopeConstraint = story.workdir ? `
@@ -41029,7 +41173,7 @@ ${llmSection}
41029
41173
  **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
41174
 
41031
41175
  Do NOT add new features \u2014 only fix the identified issues.
41032
- Commit your fixes when done.${scopeConstraint}${CONTRADICTION_ESCAPE_HATCH}`;
41176
+ Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
41033
41177
  }
41034
41178
  static dialogueAwareRectification(failedChecks, story, opts) {
41035
41179
  const scopeConstraint = story.workdir ? `
@@ -41068,9 +41212,9 @@ ${errors3}${reasoningSection}${historySection}
41068
41212
  2. Only fix findings that are actually valid problems
41069
41213
  3. Do NOT add keys, functions, or imports that already exist \u2014 check first
41070
41214
 
41071
- Do NOT change test files or test behavior \u2014 see the three narrow exceptions appended below.
41215
+ Do NOT change test files or test behavior \u2014 see the ${exceptionCountWord(story)} narrow exceptions appended below.
41072
41216
  Do NOT add new features \u2014 only fix valid issues.
41073
- Commit your fixes when done.${scopeConstraint}${CONTRADICTION_ESCAPE_HATCH}`;
41217
+ Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
41074
41218
  }
41075
41219
  static swapHandoff(basePrompt, pushMarkdown) {
41076
41220
  const trimmed = pushMarkdown?.trim();
@@ -41162,9 +41306,10 @@ Tests are failing. Fix the source so all tests pass \u2014 not just the ones lis
41162
41306
  4. Do not declare done until step 3 shows 0 failures.
41163
41307
 
41164
41308
  **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.
41309
+ - 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
41310
  - Do NOT loosen assertions to mask implementation bugs.
41167
41311
  - Focus on fixing the source code to meet the test requirements.`);
41312
+ parts.push(escapeHatchFor(opts.story));
41168
41313
  return parts.join("");
41169
41314
  }
41170
41315
  static failingTestContext(findings) {
@@ -42498,7 +42643,7 @@ var init_call = __esm(() => {
42498
42643
 
42499
42644
  // src/runtime/cost-aggregator.ts
42500
42645
  import { mkdirSync as mkdirSync2 } from "fs";
42501
- import { join as join24 } from "path";
42646
+ import { join as join25 } from "path";
42502
42647
  function makeCorrelationId() {
42503
42648
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
42504
42649
  }
@@ -42689,7 +42834,7 @@ class CostAggregator {
42689
42834
  if (events.length === 0 && errors3.length === 0)
42690
42835
  return;
42691
42836
  mkdirSync2(this._drainDir, { recursive: true });
42692
- const path5 = join24(this._drainDir, `${this._runId}.jsonl`);
42837
+ const path5 = join25(this._drainDir, `${this._runId}.jsonl`);
42693
42838
  const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
42694
42839
  await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
42695
42840
  `)}
@@ -42729,7 +42874,7 @@ var init_cost_aggregator = __esm(() => {
42729
42874
  // src/runtime/prompt-auditor.ts
42730
42875
  import { appendFileSync } from "fs";
42731
42876
  import { mkdir as mkdir4 } from "fs/promises";
42732
- import { join as join25 } from "path";
42877
+ import { join as join26 } from "path";
42733
42878
  function createNoOpPromptAuditor() {
42734
42879
  return {
42735
42880
  record() {},
@@ -42795,8 +42940,8 @@ class PromptAuditor {
42795
42940
  _jsonlPath;
42796
42941
  _featureDir;
42797
42942
  constructor(runId, flushDir, featureName) {
42798
- this._featureDir = join25(flushDir, featureName);
42799
- this._jsonlPath = join25(this._featureDir, `${runId}.jsonl`);
42943
+ this._featureDir = join26(flushDir, featureName);
42944
+ this._jsonlPath = join26(this._featureDir, `${runId}.jsonl`);
42800
42945
  }
42801
42946
  record(entry) {
42802
42947
  this._enqueue(entry);
@@ -42845,7 +42990,7 @@ class PromptAuditor {
42845
42990
  const auditEntry = entry;
42846
42991
  const filename = deriveTxtFilename(auditEntry);
42847
42992
  try {
42848
- await _promptAuditorDeps.write(join25(this._featureDir, filename), buildTxtContent(auditEntry));
42993
+ await _promptAuditorDeps.write(join26(this._featureDir, filename), buildTxtContent(auditEntry));
42849
42994
  } catch (err) {
42850
42995
  throw tagAuditError(err, "txt");
42851
42996
  }
@@ -43933,7 +44078,7 @@ var init_pid_registry = __esm(() => {
43933
44078
  // src/session/manager-deps.ts
43934
44079
  import { randomUUID as randomUUID3 } from "crypto";
43935
44080
  import { mkdir as mkdir5 } from "fs/promises";
43936
- import { isAbsolute as isAbsolute9, join as join26, relative as relative12, sep as sep3 } from "path";
44081
+ import { isAbsolute as isAbsolute9, join as join27, relative as relative12, sep as sep3 } from "path";
43937
44082
  function resolveProjectDirFromScratchDir(scratchDir) {
43938
44083
  const marker = `${sep3}.nax${sep3}features${sep3}`;
43939
44084
  const markerIdx = scratchDir.lastIndexOf(marker);
@@ -43954,7 +44099,7 @@ var init_manager_deps = __esm(() => {
43954
44099
  now: () => new Date().toISOString(),
43955
44100
  nowMs: () => Date.now(),
43956
44101
  uuid: () => randomUUID3(),
43957
- sessionScratchDir: (projectDir, featureName, sessionId) => join26(projectDir, ".nax", "features", featureName, "sessions", sessionId),
44102
+ sessionScratchDir: (projectDir, featureName, sessionId) => join27(projectDir, ".nax", "features", featureName, "sessions", sessionId),
43958
44103
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
43959
44104
  await mkdir5(scratchDir, { recursive: true });
43960
44105
  const { handle: _handle, ...persistable } = descriptor;
@@ -43965,7 +44110,7 @@ var init_manager_deps = __esm(() => {
43965
44110
  persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
43966
44111
  }
43967
44112
  }
43968
- await Bun.write(join26(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
44113
+ await Bun.write(join27(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
43969
44114
  }
43970
44115
  };
43971
44116
  });
@@ -44716,7 +44861,7 @@ __export(exports_runtime, {
44716
44861
  CostAggregator: () => CostAggregator,
44717
44862
  AgentStreamEventBus: () => AgentStreamEventBus
44718
44863
  });
44719
- import { basename as basename5, join as join27 } from "path";
44864
+ import { basename as basename5, join as join28 } from "path";
44720
44865
  function createRuntime(config2, workdir, opts) {
44721
44866
  const runId = crypto.randomUUID();
44722
44867
  const controller = new AbortController;
@@ -44732,10 +44877,10 @@ function createRuntime(config2, workdir, opts) {
44732
44877
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
44733
44878
  const globalDir = globalOutputDir();
44734
44879
  const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
44735
- const costDir = join27(outputDir, "cost");
44880
+ const costDir = join28(outputDir, "cost");
44736
44881
  const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
44737
44882
  const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
44738
- const auditDir = config2.agent?.promptAudit?.dir ?? join27(outputDir, "prompt-audit");
44883
+ const auditDir = config2.agent?.promptAudit?.dir ?? join28(outputDir, "prompt-audit");
44739
44884
  let promptAuditor;
44740
44885
  if (opts?.promptAuditor) {
44741
44886
  promptAuditor = opts.promptAuditor;
@@ -44886,9 +45031,9 @@ async function allSettledBounded(tasks, limit) {
44886
45031
 
44887
45032
  // src/context/injector.ts
44888
45033
  import { existsSync as existsSync8 } from "fs";
44889
- import { join as join28 } from "path";
45034
+ import { join as join29 } from "path";
44890
45035
  async function detectNode(workdir) {
44891
- const pkgPath = join28(workdir, "package.json");
45036
+ const pkgPath = join29(workdir, "package.json");
44892
45037
  if (!existsSync8(pkgPath))
44893
45038
  return null;
44894
45039
  try {
@@ -44905,7 +45050,7 @@ async function detectNode(workdir) {
44905
45050
  }
44906
45051
  }
44907
45052
  async function detectGo(workdir) {
44908
- const goMod = join28(workdir, "go.mod");
45053
+ const goMod = join29(workdir, "go.mod");
44909
45054
  if (!existsSync8(goMod))
44910
45055
  return null;
44911
45056
  try {
@@ -44929,7 +45074,7 @@ async function detectGo(workdir) {
44929
45074
  }
44930
45075
  }
44931
45076
  async function detectRust(workdir) {
44932
- const cargoPath = join28(workdir, "Cargo.toml");
45077
+ const cargoPath = join29(workdir, "Cargo.toml");
44933
45078
  if (!existsSync8(cargoPath))
44934
45079
  return null;
44935
45080
  try {
@@ -44945,8 +45090,8 @@ async function detectRust(workdir) {
44945
45090
  }
44946
45091
  }
44947
45092
  async function detectPython(workdir) {
44948
- const pyproject = join28(workdir, "pyproject.toml");
44949
- const requirements = join28(workdir, "requirements.txt");
45093
+ const pyproject = join29(workdir, "pyproject.toml");
45094
+ const requirements = join29(workdir, "requirements.txt");
44950
45095
  if (!existsSync8(pyproject) && !existsSync8(requirements))
44951
45096
  return null;
44952
45097
  try {
@@ -44965,7 +45110,7 @@ async function detectPython(workdir) {
44965
45110
  }
44966
45111
  }
44967
45112
  async function detectPhp(workdir) {
44968
- const composerPath = join28(workdir, "composer.json");
45113
+ const composerPath = join29(workdir, "composer.json");
44969
45114
  if (!existsSync8(composerPath))
44970
45115
  return null;
44971
45116
  try {
@@ -44978,7 +45123,7 @@ async function detectPhp(workdir) {
44978
45123
  }
44979
45124
  }
44980
45125
  async function detectRuby(workdir) {
44981
- const gemfile = join28(workdir, "Gemfile");
45126
+ const gemfile = join29(workdir, "Gemfile");
44982
45127
  if (!existsSync8(gemfile))
44983
45128
  return null;
44984
45129
  try {
@@ -44990,9 +45135,9 @@ async function detectRuby(workdir) {
44990
45135
  }
44991
45136
  }
44992
45137
  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");
45138
+ const pom = join29(workdir, "pom.xml");
45139
+ const gradle = join29(workdir, "build.gradle");
45140
+ const gradleKts = join29(workdir, "build.gradle.kts");
44996
45141
  if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
44997
45142
  return null;
44998
45143
  try {
@@ -45000,7 +45145,7 @@ async function detectJvm(workdir) {
45000
45145
  const content2 = await Bun.file(pom).text();
45001
45146
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
45002
45147
  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";
45148
+ const lang2 = existsSync8(join29(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
45004
45149
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
45005
45150
  }
45006
45151
  const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
@@ -45254,7 +45399,7 @@ var init_windsurf = __esm(() => {
45254
45399
 
45255
45400
  // src/context/generator.ts
45256
45401
  import { existsSync as existsSync9 } from "fs";
45257
- import { join as join29, relative as relative13 } from "path";
45402
+ import { join as join30, relative as relative13 } from "path";
45258
45403
  async function loadContextContent(options, config2) {
45259
45404
  if (!_generatorDeps.existsSync(options.contextPath)) {
45260
45405
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -45272,7 +45417,7 @@ async function generateFor(agent, options, config2) {
45272
45417
  try {
45273
45418
  const context = await loadContextContent(options, config2);
45274
45419
  const content = generator.generate(context);
45275
- const outputPath = join29(options.outputDir, generator.outputFile);
45420
+ const outputPath = join30(options.outputDir, generator.outputFile);
45276
45421
  validateFilePath(outputPath, options.outputDir);
45277
45422
  if (!options.dryRun) {
45278
45423
  await _generatorDeps.writeFile(outputPath, content);
@@ -45290,7 +45435,7 @@ async function generateAll(options, config2, agentFilter) {
45290
45435
  for (const [agentKey, generator] of entries) {
45291
45436
  try {
45292
45437
  const content = generator.generate(context);
45293
- const outputPath = join29(options.outputDir, generator.outputFile);
45438
+ const outputPath = join30(options.outputDir, generator.outputFile);
45294
45439
  validateFilePath(outputPath, options.outputDir);
45295
45440
  if (!options.dryRun) {
45296
45441
  await _generatorDeps.writeFile(outputPath, content);
@@ -45310,7 +45455,7 @@ async function discoverPackages(repoRoot) {
45310
45455
  const glob = new Bun.Glob(pattern);
45311
45456
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
45312
45457
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
45313
- const pkgAbsolute = join29(repoRoot, pkgRelative);
45458
+ const pkgAbsolute = join30(repoRoot, pkgRelative);
45314
45459
  if (!seen.has(pkgAbsolute)) {
45315
45460
  seen.add(pkgAbsolute);
45316
45461
  packages.push(pkgAbsolute);
@@ -45342,14 +45487,14 @@ async function discoverWorkspacePackages2(repoRoot) {
45342
45487
  }
45343
45488
  }
45344
45489
  }
45345
- const turboPath = join29(repoRoot, "turbo.json");
45490
+ const turboPath = join30(repoRoot, "turbo.json");
45346
45491
  try {
45347
45492
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
45348
45493
  if (Array.isArray(turbo.packages)) {
45349
45494
  await resolveGlobs(turbo.packages);
45350
45495
  }
45351
45496
  } catch {}
45352
- const pkgPath = join29(repoRoot, "package.json");
45497
+ const pkgPath = join30(repoRoot, "package.json");
45353
45498
  try {
45354
45499
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
45355
45500
  const ws = pkg.workspaces;
@@ -45357,7 +45502,7 @@ async function discoverWorkspacePackages2(repoRoot) {
45357
45502
  if (patterns.length > 0)
45358
45503
  await resolveGlobs(patterns);
45359
45504
  } catch {}
45360
- const pnpmPath = join29(repoRoot, "pnpm-workspace.yaml");
45505
+ const pnpmPath = join30(repoRoot, "pnpm-workspace.yaml");
45361
45506
  try {
45362
45507
  const raw = await _generatorDeps.readTextFile(pnpmPath);
45363
45508
  const lines = raw.split(`
@@ -45383,7 +45528,7 @@ async function discoverWorkspacePackages2(repoRoot) {
45383
45528
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
45384
45529
  const resolvedRepoRoot = repoRoot ?? packageDir;
45385
45530
  const relativePkgPath = relative13(resolvedRepoRoot, packageDir);
45386
- const contextPath = join29(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
45531
+ const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
45387
45532
  if (!_generatorDeps.existsSync(contextPath)) {
45388
45533
  return [
45389
45534
  {
@@ -45451,7 +45596,7 @@ var init_generator2 = __esm(() => {
45451
45596
  });
45452
45597
 
45453
45598
  // src/analyze/scanner.ts
45454
- import { join as join30 } from "path";
45599
+ import { join as join31 } from "path";
45455
45600
  function resolveFrameworkAndRunner(language, pkg) {
45456
45601
  if (language === "go")
45457
45602
  return { framework: "", testRunner: "go-test" };
@@ -45473,7 +45618,7 @@ async function scanSourceRoots(workdir) {
45473
45618
  });
45474
45619
  try {
45475
45620
  const language = await deps.detectLanguage(workdir);
45476
- const pkg = await deps.readPackageJson(join30(workdir, "package.json"));
45621
+ const pkg = await deps.readPackageJson(join31(workdir, "package.json"));
45477
45622
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
45478
45623
  return [{ path: ".", language, framework, testRunner }];
45479
45624
  } catch {
@@ -45491,9 +45636,9 @@ async function scanSourceRoots(workdir) {
45491
45636
  packages = packages.slice(0, MAX_SOURCE_ROOTS);
45492
45637
  }
45493
45638
  return Promise.all(packages.map(async (pkgPath) => {
45494
- const pkgDir = pkgPath === "." ? workdir : join30(workdir, pkgPath);
45639
+ const pkgDir = pkgPath === "." ? workdir : join31(workdir, pkgPath);
45495
45640
  const language = await deps.detectLanguage(pkgDir);
45496
- const pkg = await deps.readPackageJson(join30(pkgDir, "package.json"));
45641
+ const pkg = await deps.readPackageJson(join31(pkgDir, "package.json"));
45497
45642
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
45498
45643
  return { path: pkgPath, language, framework, testRunner };
45499
45644
  }));
@@ -45526,7 +45671,7 @@ var init_analyze = __esm(() => {
45526
45671
  });
45527
45672
 
45528
45673
  // src/debate/pre-phase/grounder.ts
45529
- import { join as join31 } from "path";
45674
+ import { join as join32 } from "path";
45530
45675
  async function buildCodebaseContext(workdir) {
45531
45676
  const roots = await _grounderDeps.scanSourceRoots(workdir);
45532
45677
  return buildSourceRootsSection(normalizeRoots(workdir, roots));
@@ -45538,7 +45683,7 @@ function normalizeRoots(workdir, roots) {
45538
45683
  }));
45539
45684
  }
45540
45685
  async function writeManifestArtifact(ctx, manifest) {
45541
- const manifestPath = join31(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
45686
+ const manifestPath = join32(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
45542
45687
  await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
45543
45688
  }
45544
45689
  var _grounderDeps, grounderStrategy = async (ctx) => {
@@ -45899,7 +46044,7 @@ function formatSpecDeltas(blockers, manifest) {
45899
46044
 
45900
46045
  // src/debate/verifiers/checks.ts
45901
46046
  import { existsSync as defaultExistsSync } from "fs";
45902
- import { join as join32 } from "path";
46047
+ import { join as join33 } from "path";
45903
46048
  function checkFilesExist(prd, workdir, deps) {
45904
46049
  const existsSync10 = deps?.existsSync ?? defaultExistsSync;
45905
46050
  const findings = [];
@@ -45909,7 +46054,7 @@ function checkFilesExist(prd, workdir, deps) {
45909
46054
  for (const entry of story.contextFiles) {
45910
46055
  const filePath = typeof entry === "string" ? entry : entry.path;
45911
46056
  const factId = typeof entry === "string" ? undefined : entry.factId;
45912
- const absPath = join32(workdir, filePath);
46057
+ const absPath = join33(workdir, filePath);
45913
46058
  if (existsSync10(absPath))
45914
46059
  continue;
45915
46060
  if (factId) {
@@ -46020,7 +46165,7 @@ var init_checks3 = () => {};
46020
46165
 
46021
46166
  // src/debate/verifiers/plan-checklist.ts
46022
46167
  import { existsSync as existsSync10 } from "fs";
46023
- import { join as join33 } from "path";
46168
+ import { join as join34 } from "path";
46024
46169
  function parsePrd(output) {
46025
46170
  if (!output)
46026
46171
  return null;
@@ -46031,7 +46176,7 @@ function parsePrd(output) {
46031
46176
  }
46032
46177
  }
46033
46178
  async function loadManifest(ctx) {
46034
- const manifestPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46179
+ const manifestPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
46035
46180
  const raw = await _planChecklistDeps.readFile(manifestPath);
46036
46181
  if (!raw)
46037
46182
  return null;
@@ -46044,7 +46189,7 @@ async function loadManifest(ctx) {
46044
46189
  }
46045
46190
  }
46046
46191
  async function emitSpecDeltas(ctx, blockers, manifest) {
46047
- const artifactPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
46192
+ const artifactPath = join34(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
46048
46193
  const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
46049
46194
  await _planChecklistDeps.write(artifactPath, content);
46050
46195
  return artifactPath;
@@ -46395,7 +46540,7 @@ var init_runner_plan_helpers = __esm(() => {
46395
46540
  });
46396
46541
 
46397
46542
  // src/debate/runner-plan.ts
46398
- import { join as join34 } from "path";
46543
+ import { join as join35 } from "path";
46399
46544
  async function runPlan(ctx, taskContext, outputFormat, opts) {
46400
46545
  const logger = _debateSessionDeps.getSafeLogger();
46401
46546
  const config2 = ctx.stageConfig;
@@ -46454,7 +46599,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
46454
46599
  sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
46455
46600
  proposers: ctx.stageConfig.proposers
46456
46601
  });
46457
- const outputPaths = resolved.map((_, i) => join34(opts.outputDir, `prd-debate-${i}.json`));
46602
+ const outputPaths = resolved.map((_, i) => join35(opts.outputDir, `prd-debate-${i}.json`));
46458
46603
  const successful = [];
46459
46604
  let rebuttalList;
46460
46605
  if (selectorKind === "verifier-pick") {
@@ -48702,9 +48847,9 @@ function validateFeatureName(feature) {
48702
48847
 
48703
48848
  // src/plan/critic.ts
48704
48849
  import { mkdir as mkdir6 } from "fs/promises";
48705
- import { dirname as dirname7, join as join37 } from "path";
48850
+ import { dirname as dirname7, join as join38 } from "path";
48706
48851
  async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
48707
- const path7 = join37(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
48852
+ const path7 = join38(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
48708
48853
  await mkdir6(dirname7(path7), { recursive: true });
48709
48854
  await Bun.write(path7, formatSpecDeltas(findings, manifest));
48710
48855
  return path7;
@@ -49917,9 +50062,9 @@ __export(exports_plan_decompose, {
49917
50062
  runReplanLoop: () => runReplanLoop,
49918
50063
  planDecomposeCommand: () => planDecomposeCommand
49919
50064
  });
49920
- import { join as join38 } from "path";
50065
+ import { join as join39 } from "path";
49921
50066
  async function planDecomposeCommand(workdir, config2, options) {
49922
- const prdPath = join38(workdir, ".nax", "features", options.feature, "prd.json");
50067
+ const prdPath = join39(workdir, ".nax", "features", options.feature, "prd.json");
49923
50068
  if (!_planDeps.existsSync(prdPath)) {
49924
50069
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
49925
50070
  stage: "decompose",
@@ -50093,7 +50238,7 @@ var init_plan_decompose = __esm(() => {
50093
50238
 
50094
50239
  // src/cli/plan-runtime.ts
50095
50240
  import { existsSync as existsSync15 } from "fs";
50096
- import { join as join39 } from "path";
50241
+ import { join as join40 } from "path";
50097
50242
  function isRuntimeWithAgentManager(value) {
50098
50243
  return typeof value === "object" && value !== null && "agentManager" in value;
50099
50244
  }
@@ -50145,7 +50290,7 @@ var init_plan_runtime = __esm(() => {
50145
50290
  writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
50146
50291
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
50147
50292
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
50148
- readPackageJson: (workdir) => Bun.file(join39(workdir, "package.json")).json().catch(() => null),
50293
+ readPackageJson: (workdir) => Bun.file(join40(workdir, "package.json")).json().catch(() => null),
50149
50294
  spawnSync: (cmd, opts) => {
50150
50295
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
50151
50296
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -50530,7 +50675,7 @@ var init_metrics = __esm(() => {
50530
50675
 
50531
50676
  // src/commands/common.ts
50532
50677
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
50533
- import { join as join40, resolve as resolve13 } from "path";
50678
+ import { join as join41, resolve as resolve13 } from "path";
50534
50679
  function resolveProject(options = {}) {
50535
50680
  const { dir, feature } = options;
50536
50681
  let projectRoot;
@@ -50538,12 +50683,12 @@ function resolveProject(options = {}) {
50538
50683
  let configPath;
50539
50684
  if (dir) {
50540
50685
  projectRoot = realpathSync3(resolve13(dir));
50541
- naxDir = join40(projectRoot, ".nax");
50686
+ naxDir = join41(projectRoot, ".nax");
50542
50687
  if (!existsSync16(naxDir)) {
50543
50688
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
50544
50689
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
50545
50690
  }
50546
- configPath = join40(naxDir, "config.json");
50691
+ configPath = join41(naxDir, "config.json");
50547
50692
  if (!existsSync16(configPath)) {
50548
50693
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
50549
50694
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -50551,17 +50696,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
50551
50696
  } else {
50552
50697
  const found = findProjectRoot(process.cwd());
50553
50698
  if (!found) {
50554
- const cwdNaxDir = join40(process.cwd(), ".nax");
50699
+ const cwdNaxDir = join41(process.cwd(), ".nax");
50555
50700
  if (existsSync16(cwdNaxDir)) {
50556
- const cwdConfigPath = join40(cwdNaxDir, "config.json");
50701
+ const cwdConfigPath = join41(cwdNaxDir, "config.json");
50557
50702
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
50558
50703
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
50559
50704
  }
50560
50705
  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
50706
  }
50562
50707
  projectRoot = found;
50563
- naxDir = join40(projectRoot, ".nax");
50564
- configPath = join40(naxDir, "config.json");
50708
+ naxDir = join41(projectRoot, ".nax");
50709
+ configPath = join41(naxDir, "config.json");
50565
50710
  }
50566
50711
  let featureDir;
50567
50712
  if (feature) {
@@ -50570,8 +50715,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
50570
50715
  } catch (error48) {
50571
50716
  throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
50572
50717
  }
50573
- const featuresDir = join40(naxDir, "features");
50574
- featureDir = join40(featuresDir, feature);
50718
+ const featuresDir = join41(naxDir, "features");
50719
+ featureDir = join41(featuresDir, feature);
50575
50720
  if (!existsSync16(featureDir)) {
50576
50721
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
50577
50722
  const availableMsg = availableFeatures.length > 0 ? `
@@ -50604,7 +50749,7 @@ async function resolveProjectAsync(options = {}) {
50604
50749
  }
50605
50750
  const isPlainName = !dir.includes("/") && !dir.includes("\\");
50606
50751
  if (isPlainName) {
50607
- const registryIdentityPath = join40(globalConfigDir(), dir, ".identity");
50752
+ const registryIdentityPath = join41(globalConfigDir(), dir, ".identity");
50608
50753
  const identityFile = Bun.file(registryIdentityPath);
50609
50754
  if (await identityFile.exists()) {
50610
50755
  try {
@@ -50636,12 +50781,12 @@ function findProjectRoot(startDir) {
50636
50781
  let current = resolve13(startDir);
50637
50782
  let depth = 0;
50638
50783
  while (depth < MAX_DIRECTORY_DEPTH) {
50639
- const naxDir = join40(current, ".nax");
50640
- const configPath = join40(naxDir, "config.json");
50784
+ const naxDir = join41(current, ".nax");
50785
+ const configPath = join41(naxDir, "config.json");
50641
50786
  if (existsSync16(configPath)) {
50642
50787
  return realpathSync3(current);
50643
50788
  }
50644
- const parent = join40(current, "..");
50789
+ const parent = join41(current, "..");
50645
50790
  if (parent === current) {
50646
50791
  break;
50647
50792
  }
@@ -51800,10 +51945,10 @@ var init_effectiveness = __esm(() => {
51800
51945
 
51801
51946
  // src/execution/progress.ts
51802
51947
  import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
51803
- import { join as join43 } from "path";
51948
+ import { join as join44 } from "path";
51804
51949
  async function appendProgress(featureDir, storyId, status, message) {
51805
51950
  await mkdir7(featureDir, { recursive: true });
51806
- const progressPath = join43(featureDir, "progress.txt");
51951
+ const progressPath = join44(featureDir, "progress.txt");
51807
51952
  const timestamp = new Date().toISOString();
51808
51953
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
51809
51954
  `;
@@ -51992,7 +52137,7 @@ var init_completion = __esm(() => {
51992
52137
 
51993
52138
  // src/constitution/loader.ts
51994
52139
  import { existsSync as existsSync19 } from "fs";
51995
- import { join as join44 } from "path";
52140
+ import { join as join45 } from "path";
51996
52141
  function truncateToTokens(text, maxTokens) {
51997
52142
  const maxChars = maxTokens * 3;
51998
52143
  if (text.length <= maxChars) {
@@ -52014,7 +52159,7 @@ async function loadConstitution(projectDir, config2) {
52014
52159
  }
52015
52160
  let combinedContent = "";
52016
52161
  if (!config2.skipGlobal) {
52017
- const globalPath = join44(globalConfigDir(), config2.path);
52162
+ const globalPath = join45(globalConfigDir(), config2.path);
52018
52163
  if (existsSync19(globalPath)) {
52019
52164
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
52020
52165
  const globalFile = Bun.file(validatedPath);
@@ -52024,7 +52169,7 @@ async function loadConstitution(projectDir, config2) {
52024
52169
  }
52025
52170
  }
52026
52171
  }
52027
- const projectPath = join44(projectDir, config2.path);
52172
+ const projectPath = join45(projectDir, config2.path);
52028
52173
  if (existsSync19(projectPath)) {
52029
52174
  const validatedPath = validateFilePath(projectPath, projectDir);
52030
52175
  const projectFile = Bun.file(validatedPath);
@@ -52847,7 +52992,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52847
52992
  continue;
52848
52993
  findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
52849
52994
  }
52850
- return findings;
52995
+ return rectification2.postValidate ? await rectification2.postValidate(findings, _validateCtx) : findings;
52851
52996
  }
52852
52997
  };
52853
52998
  const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
@@ -52861,7 +53006,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52861
53006
  "max-attempts-total",
52862
53007
  "max-attempts-per-strategy",
52863
53008
  "bail-when",
52864
- "no-strategy"
53009
+ "no-strategy",
53010
+ "agent-gave-up"
52865
53011
  ]);
52866
53012
  if (exhaustedReasons.has(cycleResult.exitReason) && cycleResult.finalFindings.length > 0) {
52867
53013
  return { rectificationExhausted: true, unfixedFindings: cycleResult.finalFindings };
@@ -53046,6 +53192,7 @@ var init_story_orchestrator = __esm(() => {
53046
53192
  });
53047
53193
 
53048
53194
  // src/execution/build-plan-for-strategy.ts
53195
+ import { join as join46 } from "path";
53049
53196
  function isThreeSessionStrategy(strategy) {
53050
53197
  return THREE_SESSION_STRATEGIES2.has(strategy);
53051
53198
  }
@@ -53057,7 +53204,7 @@ function isFreshRun(story) {
53057
53204
  const hasReviewEscalation = (story.priorFailures ?? []).some((f) => f.stage === "review");
53058
53205
  return !hasAttempts && !hasReviewEscalation;
53059
53206
  }
53060
- function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53207
+ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53061
53208
  const isThreeSession = isThreeSessionStrategy(testStrategy);
53062
53209
  const freshRun = isFreshRun(story);
53063
53210
  const builder = new StoryOrchestratorBuilder;
@@ -53092,6 +53239,9 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53092
53239
  builder.addAdversarialReview(inputs.adversarialReview);
53093
53240
  }
53094
53241
  if (shouldRunRectification(config2) && inputs.rectification) {
53242
+ const sink = makeDeclarationSink();
53243
+ const packageDir = join46(ctx.packageDir, story.workdir ?? "");
53244
+ const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
53095
53245
  const strategies = [];
53096
53246
  if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
53097
53247
  strategies.push(makeMechanicalLintFixStrategy());
@@ -53103,12 +53253,28 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53103
53253
  strategies.push(makeFullSuiteRectifyStrategy(story, config2));
53104
53254
  }
53105
53255
  if (config2.quality.autofix?.enabled !== false) {
53106
- strategies.push(makeAutofixImplementerStrategy(story, config2));
53107
- strategies.push(makeAutofixTestWriterStrategy(story, config2));
53108
- }
53256
+ strategies.push(makeAutofixImplementerStrategy(story, config2, sink));
53257
+ strategies.push(makeAutofixTestWriterStrategy(story, config2, sink));
53258
+ }
53259
+ const postValidate = async (findings, _validateCtx) => {
53260
+ if (sink.testEdits.length === 0 && sink.mockHandoffs.length === 0)
53261
+ return findings;
53262
+ const pendingMock = sink.mockHandoffs.map((h) => ({
53263
+ reason: "mock_structure",
53264
+ file: h.files[0] ?? "",
53265
+ files: h.files,
53266
+ reasonDetail: h.reasonDetail
53267
+ }));
53268
+ const { valid, invalid } = await validateMockStructureFiles(pendingMock, resolvedTestPatterns, packageDir);
53269
+ sink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
53270
+ const allDeclarations = [...sink.testEdits, ...valid];
53271
+ sink.testEdits = [];
53272
+ return applyTestEditDeclarations(findings, allDeclarations, story, invalid);
53273
+ };
53109
53274
  const rectOpts = {
53110
53275
  ...inputs.rectification,
53111
- strategies: [...strategies, ...inputs.rectification.strategies]
53276
+ strategies: [...strategies, ...inputs.rectification.strategies],
53277
+ postValidate
53112
53278
  };
53113
53279
  builder.addRectification(rectOpts);
53114
53280
  }
@@ -53119,6 +53285,7 @@ var init_build_plan_for_strategy = __esm(() => {
53119
53285
  init_operations();
53120
53286
  init_execution_gates();
53121
53287
  init_full_suite_rectify();
53288
+ init_test_runners();
53122
53289
  init_story_orchestrator();
53123
53290
  THREE_SESSION_STRATEGIES2 = new Set(["three-session-tdd", "three-session-tdd-lite"]);
53124
53291
  });
@@ -53837,7 +54004,7 @@ var init_execution = __esm(() => {
53837
54004
  } : null;
53838
54005
  const initialRef = tddMode ? await _executionDeps.captureGitRef(ctx.workdir) ?? "HEAD" : null;
53839
54006
  const inputs = await _executionDeps.assemblePlanInputsFromCtx(ctx);
53840
- const plan = buildPlanForStrategy(callCtx, ctx.story, ctx.config, ctx.routing.testStrategy, inputs);
54007
+ const plan = await buildPlanForStrategy(callCtx, ctx.story, ctx.config, ctx.routing.testStrategy, inputs);
53841
54008
  let planResult;
53842
54009
  try {
53843
54010
  planResult = await plan.run();
@@ -54784,7 +54951,7 @@ function buildFrontmatter(story, ctx, role) {
54784
54951
  }
54785
54952
 
54786
54953
  // src/cli/prompts-tdd.ts
54787
- import { join as join45 } from "path";
54954
+ import { join as join47 } from "path";
54788
54955
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
54789
54956
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
54790
54957
  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 +54970,7 @@ ${frontmatter}---
54803
54970
 
54804
54971
  ${session.prompt}`;
54805
54972
  if (outputDir) {
54806
- const promptFile = join45(outputDir, `${story.id}.${session.role}.md`);
54973
+ const promptFile = join47(outputDir, `${story.id}.${session.role}.md`);
54807
54974
  await Bun.write(promptFile, fullOutput);
54808
54975
  logger.info("cli", "Written TDD prompt file", {
54809
54976
  storyId: story.id,
@@ -54819,7 +54986,7 @@ ${"=".repeat(80)}`);
54819
54986
  }
54820
54987
  }
54821
54988
  if (outputDir && ctx.contextMarkdown) {
54822
- const contextFile = join45(outputDir, `${story.id}.context.md`);
54989
+ const contextFile = join47(outputDir, `${story.id}.context.md`);
54823
54990
  const frontmatter = buildFrontmatter(story, ctx);
54824
54991
  const contextOutput = `---
54825
54992
  ${frontmatter}---
@@ -54834,16 +55001,16 @@ var init_prompts_tdd = __esm(() => {
54834
55001
 
54835
55002
  // src/cli/prompts-main.ts
54836
55003
  import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
54837
- import { join as join46 } from "path";
55004
+ import { join as join48 } from "path";
54838
55005
  async function promptsCommand(options) {
54839
55006
  const logger = getLogger();
54840
55007
  const { feature, workdir, config: config2, storyId, outputDir } = options;
54841
- const naxDir = join46(workdir, ".nax");
55008
+ const naxDir = join48(workdir, ".nax");
54842
55009
  if (!existsSync20(naxDir)) {
54843
55010
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
54844
55011
  }
54845
- const featureDir = join46(naxDir, "features", feature);
54846
- const prdPath = join46(featureDir, "prd.json");
55012
+ const featureDir = join48(naxDir, "features", feature);
55013
+ const prdPath = join48(featureDir, "prd.json");
54847
55014
  if (!existsSync20(prdPath)) {
54848
55015
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
54849
55016
  }
@@ -54910,10 +55077,10 @@ ${frontmatter}---
54910
55077
 
54911
55078
  ${ctx.prompt}`;
54912
55079
  if (outputDir) {
54913
- const promptFile = join46(outputDir, `${story.id}.prompt.md`);
55080
+ const promptFile = join48(outputDir, `${story.id}.prompt.md`);
54914
55081
  await Bun.write(promptFile, fullOutput);
54915
55082
  if (ctx.contextMarkdown) {
54916
- const contextFile = join46(outputDir, `${story.id}.context.md`);
55083
+ const contextFile = join48(outputDir, `${story.id}.context.md`);
54917
55084
  const contextOutput = `---
54918
55085
  ${frontmatter}---
54919
55086
 
@@ -54949,12 +55116,12 @@ var init_prompts_main = __esm(() => {
54949
55116
 
54950
55117
  // src/cli/prompts-init.ts
54951
55118
  import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
54952
- import { join as join47 } from "path";
55119
+ import { join as join49 } from "path";
54953
55120
  async function promptsInitCommand(options) {
54954
55121
  const { workdir, force = false, autoWireConfig = true } = options;
54955
- const templatesDir = join47(workdir, ".nax", "templates");
55122
+ const templatesDir = join49(workdir, ".nax", "templates");
54956
55123
  mkdirSync4(templatesDir, { recursive: true });
54957
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join47(templatesDir, f)));
55124
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join49(templatesDir, f)));
54958
55125
  if (existingFiles.length > 0 && !force) {
54959
55126
  _promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
54960
55127
  Pass --force to overwrite existing templates.`);
@@ -54962,7 +55129,7 @@ async function promptsInitCommand(options) {
54962
55129
  }
54963
55130
  const written = [];
54964
55131
  for (const template of TEMPLATE_ROLES) {
54965
- const filePath = join47(templatesDir, template.file);
55132
+ const filePath = join49(templatesDir, template.file);
54966
55133
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
54967
55134
  const content = TEMPLATE_HEADER + roleBody;
54968
55135
  await Bun.write(filePath, content);
@@ -54978,7 +55145,7 @@ async function promptsInitCommand(options) {
54978
55145
  return written;
54979
55146
  }
54980
55147
  async function autoWirePromptsConfig(workdir) {
54981
- const configPath = join47(workdir, "nax.config.json");
55148
+ const configPath = join49(workdir, "nax.config.json");
54982
55149
  if (!existsSync21(configPath)) {
54983
55150
  const exampleConfig = JSON.stringify({
54984
55151
  prompts: {
@@ -55148,7 +55315,7 @@ __export(exports_init_context, {
55148
55315
  });
55149
55316
  import { existsSync as existsSync22 } from "fs";
55150
55317
  import { mkdir as mkdir8 } from "fs/promises";
55151
- import { basename as basename9, join as join48 } from "path";
55318
+ import { basename as basename9, join as join50 } from "path";
55152
55319
  async function findFiles(dir, maxFiles = 200) {
55153
55320
  try {
55154
55321
  const proc = Bun.spawnSync([
@@ -55176,7 +55343,7 @@ async function findFiles(dir, maxFiles = 200) {
55176
55343
  return [];
55177
55344
  }
55178
55345
  async function readPackageManifest(projectRoot) {
55179
- const packageJsonPath = join48(projectRoot, "package.json");
55346
+ const packageJsonPath = join50(projectRoot, "package.json");
55180
55347
  if (!existsSync22(packageJsonPath)) {
55181
55348
  return null;
55182
55349
  }
@@ -55194,7 +55361,7 @@ async function readPackageManifest(projectRoot) {
55194
55361
  }
55195
55362
  }
55196
55363
  async function readReadmeSnippet(projectRoot) {
55197
- const readmePath = join48(projectRoot, "README.md");
55364
+ const readmePath = join50(projectRoot, "README.md");
55198
55365
  if (!existsSync22(readmePath)) {
55199
55366
  return null;
55200
55367
  }
@@ -55212,7 +55379,7 @@ async function detectEntryPoints(projectRoot) {
55212
55379
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
55213
55380
  const found = [];
55214
55381
  for (const candidate of candidates) {
55215
- const path14 = join48(projectRoot, candidate);
55382
+ const path14 = join50(projectRoot, candidate);
55216
55383
  if (existsSync22(path14)) {
55217
55384
  found.push(candidate);
55218
55385
  }
@@ -55223,7 +55390,7 @@ async function detectConfigFiles(projectRoot) {
55223
55390
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
55224
55391
  const found = [];
55225
55392
  for (const candidate of candidates) {
55226
- const path14 = join48(projectRoot, candidate);
55393
+ const path14 = join50(projectRoot, candidate);
55227
55394
  if (existsSync22(path14)) {
55228
55395
  found.push(candidate);
55229
55396
  }
@@ -55384,8 +55551,8 @@ function generatePackageContextTemplate(packagePath) {
55384
55551
  }
55385
55552
  async function initPackage(repoRoot, packagePath, force = false) {
55386
55553
  const logger = getLogger();
55387
- const naxDir = join48(repoRoot, ".nax", "mono", packagePath);
55388
- const contextPath = join48(naxDir, "context.md");
55554
+ const naxDir = join50(repoRoot, ".nax", "mono", packagePath);
55555
+ const contextPath = join50(naxDir, "context.md");
55389
55556
  if (existsSync22(contextPath) && !force) {
55390
55557
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
55391
55558
  return;
@@ -55399,8 +55566,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
55399
55566
  }
55400
55567
  async function initContext(projectRoot, options = {}) {
55401
55568
  const logger = getLogger();
55402
- const naxDir = join48(projectRoot, ".nax");
55403
- const contextPath = join48(naxDir, "context.md");
55569
+ const naxDir = join50(projectRoot, ".nax");
55570
+ const contextPath = join50(naxDir, "context.md");
55404
55571
  if (existsSync22(contextPath) && !options.force) {
55405
55572
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
55406
55573
  return;
@@ -55430,9 +55597,9 @@ var init_init_context = __esm(() => {
55430
55597
 
55431
55598
  // src/cli/init-detect.ts
55432
55599
  import { existsSync as existsSync23, readFileSync } from "fs";
55433
- import { join as join49 } from "path";
55600
+ import { join as join51 } from "path";
55434
55601
  function readPackageJson(projectRoot) {
55435
- const pkgPath = join49(projectRoot, "package.json");
55602
+ const pkgPath = join51(projectRoot, "package.json");
55436
55603
  if (!existsSync23(pkgPath))
55437
55604
  return;
55438
55605
  try {
@@ -55475,41 +55642,41 @@ function detectStack(projectRoot) {
55475
55642
  };
55476
55643
  }
55477
55644
  function detectRuntime(projectRoot) {
55478
- if (existsSync23(join49(projectRoot, "bun.lockb")) || existsSync23(join49(projectRoot, "bunfig.toml"))) {
55645
+ if (existsSync23(join51(projectRoot, "bun.lockb")) || existsSync23(join51(projectRoot, "bunfig.toml"))) {
55479
55646
  return "bun";
55480
55647
  }
55481
- if (existsSync23(join49(projectRoot, "package-lock.json")) || existsSync23(join49(projectRoot, "yarn.lock")) || existsSync23(join49(projectRoot, "pnpm-lock.yaml"))) {
55648
+ if (existsSync23(join51(projectRoot, "package-lock.json")) || existsSync23(join51(projectRoot, "yarn.lock")) || existsSync23(join51(projectRoot, "pnpm-lock.yaml"))) {
55482
55649
  return "node";
55483
55650
  }
55484
55651
  return "unknown";
55485
55652
  }
55486
55653
  function detectLanguage2(projectRoot) {
55487
- if (existsSync23(join49(projectRoot, "tsconfig.json")))
55654
+ if (existsSync23(join51(projectRoot, "tsconfig.json")))
55488
55655
  return "typescript";
55489
- if (existsSync23(join49(projectRoot, "pyproject.toml")) || existsSync23(join49(projectRoot, "setup.py"))) {
55656
+ if (existsSync23(join51(projectRoot, "pyproject.toml")) || existsSync23(join51(projectRoot, "setup.py"))) {
55490
55657
  return "python";
55491
55658
  }
55492
- if (existsSync23(join49(projectRoot, "Cargo.toml")))
55659
+ if (existsSync23(join51(projectRoot, "Cargo.toml")))
55493
55660
  return "rust";
55494
- if (existsSync23(join49(projectRoot, "go.mod")))
55661
+ if (existsSync23(join51(projectRoot, "go.mod")))
55495
55662
  return "go";
55496
55663
  return "unknown";
55497
55664
  }
55498
55665
  function detectLinter(projectRoot) {
55499
- if (existsSync23(join49(projectRoot, "biome.json")) || existsSync23(join49(projectRoot, "biome.jsonc"))) {
55666
+ if (existsSync23(join51(projectRoot, "biome.json")) || existsSync23(join51(projectRoot, "biome.jsonc"))) {
55500
55667
  return "biome";
55501
55668
  }
55502
- if (existsSync23(join49(projectRoot, ".eslintrc.json")) || existsSync23(join49(projectRoot, ".eslintrc.js")) || existsSync23(join49(projectRoot, "eslint.config.js"))) {
55669
+ if (existsSync23(join51(projectRoot, ".eslintrc.json")) || existsSync23(join51(projectRoot, ".eslintrc.js")) || existsSync23(join51(projectRoot, "eslint.config.js"))) {
55503
55670
  return "eslint";
55504
55671
  }
55505
55672
  return "unknown";
55506
55673
  }
55507
55674
  function detectMonorepo(projectRoot) {
55508
- if (existsSync23(join49(projectRoot, "turbo.json")))
55675
+ if (existsSync23(join51(projectRoot, "turbo.json")))
55509
55676
  return "turborepo";
55510
- if (existsSync23(join49(projectRoot, "nx.json")))
55677
+ if (existsSync23(join51(projectRoot, "nx.json")))
55511
55678
  return "nx";
55512
- if (existsSync23(join49(projectRoot, "pnpm-workspace.yaml")))
55679
+ if (existsSync23(join51(projectRoot, "pnpm-workspace.yaml")))
55513
55680
  return "pnpm-workspaces";
55514
55681
  const pkg = readPackageJson(projectRoot);
55515
55682
  if (pkg?.workspaces)
@@ -55653,7 +55820,7 @@ __export(exports_init, {
55653
55820
  });
55654
55821
  import { existsSync as existsSync24 } from "fs";
55655
55822
  import { mkdir as mkdir9 } from "fs/promises";
55656
- import { join as join50 } from "path";
55823
+ import { join as join52 } from "path";
55657
55824
  function validateProjectName(name) {
55658
55825
  if (!name)
55659
55826
  return { valid: false, error: "name must be non-empty" };
@@ -55689,7 +55856,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
55689
55856
  }
55690
55857
  async function updateGitignore(projectRoot) {
55691
55858
  const logger = getLogger();
55692
- const gitignorePath = join50(projectRoot, ".gitignore");
55859
+ const gitignorePath = join52(projectRoot, ".gitignore");
55693
55860
  let existing = "";
55694
55861
  if (existsSync24(gitignorePath)) {
55695
55862
  existing = await Bun.file(gitignorePath).text();
@@ -55775,7 +55942,7 @@ async function initGlobal() {
55775
55942
  await mkdir9(globalDir, { recursive: true });
55776
55943
  logger.info("init", "Created global config directory", { path: globalDir });
55777
55944
  }
55778
- const configPath = join50(globalDir, "config.json");
55945
+ const configPath = join52(globalDir, "config.json");
55779
55946
  if (!existsSync24(configPath)) {
55780
55947
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
55781
55948
  `);
@@ -55783,14 +55950,14 @@ async function initGlobal() {
55783
55950
  } else {
55784
55951
  logger.info("init", "Global config already exists", { path: configPath });
55785
55952
  }
55786
- const constitutionPath = join50(globalDir, "constitution.md");
55953
+ const constitutionPath = join52(globalDir, "constitution.md");
55787
55954
  if (!existsSync24(constitutionPath)) {
55788
55955
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
55789
55956
  logger.info("init", "Created global constitution", { path: constitutionPath });
55790
55957
  } else {
55791
55958
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
55792
55959
  }
55793
- const hooksDir = join50(globalDir, "hooks");
55960
+ const hooksDir = join52(globalDir, "hooks");
55794
55961
  if (!existsSync24(hooksDir)) {
55795
55962
  await mkdir9(hooksDir, { recursive: true });
55796
55963
  logger.info("init", "Created global hooks directory", { path: hooksDir });
@@ -55823,7 +55990,7 @@ async function initProject(projectRoot, options) {
55823
55990
  if (detectedName && !options?.force) {
55824
55991
  const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
55825
55992
  if (collision.collision && collision.existing) {
55826
- const configPath2 = join50(projectDir, "config.json");
55993
+ const configPath2 = join52(projectDir, "config.json");
55827
55994
  throw new NaxError([
55828
55995
  `Project name collision: "${detectedName}"`,
55829
55996
  ` This project: ${projectRoot}`,
@@ -55851,7 +56018,7 @@ async function initProject(projectRoot, options) {
55851
56018
  linter: stack.linter,
55852
56019
  monorepo: stack.monorepo
55853
56020
  });
55854
- const configPath = join50(projectDir, "config.json");
56021
+ const configPath = join52(projectDir, "config.json");
55855
56022
  if (!existsSync24(configPath)) {
55856
56023
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
55857
56024
  `);
@@ -55860,14 +56027,14 @@ async function initProject(projectRoot, options) {
55860
56027
  logger.info("init", "Project config already exists", { path: configPath });
55861
56028
  }
55862
56029
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
55863
- const constitutionPath = join50(projectDir, "constitution.md");
56030
+ const constitutionPath = join52(projectDir, "constitution.md");
55864
56031
  if (!existsSync24(constitutionPath) || options?.force) {
55865
56032
  await Bun.write(constitutionPath, buildConstitution(stack));
55866
56033
  logger.info("init", "Created project constitution", { path: constitutionPath });
55867
56034
  } else {
55868
56035
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
55869
56036
  }
55870
- const hooksDir = join50(projectDir, "hooks");
56037
+ const hooksDir = join52(projectDir, "hooks");
55871
56038
  if (!existsSync24(hooksDir)) {
55872
56039
  await mkdir9(hooksDir, { recursive: true });
55873
56040
  logger.info("init", "Created project hooks directory", { path: hooksDir });
@@ -57302,12 +57469,12 @@ var init_loader4 = __esm(() => {
57302
57469
  });
57303
57470
 
57304
57471
  // src/utils/paths.ts
57305
- import { join as join62 } from "path";
57472
+ import { join as join64 } from "path";
57306
57473
  function getRunsDir() {
57307
- return process.env.NAX_RUNS_DIR ?? join62(globalConfigDir(), "runs");
57474
+ return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
57308
57475
  }
57309
57476
  function getEventsRootDir() {
57310
- return join62(globalConfigDir(), "events");
57477
+ return join64(globalConfigDir(), "events");
57311
57478
  }
57312
57479
  var init_paths3 = __esm(() => {
57313
57480
  init_paths();
@@ -57367,7 +57534,7 @@ var init_command_argv = __esm(() => {
57367
57534
  });
57368
57535
 
57369
57536
  // src/hooks/runner.ts
57370
- import { join as join69 } from "path";
57537
+ import { join as join71 } from "path";
57371
57538
  function createDrainDeadline2(deadlineMs) {
57372
57539
  let timeoutId;
57373
57540
  const promise2 = new Promise((resolve16) => {
@@ -57386,14 +57553,14 @@ async function loadHooksConfig(projectDir, globalDir) {
57386
57553
  let globalHooks = { hooks: {} };
57387
57554
  let projectHooks = { hooks: {} };
57388
57555
  let skipGlobal = false;
57389
- const projectPath = join69(projectDir, "hooks.json");
57556
+ const projectPath = join71(projectDir, "hooks.json");
57390
57557
  const projectData = await loadJsonFile(projectPath, "hooks");
57391
57558
  if (projectData) {
57392
57559
  projectHooks = projectData;
57393
57560
  skipGlobal = projectData.skipGlobal ?? false;
57394
57561
  }
57395
57562
  if (!skipGlobal && globalDir) {
57396
- const globalPath = join69(globalDir, "hooks.json");
57563
+ const globalPath = join71(globalDir, "hooks.json");
57397
57564
  const globalData = await loadJsonFile(globalPath, "hooks");
57398
57565
  if (globalData) {
57399
57566
  globalHooks = globalData;
@@ -57563,7 +57730,7 @@ var package_default;
57563
57730
  var init_package = __esm(() => {
57564
57731
  package_default = {
57565
57732
  name: "@nathapp/nax",
57566
- version: "0.67.11",
57733
+ version: "0.67.12",
57567
57734
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
57568
57735
  type: "module",
57569
57736
  bin: {
@@ -57658,8 +57825,8 @@ var init_version = __esm(() => {
57658
57825
  NAX_VERSION = package_default.version;
57659
57826
  NAX_COMMIT = (() => {
57660
57827
  try {
57661
- if (/^[0-9a-f]{6,10}$/.test("0db5c72e"))
57662
- return "0db5c72e";
57828
+ if (/^[0-9a-f]{6,10}$/.test("c747dea2"))
57829
+ return "c747dea2";
57663
57830
  } catch {}
57664
57831
  try {
57665
57832
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -58528,15 +58695,15 @@ var init_acceptance_loop = __esm(() => {
58528
58695
 
58529
58696
  // src/session/scratch-purge.ts
58530
58697
  import { mkdir as mkdir13, rename, rm } from "fs/promises";
58531
- import { dirname as dirname12, join as join70 } from "path";
58698
+ import { dirname as dirname12, join as join72 } from "path";
58532
58699
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
58533
- const sessionsDir = join70(projectDir, ".nax", "features", featureName, "sessions");
58700
+ const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
58534
58701
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
58535
58702
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
58536
58703
  let purged = 0;
58537
58704
  for (const sessionId of sessionIds) {
58538
- const sessionDir = join70(sessionsDir, sessionId);
58539
- const descriptorPath = join70(sessionDir, "descriptor.json");
58705
+ const sessionDir = join72(sessionsDir, sessionId);
58706
+ const descriptorPath = join72(sessionDir, "descriptor.json");
58540
58707
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
58541
58708
  continue;
58542
58709
  let lastActivityAt;
@@ -58552,7 +58719,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
58552
58719
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
58553
58720
  continue;
58554
58721
  if (archiveInsteadOfDelete) {
58555
- const archiveDest = join70(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
58722
+ const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
58556
58723
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
58557
58724
  } else {
58558
58725
  await _scratchPurgeDeps.remove(sessionDir);
@@ -59261,12 +59428,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
59261
59428
 
59262
59429
  // src/pipeline/subscribers/events-writer.ts
59263
59430
  import { appendFile as appendFile5, mkdir as mkdir14 } from "fs/promises";
59264
- import { basename as basename14, join as join71 } from "path";
59431
+ import { basename as basename14, join as join73 } from "path";
59265
59432
  function wireEventsWriter(bus, feature, runId, workdir) {
59266
59433
  const logger = getSafeLogger();
59267
59434
  const project = basename14(workdir);
59268
- const eventsDir = join71(getEventsRootDir(), project);
59269
- const eventsFile = join71(eventsDir, "events.jsonl");
59435
+ const eventsDir = join73(getEventsRootDir(), project);
59436
+ const eventsFile = join73(eventsDir, "events.jsonl");
59270
59437
  let dirReady = false;
59271
59438
  const write = (line) => {
59272
59439
  return (async () => {
@@ -59447,12 +59614,12 @@ var init_interaction2 = __esm(() => {
59447
59614
 
59448
59615
  // src/pipeline/subscribers/registry.ts
59449
59616
  import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
59450
- import { basename as basename15, join as join72 } from "path";
59617
+ import { basename as basename15, join as join74 } from "path";
59451
59618
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
59452
59619
  const logger = getSafeLogger();
59453
59620
  const project = basename15(workdir);
59454
- const runDir = join72(getRunsDir(), `${project}-${feature}-${runId}`);
59455
- const metaFile = join72(runDir, "meta.json");
59621
+ const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
59622
+ const metaFile = join74(runDir, "meta.json");
59456
59623
  const unsub = bus.on("run:started", (_ev) => {
59457
59624
  return (async () => {
59458
59625
  try {
@@ -59462,8 +59629,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
59462
59629
  project,
59463
59630
  feature,
59464
59631
  workdir,
59465
- statusPath: join72(outputDir, "features", feature, "status.json"),
59466
- eventsDir: join72(outputDir, "features", feature, "runs"),
59632
+ statusPath: join74(outputDir, "features", feature, "status.json"),
59633
+ eventsDir: join74(outputDir, "features", feature, "runs"),
59467
59634
  registeredAt: new Date().toISOString()
59468
59635
  };
59469
59636
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -59709,7 +59876,7 @@ var init_types9 = __esm(() => {
59709
59876
 
59710
59877
  // src/worktree/dependencies.ts
59711
59878
  import { existsSync as existsSync32 } from "fs";
59712
- import { join as join73 } from "path";
59879
+ import { join as join75 } from "path";
59713
59880
  async function prepareWorktreeDependencies(options) {
59714
59881
  const mode = options.config.execution.worktreeDependencies.mode;
59715
59882
  const resolvedCwd = resolveDependencyCwd(options);
@@ -59723,7 +59890,7 @@ async function prepareWorktreeDependencies(options) {
59723
59890
  }
59724
59891
  }
59725
59892
  function resolveDependencyCwd(options) {
59726
- return options.storyWorkdir ? join73(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
59893
+ return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
59727
59894
  }
59728
59895
  function resolveInheritedDependencies(options, resolvedCwd) {
59729
59896
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -59733,7 +59900,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
59733
59900
  }
59734
59901
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
59735
59902
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
59736
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join73(directory, filename))));
59903
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
59737
59904
  }
59738
59905
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
59739
59906
  const setupCommand = config2.execution.worktreeDependencies.setupCommand;
@@ -59797,13 +59964,13 @@ __export(exports_manager, {
59797
59964
  });
59798
59965
  import { existsSync as existsSync33, symlinkSync } from "fs";
59799
59966
  import { mkdir as mkdir16 } from "fs/promises";
59800
- import { join as join74 } from "path";
59967
+ import { join as join76 } from "path";
59801
59968
 
59802
59969
  class WorktreeManager {
59803
59970
  async ensureGitExcludes(projectRoot) {
59804
59971
  const logger = getSafeLogger();
59805
- const infoDir = join74(projectRoot, ".git", "info");
59806
- const excludePath = join74(infoDir, "exclude");
59972
+ const infoDir = join76(projectRoot, ".git", "info");
59973
+ const excludePath = join76(infoDir, "exclude");
59807
59974
  try {
59808
59975
  await mkdir16(infoDir, { recursive: true });
59809
59976
  let existing = "";
@@ -59830,7 +59997,7 @@ ${missing.join(`
59830
59997
  }
59831
59998
  async create(projectRoot, storyId) {
59832
59999
  validateStoryId(storyId);
59833
- const worktreePath = join74(projectRoot, ".nax-wt", storyId);
60000
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
59834
60001
  const branchName = `nax/${storyId}`;
59835
60002
  try {
59836
60003
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -59871,9 +60038,9 @@ ${missing.join(`
59871
60038
  }
59872
60039
  throw new Error(`Failed to create worktree: ${String(error48)}`);
59873
60040
  }
59874
- const envSource = join74(projectRoot, ".env");
60041
+ const envSource = join76(projectRoot, ".env");
59875
60042
  if (existsSync33(envSource)) {
59876
- const envTarget = join74(worktreePath, ".env");
60043
+ const envTarget = join76(worktreePath, ".env");
59877
60044
  try {
59878
60045
  symlinkSync(envSource, envTarget, "file");
59879
60046
  } catch (error48) {
@@ -59884,7 +60051,7 @@ ${missing.join(`
59884
60051
  }
59885
60052
  async remove(projectRoot, storyId) {
59886
60053
  validateStoryId(storyId);
59887
- const worktreePath = join74(projectRoot, ".nax-wt", storyId);
60054
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
59888
60055
  const branchName = `nax/${storyId}`;
59889
60056
  try {
59890
60057
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -60684,10 +60851,10 @@ var init_merge_conflict_rectify = __esm(() => {
60684
60851
  });
60685
60852
 
60686
60853
  // src/execution/pipeline-result-handler.ts
60687
- import { join as join75 } from "path";
60854
+ import { join as join77 } from "path";
60688
60855
  async function removeWorktreeDirectory(projectRoot, storyId) {
60689
60856
  const logger = getSafeLogger();
60690
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
60857
+ const worktreePath = join77(projectRoot, ".nax-wt", storyId);
60691
60858
  try {
60692
60859
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
60693
60860
  cwd: projectRoot,
@@ -60898,7 +61065,7 @@ var init_pipeline_result_handler = __esm(() => {
60898
61065
 
60899
61066
  // src/execution/iteration-runner.ts
60900
61067
  import { existsSync as existsSync34 } from "fs";
60901
- import { join as join76 } from "path";
61068
+ import { join as join78 } from "path";
60902
61069
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
60903
61070
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
60904
61071
  if (ctx.dryRun) {
@@ -60923,7 +61090,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60923
61090
  const storyStartTime = Date.now();
60924
61091
  let effectiveWorkdir = ctx.workdir;
60925
61092
  if (ctx.config.execution.storyIsolation === "worktree") {
60926
- const worktreePath = join76(ctx.workdir, ".nax-wt", story.id);
61093
+ const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
60927
61094
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
60928
61095
  if (!worktreeExists) {
60929
61096
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -60943,7 +61110,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60943
61110
  }
60944
61111
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
60945
61112
  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;
61113
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
60947
61114
  let dependencyContext;
60948
61115
  if (ctx.config.execution.storyIsolation === "worktree") {
60949
61116
  try {
@@ -60970,7 +61137,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60970
61137
  };
60971
61138
  }
60972
61139
  }
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;
61140
+ 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
61141
  const pipelineContext = {
60975
61142
  config: effectiveConfig,
60976
61143
  rootConfig: ctx.config,
@@ -61171,7 +61338,7 @@ __export(exports_parallel_worker, {
61171
61338
  executeParallelBatch: () => executeParallelBatch,
61172
61339
  _parallelWorkerDeps: () => _parallelWorkerDeps
61173
61340
  });
61174
- import { join as join77 } from "path";
61341
+ import { join as join79 } from "path";
61175
61342
  async function executeStoryInWorktree(story, worktreePath, dependencyContext, context, routing, eventEmitter) {
61176
61343
  const logger = getSafeLogger();
61177
61344
  try {
@@ -61191,7 +61358,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
61191
61358
  story,
61192
61359
  stories: [story],
61193
61360
  projectDir: context.projectDir,
61194
- workdir: dependencyContext.cwd ?? (story.workdir ? join77(worktreePath, story.workdir) : worktreePath),
61361
+ workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
61195
61362
  worktreeDependencyContext: dependencyContext,
61196
61363
  routing,
61197
61364
  storyGitRef: storyGitRef ?? undefined
@@ -62020,7 +62187,7 @@ async function writeStatusFile(filePath, status) {
62020
62187
  var init_status_file = () => {};
62021
62188
 
62022
62189
  // src/execution/status-writer.ts
62023
- import { join as join78 } from "path";
62190
+ import { join as join80 } from "path";
62024
62191
 
62025
62192
  class StatusWriter {
62026
62193
  statusFile;
@@ -62139,7 +62306,7 @@ class StatusWriter {
62139
62306
  if (!this._prd)
62140
62307
  return;
62141
62308
  const safeLogger = getSafeLogger();
62142
- const featureStatusPath = join78(featureDir, "status.json");
62309
+ const featureStatusPath = join80(featureDir, "status.json");
62143
62310
  const write = async () => {
62144
62311
  try {
62145
62312
  const base = this.getSnapshot(totalCost, iterations);
@@ -62573,7 +62740,7 @@ __export(exports_run_initialization, {
62573
62740
  initializeRun: () => initializeRun,
62574
62741
  _reconcileDeps: () => _reconcileDeps
62575
62742
  });
62576
- import { join as join79 } from "path";
62743
+ import { join as join81 } from "path";
62577
62744
  async function reconcileState(prd, prdPath, workdir, config2) {
62578
62745
  const logger = getSafeLogger();
62579
62746
  let reconciledCount = 0;
@@ -62590,7 +62757,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
62590
62757
  });
62591
62758
  continue;
62592
62759
  }
62593
- const effectiveWorkdir = story.workdir ? join79(workdir, story.workdir) : workdir;
62760
+ const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
62594
62761
  try {
62595
62762
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
62596
62763
  if (!reviewResult.success) {
@@ -94022,7 +94189,7 @@ __export(exports_curator, {
94022
94189
  });
94023
94190
  import { readdirSync as readdirSync9 } from "fs";
94024
94191
  import { unlink as unlink4 } from "fs/promises";
94025
- import { basename as basename16, join as join81 } from "path";
94192
+ import { basename as basename16, join as join83 } from "path";
94026
94193
  function getProjectKey(config2, projectDir) {
94027
94194
  return config2.name?.trim() || basename16(projectDir);
94028
94195
  }
@@ -94105,7 +94272,7 @@ async function curatorStatus(options) {
94105
94272
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
94106
94273
  const projectKey = getProjectKey(config2, resolved.projectDir);
94107
94274
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94108
- const runsDir = join81(outputDir, "runs");
94275
+ const runsDir = join83(outputDir, "runs");
94109
94276
  const runIds = listRunIds(runsDir);
94110
94277
  let runId;
94111
94278
  if (options.run) {
@@ -94122,8 +94289,8 @@ async function curatorStatus(options) {
94122
94289
  runId = runIds[runIds.length - 1];
94123
94290
  }
94124
94291
  console.log(`Run: ${runId}`);
94125
- const runDir = join81(runsDir, runId);
94126
- const observationsPath = join81(runDir, "observations.jsonl");
94292
+ const runDir = join83(runsDir, runId);
94293
+ const observationsPath = join83(runDir, "observations.jsonl");
94127
94294
  const observations = await parseObservations(observationsPath);
94128
94295
  const counts = new Map;
94129
94296
  for (const obs of observations) {
@@ -94133,7 +94300,7 @@ async function curatorStatus(options) {
94133
94300
  for (const [kind, count] of counts.entries()) {
94134
94301
  console.log(` ${kind}: ${count}`);
94135
94302
  }
94136
- const proposalsPath = join81(runDir, "curator-proposals.md");
94303
+ const proposalsPath = join83(runDir, "curator-proposals.md");
94137
94304
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
94138
94305
  if (proposalText !== null) {
94139
94306
  console.log("");
@@ -94147,8 +94314,8 @@ async function curatorCommit(options) {
94147
94314
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
94148
94315
  const projectKey = getProjectKey(config2, resolved.projectDir);
94149
94316
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94150
- const runDir = join81(outputDir, "runs", options.runId);
94151
- const proposalsPath = join81(runDir, "curator-proposals.md");
94317
+ const runDir = join83(outputDir, "runs", options.runId);
94318
+ const proposalsPath = join83(runDir, "curator-proposals.md");
94152
94319
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
94153
94320
  if (proposalText === null) {
94154
94321
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -94164,7 +94331,7 @@ async function curatorCommit(options) {
94164
94331
  const dropFileState = new Map;
94165
94332
  const skippedDrops = new Set;
94166
94333
  for (const drop2 of drops) {
94167
- const targetPath = join81(resolved.projectDir, drop2.canonicalFile);
94334
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94168
94335
  if (!dropFileState.has(targetPath)) {
94169
94336
  const fileExists2 = await Bun.file(targetPath).exists();
94170
94337
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -94198,7 +94365,7 @@ async function curatorCommit(options) {
94198
94365
  if (skippedDrops.has(drop2)) {
94199
94366
  continue;
94200
94367
  }
94201
- const targetPath = join81(resolved.projectDir, drop2.canonicalFile);
94368
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
94202
94369
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
94203
94370
  const filtered = filterDropContent(existing, drop2.description);
94204
94371
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -94207,7 +94374,7 @@ async function curatorCommit(options) {
94207
94374
  }
94208
94375
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
94209
94376
  for (const add2 of adds) {
94210
- const targetPath = join81(resolved.projectDir, add2.canonicalFile);
94377
+ const targetPath = join83(resolved.projectDir, add2.canonicalFile);
94211
94378
  const content = buildAddContent(add2);
94212
94379
  await _curatorCmdDeps.appendFile(targetPath, content);
94213
94380
  modifiedFiles.add(targetPath);
@@ -94244,7 +94411,7 @@ async function curatorDryrun(options) {
94244
94411
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
94245
94412
  const projectKey = getProjectKey(config2, resolved.projectDir);
94246
94413
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94247
- const runsDir = join81(outputDir, "runs");
94414
+ const runsDir = join83(outputDir, "runs");
94248
94415
  const runIds = listRunIds(runsDir);
94249
94416
  if (runIds.length === 0) {
94250
94417
  console.log("No runs found.");
@@ -94255,7 +94422,7 @@ async function curatorDryrun(options) {
94255
94422
  console.log(`Run ${options.run} not found in ${runsDir}.`);
94256
94423
  return;
94257
94424
  }
94258
- const observationsPath = join81(runsDir, runId, "observations.jsonl");
94425
+ const observationsPath = join83(runsDir, runId, "observations.jsonl");
94259
94426
  const observations = await parseObservations(observationsPath);
94260
94427
  const thresholds = getThresholds(config2);
94261
94428
  const proposals = runHeuristics(observations, thresholds);
@@ -94296,12 +94463,12 @@ async function curatorGc(options) {
94296
94463
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
94297
94464
  const projectKey = getProjectKey(config2, resolved.projectDir);
94298
94465
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
94299
- const perRunsDir = join81(outputDir, "runs");
94466
+ const perRunsDir = join83(outputDir, "runs");
94300
94467
  for (const runId of uniqueRunIds) {
94301
94468
  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"));
94469
+ const runDir = join83(perRunsDir, runId);
94470
+ await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
94471
+ await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
94305
94472
  }
94306
94473
  }
94307
94474
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -94346,7 +94513,7 @@ var init_curator2 = __esm(() => {
94346
94513
  init_source();
94347
94514
  import { existsSync as existsSync37, mkdirSync as mkdirSync7 } from "fs";
94348
94515
  import { homedir as homedir3 } from "os";
94349
- import { basename as basename17, join as join82 } from "path";
94516
+ import { basename as basename17, join as join84 } from "path";
94350
94517
 
94351
94518
  // node_modules/commander/esm.mjs
94352
94519
  var import__ = __toESM(require_commander(), 1);
@@ -94370,12 +94537,12 @@ init_errors();
94370
94537
  init_operations();
94371
94538
 
94372
94539
  // src/plan/strategies/context-builder.ts
94373
- import { join as join36 } from "path";
94540
+ import { join as join37 } from "path";
94374
94541
  init_config();
94375
94542
  init_errors();
94376
94543
  init_interaction();
94377
94544
  async function buildPlanModeContext(workdir, fullConfig, options, deps) {
94378
- const naxDir = join36(workdir, ".nax");
94545
+ const naxDir = join37(workdir, ".nax");
94379
94546
  if (!deps.existsSync(naxDir)) {
94380
94547
  throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
94381
94548
  stage: "plan",
@@ -94383,8 +94550,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
94383
94550
  });
94384
94551
  }
94385
94552
  validateFeatureName(options.feature);
94386
- const outputDir = join36(naxDir, "features", options.feature);
94387
- const outputPath = join36(outputDir, "prd.json");
94553
+ const outputDir = join37(naxDir, "features", options.feature);
94554
+ const outputPath = join37(outputDir, "prd.json");
94388
94555
  const [specContent, sourceRoots, pkg] = await Promise.all([
94389
94556
  deps.readFile(options.from),
94390
94557
  deps.scanSourceRoots(workdir),
@@ -94399,7 +94566,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
94399
94566
  ...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
94400
94567
  ];
94401
94568
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
94402
- const packageJson = await deps.readPackageJsonAt(join36(workdir, relativePath, "package.json"));
94569
+ const packageJson = await deps.readPackageJsonAt(join37(workdir, relativePath, "package.json"));
94403
94570
  return buildPackageSummary(relativePath, packageJson);
94404
94571
  }));
94405
94572
  const projectName = detectProjectName(workdir, pkg);
@@ -95000,7 +95167,7 @@ init_interaction();
95000
95167
  init_prd();
95001
95168
  init_runtime();
95002
95169
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
95003
- import { basename as basename7, join as join41, resolve as resolve14 } from "path";
95170
+ import { basename as basename7, join as join42, resolve as resolve14 } from "path";
95004
95171
  var _statusFeaturesDeps = {
95005
95172
  projectOutputDir,
95006
95173
  loadConfig
@@ -95014,7 +95181,7 @@ function isPidAlive(pid) {
95014
95181
  }
95015
95182
  }
95016
95183
  async function loadStatusFile(featureDir) {
95017
- const statusPath = join41(featureDir, "status.json");
95184
+ const statusPath = join42(featureDir, "status.json");
95018
95185
  if (!existsSync17(statusPath)) {
95019
95186
  return null;
95020
95187
  }
@@ -95029,7 +95196,7 @@ async function loadProjectStatusFile(projectDir) {
95029
95196
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
95030
95197
  const projectKey = config2?.name?.trim() || basename7(projectDir);
95031
95198
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
95032
- const statusPath = join41(outputDir, "status.json");
95199
+ const statusPath = join42(outputDir, "status.json");
95033
95200
  if (!existsSync17(statusPath)) {
95034
95201
  return null;
95035
95202
  }
@@ -95041,7 +95208,7 @@ async function loadProjectStatusFile(projectDir) {
95041
95208
  }
95042
95209
  }
95043
95210
  async function getFeatureSummary(featureName, featureDir) {
95044
- const prdPath = join41(featureDir, "prd.json");
95211
+ const prdPath = join42(featureDir, "prd.json");
95045
95212
  if (!existsSync17(prdPath)) {
95046
95213
  return {
95047
95214
  name: featureName,
@@ -95084,7 +95251,7 @@ async function getFeatureSummary(featureName, featureDir) {
95084
95251
  };
95085
95252
  }
95086
95253
  }
95087
- const runsDir = join41(featureDir, "runs");
95254
+ const runsDir = join42(featureDir, "runs");
95088
95255
  if (existsSync17(runsDir)) {
95089
95256
  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
95257
  if (runs.length > 0) {
@@ -95098,7 +95265,7 @@ async function displayAllFeatures(projectDir) {
95098
95265
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
95099
95266
  const projectKey = config2?.name?.trim() || basename7(projectDir);
95100
95267
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
95101
- const featuresDir = join41(outputDir, "features");
95268
+ const featuresDir = join42(outputDir, "features");
95102
95269
  if (!existsSync17(featuresDir)) {
95103
95270
  console.log(source_default.dim("No features found."));
95104
95271
  return;
@@ -95139,7 +95306,7 @@ async function displayAllFeatures(projectDir) {
95139
95306
  console.log();
95140
95307
  }
95141
95308
  }
95142
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join41(featuresDir, name))));
95309
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join42(featuresDir, name))));
95143
95310
  console.log(source_default.bold(`\uD83D\uDCCA Features
95144
95311
  `));
95145
95312
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -95165,7 +95332,7 @@ async function displayAllFeatures(projectDir) {
95165
95332
  console.log();
95166
95333
  }
95167
95334
  async function displayFeatureDetails(featureName, featureDir) {
95168
- const prdPath = join41(featureDir, "prd.json");
95335
+ const prdPath = join42(featureDir, "prd.json");
95169
95336
  if (!existsSync17(prdPath)) {
95170
95337
  console.log(source_default.bold(`
95171
95338
  \uD83D\uDCCA ${featureName}
@@ -95311,7 +95478,7 @@ async function displayFeatureStatus(options = {}) {
95311
95478
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
95312
95479
  const projectKey = config2?.name?.trim() || basename7(projectDir);
95313
95480
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
95314
- featureDir = join41(outputDir, "features", options.feature);
95481
+ featureDir = join42(outputDir, "features", options.feature);
95315
95482
  } else {
95316
95483
  const resolved = resolveProject({ feature: options.feature });
95317
95484
  if (!resolved.featureDir) {
@@ -95331,7 +95498,7 @@ init_errors();
95331
95498
  init_logger2();
95332
95499
  init_runtime();
95333
95500
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
95334
- import { basename as basename8, join as join42 } from "path";
95501
+ import { basename as basename8, join as join43 } from "path";
95335
95502
  async function resolveOutputDir2(workdir, override) {
95336
95503
  if (override)
95337
95504
  return override;
@@ -95355,7 +95522,7 @@ async function runsListCommand(options) {
95355
95522
  const logger = getLogger();
95356
95523
  const { feature, workdir } = options;
95357
95524
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
95358
- const runsDir = join42(outputDir, "features", feature, "runs");
95525
+ const runsDir = join43(outputDir, "features", feature, "runs");
95359
95526
  if (!existsSync18(runsDir)) {
95360
95527
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
95361
95528
  return;
@@ -95367,7 +95534,7 @@ async function runsListCommand(options) {
95367
95534
  }
95368
95535
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
95369
95536
  for (const file3 of files.sort().reverse()) {
95370
- const logPath = join42(runsDir, file3);
95537
+ const logPath = join43(runsDir, file3);
95371
95538
  const entries = await parseRunLog(logPath);
95372
95539
  const startEvent = entries.find((e) => e.message === "run.start");
95373
95540
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -95394,7 +95561,7 @@ async function runsShowCommand(options) {
95394
95561
  const logger = getLogger();
95395
95562
  const { runId, feature, workdir } = options;
95396
95563
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
95397
- const logPath = join42(outputDir, "features", feature, "runs", `${runId}.jsonl`);
95564
+ const logPath = join43(outputDir, "features", feature, "runs", `${runId}.jsonl`);
95398
95565
  if (!existsSync18(logPath)) {
95399
95566
  logger.error("cli", "Run not found", { runId, feature, logPath });
95400
95567
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -95507,7 +95674,7 @@ init_logger2();
95507
95674
  init_prd();
95508
95675
  init_runtime();
95509
95676
  import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
95510
- import { basename as basename12, join as join55 } from "path";
95677
+ import { basename as basename12, join as join57 } from "path";
95511
95678
 
95512
95679
  // src/cli/diagnose-analysis.ts
95513
95680
  function detectFailurePattern(story, _prd, status) {
@@ -95710,7 +95877,7 @@ function isProcessAlive2(pid) {
95710
95877
  }
95711
95878
  }
95712
95879
  async function loadStatusFile2(outputDir) {
95713
- const statusPath = join55(outputDir, "status.json");
95880
+ const statusPath = join57(outputDir, "status.json");
95714
95881
  if (!existsSync25(statusPath))
95715
95882
  return null;
95716
95883
  try {
@@ -95738,7 +95905,7 @@ async function countCommitsSince(workdir, since) {
95738
95905
  }
95739
95906
  }
95740
95907
  async function checkLock(workdir) {
95741
- const lockFile = Bun.file(join55(workdir, "nax.lock"));
95908
+ const lockFile = Bun.file(join57(workdir, "nax.lock"));
95742
95909
  if (!await lockFile.exists())
95743
95910
  return { lockPresent: false };
95744
95911
  try {
@@ -95756,8 +95923,8 @@ async function diagnoseCommand(options = {}) {
95756
95923
  const logger = getLogger();
95757
95924
  const workdir = options.workdir ?? process.cwd();
95758
95925
  const naxSubdir = findProjectDir(workdir);
95759
- let projectDir = naxSubdir ? join55(naxSubdir, "..") : null;
95760
- if (!projectDir && existsSync25(join55(workdir, ".nax"))) {
95926
+ let projectDir = naxSubdir ? join57(naxSubdir, "..") : null;
95927
+ if (!projectDir && existsSync25(join57(workdir, ".nax"))) {
95761
95928
  projectDir = workdir;
95762
95929
  }
95763
95930
  if (!projectDir)
@@ -95771,7 +95938,7 @@ async function diagnoseCommand(options = {}) {
95771
95938
  if (status2) {
95772
95939
  feature = status2.run.feature;
95773
95940
  } else {
95774
- const featuresDir = join55(outputDir, "features");
95941
+ const featuresDir = join57(outputDir, "features");
95775
95942
  if (!existsSync25(featuresDir))
95776
95943
  throw new Error("No features found in project");
95777
95944
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -95781,8 +95948,8 @@ async function diagnoseCommand(options = {}) {
95781
95948
  logger.info("diagnose", "No feature specified, using first found", { feature });
95782
95949
  }
95783
95950
  }
95784
- const featureDir = join55(outputDir, "features", feature);
95785
- const prdPath = join55(featureDir, "prd.json");
95951
+ const featureDir = join57(outputDir, "features", feature);
95952
+ const prdPath = join57(featureDir, "prd.json");
95786
95953
  if (!existsSync25(prdPath))
95787
95954
  throw new Error(`Feature not found: ${feature}`);
95788
95955
  const prd = await loadPRD(prdPath);
@@ -95827,7 +95994,7 @@ init_source();
95827
95994
  init_loader();
95828
95995
  init_generator2();
95829
95996
  import { existsSync as existsSync26 } from "fs";
95830
- import { join as join56 } from "path";
95997
+ import { join as join58 } from "path";
95831
95998
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
95832
95999
  async function generateCommand(options) {
95833
96000
  const workdir = options.dir ?? process.cwd();
@@ -95870,7 +96037,7 @@ async function generateCommand(options) {
95870
96037
  return;
95871
96038
  }
95872
96039
  if (options.package) {
95873
- const packageDir = join56(workdir, options.package);
96040
+ const packageDir = join58(workdir, options.package);
95874
96041
  if (dryRun) {
95875
96042
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95876
96043
  }
@@ -95890,8 +96057,8 @@ async function generateCommand(options) {
95890
96057
  process.exit(1);
95891
96058
  return;
95892
96059
  }
95893
- const contextPath = options.context ? join56(workdir, options.context) : join56(workdir, ".nax/context.md");
95894
- const outputDir = options.output ? join56(workdir, options.output) : workdir;
96060
+ const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
96061
+ const outputDir = options.output ? join58(workdir, options.output) : workdir;
95895
96062
  const autoInject = !options.noAutoInject;
95896
96063
  if (!existsSync26(contextPath)) {
95897
96064
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -95997,7 +96164,7 @@ async function generateCommand(options) {
95997
96164
  // src/cli/config-display.ts
95998
96165
  init_loader();
95999
96166
  import { existsSync as existsSync28 } from "fs";
96000
- import { join as join58 } from "path";
96167
+ import { join as join60 } from "path";
96001
96168
 
96002
96169
  // src/cli/config-descriptions.ts
96003
96170
  var FIELD_DESCRIPTIONS = {
@@ -96248,7 +96415,7 @@ function deepEqual(a, b) {
96248
96415
  init_defaults();
96249
96416
  init_loader();
96250
96417
  import { existsSync as existsSync27 } from "fs";
96251
- import { join as join57 } from "path";
96418
+ import { join as join59 } from "path";
96252
96419
  async function loadConfigFile(path19) {
96253
96420
  if (!existsSync27(path19))
96254
96421
  return null;
@@ -96270,7 +96437,7 @@ async function loadProjectConfig() {
96270
96437
  const projectDir = findProjectDir();
96271
96438
  if (!projectDir)
96272
96439
  return null;
96273
- const projectPath = join57(projectDir, "config.json");
96440
+ const projectPath = join59(projectDir, "config.json");
96274
96441
  return await loadConfigFile(projectPath);
96275
96442
  }
96276
96443
 
@@ -96330,7 +96497,7 @@ async function configCommand(config2, options = {}) {
96330
96497
  function determineConfigSources() {
96331
96498
  const globalPath = globalConfigPath();
96332
96499
  const projectDir = findProjectDir();
96333
- const projectPath = projectDir ? join58(projectDir, "config.json") : null;
96500
+ const projectPath = projectDir ? join60(projectDir, "config.json") : null;
96334
96501
  return {
96335
96502
  global: fileExists(globalPath) ? globalPath : null,
96336
96503
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -96479,15 +96646,15 @@ init_paths();
96479
96646
  init_profile();
96480
96647
  import { mkdirSync as mkdirSync5 } from "fs";
96481
96648
  import { readdirSync as readdirSync6 } from "fs";
96482
- import { join as join59 } from "path";
96649
+ import { join as join61 } from "path";
96483
96650
  var _profileCLIDeps = {
96484
96651
  env: process.env
96485
96652
  };
96486
96653
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
96487
96654
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
96488
96655
  async function profileListCommand(startDir) {
96489
- const globalProfilesDir = join59(globalConfigDir(), "profiles");
96490
- const projectProfilesDir = join59(projectConfigDir(startDir), "profiles");
96656
+ const globalProfilesDir = join61(globalConfigDir(), "profiles");
96657
+ const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
96491
96658
  const globalProfiles = scanProfileDir(globalProfilesDir);
96492
96659
  const projectProfiles = scanProfileDir(projectProfilesDir);
96493
96660
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -96546,7 +96713,7 @@ function maskProfileValues(obj) {
96546
96713
  return result;
96547
96714
  }
96548
96715
  async function profileUseCommand(profileName, startDir) {
96549
- const configPath = join59(projectConfigDir(startDir), "config.json");
96716
+ const configPath = join61(projectConfigDir(startDir), "config.json");
96550
96717
  const configFile = Bun.file(configPath);
96551
96718
  let existing = {};
96552
96719
  if (await configFile.exists()) {
@@ -96565,8 +96732,8 @@ async function profileCurrentCommand(startDir) {
96565
96732
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
96566
96733
  }
96567
96734
  async function profileCreateCommand(profileName, startDir) {
96568
- const profilesDir = join59(projectConfigDir(startDir), "profiles");
96569
- const profilePath = join59(profilesDir, `${profileName}.json`);
96735
+ const profilesDir = join61(projectConfigDir(startDir), "profiles");
96736
+ const profilePath = join61(profilesDir, `${profileName}.json`);
96570
96737
  const profileFile = Bun.file(profilePath);
96571
96738
  if (await profileFile.exists()) {
96572
96739
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -96688,7 +96855,7 @@ async function contextInspectCommand(options) {
96688
96855
  init_canonical_loader();
96689
96856
  init_errors();
96690
96857
  import { mkdir as mkdir12 } from "fs/promises";
96691
- import { basename as basename13, join as join60 } from "path";
96858
+ import { basename as basename13, join as join62 } from "path";
96692
96859
  var _rulesCLIDeps = {
96693
96860
  readFile: async (path19) => Bun.file(path19).text(),
96694
96861
  writeFile: async (path19, content) => {
@@ -96697,7 +96864,7 @@ var _rulesCLIDeps = {
96697
96864
  fileExists: async (path19) => Bun.file(path19).exists(),
96698
96865
  globInDir: (dir) => {
96699
96866
  try {
96700
- return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join60(dir, f));
96867
+ return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
96701
96868
  } catch {
96702
96869
  return [];
96703
96870
  }
@@ -96746,7 +96913,7 @@ ${r.content}`).join(`
96746
96913
  `);
96747
96914
  const shimContent = `${header + body}
96748
96915
  `;
96749
- const shimPath = join60(workdir, shimFileName);
96916
+ const shimPath = join62(workdir, shimFileName);
96750
96917
  if (options.dryRun) {
96751
96918
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
96752
96919
  return;
@@ -96775,14 +96942,14 @@ function neutralizeContent(content) {
96775
96942
  }
96776
96943
  async function collectMigrationSources(workdir) {
96777
96944
  const sources = [];
96778
- const claudeMdPath = join60(workdir, "CLAUDE.md");
96945
+ const claudeMdPath = join62(workdir, "CLAUDE.md");
96779
96946
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
96780
96947
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
96781
96948
  if (content.trim()) {
96782
96949
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
96783
96950
  }
96784
96951
  }
96785
- const rulesDir = join60(workdir, ".claude", "rules");
96952
+ const rulesDir = join62(workdir, ".claude", "rules");
96786
96953
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
96787
96954
  for (const filePath of ruleFiles) {
96788
96955
  try {
@@ -96802,7 +96969,7 @@ async function rulesMigrateCommand(options) {
96802
96969
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
96803
96970
  return;
96804
96971
  }
96805
- const targetDir = join60(workdir, CANONICAL_RULES_DIR);
96972
+ const targetDir = join62(workdir, CANONICAL_RULES_DIR);
96806
96973
  if (!options.dryRun) {
96807
96974
  try {
96808
96975
  await _rulesCLIDeps.mkdir(targetDir);
@@ -96813,7 +96980,7 @@ async function rulesMigrateCommand(options) {
96813
96980
  let written = 0;
96814
96981
  let skipped = 0;
96815
96982
  for (const { sourcePath, targetFileName, content } of sources) {
96816
- const targetPath = join60(targetDir, targetFileName);
96983
+ const targetPath = join62(targetDir, targetFileName);
96817
96984
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
96818
96985
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
96819
96986
  skipped++;
@@ -96852,7 +97019,7 @@ function collectCanonicalRuleRoots(workdir) {
96852
97019
  const packageRel = normalized.slice(0, idx);
96853
97020
  if (!packageRel)
96854
97021
  continue;
96855
- roots.add(join60(workdir, packageRel));
97022
+ roots.add(join62(workdir, packageRel));
96856
97023
  }
96857
97024
  return [...roots].sort();
96858
97025
  }
@@ -96874,7 +97041,7 @@ init_logger2();
96874
97041
  init_detect2();
96875
97042
  init_workspace();
96876
97043
  init_common();
96877
- import { join as join61 } from "path";
97044
+ import { join as join63 } from "path";
96878
97045
  function resolveEffective(detected, configPatterns) {
96879
97046
  if (configPatterns !== undefined)
96880
97047
  return "config";
@@ -96959,7 +97126,7 @@ async function detectCommand(options) {
96959
97126
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96960
97127
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96961
97128
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96962
- const pkgConfigPath = join61(workdir, ".nax", "mono", dir, "config.json");
97129
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96963
97130
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96964
97131
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96965
97132
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -97013,13 +97180,13 @@ async function detectCommand(options) {
97013
97180
  if (rootDetected.confidence === "empty") {
97014
97181
  console.log(source_default.yellow(" root: skipped (empty detection)"));
97015
97182
  } else {
97016
- const rootConfigPath = join61(workdir, ".nax", "config.json");
97183
+ const rootConfigPath = join63(workdir, ".nax", "config.json");
97017
97184
  try {
97018
97185
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
97019
97186
  if (status === "skipped") {
97020
97187
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
97021
97188
  } else {
97022
- console.log(source_default.green(` root: ${status} \u2192 ${join61(".nax", "config.json")}`));
97189
+ console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
97023
97190
  }
97024
97191
  } catch (err) {
97025
97192
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -97032,13 +97199,13 @@ async function detectCommand(options) {
97032
97199
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
97033
97200
  continue;
97034
97201
  }
97035
- const pkgConfigPath = join61(workdir, ".nax", "mono", dir, "config.json");
97202
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
97036
97203
  try {
97037
97204
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
97038
97205
  if (status === "skipped") {
97039
97206
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
97040
97207
  } else {
97041
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join61(".nax", "mono", dir, "config.json")}`));
97208
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
97042
97209
  }
97043
97210
  } catch (err) {
97044
97211
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -97061,19 +97228,19 @@ async function diagnose(options) {
97061
97228
  // src/commands/logs.ts
97062
97229
  init_common();
97063
97230
  import { existsSync as existsSync30 } from "fs";
97064
- import { join as join65 } from "path";
97231
+ import { join as join67 } from "path";
97065
97232
 
97066
97233
  // src/commands/logs-formatter.ts
97067
97234
  init_source();
97068
97235
  init_formatter();
97069
97236
  import { readdirSync as readdirSync8 } from "fs";
97070
- import { join as join64 } from "path";
97237
+ import { join as join66 } from "path";
97071
97238
 
97072
97239
  // src/commands/logs-reader.ts
97073
97240
  init_paths3();
97074
97241
  import { existsSync as existsSync29, readdirSync as readdirSync7 } from "fs";
97075
97242
  import { readdir as readdir4 } from "fs/promises";
97076
- import { join as join63 } from "path";
97243
+ import { join as join65 } from "path";
97077
97244
  var _logsReaderDeps = {
97078
97245
  getRunsDir
97079
97246
  };
@@ -97087,7 +97254,7 @@ async function resolveRunFileFromRegistry(runId) {
97087
97254
  }
97088
97255
  let matched = null;
97089
97256
  for (const entry of entries) {
97090
- const metaPath = join63(runsDir, entry, "meta.json");
97257
+ const metaPath = join65(runsDir, entry, "meta.json");
97091
97258
  try {
97092
97259
  const meta3 = await Bun.file(metaPath).json();
97093
97260
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -97109,14 +97276,14 @@ async function resolveRunFileFromRegistry(runId) {
97109
97276
  return null;
97110
97277
  }
97111
97278
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
97112
- return join63(matched.eventsDir, specificFile ?? files[0]);
97279
+ return join65(matched.eventsDir, specificFile ?? files[0]);
97113
97280
  }
97114
97281
  async function selectRunFile(runsDir) {
97115
97282
  const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
97116
97283
  if (files.length === 0) {
97117
97284
  return null;
97118
97285
  }
97119
- return join63(runsDir, files[0]);
97286
+ return join65(runsDir, files[0]);
97120
97287
  }
97121
97288
  async function extractRunSummary(filePath) {
97122
97289
  const file3 = Bun.file(filePath);
@@ -97202,7 +97369,7 @@ Runs:
97202
97369
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
97203
97370
  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
97371
  for (const file3 of files) {
97205
- const filePath = join64(runsDir, file3);
97372
+ const filePath = join66(runsDir, file3);
97206
97373
  const summary = await extractRunSummary(filePath);
97207
97374
  const timestamp = file3.replace(".jsonl", "");
97208
97375
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -97316,7 +97483,7 @@ async function logsCommand(options) {
97316
97483
  return;
97317
97484
  }
97318
97485
  const resolved = resolveProject({ dir: options.dir });
97319
- const naxDir = join65(resolved.projectDir, ".nax");
97486
+ const naxDir = join67(resolved.projectDir, ".nax");
97320
97487
  const configPath = resolved.configPath;
97321
97488
  const configFile = Bun.file(configPath);
97322
97489
  const config2 = await configFile.json();
@@ -97324,8 +97491,8 @@ async function logsCommand(options) {
97324
97491
  if (!featureName) {
97325
97492
  throw new Error("No feature specified in config.json");
97326
97493
  }
97327
- const featureDir = join65(naxDir, "features", featureName);
97328
- const runsDir = join65(featureDir, "runs");
97494
+ const featureDir = join67(naxDir, "features", featureName);
97495
+ const runsDir = join67(featureDir, "runs");
97329
97496
  if (!existsSync30(runsDir)) {
97330
97497
  throw new Error(`No runs directory found for feature: ${featureName}`);
97331
97498
  }
@@ -97351,7 +97518,7 @@ init_prd();
97351
97518
  init_precheck();
97352
97519
  init_common();
97353
97520
  import { existsSync as existsSync31 } from "fs";
97354
- import { join as join66 } from "path";
97521
+ import { join as join68 } from "path";
97355
97522
  async function precheckCommand(options) {
97356
97523
  const resolved = resolveProject({
97357
97524
  dir: options.dir,
@@ -97373,9 +97540,9 @@ async function precheckCommand(options) {
97373
97540
  process.exit(1);
97374
97541
  }
97375
97542
  }
97376
- const naxDir = join66(resolved.projectDir, ".nax");
97377
- const featureDir = join66(naxDir, "features", featureName);
97378
- const prdPath = join66(featureDir, "prd.json");
97543
+ const naxDir = join68(resolved.projectDir, ".nax");
97544
+ const featureDir = join68(naxDir, "features", featureName);
97545
+ const prdPath = join68(featureDir, "prd.json");
97379
97546
  if (!existsSync31(featureDir)) {
97380
97547
  console.error(source_default.red(`Feature not found: ${featureName}`));
97381
97548
  process.exit(1);
@@ -97398,7 +97565,7 @@ async function precheckCommand(options) {
97398
97565
  init_source();
97399
97566
  init_paths3();
97400
97567
  import { readdir as readdir5 } from "fs/promises";
97401
- import { join as join67 } from "path";
97568
+ import { join as join69 } from "path";
97402
97569
  var DEFAULT_LIMIT = 20;
97403
97570
  var _runsCmdDeps = {
97404
97571
  getRunsDir
@@ -97453,7 +97620,7 @@ async function runsCommand(options = {}) {
97453
97620
  }
97454
97621
  const rows = [];
97455
97622
  for (const entry of entries) {
97456
- const metaPath = join67(runsDir, entry, "meta.json");
97623
+ const metaPath = join69(runsDir, entry, "meta.json");
97457
97624
  let meta3;
97458
97625
  try {
97459
97626
  meta3 = await Bun.file(metaPath).json();
@@ -97530,7 +97697,7 @@ async function runsCommand(options = {}) {
97530
97697
 
97531
97698
  // src/commands/unlock.ts
97532
97699
  init_source();
97533
- import { join as join68 } from "path";
97700
+ import { join as join70 } from "path";
97534
97701
  function isProcessAlive3(pid) {
97535
97702
  try {
97536
97703
  process.kill(pid, 0);
@@ -97545,7 +97712,7 @@ function formatLockAge(ageMs) {
97545
97712
  }
97546
97713
  async function unlockCommand(options) {
97547
97714
  const workdir = options.dir ?? process.cwd();
97548
- const lockPath = join68(workdir, "nax.lock");
97715
+ const lockPath = join70(workdir, "nax.lock");
97549
97716
  const lockFile = Bun.file(lockPath);
97550
97717
  const exists = await lockFile.exists();
97551
97718
  if (!exists) {
@@ -105618,7 +105785,7 @@ Next: nax generate --package ${options.package}`));
105618
105785
  }
105619
105786
  return;
105620
105787
  }
105621
- const naxDir = join82(workdir, ".nax");
105788
+ const naxDir = join84(workdir, ".nax");
105622
105789
  if (existsSync37(naxDir) && !options.force) {
105623
105790
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
105624
105791
  return;
@@ -105647,11 +105814,11 @@ Next: nax generate --package ${options.package}`));
105647
105814
  }
105648
105815
  }
105649
105816
  }
105650
- mkdirSync7(join82(naxDir, "features"), { recursive: true });
105651
- mkdirSync7(join82(naxDir, "hooks"), { recursive: true });
105817
+ mkdirSync7(join84(naxDir, "features"), { recursive: true });
105818
+ mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
105652
105819
  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({
105820
+ await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105821
+ await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
105655
105822
  hooks: {
105656
105823
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
105657
105824
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -105659,12 +105826,12 @@ Next: nax generate --package ${options.package}`));
105659
105826
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
105660
105827
  }
105661
105828
  }, null, 2));
105662
- await Bun.write(join82(naxDir, ".gitignore"), `# nax temp files
105829
+ await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
105663
105830
  *.tmp
105664
105831
  .paused.json
105665
105832
  .nax-verifier-verdict.json
105666
105833
  `);
105667
- await Bun.write(join82(naxDir, "context.md"), `# Project Context
105834
+ await Bun.write(join84(naxDir, "context.md"), `# Project Context
105668
105835
 
105669
105836
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
105670
105837
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105794,8 +105961,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
105794
105961
  console.error(source_default.red("nax not initialized. Run: nax init"));
105795
105962
  process.exit(1);
105796
105963
  }
105797
- const featureDir = join82(naxDir, "features", options.feature);
105798
- const prdPath = join82(featureDir, "prd.json");
105964
+ const featureDir = join84(naxDir, "features", options.feature);
105965
+ const prdPath = join84(featureDir, "prd.json");
105799
105966
  if (options.plan && options.from) {
105800
105967
  if (existsSync37(prdPath) && !options.force) {
105801
105968
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -105817,10 +105984,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105817
105984
  }
105818
105985
  }
105819
105986
  try {
105820
- const planLogDir = join82(featureDir, "plan");
105987
+ const planLogDir = join84(featureDir, "plan");
105821
105988
  mkdirSync7(planLogDir, { recursive: true });
105822
105989
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105823
- const planLogPath = join82(planLogDir, `${planLogId}.jsonl`);
105990
+ const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
105824
105991
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105825
105992
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105826
105993
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105866,10 +106033,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105866
106033
  resetLogger();
105867
106034
  const projectKey = config2.name?.trim() || basename17(workdir);
105868
106035
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105869
- const runsDir = join82(outputDir, "features", options.feature, "runs");
106036
+ const runsDir = join84(outputDir, "features", options.feature, "runs");
105870
106037
  mkdirSync7(runsDir, { recursive: true });
105871
106038
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105872
- const logFilePath = join82(runsDir, `${runId}.jsonl`);
106039
+ const logFilePath = join84(runsDir, `${runId}.jsonl`);
105873
106040
  const isTTY = process.stdout.isTTY ?? false;
105874
106041
  const headlessFlag = options.headless ?? false;
105875
106042
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105886,7 +106053,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105886
106053
  config2.agent.default = options.agent;
105887
106054
  }
105888
106055
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105889
- const globalNaxDir = join82(homedir3(), ".nax");
106056
+ const globalNaxDir = join84(homedir3(), ".nax");
105890
106057
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105891
106058
  const eventEmitter = new PipelineEventEmitter;
105892
106059
  let tuiInstance;
@@ -105909,7 +106076,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105909
106076
  } else {
105910
106077
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105911
106078
  }
105912
- const statusFilePath = join82(outputDir, "status.json");
106079
+ const statusFilePath = join84(outputDir, "status.json");
105913
106080
  let parallel;
105914
106081
  if (options.parallel !== undefined) {
105915
106082
  parallel = Number.parseInt(options.parallel, 10);
@@ -105935,7 +106102,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105935
106102
  headless: useHeadless,
105936
106103
  skipPrecheck: options.skipPrecheck ?? false
105937
106104
  });
105938
- const latestSymlink = join82(runsDir, "latest.jsonl");
106105
+ const latestSymlink = join84(runsDir, "latest.jsonl");
105939
106106
  try {
105940
106107
  if (existsSync37(latestSymlink)) {
105941
106108
  Bun.spawnSync(["rm", latestSymlink]);
@@ -105996,9 +106163,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105996
106163
  console.error(source_default.red("nax not initialized. Run: nax init"));
105997
106164
  process.exit(1);
105998
106165
  }
105999
- const featureDir = join82(naxDir, "features", name);
106166
+ const featureDir = join84(naxDir, "features", name);
106000
106167
  mkdirSync7(featureDir, { recursive: true });
106001
- await Bun.write(join82(featureDir, "spec.md"), `# Feature: ${name}
106168
+ await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
106002
106169
 
106003
106170
  ## Overview
106004
106171
 
@@ -106031,7 +106198,7 @@ features.command("create <name>").description("Create a new feature").option("-d
106031
106198
 
106032
106199
  <!-- What this feature explicitly does NOT cover. -->
106033
106200
  `);
106034
- await Bun.write(join82(featureDir, "progress.txt"), `# Progress: ${name}
106201
+ await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
106035
106202
 
106036
106203
  Created: ${new Date().toISOString()}
106037
106204
 
@@ -106057,7 +106224,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
106057
106224
  console.error(source_default.red("nax not initialized."));
106058
106225
  process.exit(1);
106059
106226
  }
106060
- const featuresDir = join82(naxDir, "features");
106227
+ const featuresDir = join84(naxDir, "features");
106061
106228
  if (!existsSync37(featuresDir)) {
106062
106229
  console.log(source_default.dim("No features yet."));
106063
106230
  return;
@@ -106072,7 +106239,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
106072
106239
  Features:
106073
106240
  `));
106074
106241
  for (const name of entries) {
106075
- const prdPath = join82(featuresDir, name, "prd.json");
106242
+ const prdPath = join84(featuresDir, name, "prd.json");
106076
106243
  if (existsSync37(prdPath)) {
106077
106244
  const prd = await loadPRD(prdPath);
106078
106245
  const c = countStories(prd);
@@ -106107,10 +106274,10 @@ Use: nax plan -f <feature> --from <spec>`));
106107
106274
  cliOverrides.profile = options.profile;
106108
106275
  }
106109
106276
  const config2 = await loadConfig(workdir, cliOverrides);
106110
- const featureLogDir = join82(naxDir, "features", options.feature, "plan");
106277
+ const featureLogDir = join84(naxDir, "features", options.feature, "plan");
106111
106278
  mkdirSync7(featureLogDir, { recursive: true });
106112
106279
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
106113
- const planLogPath = join82(featureLogDir, `${planLogId}.jsonl`);
106280
+ const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
106114
106281
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
106115
106282
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
106116
106283
  try {