@nathapp/nax 0.67.10 → 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 +1235 -572
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -16833,7 +16833,8 @@ var init_schemas_execution = __esm(() => {
16833
16833
  });
16834
16834
  RectificationConfigSchema = exports_external.object({
16835
16835
  enabled: exports_external.boolean().default(true),
16836
- maxRetries: exports_external.number().int().min(0).max(10).default(2),
16836
+ maxAttemptsTotal: exports_external.number().int().min(1).max(50).default(12),
16837
+ maxAttemptsPerStrategy: exports_external.number().int().min(1).max(20).default(3),
16837
16838
  fullSuiteTimeoutSeconds: exports_external.number().int().min(10).max(600).default(120),
16838
16839
  maxFailureSummaryChars: exports_external.number().int().min(500).max(1e4).default(2000),
16839
16840
  abortOnIncreasingFailures: exports_external.boolean().default(true),
@@ -16845,8 +16846,7 @@ var init_schemas_execution = __esm(() => {
16845
16846
  enabled: exports_external.boolean().default(true),
16846
16847
  timeoutSeconds: exports_external.number().int().min(10).max(600).default(120),
16847
16848
  acceptOnTimeout: exports_external.boolean().default(true),
16848
- mode: exports_external.enum(["deferred", "per-story", "disabled"]).default("deferred"),
16849
- maxRectificationAttempts: exports_external.number().int().min(1).default(2)
16849
+ mode: exports_external.enum(["deferred", "per-story", "disabled"]).default("deferred")
16850
16850
  });
16851
16851
  SmartTestRunnerConfigSchema = exports_external.object({
16852
16852
  enabled: exports_external.boolean().default(true),
@@ -16928,16 +16928,10 @@ var init_schemas_execution = __esm(() => {
16928
16928
  autofix: exports_external.object({
16929
16929
  enabled: exports_external.boolean().default(true),
16930
16930
  maxAttempts: exports_external.number().int().min(1).default(3),
16931
- maxTotalAttempts: exports_external.number().int().min(1).default(12),
16932
- rethinkAtAttempt: exports_external.number().int().min(1).default(2),
16933
- urgencyAtAttempt: exports_external.number().int().min(1).default(3),
16934
16931
  enforceTestWriterIsolation: exports_external.boolean().default(true)
16935
16932
  }).default({
16936
16933
  enabled: true,
16937
16934
  maxAttempts: 3,
16938
- maxTotalAttempts: 12,
16939
- rethinkAtAttempt: 2,
16940
- urgencyAtAttempt: 3,
16941
16935
  enforceTestWriterIsolation: true
16942
16936
  }),
16943
16937
  forceExit: exports_external.boolean().default(false),
@@ -17225,6 +17219,7 @@ var init_schemas_review = __esm(() => {
17225
17219
  excludePatterns: exports_external.array(exports_external.string()).optional(),
17226
17220
  parallel: exports_external.boolean().default(false),
17227
17221
  maxConcurrentSessions: exports_external.number().int().min(1).max(4).default(2),
17222
+ acRegroundOnDrop: exports_external.boolean().default(true),
17228
17223
  substantiation: exports_external.object({
17229
17224
  requote: exports_external.boolean().default(true),
17230
17225
  maxRequotes: exports_external.number().int().min(0).default(5)
@@ -17324,7 +17319,8 @@ var init_schemas3 = __esm(() => {
17324
17319
  maxStoriesPerFeature: 500,
17325
17320
  rectification: {
17326
17321
  enabled: true,
17327
- maxRetries: 2,
17322
+ maxAttemptsTotal: 12,
17323
+ maxAttemptsPerStrategy: 3,
17328
17324
  fullSuiteTimeoutSeconds: 300,
17329
17325
  maxFailureSummaryChars: 2000,
17330
17326
  abortOnIncreasingFailures: true,
@@ -17336,8 +17332,7 @@ var init_schemas3 = __esm(() => {
17336
17332
  enabled: true,
17337
17333
  timeoutSeconds: 300,
17338
17334
  acceptOnTimeout: true,
17339
- mode: "deferred",
17340
- maxRectificationAttempts: 3
17335
+ mode: "deferred"
17341
17336
  },
17342
17337
  contextProviderTokenBudget: 2000,
17343
17338
  permissionProfile: "unrestricted",
@@ -17363,9 +17358,6 @@ var init_schemas3 = __esm(() => {
17363
17358
  autofix: {
17364
17359
  enabled: true,
17365
17360
  maxAttempts: 3,
17366
- maxTotalAttempts: 12,
17367
- rethinkAtAttempt: 2,
17368
- urgencyAtAttempt: 3,
17369
17361
  enforceTestWriterIsolation: true
17370
17362
  },
17371
17363
  forceExit: false,
@@ -17462,6 +17454,7 @@ var init_schemas3 = __esm(() => {
17462
17454
  timeoutMs: 600000,
17463
17455
  parallel: false,
17464
17456
  maxConcurrentSessions: 2,
17457
+ acRegroundOnDrop: true,
17465
17458
  substantiation: {
17466
17459
  requote: true,
17467
17460
  maxRequotes: 5
@@ -18678,6 +18671,47 @@ function rejectLegacyAgentKeys(conf) {
18678
18671
  `);
18679
18672
  throw new NaxError(message, "CONFIG_LEGACY_AGENT_KEYS", { stage: "config", legacyKeys });
18680
18673
  }
18674
+ function rejectLegacyRectificationKeys(conf) {
18675
+ const legacyKeys = [];
18676
+ const migrationHints = [];
18677
+ const quality = conf.quality;
18678
+ const autofix = quality?.autofix;
18679
+ if (autofix && typeof autofix === "object") {
18680
+ if ("maxTotalAttempts" in autofix) {
18681
+ legacyKeys.push("quality.autofix.maxTotalAttempts");
18682
+ migrationHints.push("- Move `quality.autofix.maxTotalAttempts` \u2192 `execution.rectification.maxAttemptsTotal`");
18683
+ }
18684
+ if ("rethinkAtAttempt" in autofix) {
18685
+ legacyKeys.push("quality.autofix.rethinkAtAttempt");
18686
+ migrationHints.push("- Move `quality.autofix.rethinkAtAttempt` \u2192 `execution.rectification.rethinkAtAttempt`");
18687
+ }
18688
+ if ("urgencyAtAttempt" in autofix) {
18689
+ legacyKeys.push("quality.autofix.urgencyAtAttempt");
18690
+ migrationHints.push("- Move `quality.autofix.urgencyAtAttempt` \u2192 `execution.rectification.urgencyAtAttempt`");
18691
+ }
18692
+ }
18693
+ const execution = conf.execution;
18694
+ const rectification = execution?.rectification;
18695
+ if (rectification && typeof rectification === "object" && "maxRetries" in rectification) {
18696
+ legacyKeys.push("execution.rectification.maxRetries");
18697
+ migrationHints.push("- Rename `execution.rectification.maxRetries` \u2192 `execution.rectification.maxAttemptsTotal` (default changed from 2 to 12)");
18698
+ }
18699
+ const regressionGate = execution?.regressionGate;
18700
+ if (regressionGate && typeof regressionGate === "object" && "maxRectificationAttempts" in regressionGate) {
18701
+ legacyKeys.push("execution.regressionGate.maxRectificationAttempts");
18702
+ migrationHints.push("- Remove `execution.regressionGate.maxRectificationAttempts` \u2014 the regression cycle now shares `execution.rectification.maxAttemptsTotal`");
18703
+ }
18704
+ if (legacyKeys.length === 0)
18705
+ return;
18706
+ const message = [
18707
+ `Invalid configuration \u2014 legacy rectification-cap keys detected: ${legacyKeys.join(", ")}.`,
18708
+ "These were consolidated under `execution.rectification.*` so one config controls the unified",
18709
+ "fix cycle (semantic + adversarial + mechanical + regression). Migrate as follows:",
18710
+ ...migrationHints
18711
+ ].join(`
18712
+ `);
18713
+ throw new NaxError(message, "CONFIG_LEGACY_RECTIFICATION_KEYS", { stage: "config", legacyKeys });
18714
+ }
18681
18715
  function applyBatchModeCompat(conf) {
18682
18716
  const routing = conf.routing;
18683
18717
  const llm = routing?.llm;
@@ -18784,6 +18818,7 @@ async function loadConfig(startDir, cliOverrides) {
18784
18818
  return structuredClone(DEFAULT_CONFIG);
18785
18819
  }
18786
18820
  rejectLegacyAgentKeys(rawConfig);
18821
+ rejectLegacyRectificationKeys(rawConfig);
18787
18822
  const result = NaxConfigSchema.safeParse(rawConfig);
18788
18823
  if (!result.success) {
18789
18824
  const errors3 = result.error.issues.map((err) => {
@@ -18836,6 +18871,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir, cliOverrides) {
18836
18871
  const rawMerged = deepMergeConfig(merged, profileData);
18837
18872
  rawMerged.profile = packageProfile;
18838
18873
  rejectLegacyAgentKeys(rawMerged);
18874
+ rejectLegacyRectificationKeys(rawMerged);
18839
18875
  const result = NaxConfigSchema.safeParse(rawMerged);
18840
18876
  if (result.success) {
18841
18877
  merged = result.data;
@@ -21059,6 +21095,7 @@ class DispatchEventBus {
21059
21095
  _completedListeners = new Set;
21060
21096
  _errorListeners = new Set;
21061
21097
  _reviewDecisionListeners = new Set;
21098
+ _reviewRepromptListeners = new Set;
21062
21099
  onDispatch(l) {
21063
21100
  this._dispatchListeners.add(l);
21064
21101
  return () => this._dispatchListeners.delete(l);
@@ -21075,6 +21112,10 @@ class DispatchEventBus {
21075
21112
  this._reviewDecisionListeners.add(l);
21076
21113
  return () => this._reviewDecisionListeners.delete(l);
21077
21114
  }
21115
+ onReviewReprompt(l) {
21116
+ this._reviewRepromptListeners.add(l);
21117
+ return () => this._reviewRepromptListeners.delete(l);
21118
+ }
21078
21119
  emitDispatch(event) {
21079
21120
  for (const l of this._dispatchListeners) {
21080
21121
  try {
@@ -21111,6 +21152,15 @@ class DispatchEventBus {
21111
21152
  }
21112
21153
  }
21113
21154
  }
21155
+ emitReviewReprompt(event) {
21156
+ for (const l of this._reviewRepromptListeners) {
21157
+ try {
21158
+ l(event);
21159
+ } catch (err) {
21160
+ getSafeLogger()?.warn("dispatch-bus", "review-reprompt-listener threw", { error: errorMessage(err) });
21161
+ }
21162
+ }
21163
+ }
21114
21164
  }
21115
21165
  var init_dispatch_events = __esm(() => {
21116
21166
  init_logger2();
@@ -30342,78 +30392,6 @@ function truncate(s, max) {
30342
30392
  var MAX_BLOCK_CHARS = 6000;
30343
30393
 
30344
30394
  // src/prompts/builders/review-builder.ts
30345
- class ReviewPromptBuilder {
30346
- buildSemanticReviewPrompt(story, semanticConfig, options) {
30347
- const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
30348
- `);
30349
- const customRulesBlock = semanticConfig.rules.length > 0 ? `
30350
- ## Additional Review Rules
30351
- ${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
30352
- `)}
30353
- ` : "";
30354
- const priorIterationsBlock = buildPriorIterationsBlock(options.priorSemanticIterations ?? []);
30355
- let diffSection;
30356
- if (options.mode === "ref") {
30357
- diffSection = buildRefDiffSection(options.storyGitRef ?? "", options.stat ?? "", options.excludePatterns ?? []);
30358
- } else {
30359
- diffSection = buildEmbeddedDiffSection(options.diff ?? "");
30360
- }
30361
- const core2 = `${SEMANTIC_ROLE}
30362
-
30363
- ## Story: ${story.title}
30364
-
30365
- ### Description
30366
- ${story.description}
30367
-
30368
- ### Acceptance Criteria
30369
- ${acList}
30370
- ${customRulesBlock}${priorIterationsBlock}
30371
- ${SEMANTIC_INSTRUCTIONS}
30372
- ${SEMANTIC_OUTPUT_SCHEMA}
30373
-
30374
- ${diffSection}`;
30375
- return wrapJsonPrompt(core2);
30376
- }
30377
- static jsonRetry() {
30378
- return `Your previous response could not be parsed as valid JSON.
30379
- ` + `Output ONLY the JSON object from your review \u2014 no markdown fences, no explanation.
30380
- ` + "The object must start with { and end with }.";
30381
- }
30382
- static jsonRetryCondensed(opts) {
30383
- const threshold = opts?.blockingThreshold ?? "error";
30384
- const advisoryCap = opts?.advisoryCap ?? 3;
30385
- const blockingList = threshold === "error" ? '"error"' : threshold === "warning" ? '"error" and "warning"' : '"error", "warning", and "info"';
30386
- const blockingClause = threshold === "info" ? "Include ALL findings \u2014 do not drop any by severity." : `Include ALL findings with severity ${blockingList} (these are blocking \u2014 do not drop them).`;
30387
- const advisoryClause = threshold === "info" ? "If your response would still exceed limits, prioritize the highest-severity findings first." : `Below that, include at most ${advisoryCap} additional findings (highest severity first).`;
30388
- return `Your previous response was truncated and could not be parsed as valid JSON.
30389
- Respond with a condensed summary:
30390
- - ${blockingClause}
30391
- - ${advisoryClause}
30392
- - Keep \`verifiedBy\` for every finding. If \`verifiedBy.observed\` is long, abbreviate it to one line \u2014 never drop the field.
30393
- Output ONLY a complete, valid JSON object. It must start with { and end with }.
30394
- Schema: {"passed": boolean, "findings": [{"severity": string, "category": string, "file": string, "line": number, "issue": string, "suggestion": string, "verifiedBy": {"command": string, "file": string, "line": number, "observed": string}}]}`;
30395
- }
30396
- static requoteVerbatim(opts) {
30397
- const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
30398
- const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
30399
- return `Your previous verifiedBy.observed value did not match the referenced file on disk.
30400
-
30401
- You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
30402
-
30403
- Return ONLY this JSON object:
30404
- {"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
30405
-
30406
- Finding issue: ${opts.finding.issue}
30407
- Referenced file: ${file3}
30408
- Referenced line: ${line}
30409
-
30410
- Rules:
30411
- - Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
30412
- - observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
30413
- - If after reading the file you cannot find anything that proves the claim, set observed to "".
30414
- - Do not return a full review. Do not include markdown fences or explanation.`;
30415
- }
30416
- }
30417
30395
  function buildEmbeddedDiffSection(diff) {
30418
30396
  return `## Git Diff (production code only \u2014 test files excluded)
30419
30397
 
@@ -30493,9 +30471,114 @@ Notes:
30493
30471
  - \`acIndex\` is required when severity is "error" (1-based, into the Acceptance Criteria list above).
30494
30472
  - \`acQuote\` is optional advisory metadata for human auditors \u2014 not validated.
30495
30473
  - Omit both for "warning", "info", "unverifiable".
30496
- If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`;
30474
+ If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`, ReviewPromptBuilder;
30497
30475
  var init_review_builder = __esm(() => {
30498
30476
  SEMANTIC_ROLE = "You are a semantic code reviewer with access to the repository files. " + "Your job is to walk each acceptance criterion (AC) and judge whether the production code fulfills it \u2014 fully, partially, or not at all. " + "Test coverage gaps and convention/lint issues are out of scope \u2014 adversarial review and lint/typecheck handle those.";
30477
+ ReviewPromptBuilder = class ReviewPromptBuilder {
30478
+ buildSemanticReviewPrompt(story, semanticConfig, options) {
30479
+ const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
30480
+ `);
30481
+ const customRulesBlock = semanticConfig.rules.length > 0 ? `
30482
+ ## Additional Review Rules
30483
+ ${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
30484
+ `)}
30485
+ ` : "";
30486
+ const priorIterationsBlock = buildPriorIterationsBlock(options.priorSemanticIterations ?? []);
30487
+ let diffSection;
30488
+ if (options.mode === "ref") {
30489
+ diffSection = buildRefDiffSection(options.storyGitRef ?? "", options.stat ?? "", options.excludePatterns ?? []);
30490
+ } else {
30491
+ diffSection = buildEmbeddedDiffSection(options.diff ?? "");
30492
+ }
30493
+ const core2 = `${SEMANTIC_ROLE}
30494
+
30495
+ ## Story: ${story.title}
30496
+
30497
+ ### Description
30498
+ ${story.description}
30499
+
30500
+ ### Acceptance Criteria
30501
+ ${acList}
30502
+ ${customRulesBlock}${priorIterationsBlock}
30503
+ ${SEMANTIC_INSTRUCTIONS}
30504
+ ${SEMANTIC_OUTPUT_SCHEMA}
30505
+
30506
+ ${diffSection}`;
30507
+ return wrapJsonPrompt(core2);
30508
+ }
30509
+ static jsonRetry() {
30510
+ return `Your previous response could not be parsed as valid JSON.
30511
+ ` + `Output ONLY the JSON object from your review \u2014 no markdown fences, no explanation.
30512
+ ` + "The object must start with { and end with }.";
30513
+ }
30514
+ static jsonRetryCondensed(opts) {
30515
+ const threshold = opts?.blockingThreshold ?? "error";
30516
+ const advisoryCap = opts?.advisoryCap ?? 3;
30517
+ const blockingList = threshold === "error" ? '"error"' : threshold === "warning" ? '"error" and "warning"' : '"error", "warning", and "info"';
30518
+ const blockingClause = threshold === "info" ? "Include ALL findings \u2014 do not drop any by severity." : `Include ALL findings with severity ${blockingList} (these are blocking \u2014 do not drop them).`;
30519
+ const advisoryClause = threshold === "info" ? "If your response would still exceed limits, prioritize the highest-severity findings first." : `Below that, include at most ${advisoryCap} additional findings (highest severity first).`;
30520
+ return `Your previous response was truncated and could not be parsed as valid JSON.
30521
+ Respond with a condensed summary:
30522
+ - ${blockingClause}
30523
+ - ${advisoryClause}
30524
+ - Keep \`verifiedBy\` for every finding. If \`verifiedBy.observed\` is long, abbreviate it to one line \u2014 never drop the field.
30525
+ Output ONLY a complete, valid JSON object. It must start with { and end with }.
30526
+ Schema: {"passed": boolean, "findings": [{"severity": string, "category": string, "file": string, "line": number, "issue": string, "suggestion": string, "verifiedBy": {"command": string, "file": string, "line": number, "observed": string}}]}`;
30527
+ }
30528
+ static requoteVerbatim(opts) {
30529
+ const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
30530
+ const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
30531
+ return `Your previous verifiedBy.observed value did not match the referenced file on disk.
30532
+
30533
+ You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
30534
+
30535
+ Return ONLY this JSON object:
30536
+ {"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
30537
+
30538
+ Finding issue: ${opts.finding.issue}
30539
+ Referenced file: ${file3}
30540
+ Referenced line: ${line}
30541
+
30542
+ Rules:
30543
+ - Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
30544
+ - observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
30545
+ - If after reading the file you cannot find anything that proves the claim, set observed to "".
30546
+ - Do not return a full review. Do not include markdown fences or explanation.`;
30547
+ }
30548
+ static DROP_CODE_MESSAGES_MINIMAL = {
30549
+ missing_ac_index: "no `acIndex` field was provided \u2014 every blocking finding must cite an AC by 1-based index",
30550
+ ac_index_out_of_range: "`acIndex` is 0 or larger than the AC list \u2014 ACs are 1-indexed; the lowest valid value is 1"
30551
+ };
30552
+ static regroundDroppedFindings(opts) {
30553
+ const { drops, acceptanceCriteria } = opts;
30554
+ if (drops.length === 0)
30555
+ return "";
30556
+ const firstDrop = drops[0];
30557
+ const codeMessage = ReviewPromptBuilder.DROP_CODE_MESSAGES_MINIMAL[firstDrop.code];
30558
+ const acList = acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
30559
+ `);
30560
+ return `Your previous review produced ${drops.length} finding${drops.length > 1 ? "s" : ""} that ${drops.length > 1 ? "were" : "was"} dropped because:
30561
+
30562
+ ${codeMessage}
30563
+
30564
+ The dropped finding${drops.length > 1 ? "s" : ""} ${drops.length > 1 ? "are" : "is"}:
30565
+ ${drops.map((d, i) => `${i + 1}. [${d.finding.severity}] ${d.finding.issue}`).join(`
30566
+ `)}
30567
+
30568
+ Please re-review the code and re-issue any valid findings. For each finding you re-issue:
30569
+ - You MUST include a valid \`acIndex\` (1-based index into the AC list below)
30570
+ - You MUST include a \`verifiedBy\` field with verified evidence
30571
+
30572
+ ## Acceptance Criteria
30573
+ ${acList}
30574
+
30575
+ ## Rules
30576
+ - If a finding's locus (file / symbol) is not named in any AC bullet, downgrade it to \`"info"\` or \`"warning"\`
30577
+ - Only re-issue findings that are genuinely substantiated by the code and constrained by an AC
30578
+ - Return ONLY a JSON object with the same shape as before:
30579
+ {"passed":true|false,"findings":[...]}`;
30580
+ }
30581
+ };
30499
30582
  });
30500
30583
 
30501
30584
  // src/prompts/builders/adversarial-review-builder.ts
@@ -30578,92 +30661,6 @@ ${diff}\`\`\`
30578
30661
 
30579
30662
  `;
30580
30663
  }
30581
-
30582
- class AdversarialReviewPromptBuilder {
30583
- buildAdversarialReviewPrompt(story, config2, options) {
30584
- const {
30585
- mode,
30586
- diff,
30587
- storyGitRef,
30588
- stat,
30589
- testInventory,
30590
- excludePatterns,
30591
- testGlobs,
30592
- refExcludePatterns,
30593
- priorAdversarialIterations,
30594
- blockingThreshold
30595
- } = options;
30596
- const priorFindingsBlock = buildPriorIterationsBlock(priorAdversarialIterations ?? []);
30597
- const storyBlock = `## Story Under Review
30598
-
30599
- **ID:** ${story.id}
30600
- **Title:** ${story.title}
30601
- **Description:** ${story.description || "(none)"}
30602
-
30603
- **Acceptance Criteria:**
30604
- ${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
30605
- `)}
30606
-
30607
- `;
30608
- const customRulesBlock = config2.rules.length > 0 ? `## Project-Specific Adversarial Rules
30609
-
30610
- ${config2.rules.map((r) => `- ${r}`).join(`
30611
- `)}
30612
-
30613
- ` : "";
30614
- let diffBlock;
30615
- if (mode === "ref" && storyGitRef) {
30616
- diffBlock = buildAdversarialRefDiffSection(storyGitRef, stat, excludePatterns ?? [], testGlobs ?? [], refExcludePatterns ?? []);
30617
- } else if (mode === "embedded" && diff) {
30618
- diffBlock = buildAdversarialEmbeddedDiffSection(diff, testInventory);
30619
- } else {
30620
- diffBlock = `## Diff
30621
-
30622
- (No diff available \u2014 review based on story context only)
30623
-
30624
- `;
30625
- }
30626
- return [
30627
- ADVERSARIAL_ROLE,
30628
- `
30629
-
30630
- `,
30631
- priorFindingsBlock,
30632
- storyBlock,
30633
- ADVERSARIAL_INSTRUCTIONS,
30634
- `
30635
-
30636
- `,
30637
- customRulesBlock,
30638
- buildBlockingThresholdBlock(blockingThreshold ?? "error"),
30639
- OUTPUT_SCHEMA,
30640
- `
30641
-
30642
- `,
30643
- diffBlock
30644
- ].join("");
30645
- }
30646
- static requoteVerbatim(opts) {
30647
- const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
30648
- const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
30649
- return `Your previous verifiedBy.observed value did not match the referenced file on disk.
30650
-
30651
- You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
30652
-
30653
- Return ONLY this JSON object:
30654
- {"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
30655
-
30656
- Finding issue: ${opts.finding.issue}
30657
- Referenced file: ${file3}
30658
- Referenced line: ${line}
30659
-
30660
- Rules:
30661
- - Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
30662
- - observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
30663
- - If after reading the file you cannot find anything that proves the claim, set observed to "".
30664
- - Do not return a full review. Do not include markdown fences or explanation.`;
30665
- }
30666
- }
30667
30664
  var ADVERSARIAL_ROLE = `You are an adversarial code reviewer with full access to the repository.
30668
30665
 
30669
30666
  Your job is NOT to re-verify that the code satisfies the acceptance criteria \u2014 semantic review owns that question. Don't re-litigate AC correctness.
@@ -30772,8 +30769,130 @@ Worked example:
30772
30769
  **Scope constraints are not Acceptance Criteria:**
30773
30770
  The story description may contain a "Scope" section with "In:" and "Out:" bullets. These are implementation guidelines, not ACs. A finding about code changed outside the stated scope (e.g., a file listed under "Out:") cannot cite a scope constraint as its \`acQuote\`/\`acIndex\` because scope text is not in the numbered AC list. Emit scope-violation findings as \`"warning"\` \u2014 never \`"error"\`. Never use \`acIndex: 0\`; \`acIndex\` is 1-based (first AC bullet = 1).
30774
30771
 
30775
- If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\` or \`"warning"\`. A finding dropped by the validator is worse than one correctly classified as advisory.`;
30776
- var init_adversarial_review_builder = () => {};
30772
+ If you cannot find an AC that names the **specific symbol** in your finding, downgrade to \`"info"\` or \`"warning"\`. A finding dropped by the validator is worse than one correctly classified as advisory.`, AdversarialReviewPromptBuilder;
30773
+ var init_adversarial_review_builder = __esm(() => {
30774
+ AdversarialReviewPromptBuilder = class AdversarialReviewPromptBuilder {
30775
+ buildAdversarialReviewPrompt(story, config2, options) {
30776
+ const {
30777
+ mode,
30778
+ diff,
30779
+ storyGitRef,
30780
+ stat,
30781
+ testInventory,
30782
+ excludePatterns,
30783
+ testGlobs,
30784
+ refExcludePatterns,
30785
+ priorAdversarialIterations,
30786
+ blockingThreshold
30787
+ } = options;
30788
+ const priorFindingsBlock = buildPriorIterationsBlock(priorAdversarialIterations ?? []);
30789
+ const storyBlock = `## Story Under Review
30790
+
30791
+ **ID:** ${story.id}
30792
+ **Title:** ${story.title}
30793
+ **Description:** ${story.description || "(none)"}
30794
+
30795
+ **Acceptance Criteria:**
30796
+ ${story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
30797
+ `)}
30798
+
30799
+ `;
30800
+ const customRulesBlock = config2.rules.length > 0 ? `## Project-Specific Adversarial Rules
30801
+
30802
+ ${config2.rules.map((r) => `- ${r}`).join(`
30803
+ `)}
30804
+
30805
+ ` : "";
30806
+ let diffBlock;
30807
+ if (mode === "ref" && storyGitRef) {
30808
+ diffBlock = buildAdversarialRefDiffSection(storyGitRef, stat, excludePatterns ?? [], testGlobs ?? [], refExcludePatterns ?? []);
30809
+ } else if (mode === "embedded" && diff) {
30810
+ diffBlock = buildAdversarialEmbeddedDiffSection(diff, testInventory);
30811
+ } else {
30812
+ diffBlock = `## Diff
30813
+
30814
+ (No diff available \u2014 review based on story context only)
30815
+
30816
+ `;
30817
+ }
30818
+ return [
30819
+ ADVERSARIAL_ROLE,
30820
+ `
30821
+
30822
+ `,
30823
+ priorFindingsBlock,
30824
+ storyBlock,
30825
+ ADVERSARIAL_INSTRUCTIONS,
30826
+ `
30827
+
30828
+ `,
30829
+ customRulesBlock,
30830
+ buildBlockingThresholdBlock(blockingThreshold ?? "error"),
30831
+ OUTPUT_SCHEMA,
30832
+ `
30833
+
30834
+ `,
30835
+ diffBlock
30836
+ ].join("");
30837
+ }
30838
+ static requoteVerbatim(opts) {
30839
+ const file3 = opts.finding.verifiedBy?.file ?? opts.finding.file;
30840
+ const line = opts.finding.verifiedBy?.line ?? opts.finding.line;
30841
+ return `Your previous verifiedBy.observed value did not match the referenced file on disk.
30842
+
30843
+ You MUST use your file-reading tool to open ${file3} and copy the actual bytes around line ${line}. Do NOT quote from memory or from the prior conversation \u2014 the previous quote was wrong precisely because it was not read from disk. If you reply without a file-read tool call, the quote will be rejected.
30844
+
30845
+ Return ONLY this JSON object:
30846
+ {"file":"${file3}","line":${line},"observed":"exact 1-3 line quote"}
30847
+
30848
+ Finding issue: ${opts.finding.issue}
30849
+ Referenced file: ${file3}
30850
+ Referenced line: ${line}
30851
+
30852
+ Rules:
30853
+ - Read ${file3} with your file tool first. Then copy observed verbatim from the read result.
30854
+ - observed must be a 1-3 line excerpt that proves the claim, taken from at or near line ${line}.
30855
+ - If after reading the file you cannot find anything that proves the claim, set observed to "".
30856
+ - Do not return a full review. Do not include markdown fences or explanation.`;
30857
+ }
30858
+ static DROP_CODE_MESSAGES_QUOTE = {
30859
+ missing_ac_quote: "no `acQuote` field was provided \u2014 every blocking finding must cite an AC",
30860
+ ac_index_out_of_range: "`acIndex` is 0 or larger than the AC list \u2014 ACs are 1-indexed; the lowest valid value is 1",
30861
+ ac_quote_not_substring: "`acQuote` text does not appear verbatim in any AC bullet \u2014 copy the AC text character-for-character",
30862
+ ac_quote_does_not_constrain_locus: "the cited AC mentions the file but not the specific symbol your finding flags \u2014 pick a different AC, or downgrade to `info` / `warning`"
30863
+ };
30864
+ static regroundDroppedFindings(opts) {
30865
+ const { drops, acceptanceCriteria } = opts;
30866
+ if (drops.length === 0)
30867
+ return "";
30868
+ const firstDrop = drops[0];
30869
+ const codeMessage = AdversarialReviewPromptBuilder.DROP_CODE_MESSAGES_QUOTE[firstDrop.code] ?? `rejection code: ${firstDrop.code}`;
30870
+ const acList = acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
30871
+ `);
30872
+ return `Your previous review produced ${drops.length} finding${drops.length > 1 ? "s" : ""} that ${drops.length > 1 ? "were" : "was"} dropped because:
30873
+
30874
+ ${codeMessage}
30875
+
30876
+ The dropped finding${drops.length > 1 ? "s" : ""} ${drops.length > 1 ? "are" : "is"}:
30877
+ ${drops.map((d, i) => `${i + 1}. [${d.finding.severity}] ${d.finding.issue}`).join(`
30878
+ `)}
30879
+
30880
+ Please re-review the code and re-issue any valid findings. For each finding you re-issue:
30881
+ - You MUST include a valid \`acQuote\` that appears verbatim in one of the AC bullets below
30882
+ - You MUST include a valid \`acIndex\` (1-based index into the AC list)
30883
+ - The \`acQuote\` must cite the specific symbol you are flagging, not just the file
30884
+
30885
+ ## Acceptance Criteria
30886
+ ${acList}
30887
+
30888
+ ## Rules
30889
+ - If a finding's locus (file / symbol) is not named in any AC bullet, downgrade it to \`"info"\` or \`"warning"\`
30890
+ - Only re-issue findings that are genuinely substantiated by the code and constrained by an AC
30891
+ - Return ONLY a JSON object with the same shape as before:
30892
+ {"passed":true|false,"findings":[...]}`;
30893
+ }
30894
+ };
30895
+ });
30777
30896
 
30778
30897
  // src/prompts/builders/acceptance-builder-helpers.ts
30779
30898
  function formatTestOutputForFix(rawOutput) {
@@ -31546,6 +31665,28 @@ function isRecord(value) {
31546
31665
  var init_requote_response = () => {};
31547
31666
 
31548
31667
  // src/operations/adversarial-review.ts
31668
+ function withRepromptMarker(output, info) {
31669
+ const parsed = tryParseLLMJson(output);
31670
+ if (!parsed || typeof parsed !== "object")
31671
+ return output;
31672
+ return JSON.stringify({ ...parsed, _repromptInfo: info });
31673
+ }
31674
+ function extractRepromptInfo(raw) {
31675
+ if (!raw || typeof raw !== "object")
31676
+ return;
31677
+ const info = raw._repromptInfo;
31678
+ if (!info || typeof info !== "object")
31679
+ return;
31680
+ const i = info;
31681
+ if (typeof i.dropCount !== "number" || typeof i.costUsd !== "number" || typeof i.outcome !== "string") {
31682
+ return;
31683
+ }
31684
+ return {
31685
+ dropCount: i.dropCount,
31686
+ costUsd: i.costUsd,
31687
+ outcome: i.outcome
31688
+ };
31689
+ }
31549
31690
  async function requoteBlockingAdversarialFindings(findings, ctx) {
31550
31691
  const threshold = ctx.input.blockingThreshold ?? "error";
31551
31692
  const maxRequotes = ctx.input.adversarialConfig.substantiation?.maxRequotes ?? DEFAULT_MAX_REQUOTES;
@@ -31614,6 +31755,69 @@ async function requoteBlockingAdversarialFindings(findings, ctx) {
31614
31755
  }
31615
31756
  return { findings: next, changed, extraCostUsd };
31616
31757
  }
31758
+ function evaluateRepromptTrigger(shape, input) {
31759
+ if (input.adversarialConfig.acRegroundOnDrop === false)
31760
+ return { shouldReprompt: false };
31761
+ if (shape.passed)
31762
+ return { shouldReprompt: false };
31763
+ const { accepted, dropped } = filterByAcQuote(shape.findings, input.story.acceptanceCriteria);
31764
+ const threshold = input.blockingThreshold ?? "error";
31765
+ const blockingAccepted = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
31766
+ if (blockingAccepted.length > 0)
31767
+ return { shouldReprompt: false };
31768
+ if (dropped.length === 0)
31769
+ return { shouldReprompt: false };
31770
+ return { shouldReprompt: true, acDropped: dropped };
31771
+ }
31772
+ async function performAdversarialReground(turn, firstParsed, drops, ctx) {
31773
+ const threshold = ctx.input.blockingThreshold ?? "error";
31774
+ const acceptanceCriteria = ctx.input.story.acceptanceCriteria;
31775
+ const { accepted: firstAccepted } = filterByAcQuote(firstParsed.findings, acceptanceCriteria);
31776
+ const firstAdvisory = firstAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
31777
+ const repromptPrompt = AdversarialReviewPromptBuilder.regroundDroppedFindings({
31778
+ drops,
31779
+ acceptanceCriteria
31780
+ });
31781
+ const secondTurn = await ctx.send(repromptPrompt);
31782
+ const secondParsed = validateAdversarialShape(tryParseLLMJson(secondTurn.output));
31783
+ const costUsd = (turn.estimatedCostUsd ?? 0) + (secondTurn.estimatedCostUsd ?? 0);
31784
+ const dropCount = drops.length;
31785
+ if (!secondParsed) {
31786
+ return {
31787
+ ...turn,
31788
+ output: withRepromptMarker(turn.output, { dropCount, outcome: "parse-failed", costUsd })
31789
+ };
31790
+ }
31791
+ const { accepted: secondAccepted } = filterByAcQuote(secondParsed.findings, acceptanceCriteria);
31792
+ const secondBlocking = secondAccepted.filter((f) => isBlockingSeverity(f.severity, threshold));
31793
+ if (secondBlocking.length > 0) {
31794
+ return {
31795
+ ...turn,
31796
+ output: JSON.stringify({
31797
+ passed: false,
31798
+ findings: secondParsed.findings,
31799
+ _repromptInfo: { dropCount, outcome: "recovered-blocking", costUsd }
31800
+ }),
31801
+ estimatedCostUsd: costUsd
31802
+ };
31803
+ }
31804
+ if (secondParsed.passed) {
31805
+ const secondAdvisory = secondAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
31806
+ return {
31807
+ ...turn,
31808
+ output: JSON.stringify({
31809
+ passed: true,
31810
+ findings: [...firstAdvisory, ...secondAdvisory],
31811
+ _repromptInfo: { dropCount, outcome: "recovered-advisory-only", costUsd }
31812
+ }),
31813
+ estimatedCostUsd: costUsd
31814
+ };
31815
+ }
31816
+ return {
31817
+ ...turn,
31818
+ output: withRepromptMarker(turn.output, { dropCount, outcome: "still-dropped", costUsd })
31819
+ };
31820
+ }
31617
31821
  var FAIL_OPEN, ADVERSARIAL_REQUOTE_RECOVERED_EVENT = "review.adversarial.finding.requote_recovered", ADVERSARIAL_REQUOTE_FAILED_EVENT = "review.adversarial.finding.requote_failed", DEFAULT_MAX_REQUOTES = 5, adversarialParseRetry = (input) => makeParseRetryStrategy({
31618
31822
  validate: (parsed) => validateAdversarialShape(parsed) !== null,
31619
31823
  reviewerKind: "adversarial",
@@ -31654,15 +31858,30 @@ var init_adversarial_review = __esm(() => {
31654
31858
  const parsed = validateAdversarialShape(tryParseLLMJson(turn.output));
31655
31859
  if (!parsed)
31656
31860
  return turn;
31657
- const requoted = await requoteBlockingAdversarialFindings(parsed.findings, ctx);
31658
- if (!requoted.changed)
31861
+ if (ctx.input.mode !== "ref")
31659
31862
  return turn;
31660
- const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
31661
- return {
31662
- ...turn,
31663
- output: JSON.stringify({ passed, findings: requoted.findings }),
31664
- estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
31665
- };
31863
+ const regroundEnabled = ctx.input.adversarialConfig.acRegroundOnDrop !== false;
31864
+ if (regroundEnabled) {
31865
+ const firstShape = { passed: parsed.passed, findings: parsed.findings };
31866
+ const trigger = evaluateRepromptTrigger(firstShape, ctx.input);
31867
+ if (trigger.shouldReprompt) {
31868
+ return await performAdversarialReground(turn, parsed, trigger.acDropped, ctx);
31869
+ }
31870
+ }
31871
+ const requoteEnabled = ctx.input.adversarialConfig.substantiation?.requote ?? true;
31872
+ const maxRequotes = ctx.input.adversarialConfig.substantiation?.maxRequotes ?? DEFAULT_MAX_REQUOTES;
31873
+ if (!requoteEnabled || maxRequotes <= 0)
31874
+ return turn;
31875
+ const requoted = await requoteBlockingAdversarialFindings(parsed.findings, ctx);
31876
+ if (requoted.changed) {
31877
+ const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
31878
+ return {
31879
+ ...turn,
31880
+ output: JSON.stringify({ passed, findings: requoted.findings }),
31881
+ estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
31882
+ };
31883
+ }
31884
+ return turn;
31666
31885
  },
31667
31886
  build(input, _ctx) {
31668
31887
  const base = new AdversarialReviewPromptBuilder().buildAdversarialReviewPrompt(input.story, input.adversarialConfig, {
@@ -31686,16 +31905,25 @@ var init_adversarial_review = __esm(() => {
31686
31905
  parse(output, _input, _ctx) {
31687
31906
  const raw = tryParseLLMJson(output);
31688
31907
  const parsed = validateAdversarialShape(raw);
31908
+ const repromptEvent = extractRepromptInfo(raw);
31689
31909
  if (parsed) {
31690
31910
  return {
31691
31911
  passed: parsed.passed,
31692
31912
  findings: parsed.findings,
31693
31913
  normalizedFindings: [],
31694
- acDropped: []
31914
+ acDropped: [],
31915
+ repromptEvent
31695
31916
  };
31696
31917
  }
31697
31918
  if (/"passed"\s*:\s*false/.test(output) && !/"findings"\s*:\s*\[\s*\{/.test(output)) {
31698
- return { passed: false, findings: [], normalizedFindings: [], acDropped: [], looksLikeFail: true };
31919
+ return {
31920
+ passed: false,
31921
+ findings: [],
31922
+ normalizedFindings: [],
31923
+ acDropped: [],
31924
+ looksLikeFail: true,
31925
+ repromptEvent
31926
+ };
31699
31927
  }
31700
31928
  throw new ParseValidationError("[adversarial-review] parse failed: invalid JSON shape");
31701
31929
  },
@@ -32198,7 +32426,7 @@ async function runAdversarialReview(opts) {
32198
32426
  } = opts;
32199
32427
  const startTime = Date.now();
32200
32428
  const logger = getSafeLogger();
32201
- const effectiveRef = await resolveEffectiveRef(workdir, storyGitRef, story.id);
32429
+ const effectiveRef = await _adversarialDeps.resolveEffectiveRef(workdir, storyGitRef, story.id);
32202
32430
  if (!effectiveRef) {
32203
32431
  return {
32204
32432
  check: "adversarial",
@@ -32217,7 +32445,7 @@ async function runAdversarialReview(opts) {
32217
32445
  });
32218
32446
  const repoRoot = projectDir ?? workdir;
32219
32447
  const packageDir = workdir !== repoRoot ? workdir : undefined;
32220
- const stat = await collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32448
+ const stat = await _adversarialDeps.collectDiffStat(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32221
32449
  if (!stat) {
32222
32450
  return {
32223
32451
  check: "adversarial",
@@ -32399,6 +32627,16 @@ async function runAdversarialReview(opts) {
32399
32627
  durationMs: Date.now() - startTime
32400
32628
  };
32401
32629
  }
32630
+ if (opResult.repromptEvent) {
32631
+ runtime.dispatchEvents.emitReviewReprompt({
32632
+ kind: "review-reprompt-on-drop",
32633
+ storyId: story.id,
32634
+ reviewer: "adversarial",
32635
+ dropCount: opResult.repromptEvent.dropCount,
32636
+ repromptOutcome: opResult.repromptEvent.outcome,
32637
+ costUsd: opResult.repromptEvent.costUsd
32638
+ });
32639
+ }
32402
32640
  const threshold = blockingThreshold ?? "error";
32403
32641
  const allFindings = opResult.findings;
32404
32642
  const blockingFindings = allFindings.filter((f) => isBlockingSeverity(f.severity, threshold));
@@ -32410,7 +32648,7 @@ async function runAdversarialReview(opts) {
32410
32648
  diffFiles = extractDiffFiles(diff);
32411
32649
  diffAvailable = true;
32412
32650
  } else {
32413
- const list = await collectDiffFileList(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32651
+ const list = await _adversarialDeps.collectDiffFileList(workdir, effectiveRef, { naxIgnoreIndex, packageDir });
32414
32652
  if (list === undefined) {
32415
32653
  diffFiles = new Set;
32416
32654
  diffAvailable = false;
@@ -32586,7 +32824,10 @@ var init_adversarial = __esm(() => {
32586
32824
  init_review_audit();
32587
32825
  _adversarialDeps = {
32588
32826
  writeReviewAudit,
32589
- callOp
32827
+ callOp,
32828
+ resolveEffectiveRef,
32829
+ collectDiffStat,
32830
+ collectDiffFileList
32590
32831
  };
32591
32832
  });
32592
32833
 
@@ -33289,6 +33530,13 @@ class ScopedStrategy {
33289
33530
  const durationMs = Date.now() - start;
33290
33531
  if (result.success) {
33291
33532
  const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
33533
+ logger.info("verify[scoped]", "Scoped tests passed", {
33534
+ storyId: ctx.storyId,
33535
+ passCount: parsed2.passed,
33536
+ durationMs,
33537
+ scopeTestFallback: scopeTestFallback ?? false,
33538
+ isFullSuite
33539
+ });
33292
33540
  return makePassResult(ctx.storyId, "scoped", {
33293
33541
  rawOutput: result.output,
33294
33542
  passCount: parsed2.passed,
@@ -33297,6 +33545,12 @@ class ScopedStrategy {
33297
33545
  });
33298
33546
  }
33299
33547
  if (result.status === "TIMEOUT") {
33548
+ logger.warn("verify[scoped]", "Scoped tests timed out", {
33549
+ storyId: ctx.storyId,
33550
+ durationMs,
33551
+ scopeTestFallback: scopeTestFallback ?? false,
33552
+ isFullSuite
33553
+ });
33300
33554
  return makeFailResult(ctx.storyId, "scoped", "TIMEOUT", {
33301
33555
  rawOutput: result.output,
33302
33556
  durationMs,
@@ -33305,6 +33559,14 @@ class ScopedStrategy {
33305
33559
  });
33306
33560
  }
33307
33561
  const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
33562
+ logger.warn("verify[scoped]", "Scoped tests failed", {
33563
+ storyId: ctx.storyId,
33564
+ passCount: parsed.passed,
33565
+ failCount: parsed.failed,
33566
+ durationMs,
33567
+ scopeTestFallback: scopeTestFallback ?? false,
33568
+ isFullSuite
33569
+ });
33308
33570
  return makeFailResult(ctx.storyId, "scoped", "TEST_FAILURE", {
33309
33571
  rawOutput: result.output,
33310
33572
  passCount: parsed.passed,
@@ -35168,6 +35430,91 @@ var init_acceptance_fix = __esm(() => {
35168
35430
  });
35169
35431
 
35170
35432
  // src/operations/semantic-review.ts
35433
+ function withRepromptMarker2(output, info) {
35434
+ const parsed = tryParseLLMJson(output);
35435
+ if (!parsed || typeof parsed !== "object")
35436
+ return output;
35437
+ return JSON.stringify({ ...parsed, _repromptInfo: info });
35438
+ }
35439
+ function extractRepromptInfo2(raw) {
35440
+ if (!raw || typeof raw !== "object")
35441
+ return;
35442
+ const info = raw._repromptInfo;
35443
+ if (!info || typeof info !== "object")
35444
+ return;
35445
+ const i = info;
35446
+ if (typeof i.dropCount !== "number" || typeof i.costUsd !== "number" || typeof i.outcome !== "string") {
35447
+ return;
35448
+ }
35449
+ return {
35450
+ dropCount: i.dropCount,
35451
+ costUsd: i.costUsd,
35452
+ outcome: i.outcome
35453
+ };
35454
+ }
35455
+ function evaluateRepromptTrigger2(shape, input) {
35456
+ if (input.semanticConfig.acRegroundOnDrop === false)
35457
+ return { shouldReprompt: false };
35458
+ if (shape.passed)
35459
+ return { shouldReprompt: false };
35460
+ const { accepted, dropped } = filterByAcGroundingMinimal(shape.findings, input.story.acceptanceCriteria);
35461
+ const threshold = input.blockingThreshold ?? "error";
35462
+ const blockingAccepted = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
35463
+ if (blockingAccepted.length > 0)
35464
+ return { shouldReprompt: false };
35465
+ if (dropped.length === 0)
35466
+ return { shouldReprompt: false };
35467
+ return { shouldReprompt: true, acDropped: dropped };
35468
+ }
35469
+ async function performSemanticReground(turn, firstParsed, drops, ctx) {
35470
+ const threshold = ctx.input.blockingThreshold ?? "error";
35471
+ const acceptanceCriteria = ctx.input.story.acceptanceCriteria;
35472
+ const { accepted: firstAccepted } = filterByAcGroundingMinimal(firstParsed.findings, acceptanceCriteria);
35473
+ const firstAdvisory = firstAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
35474
+ const repromptPrompt = ReviewPromptBuilder.regroundDroppedFindings({
35475
+ drops,
35476
+ acceptanceCriteria
35477
+ });
35478
+ const secondTurn = await ctx.send(repromptPrompt);
35479
+ const secondParsed = validateLLMShape(tryParseLLMJson(secondTurn.output));
35480
+ const costUsd = (turn.estimatedCostUsd ?? 0) + (secondTurn.estimatedCostUsd ?? 0);
35481
+ const dropCount = drops.length;
35482
+ if (!secondParsed) {
35483
+ return {
35484
+ ...turn,
35485
+ output: withRepromptMarker2(turn.output, { dropCount, outcome: "parse-failed", costUsd })
35486
+ };
35487
+ }
35488
+ const { accepted: secondAccepted } = filterByAcGroundingMinimal(secondParsed.findings, acceptanceCriteria);
35489
+ const secondBlocking = secondAccepted.filter((f) => isBlockingSeverity(f.severity, threshold));
35490
+ if (secondBlocking.length > 0) {
35491
+ return {
35492
+ ...turn,
35493
+ output: JSON.stringify({
35494
+ passed: false,
35495
+ findings: secondParsed.findings,
35496
+ _repromptInfo: { dropCount, outcome: "recovered-blocking", costUsd }
35497
+ }),
35498
+ estimatedCostUsd: costUsd
35499
+ };
35500
+ }
35501
+ if (secondParsed.passed) {
35502
+ const secondAdvisory = secondAccepted.filter((f) => !isBlockingSeverity(f.severity, threshold));
35503
+ return {
35504
+ ...turn,
35505
+ output: JSON.stringify({
35506
+ passed: true,
35507
+ findings: [...firstAdvisory, ...secondAdvisory],
35508
+ _repromptInfo: { dropCount, outcome: "recovered-advisory-only", costUsd }
35509
+ }),
35510
+ estimatedCostUsd: costUsd
35511
+ };
35512
+ }
35513
+ return {
35514
+ ...turn,
35515
+ output: withRepromptMarker2(turn.output, { dropCount, outcome: "still-dropped", costUsd })
35516
+ };
35517
+ }
35171
35518
  async function requoteBlockingFindings(findings, ctx) {
35172
35519
  const threshold = ctx.input.blockingThreshold ?? "error";
35173
35520
  const maxRequotes = ctx.input.semanticConfig.substantiation?.maxRequotes ?? DEFAULT_MAX_REQUOTES2;
@@ -35243,14 +35590,24 @@ var FAIL_OPEN2, SEMANTIC_REQUOTE_RECOVERED_EVENT = "review.semantic.finding.requ
35243
35590
  if (!parsed)
35244
35591
  return turn;
35245
35592
  const requoted = await requoteBlockingFindings(parsed.findings, ctx);
35246
- if (!requoted.changed)
35593
+ if (requoted.changed) {
35594
+ const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
35595
+ return {
35596
+ ...turn,
35597
+ output: JSON.stringify({ passed, findings: requoted.findings }),
35598
+ estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
35599
+ };
35600
+ }
35601
+ if (ctx.input.mode !== "ref")
35247
35602
  return turn;
35248
- const passed = !requoted.findings.some((finding) => isBlockingSeverity(finding.severity, ctx.input.blockingThreshold ?? "error"));
35249
- return {
35250
- ...turn,
35251
- output: JSON.stringify({ passed, findings: requoted.findings }),
35252
- estimatedCostUsd: (turn.estimatedCostUsd ?? 0) + requoted.extraCostUsd
35253
- };
35603
+ const regroundEnabled = ctx.input.semanticConfig.acRegroundOnDrop !== false;
35604
+ if (!regroundEnabled)
35605
+ return turn;
35606
+ const firstShape = { passed: parsed.passed, findings: requoted.findings };
35607
+ const trigger = evaluateRepromptTrigger2(firstShape, ctx.input);
35608
+ if (!trigger.shouldReprompt)
35609
+ return turn;
35610
+ return performSemanticReground(turn, firstShape, trigger.acDropped, ctx);
35254
35611
  }, semanticReviewOp;
35255
35612
  var init_semantic_review = __esm(() => {
35256
35613
  init_retry();
@@ -35259,7 +35616,13 @@ var init_semantic_review = __esm(() => {
35259
35616
  init_prompts();
35260
35617
  init_finding_filters();
35261
35618
  init_requote_response();
35262
- FAIL_OPEN2 = { passed: true, findings: [], normalizedFindings: [], failOpen: true };
35619
+ FAIL_OPEN2 = {
35620
+ passed: true,
35621
+ findings: [],
35622
+ normalizedFindings: [],
35623
+ acDropped: [],
35624
+ failOpen: true
35625
+ };
35263
35626
  semanticReviewOp = {
35264
35627
  kind: "run",
35265
35628
  name: "semantic-review",
@@ -35276,7 +35639,7 @@ var init_semantic_review = __esm(() => {
35276
35639
  invalid: () => ReviewPromptBuilder.jsonRetry(),
35277
35640
  truncated: () => ReviewPromptBuilder.jsonRetryCondensed({ blockingThreshold: input.blockingThreshold })
35278
35641
  },
35279
- exhaustedFallback: (lastOutput) => /"passed"\s*:\s*false/.test(lastOutput) ? { passed: false, findings: [], normalizedFindings: [], looksLikeFail: true } : FAIL_OPEN2,
35642
+ exhaustedFallback: (lastOutput) => /"passed"\s*:\s*false/.test(lastOutput) ? { passed: false, findings: [], normalizedFindings: [], acDropped: [], looksLikeFail: true } : FAIL_OPEN2,
35280
35643
  logContext: { blockingThreshold: input.blockingThreshold ?? "error" }
35281
35644
  }),
35282
35645
  hopBody: semanticReviewHopBody,
@@ -35298,15 +35661,25 @@ var init_semantic_review = __esm(() => {
35298
35661
  parse(output, _input, _ctx) {
35299
35662
  const raw = tryParseLLMJson(output);
35300
35663
  const parsed = validateLLMShape(raw);
35664
+ const repromptEvent = extractRepromptInfo2(raw);
35301
35665
  if (parsed) {
35302
35666
  return {
35303
35667
  passed: parsed.passed,
35304
35668
  findings: parsed.findings,
35305
- normalizedFindings: []
35669
+ normalizedFindings: [],
35670
+ acDropped: [],
35671
+ repromptEvent
35306
35672
  };
35307
35673
  }
35308
35674
  if (/"passed"\s*:\s*false/.test(output)) {
35309
- return { passed: false, findings: [], normalizedFindings: [], looksLikeFail: true };
35675
+ return {
35676
+ passed: false,
35677
+ findings: [],
35678
+ normalizedFindings: [],
35679
+ acDropped: [],
35680
+ looksLikeFail: true,
35681
+ repromptEvent
35682
+ };
35310
35683
  }
35311
35684
  return FAIL_OPEN2;
35312
35685
  },
@@ -35319,14 +35692,15 @@ var init_semantic_review = __esm(() => {
35319
35692
  const findings = parsed.findings;
35320
35693
  const sanitized = sanitizeRefModeFindings(findings, input.mode, threshold);
35321
35694
  const substantiated = await substantiateSemanticEvidence(sanitized, input.mode, input.workdir, input.story.id, threshold);
35322
- const { accepted } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
35695
+ const { accepted, dropped } = filterByAcGroundingMinimal(substantiated, input.story.acceptanceCriteria);
35323
35696
  const blocking = accepted.filter((f) => isBlockingSeverity(f.severity, threshold));
35324
35697
  const passed = parsed.passed && blocking.length === 0;
35325
35698
  return {
35326
35699
  ...parsed,
35327
35700
  passed,
35328
35701
  findings: accepted,
35329
- normalizedFindings: toReviewFindings(blocking)
35702
+ normalizedFindings: toReviewFindings(blocking),
35703
+ acDropped: dropped
35330
35704
  };
35331
35705
  }
35332
35706
  };
@@ -35398,6 +35772,16 @@ function parseTestEditDeclarations(output) {
35398
35772
  }
35399
35773
  return result;
35400
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
+ }
35401
35785
  var REASON_RE;
35402
35786
  var init_test_edit_declaration = __esm(() => {
35403
35787
  REASON_RE = /^TEST_EDIT_REASON:\s*(prd_contract|lint_only|sibling_scope|mock_structure)\s*$/m;
@@ -37368,7 +37752,7 @@ var init_greenfield_gate = __esm(() => {
37368
37752
  });
37369
37753
  // src/verification/rectification.ts
37370
37754
  function shouldRetryRectification(state, config2) {
37371
- if (state.attempt >= config2.maxRetries) {
37755
+ if (state.attempt >= config2.maxAttemptsTotal) {
37372
37756
  return false;
37373
37757
  }
37374
37758
  if (state.lastExitCode !== undefined && state.lastExitCode !== 0 && state.currentFailures === 0) {
@@ -37471,7 +37855,7 @@ var init_full_suite_gate = __esm(() => {
37471
37855
  });
37472
37856
 
37473
37857
  // src/operations/full-suite-rectify.ts
37474
- function makeFullSuiteRectifyStrategy(story) {
37858
+ function makeFullSuiteRectifyStrategy(story, config2) {
37475
37859
  return {
37476
37860
  name: "full-suite-rectify",
37477
37861
  appliesTo: (finding) => finding.source === "test-runner" && finding.category === "failed-test",
@@ -37481,7 +37865,7 @@ function makeFullSuiteRectifyStrategy(story) {
37481
37865
  contextMarkdown: RectifierPromptBuilder.failingTestContext(findings)
37482
37866
  }),
37483
37867
  extractApplied: () => ({ targetFiles: [], summary: "Fixed failing tests" }),
37484
- maxAttempts: 3,
37868
+ maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37485
37869
  coRun: "exclusive"
37486
37870
  };
37487
37871
  }
@@ -37522,7 +37906,7 @@ var init__finding_to_check = __esm(() => {
37522
37906
  });
37523
37907
 
37524
37908
  // src/operations/autofix-implementer-strategy.ts
37525
- function makeAutofixImplementerStrategy(story) {
37909
+ function makeAutofixImplementerStrategy(story, config2, sink) {
37526
37910
  return {
37527
37911
  name: "autofix-implementer",
37528
37912
  appliesTo: (f) => f.fixTarget === "source" && IMPLEMENTER_SOURCES.has(f.source),
@@ -37531,11 +37915,20 @@ function makeAutofixImplementerStrategy(story) {
37531
37915
  failedChecks: findingsToFailedChecks(findings),
37532
37916
  story
37533
37917
  }),
37534
- extractApplied: (output) => ({
37535
- summary: output.unresolvedReason ?? "",
37536
- unresolved: output.unresolvedReason
37537
- }),
37538
- maxAttempts: 3,
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
+ },
37931
+ maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37539
37932
  coRun: "co-run-sequential"
37540
37933
  };
37541
37934
  }
@@ -37547,17 +37940,43 @@ var init_autofix_implementer_strategy = __esm(() => {
37547
37940
  });
37548
37941
 
37549
37942
  // src/operations/autofix-test-writer-strategy.ts
37550
- function makeAutofixTestWriterStrategy(story, config2) {
37943
+ function makeAutofixTestWriterStrategy(story, config2, sink) {
37551
37944
  return {
37552
37945
  name: "autofix-test-writer",
37553
- appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review",
37946
+ appliesTo: (f) => f.fixTarget === "test" || f.source === "adversarial-review" || sink.mockHandoffs.length > 0,
37554
37947
  fixOp: testWriterRectifyOp,
37555
- buildInput: (findings, _prior, _cycleCtx) => ({
37556
- failedChecks: findingsToFailedChecks(findings),
37557
- story,
37558
- blockingThreshold: config2.review?.blockingThreshold
37559
- }),
37560
- maxAttempts: 2,
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
+ },
37979
+ maxAttempts: config2.execution.rectification.maxAttemptsPerStrategy,
37561
37980
  coRun: "co-run-sequential"
37562
37981
  };
37563
37982
  }
@@ -37566,6 +37985,100 @@ var init_autofix_test_writer_strategy = __esm(() => {
37566
37985
  init_autofix_test_writer();
37567
37986
  });
37568
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
+
37569
38082
  // src/operations/mechanical-lintfix-strategy.ts
37570
38083
  function shellQuotePath2(path5) {
37571
38084
  return `'${path5.replaceAll("'", `'\\''`)}'`;
@@ -38046,6 +38559,8 @@ var init_operations = __esm(() => {
38046
38559
  init_full_suite_rectify();
38047
38560
  init_autofix_implementer_strategy();
38048
38561
  init_autofix_test_writer_strategy();
38562
+ init_apply_test_edit_declarations();
38563
+ init_validate_mock_structure_files();
38049
38564
  init__finding_to_check();
38050
38565
  init_mechanical_lintfix_strategy();
38051
38566
  init_mechanical_formatfix_strategy();
@@ -38696,7 +39211,7 @@ var init_lint_parsing = __esm(() => {
38696
39211
  });
38697
39212
 
38698
39213
  // src/review/scoped-lint.ts
38699
- import { join as join23, relative as relative10 } from "path";
39214
+ import { join as join24, relative as relative10 } from "path";
38700
39215
  function shellQuotePath4(path5) {
38701
39216
  return `'${path5.replaceAll("'", "'\\''")}'`;
38702
39217
  }
@@ -38744,7 +39259,7 @@ function uniqueFiles(files) {
38744
39259
  async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
38745
39260
  const inScope = [];
38746
39261
  for (const relPath of files) {
38747
- const absPath = join23(workdir, relPath);
39262
+ const absPath = join24(workdir, relPath);
38748
39263
  const exists = await _scopedLintDeps.fileExists(absPath);
38749
39264
  if (!exists)
38750
39265
  continue;
@@ -39502,6 +40017,42 @@ async function runSemanticReview(opts) {
39502
40017
  durationMs: Date.now() - startTime
39503
40018
  };
39504
40019
  }
40020
+ if (opResult.looksLikeFail) {
40021
+ logger?.warn("semantic", "LLM returned truncated JSON with passed:false \u2014 treating as failure", {
40022
+ storyId: story.id
40023
+ });
40024
+ recordSemanticAudit({
40025
+ runtime,
40026
+ workdir,
40027
+ projectDir,
40028
+ storyId: story.id,
40029
+ featureName,
40030
+ parsed: false,
40031
+ looksLikeFail: true,
40032
+ failOpen: false,
40033
+ passed: false,
40034
+ blockingThreshold,
40035
+ result: null
40036
+ });
40037
+ return {
40038
+ check: "semantic",
40039
+ success: false,
40040
+ command: "",
40041
+ exitCode: 1,
40042
+ output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
40043
+ durationMs: Date.now() - startTime
40044
+ };
40045
+ }
40046
+ if (opResult.repromptEvent) {
40047
+ runtime.dispatchEvents.emitReviewReprompt({
40048
+ kind: "review-reprompt-on-drop",
40049
+ storyId: story.id,
40050
+ reviewer: "semantic",
40051
+ dropCount: opResult.repromptEvent.dropCount,
40052
+ repromptOutcome: opResult.repromptEvent.outcome,
40053
+ costUsd: opResult.repromptEvent.costUsd
40054
+ });
40055
+ }
39505
40056
  const threshold = blockingThreshold ?? "error";
39506
40057
  const allFindings = opResult.findings;
39507
40058
  const blockingFindings = allFindings.filter((f) => isBlockingSeverity(f.severity, threshold));
@@ -39903,6 +40454,18 @@ async function runReview(opts) {
39903
40454
  naxIgnoreIndex
39904
40455
  }) : normalizeMechanicalFindings(checkName, await runCheck(checkName, command, workdir, storyId, env2), workdir);
39905
40456
  checks3.push(result);
40457
+ if (result.success) {
40458
+ logger?.info("review", `${checkName} passed`, {
40459
+ storyId,
40460
+ durationMs: result.durationMs
40461
+ });
40462
+ } else {
40463
+ logger?.warn("review", `${checkName} failed`, {
40464
+ storyId,
40465
+ exitCode: result.exitCode,
40466
+ durationMs: result.durationMs
40467
+ });
40468
+ }
39906
40469
  if (!result.success && !firstFailure) {
39907
40470
  firstFailure = `${checkName} failed (exit code ${result.exitCode})`;
39908
40471
  }
@@ -39962,9 +40525,34 @@ var init_review = __esm(() => {
39962
40525
  });
39963
40526
 
39964
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
+ }
39965
40553
  function escapeHatchFor(story) {
39966
40554
  const isTdd = THREE_SESSION_STRATEGIES.has(story.routing?.testStrategy ?? "");
39967
- return isTdd ? CONTRADICTION_ESCAPE_HATCH : CONTRADICTION_ESCAPE_HATCH.replace(EXCEPTION_4_MOCK_HANDOFF, "");
40555
+ return buildEscapeHatch({ includeMockHandoff: isTdd });
39968
40556
  }
39969
40557
  function noTestIsolationBlock(story) {
39970
40558
  if (story.routing?.testStrategy !== "no-test")
@@ -40034,7 +40622,7 @@ ${errors3}
40034
40622
  2. Only fix findings that are actually valid problems
40035
40623
  3. Do NOT add keys, functions, or imports that already exist \u2014 check first
40036
40624
 
40037
- 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.
40038
40626
  Do NOT add new features \u2014 only fix valid issues.
40039
40627
  Commit your fixes when done.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
40040
40628
  }
@@ -40096,21 +40684,11 @@ The following quality checks failed after implementation:
40096
40684
 
40097
40685
  ${errors3}
40098
40686
 
40099
- 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.
40100
40688
  Do NOT add new features \u2014 only fix the quality check errors.
40101
40689
  After fixing, re-run the failing check(s) to verify they pass, then commit your changes.${scopeConstraint}${noTestIsolationBlock(story)}${escapeHatchFor(story)}`;
40102
40690
  }
40103
- var CONTRADICTION_ESCAPE_HATCH = `
40104
- If two findings in this list contradict each other and you cannot satisfy both, do not guess.
40105
- Emit fixes for defects you can resolve, then output a line in this exact format:
40106
- UNRESOLVED: <brief explanation of which findings conflicted and why they cannot both be satisfied>
40107
-
40108
- ## Test-file edit exceptions
40109
-
40110
- The "do not modify test files" rule has three narrow escape valves. Each requires a
40111
- declaration in your output. Outside these three cases the rule is absolute.
40112
-
40113
- ### Exception 1 \u2014 Lint-only edit
40691
+ var EXCEPTION_1_LINT_ONLY = `### Exception 1 \u2014 Lint-only edit
40114
40692
 
40115
40693
  You MAY edit a test file ONLY when ALL of the following hold:
40116
40694
  - The failing check is \`lint\` \u2014 not \`test\`, \`typecheck\`, \`semantic\`, or \`adversarial\`.
@@ -40126,9 +40704,7 @@ TEST_EDIT_REASON: lint_only
40126
40704
  FILE: <test file path>
40127
40705
  FINDING: <lint rule or message verbatim>
40128
40706
  CHANGE: <before line> \u2192 <after line>
40129
- \`\`\`
40130
-
40131
- ### Exception 2 \u2014 PRD-contract mismatch
40707
+ \`\`\``, EXCEPTION_2_PRD_CONTRACT = `### Exception 2 \u2014 PRD-contract mismatch
40132
40708
 
40133
40709
  You MAY correct a test's argument arity, type, or return-handling ONLY when the test's
40134
40710
  call contradicts a literal interface signature stated in this story's description or
@@ -40144,9 +40720,7 @@ TEST_AFTER: <corrected call line>
40144
40720
  \`\`\`
40145
40721
 
40146
40722
  Do NOT use this exception to change test logic, assertions, or mock setup \u2014 only call
40147
- signatures that directly contradict a quoted PRD interface.
40148
-
40149
- ### 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
40150
40724
 
40151
40725
  When a lint or typecheck error is outside this story's intended scope, do NOT edit that
40152
40726
  file. If the smallest package-local fix is required to satisfy this story's acceptance
@@ -40156,46 +40730,37 @@ TEST_EDIT_REASON: sibling_scope
40156
40730
  SIBLING_FILE: <file path>
40157
40731
  FINDING: <error summary>
40158
40732
  \`\`\`
40159
- and continue. Sibling-scope failures do not block your story.
40160
-
40161
- ### 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
40162
40734
 
40163
40735
  Use ONLY when the only path to satisfy the ACs requires a structural test rewrite
40164
- that does NOT fit Exception 2. Examples: mocks reference primitives the new code
40165
- bypasses; assertion topology must change to match a new dispatch shape.
40166
-
40167
- Declare with:
40168
- \`\`\`
40169
- TEST_EDIT_REASON: mock_structure
40170
- FILES: <comma-separated test file paths>
40171
- REASON: <one paragraph: which mock is wrong vs which dispatch the new code uses>
40172
- \`\`\`
40736
+ that does NOT fit Exception 2. Two cases qualify:
40173
40737
 
40174
- Rules:
40175
- - Do NOT make any edits yourself; the test-writer will fulfill.
40176
- - Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
40177
- - FILES must list real test files. Each path must exist and be a test file.`, EXCEPTION_4_MOCK_HANDOFF = `
40178
- ### Exception 4 \u2014 Mock-structure handoff
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.
40179
40740
 
40180
- Use ONLY when the only path to satisfy the ACs requires a structural test rewrite
40181
- that does NOT fit Exception 2. Examples: mocks reference primitives the new code
40182
- 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.
40183
40746
 
40184
40747
  Declare with:
40185
40748
  \`\`\`
40186
40749
  TEST_EDIT_REASON: mock_structure
40187
40750
  FILES: <comma-separated test file paths>
40188
- 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>
40189
40753
  \`\`\`
40190
40754
 
40191
40755
  Rules:
40192
40756
  - Do NOT make any edits yourself; the test-writer will fulfill.
40193
40757
  - Do NOT also emit \`UNRESOLVED:\` in the same turn \u2014 this declaration IS the handoff.
40194
- - 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;
40195
40759
  var init_rectifier_builder_helpers = __esm(() => {
40196
40760
  init_review();
40197
40761
  init_sections2();
40198
40762
  THREE_SESSION_STRATEGIES = new Set(["three-session-tdd", "three-session-tdd-lite"]);
40763
+ CONTRADICTION_ESCAPE_HATCH = buildEscapeHatch({ includeMockHandoff: false });
40199
40764
  });
40200
40765
 
40201
40766
  // src/prompts/builders/rectifier-builder.ts
@@ -40276,15 +40841,16 @@ function renderPrioritizedFailures(failedChecks, opts) {
40276
40841
  }
40277
40842
 
40278
40843
  class RectifierPromptBuilder {
40279
- static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel) {
40844
+ static firstAttemptDelta(failedChecks, maxAttempts, guardrailLevel, story) {
40280
40845
  const parts = [];
40281
40846
  const attemptWord = maxAttempts === 1 ? "1 attempt" : `${maxAttempts} attempts`;
40847
+ const exCount = story ? exceptionCountWord(story) : "three";
40282
40848
  parts.push(`Review failed after your implementation. Fix the following issues (${attemptWord} available before escalation):
40283
40849
  `);
40284
40850
  parts.push(renderPrioritizedFailures(failedChecks));
40285
40851
  parts.push(`
40286
- 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.`);
40287
- 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);
40288
40854
  const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
40289
40855
  if (guardrails) {
40290
40856
  parts.push(`
@@ -40294,7 +40860,7 @@ ${guardrails}`);
40294
40860
  return parts.join(`
40295
40861
  `);
40296
40862
  }
40297
- static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel) {
40863
+ static continuation(failedChecks, attempt, rethinkAtAttempt, urgencyAtAttempt, guardrailLevel, story) {
40298
40864
  const parts = [];
40299
40865
  parts.push(`Your previous fix attempt did not resolve all issues. Here are the remaining failures:
40300
40866
  `);
@@ -40307,7 +40873,7 @@ ${guardrails}`);
40307
40873
  if (attempt >= urgencyAtAttempt) {
40308
40874
  parts.push("\n**URGENT: This is your final attempt.** If you cannot fix all issues, emit `UNRESOLVED: <reason>` to escalate.\n");
40309
40875
  }
40310
- parts.push(CONTRADICTION_ESCAPE_HATCH);
40876
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40311
40877
  const guardrails = buildBehavioralGuardrailsSection("implementer", guardrailLevel ?? "lite");
40312
40878
  if (guardrails) {
40313
40879
  parts.push(`
@@ -40429,7 +40995,7 @@ ${importantNote}
40429
40995
 
40430
40996
  Commit your fixes when done.${scopeConstraint}`;
40431
40997
  }
40432
- static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts) {
40998
+ static noOpReprompt(failedChecks, noOpCount, maxNoOpReprompts, opts, story) {
40433
40999
  const parts = [];
40434
41000
  parts.push(`**Your previous turn produced no committed file changes.**
40435
41001
 
@@ -40470,7 +41036,7 @@ ${output}
40470
41036
  `);
40471
41037
  }
40472
41038
  }
40473
- parts.push(CONTRADICTION_ESCAPE_HATCH);
41039
+ parts.push(story ? escapeHatchFor(story) : CONTRADICTION_ESCAPE_HATCH);
40474
41040
  return parts.join("");
40475
41041
  }
40476
41042
  static escalated(failures, story, priorAttempts, originalTier, targetTier, config2, testCommand, testScopedTemplate) {
@@ -40546,11 +41112,11 @@ ${testCommands}
40546
41112
  6. Ensure ALL tests pass before completing.
40547
41113
 
40548
41114
  **IMPORTANT:**
40549
- - 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.
40550
41116
  - Do NOT loosen assertions to mask implementation bugs.
40551
41117
  - Focus on fixing the source code to meet the test requirements.
40552
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"}.
40553
- `;
41119
+ ${escapeHatchFor(story)}`;
40554
41120
  }
40555
41121
  static reviewRectification(failedChecks, story, opts) {
40556
41122
  const scopeConstraint = story.workdir ? `
@@ -40607,7 +41173,7 @@ ${llmSection}
40607
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.
40608
41174
 
40609
41175
  Do NOT add new features \u2014 only fix the identified issues.
40610
- Commit your fixes when done.${scopeConstraint}${CONTRADICTION_ESCAPE_HATCH}`;
41176
+ Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
40611
41177
  }
40612
41178
  static dialogueAwareRectification(failedChecks, story, opts) {
40613
41179
  const scopeConstraint = story.workdir ? `
@@ -40646,9 +41212,9 @@ ${errors3}${reasoningSection}${historySection}
40646
41212
  2. Only fix findings that are actually valid problems
40647
41213
  3. Do NOT add keys, functions, or imports that already exist \u2014 check first
40648
41214
 
40649
- 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.
40650
41216
  Do NOT add new features \u2014 only fix valid issues.
40651
- Commit your fixes when done.${scopeConstraint}${CONTRADICTION_ESCAPE_HATCH}`;
41217
+ Commit your fixes when done.${scopeConstraint}${escapeHatchFor(story)}`;
40652
41218
  }
40653
41219
  static swapHandoff(basePrompt, pushMarkdown) {
40654
41220
  const trimmed = pushMarkdown?.trim();
@@ -40740,9 +41306,10 @@ Tests are failing. Fix the source so all tests pass \u2014 not just the ones lis
40740
41306
  4. Do not declare done until step 3 shows 0 failures.
40741
41307
 
40742
41308
  **IMPORTANT:**
40743
- - 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.
40744
41310
  - Do NOT loosen assertions to mask implementation bugs.
40745
41311
  - Focus on fixing the source code to meet the test requirements.`);
41312
+ parts.push(escapeHatchFor(opts.story));
40746
41313
  return parts.join("");
40747
41314
  }
40748
41315
  static failingTestContext(findings) {
@@ -42076,7 +42643,7 @@ var init_call = __esm(() => {
42076
42643
 
42077
42644
  // src/runtime/cost-aggregator.ts
42078
42645
  import { mkdirSync as mkdirSync2 } from "fs";
42079
- import { join as join24 } from "path";
42646
+ import { join as join25 } from "path";
42080
42647
  function makeCorrelationId() {
42081
42648
  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
42082
42649
  }
@@ -42267,7 +42834,7 @@ class CostAggregator {
42267
42834
  if (events.length === 0 && errors3.length === 0)
42268
42835
  return;
42269
42836
  mkdirSync2(this._drainDir, { recursive: true });
42270
- const path5 = join24(this._drainDir, `${this._runId}.jsonl`);
42837
+ const path5 = join25(this._drainDir, `${this._runId}.jsonl`);
42271
42838
  const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
42272
42839
  await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
42273
42840
  `)}
@@ -42307,7 +42874,7 @@ var init_cost_aggregator = __esm(() => {
42307
42874
  // src/runtime/prompt-auditor.ts
42308
42875
  import { appendFileSync } from "fs";
42309
42876
  import { mkdir as mkdir4 } from "fs/promises";
42310
- import { join as join25 } from "path";
42877
+ import { join as join26 } from "path";
42311
42878
  function createNoOpPromptAuditor() {
42312
42879
  return {
42313
42880
  record() {},
@@ -42373,8 +42940,8 @@ class PromptAuditor {
42373
42940
  _jsonlPath;
42374
42941
  _featureDir;
42375
42942
  constructor(runId, flushDir, featureName) {
42376
- this._featureDir = join25(flushDir, featureName);
42377
- this._jsonlPath = join25(this._featureDir, `${runId}.jsonl`);
42943
+ this._featureDir = join26(flushDir, featureName);
42944
+ this._jsonlPath = join26(this._featureDir, `${runId}.jsonl`);
42378
42945
  }
42379
42946
  record(entry) {
42380
42947
  this._enqueue(entry);
@@ -42423,7 +42990,7 @@ class PromptAuditor {
42423
42990
  const auditEntry = entry;
42424
42991
  const filename = deriveTxtFilename(auditEntry);
42425
42992
  try {
42426
- await _promptAuditorDeps.write(join25(this._featureDir, filename), buildTxtContent(auditEntry));
42993
+ await _promptAuditorDeps.write(join26(this._featureDir, filename), buildTxtContent(auditEntry));
42427
42994
  } catch (err) {
42428
42995
  throw tagAuditError(err, "txt");
42429
42996
  }
@@ -43511,7 +44078,7 @@ var init_pid_registry = __esm(() => {
43511
44078
  // src/session/manager-deps.ts
43512
44079
  import { randomUUID as randomUUID3 } from "crypto";
43513
44080
  import { mkdir as mkdir5 } from "fs/promises";
43514
- 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";
43515
44082
  function resolveProjectDirFromScratchDir(scratchDir) {
43516
44083
  const marker = `${sep3}.nax${sep3}features${sep3}`;
43517
44084
  const markerIdx = scratchDir.lastIndexOf(marker);
@@ -43532,7 +44099,7 @@ var init_manager_deps = __esm(() => {
43532
44099
  now: () => new Date().toISOString(),
43533
44100
  nowMs: () => Date.now(),
43534
44101
  uuid: () => randomUUID3(),
43535
- sessionScratchDir: (projectDir, featureName, sessionId) => join26(projectDir, ".nax", "features", featureName, "sessions", sessionId),
44102
+ sessionScratchDir: (projectDir, featureName, sessionId) => join27(projectDir, ".nax", "features", featureName, "sessions", sessionId),
43536
44103
  writeDescriptor: async (scratchDir, descriptor, projectDir) => {
43537
44104
  await mkdir5(scratchDir, { recursive: true });
43538
44105
  const { handle: _handle, ...persistable } = descriptor;
@@ -43543,7 +44110,7 @@ var init_manager_deps = __esm(() => {
43543
44110
  persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
43544
44111
  }
43545
44112
  }
43546
- 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));
43547
44114
  }
43548
44115
  };
43549
44116
  });
@@ -44294,7 +44861,7 @@ __export(exports_runtime, {
44294
44861
  CostAggregator: () => CostAggregator,
44295
44862
  AgentStreamEventBus: () => AgentStreamEventBus
44296
44863
  });
44297
- import { basename as basename5, join as join27 } from "path";
44864
+ import { basename as basename5, join as join28 } from "path";
44298
44865
  function createRuntime(config2, workdir, opts) {
44299
44866
  const runId = crypto.randomUUID();
44300
44867
  const controller = new AbortController;
@@ -44310,10 +44877,10 @@ function createRuntime(config2, workdir, opts) {
44310
44877
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
44311
44878
  const globalDir = globalOutputDir();
44312
44879
  const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
44313
- const costDir = join27(outputDir, "cost");
44880
+ const costDir = join28(outputDir, "cost");
44314
44881
  const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
44315
44882
  const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
44316
- const auditDir = config2.agent?.promptAudit?.dir ?? join27(outputDir, "prompt-audit");
44883
+ const auditDir = config2.agent?.promptAudit?.dir ?? join28(outputDir, "prompt-audit");
44317
44884
  let promptAuditor;
44318
44885
  if (opts?.promptAuditor) {
44319
44886
  promptAuditor = opts.promptAuditor;
@@ -44464,9 +45031,9 @@ async function allSettledBounded(tasks, limit) {
44464
45031
 
44465
45032
  // src/context/injector.ts
44466
45033
  import { existsSync as existsSync8 } from "fs";
44467
- import { join as join28 } from "path";
45034
+ import { join as join29 } from "path";
44468
45035
  async function detectNode(workdir) {
44469
- const pkgPath = join28(workdir, "package.json");
45036
+ const pkgPath = join29(workdir, "package.json");
44470
45037
  if (!existsSync8(pkgPath))
44471
45038
  return null;
44472
45039
  try {
@@ -44483,7 +45050,7 @@ async function detectNode(workdir) {
44483
45050
  }
44484
45051
  }
44485
45052
  async function detectGo(workdir) {
44486
- const goMod = join28(workdir, "go.mod");
45053
+ const goMod = join29(workdir, "go.mod");
44487
45054
  if (!existsSync8(goMod))
44488
45055
  return null;
44489
45056
  try {
@@ -44507,7 +45074,7 @@ async function detectGo(workdir) {
44507
45074
  }
44508
45075
  }
44509
45076
  async function detectRust(workdir) {
44510
- const cargoPath = join28(workdir, "Cargo.toml");
45077
+ const cargoPath = join29(workdir, "Cargo.toml");
44511
45078
  if (!existsSync8(cargoPath))
44512
45079
  return null;
44513
45080
  try {
@@ -44523,8 +45090,8 @@ async function detectRust(workdir) {
44523
45090
  }
44524
45091
  }
44525
45092
  async function detectPython(workdir) {
44526
- const pyproject = join28(workdir, "pyproject.toml");
44527
- const requirements = join28(workdir, "requirements.txt");
45093
+ const pyproject = join29(workdir, "pyproject.toml");
45094
+ const requirements = join29(workdir, "requirements.txt");
44528
45095
  if (!existsSync8(pyproject) && !existsSync8(requirements))
44529
45096
  return null;
44530
45097
  try {
@@ -44543,7 +45110,7 @@ async function detectPython(workdir) {
44543
45110
  }
44544
45111
  }
44545
45112
  async function detectPhp(workdir) {
44546
- const composerPath = join28(workdir, "composer.json");
45113
+ const composerPath = join29(workdir, "composer.json");
44547
45114
  if (!existsSync8(composerPath))
44548
45115
  return null;
44549
45116
  try {
@@ -44556,7 +45123,7 @@ async function detectPhp(workdir) {
44556
45123
  }
44557
45124
  }
44558
45125
  async function detectRuby(workdir) {
44559
- const gemfile = join28(workdir, "Gemfile");
45126
+ const gemfile = join29(workdir, "Gemfile");
44560
45127
  if (!existsSync8(gemfile))
44561
45128
  return null;
44562
45129
  try {
@@ -44568,9 +45135,9 @@ async function detectRuby(workdir) {
44568
45135
  }
44569
45136
  }
44570
45137
  async function detectJvm(workdir) {
44571
- const pom = join28(workdir, "pom.xml");
44572
- const gradle = join28(workdir, "build.gradle");
44573
- 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");
44574
45141
  if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
44575
45142
  return null;
44576
45143
  try {
@@ -44578,7 +45145,7 @@ async function detectJvm(workdir) {
44578
45145
  const content2 = await Bun.file(pom).text();
44579
45146
  const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
44580
45147
  const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
44581
- const lang2 = existsSync8(join28(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
45148
+ const lang2 = existsSync8(join29(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
44582
45149
  return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
44583
45150
  }
44584
45151
  const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
@@ -44832,7 +45399,7 @@ var init_windsurf = __esm(() => {
44832
45399
 
44833
45400
  // src/context/generator.ts
44834
45401
  import { existsSync as existsSync9 } from "fs";
44835
- import { join as join29, relative as relative13 } from "path";
45402
+ import { join as join30, relative as relative13 } from "path";
44836
45403
  async function loadContextContent(options, config2) {
44837
45404
  if (!_generatorDeps.existsSync(options.contextPath)) {
44838
45405
  throw new Error(`Context file not found: ${options.contextPath}`);
@@ -44850,7 +45417,7 @@ async function generateFor(agent, options, config2) {
44850
45417
  try {
44851
45418
  const context = await loadContextContent(options, config2);
44852
45419
  const content = generator.generate(context);
44853
- const outputPath = join29(options.outputDir, generator.outputFile);
45420
+ const outputPath = join30(options.outputDir, generator.outputFile);
44854
45421
  validateFilePath(outputPath, options.outputDir);
44855
45422
  if (!options.dryRun) {
44856
45423
  await _generatorDeps.writeFile(outputPath, content);
@@ -44868,7 +45435,7 @@ async function generateAll(options, config2, agentFilter) {
44868
45435
  for (const [agentKey, generator] of entries) {
44869
45436
  try {
44870
45437
  const content = generator.generate(context);
44871
- const outputPath = join29(options.outputDir, generator.outputFile);
45438
+ const outputPath = join30(options.outputDir, generator.outputFile);
44872
45439
  validateFilePath(outputPath, options.outputDir);
44873
45440
  if (!options.dryRun) {
44874
45441
  await _generatorDeps.writeFile(outputPath, content);
@@ -44888,7 +45455,7 @@ async function discoverPackages(repoRoot) {
44888
45455
  const glob = new Bun.Glob(pattern);
44889
45456
  for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
44890
45457
  const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
44891
- const pkgAbsolute = join29(repoRoot, pkgRelative);
45458
+ const pkgAbsolute = join30(repoRoot, pkgRelative);
44892
45459
  if (!seen.has(pkgAbsolute)) {
44893
45460
  seen.add(pkgAbsolute);
44894
45461
  packages.push(pkgAbsolute);
@@ -44920,14 +45487,14 @@ async function discoverWorkspacePackages2(repoRoot) {
44920
45487
  }
44921
45488
  }
44922
45489
  }
44923
- const turboPath = join29(repoRoot, "turbo.json");
45490
+ const turboPath = join30(repoRoot, "turbo.json");
44924
45491
  try {
44925
45492
  const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
44926
45493
  if (Array.isArray(turbo.packages)) {
44927
45494
  await resolveGlobs(turbo.packages);
44928
45495
  }
44929
45496
  } catch {}
44930
- const pkgPath = join29(repoRoot, "package.json");
45497
+ const pkgPath = join30(repoRoot, "package.json");
44931
45498
  try {
44932
45499
  const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
44933
45500
  const ws = pkg.workspaces;
@@ -44935,7 +45502,7 @@ async function discoverWorkspacePackages2(repoRoot) {
44935
45502
  if (patterns.length > 0)
44936
45503
  await resolveGlobs(patterns);
44937
45504
  } catch {}
44938
- const pnpmPath = join29(repoRoot, "pnpm-workspace.yaml");
45505
+ const pnpmPath = join30(repoRoot, "pnpm-workspace.yaml");
44939
45506
  try {
44940
45507
  const raw = await _generatorDeps.readTextFile(pnpmPath);
44941
45508
  const lines = raw.split(`
@@ -44961,7 +45528,7 @@ async function discoverWorkspacePackages2(repoRoot) {
44961
45528
  async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
44962
45529
  const resolvedRepoRoot = repoRoot ?? packageDir;
44963
45530
  const relativePkgPath = relative13(resolvedRepoRoot, packageDir);
44964
- const contextPath = join29(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
45531
+ const contextPath = join30(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
44965
45532
  if (!_generatorDeps.existsSync(contextPath)) {
44966
45533
  return [
44967
45534
  {
@@ -45029,7 +45596,7 @@ var init_generator2 = __esm(() => {
45029
45596
  });
45030
45597
 
45031
45598
  // src/analyze/scanner.ts
45032
- import { join as join30 } from "path";
45599
+ import { join as join31 } from "path";
45033
45600
  function resolveFrameworkAndRunner(language, pkg) {
45034
45601
  if (language === "go")
45035
45602
  return { framework: "", testRunner: "go-test" };
@@ -45051,7 +45618,7 @@ async function scanSourceRoots(workdir) {
45051
45618
  });
45052
45619
  try {
45053
45620
  const language = await deps.detectLanguage(workdir);
45054
- const pkg = await deps.readPackageJson(join30(workdir, "package.json"));
45621
+ const pkg = await deps.readPackageJson(join31(workdir, "package.json"));
45055
45622
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
45056
45623
  return [{ path: ".", language, framework, testRunner }];
45057
45624
  } catch {
@@ -45069,9 +45636,9 @@ async function scanSourceRoots(workdir) {
45069
45636
  packages = packages.slice(0, MAX_SOURCE_ROOTS);
45070
45637
  }
45071
45638
  return Promise.all(packages.map(async (pkgPath) => {
45072
- const pkgDir = pkgPath === "." ? workdir : join30(workdir, pkgPath);
45639
+ const pkgDir = pkgPath === "." ? workdir : join31(workdir, pkgPath);
45073
45640
  const language = await deps.detectLanguage(pkgDir);
45074
- const pkg = await deps.readPackageJson(join30(pkgDir, "package.json"));
45641
+ const pkg = await deps.readPackageJson(join31(pkgDir, "package.json"));
45075
45642
  const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
45076
45643
  return { path: pkgPath, language, framework, testRunner };
45077
45644
  }));
@@ -45104,7 +45671,7 @@ var init_analyze = __esm(() => {
45104
45671
  });
45105
45672
 
45106
45673
  // src/debate/pre-phase/grounder.ts
45107
- import { join as join31 } from "path";
45674
+ import { join as join32 } from "path";
45108
45675
  async function buildCodebaseContext(workdir) {
45109
45676
  const roots = await _grounderDeps.scanSourceRoots(workdir);
45110
45677
  return buildSourceRootsSection(normalizeRoots(workdir, roots));
@@ -45116,7 +45683,7 @@ function normalizeRoots(workdir, roots) {
45116
45683
  }));
45117
45684
  }
45118
45685
  async function writeManifestArtifact(ctx, manifest) {
45119
- 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");
45120
45687
  await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
45121
45688
  }
45122
45689
  var _grounderDeps, grounderStrategy = async (ctx) => {
@@ -45477,7 +46044,7 @@ function formatSpecDeltas(blockers, manifest) {
45477
46044
 
45478
46045
  // src/debate/verifiers/checks.ts
45479
46046
  import { existsSync as defaultExistsSync } from "fs";
45480
- import { join as join32 } from "path";
46047
+ import { join as join33 } from "path";
45481
46048
  function checkFilesExist(prd, workdir, deps) {
45482
46049
  const existsSync10 = deps?.existsSync ?? defaultExistsSync;
45483
46050
  const findings = [];
@@ -45487,7 +46054,7 @@ function checkFilesExist(prd, workdir, deps) {
45487
46054
  for (const entry of story.contextFiles) {
45488
46055
  const filePath = typeof entry === "string" ? entry : entry.path;
45489
46056
  const factId = typeof entry === "string" ? undefined : entry.factId;
45490
- const absPath = join32(workdir, filePath);
46057
+ const absPath = join33(workdir, filePath);
45491
46058
  if (existsSync10(absPath))
45492
46059
  continue;
45493
46060
  if (factId) {
@@ -45598,7 +46165,7 @@ var init_checks3 = () => {};
45598
46165
 
45599
46166
  // src/debate/verifiers/plan-checklist.ts
45600
46167
  import { existsSync as existsSync10 } from "fs";
45601
- import { join as join33 } from "path";
46168
+ import { join as join34 } from "path";
45602
46169
  function parsePrd(output) {
45603
46170
  if (!output)
45604
46171
  return null;
@@ -45609,7 +46176,7 @@ function parsePrd(output) {
45609
46176
  }
45610
46177
  }
45611
46178
  async function loadManifest(ctx) {
45612
- 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");
45613
46180
  const raw = await _planChecklistDeps.readFile(manifestPath);
45614
46181
  if (!raw)
45615
46182
  return null;
@@ -45622,7 +46189,7 @@ async function loadManifest(ctx) {
45622
46189
  }
45623
46190
  }
45624
46191
  async function emitSpecDeltas(ctx, blockers, manifest) {
45625
- 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");
45626
46193
  const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
45627
46194
  await _planChecklistDeps.write(artifactPath, content);
45628
46195
  return artifactPath;
@@ -45973,7 +46540,7 @@ var init_runner_plan_helpers = __esm(() => {
45973
46540
  });
45974
46541
 
45975
46542
  // src/debate/runner-plan.ts
45976
- import { join as join34 } from "path";
46543
+ import { join as join35 } from "path";
45977
46544
  async function runPlan(ctx, taskContext, outputFormat, opts) {
45978
46545
  const logger = _debateSessionDeps.getSafeLogger();
45979
46546
  const config2 = ctx.stageConfig;
@@ -46032,7 +46599,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
46032
46599
  sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
46033
46600
  proposers: ctx.stageConfig.proposers
46034
46601
  });
46035
- 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`));
46036
46603
  const successful = [];
46037
46604
  let rebuttalList;
46038
46605
  if (selectorKind === "verifier-pick") {
@@ -48280,9 +48847,9 @@ function validateFeatureName(feature) {
48280
48847
 
48281
48848
  // src/plan/critic.ts
48282
48849
  import { mkdir as mkdir6 } from "fs/promises";
48283
- import { dirname as dirname7, join as join37 } from "path";
48850
+ import { dirname as dirname7, join as join38 } from "path";
48284
48851
  async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
48285
- 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");
48286
48853
  await mkdir6(dirname7(path7), { recursive: true });
48287
48854
  await Bun.write(path7, formatSpecDeltas(findings, manifest));
48288
48855
  return path7;
@@ -49495,9 +50062,9 @@ __export(exports_plan_decompose, {
49495
50062
  runReplanLoop: () => runReplanLoop,
49496
50063
  planDecomposeCommand: () => planDecomposeCommand
49497
50064
  });
49498
- import { join as join38 } from "path";
50065
+ import { join as join39 } from "path";
49499
50066
  async function planDecomposeCommand(workdir, config2, options) {
49500
- const prdPath = join38(workdir, ".nax", "features", options.feature, "prd.json");
50067
+ const prdPath = join39(workdir, ".nax", "features", options.feature, "prd.json");
49501
50068
  if (!_planDeps.existsSync(prdPath)) {
49502
50069
  throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
49503
50070
  stage: "decompose",
@@ -49671,7 +50238,7 @@ var init_plan_decompose = __esm(() => {
49671
50238
 
49672
50239
  // src/cli/plan-runtime.ts
49673
50240
  import { existsSync as existsSync15 } from "fs";
49674
- import { join as join39 } from "path";
50241
+ import { join as join40 } from "path";
49675
50242
  function isRuntimeWithAgentManager(value) {
49676
50243
  return typeof value === "object" && value !== null && "agentManager" in value;
49677
50244
  }
@@ -49723,7 +50290,7 @@ var init_plan_runtime = __esm(() => {
49723
50290
  writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
49724
50291
  scanSourceRoots: (workdir) => scanSourceRoots(workdir),
49725
50292
  createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
49726
- readPackageJson: (workdir) => Bun.file(join39(workdir, "package.json")).json().catch(() => null),
50293
+ readPackageJson: (workdir) => Bun.file(join40(workdir, "package.json")).json().catch(() => null),
49727
50294
  spawnSync: (cmd, opts) => {
49728
50295
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
49729
50296
  return { stdout: result.stdout, exitCode: result.exitCode };
@@ -50108,7 +50675,7 @@ var init_metrics = __esm(() => {
50108
50675
 
50109
50676
  // src/commands/common.ts
50110
50677
  import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
50111
- import { join as join40, resolve as resolve13 } from "path";
50678
+ import { join as join41, resolve as resolve13 } from "path";
50112
50679
  function resolveProject(options = {}) {
50113
50680
  const { dir, feature } = options;
50114
50681
  let projectRoot;
@@ -50116,12 +50683,12 @@ function resolveProject(options = {}) {
50116
50683
  let configPath;
50117
50684
  if (dir) {
50118
50685
  projectRoot = realpathSync3(resolve13(dir));
50119
- naxDir = join40(projectRoot, ".nax");
50686
+ naxDir = join41(projectRoot, ".nax");
50120
50687
  if (!existsSync16(naxDir)) {
50121
50688
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
50122
50689
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
50123
50690
  }
50124
- configPath = join40(naxDir, "config.json");
50691
+ configPath = join41(naxDir, "config.json");
50125
50692
  if (!existsSync16(configPath)) {
50126
50693
  throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
50127
50694
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
@@ -50129,17 +50696,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
50129
50696
  } else {
50130
50697
  const found = findProjectRoot(process.cwd());
50131
50698
  if (!found) {
50132
- const cwdNaxDir = join40(process.cwd(), ".nax");
50699
+ const cwdNaxDir = join41(process.cwd(), ".nax");
50133
50700
  if (existsSync16(cwdNaxDir)) {
50134
- const cwdConfigPath = join40(cwdNaxDir, "config.json");
50701
+ const cwdConfigPath = join41(cwdNaxDir, "config.json");
50135
50702
  throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
50136
50703
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
50137
50704
  }
50138
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() });
50139
50706
  }
50140
50707
  projectRoot = found;
50141
- naxDir = join40(projectRoot, ".nax");
50142
- configPath = join40(naxDir, "config.json");
50708
+ naxDir = join41(projectRoot, ".nax");
50709
+ configPath = join41(naxDir, "config.json");
50143
50710
  }
50144
50711
  let featureDir;
50145
50712
  if (feature) {
@@ -50148,8 +50715,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
50148
50715
  } catch (error48) {
50149
50716
  throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
50150
50717
  }
50151
- const featuresDir = join40(naxDir, "features");
50152
- featureDir = join40(featuresDir, feature);
50718
+ const featuresDir = join41(naxDir, "features");
50719
+ featureDir = join41(featuresDir, feature);
50153
50720
  if (!existsSync16(featureDir)) {
50154
50721
  const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
50155
50722
  const availableMsg = availableFeatures.length > 0 ? `
@@ -50182,7 +50749,7 @@ async function resolveProjectAsync(options = {}) {
50182
50749
  }
50183
50750
  const isPlainName = !dir.includes("/") && !dir.includes("\\");
50184
50751
  if (isPlainName) {
50185
- const registryIdentityPath = join40(globalConfigDir(), dir, ".identity");
50752
+ const registryIdentityPath = join41(globalConfigDir(), dir, ".identity");
50186
50753
  const identityFile = Bun.file(registryIdentityPath);
50187
50754
  if (await identityFile.exists()) {
50188
50755
  try {
@@ -50214,12 +50781,12 @@ function findProjectRoot(startDir) {
50214
50781
  let current = resolve13(startDir);
50215
50782
  let depth = 0;
50216
50783
  while (depth < MAX_DIRECTORY_DEPTH) {
50217
- const naxDir = join40(current, ".nax");
50218
- const configPath = join40(naxDir, "config.json");
50784
+ const naxDir = join41(current, ".nax");
50785
+ const configPath = join41(naxDir, "config.json");
50219
50786
  if (existsSync16(configPath)) {
50220
50787
  return realpathSync3(current);
50221
50788
  }
50222
- const parent = join40(current, "..");
50789
+ const parent = join41(current, "..");
50223
50790
  if (parent === current) {
50224
50791
  break;
50225
50792
  }
@@ -51378,10 +51945,10 @@ var init_effectiveness = __esm(() => {
51378
51945
 
51379
51946
  // src/execution/progress.ts
51380
51947
  import { appendFile as appendFile3, mkdir as mkdir7 } from "fs/promises";
51381
- import { join as join43 } from "path";
51948
+ import { join as join44 } from "path";
51382
51949
  async function appendProgress(featureDir, storyId, status, message) {
51383
51950
  await mkdir7(featureDir, { recursive: true });
51384
- const progressPath = join43(featureDir, "progress.txt");
51951
+ const progressPath = join44(featureDir, "progress.txt");
51385
51952
  const timestamp = new Date().toISOString();
51386
51953
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
51387
51954
  `;
@@ -51570,7 +52137,7 @@ var init_completion = __esm(() => {
51570
52137
 
51571
52138
  // src/constitution/loader.ts
51572
52139
  import { existsSync as existsSync19 } from "fs";
51573
- import { join as join44 } from "path";
52140
+ import { join as join45 } from "path";
51574
52141
  function truncateToTokens(text, maxTokens) {
51575
52142
  const maxChars = maxTokens * 3;
51576
52143
  if (text.length <= maxChars) {
@@ -51592,7 +52159,7 @@ async function loadConstitution(projectDir, config2) {
51592
52159
  }
51593
52160
  let combinedContent = "";
51594
52161
  if (!config2.skipGlobal) {
51595
- const globalPath = join44(globalConfigDir(), config2.path);
52162
+ const globalPath = join45(globalConfigDir(), config2.path);
51596
52163
  if (existsSync19(globalPath)) {
51597
52164
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
51598
52165
  const globalFile = Bun.file(validatedPath);
@@ -51602,7 +52169,7 @@ async function loadConstitution(projectDir, config2) {
51602
52169
  }
51603
52170
  }
51604
52171
  }
51605
- const projectPath = join44(projectDir, config2.path);
52172
+ const projectPath = join45(projectDir, config2.path);
51606
52173
  if (existsSync19(projectPath)) {
51607
52174
  const validatedPath = validateFilePath(projectPath, projectDir);
51608
52175
  const projectFile = Bun.file(validatedPath);
@@ -52209,11 +52776,24 @@ function toReviewDecisionPayload(opName, output) {
52209
52776
  if (typeof record2.passed !== "boolean" || !Array.isArray(record2.findings)) {
52210
52777
  return null;
52211
52778
  }
52779
+ const acDropped = Array.isArray(record2.acDropped) ? record2.acDropped.map((d) => {
52780
+ const entry = d ?? {};
52781
+ const finding = entry.finding ?? {};
52782
+ return {
52783
+ code: typeof entry.code === "string" ? entry.code : undefined,
52784
+ severity: typeof finding.severity === "string" ? finding.severity : undefined,
52785
+ file: typeof finding.file === "string" ? finding.file : undefined,
52786
+ line: typeof finding.line === "number" ? finding.line : undefined,
52787
+ issue: typeof finding.issue === "string" ? finding.issue : undefined,
52788
+ acIndex: typeof finding.acIndex === "number" ? finding.acIndex : undefined
52789
+ };
52790
+ }) : undefined;
52212
52791
  return {
52213
52792
  reviewer,
52214
52793
  parsed: true,
52215
52794
  passed: record2.passed,
52216
- result: { passed: record2.passed, findings: record2.findings }
52795
+ result: { passed: record2.passed, findings: record2.findings },
52796
+ acDropped
52217
52797
  };
52218
52798
  }
52219
52799
  function emitReviewDecision(ctx, opName, output) {
@@ -52262,12 +52842,38 @@ function logUnifiedReviewPhaseResult(storyId, opName, output) {
52262
52842
  const title = payload.reviewer === "semantic" ? "Semantic review" : "Adversarial review";
52263
52843
  if (payload.passed) {
52264
52844
  logger?.info("review", `${title} passed`, { storyId });
52265
- } else {
52266
- logger?.warn("review", `${title} failed: ${findingsCount} findings`, {
52845
+ return;
52846
+ }
52847
+ if (findingsCount === 0) {
52848
+ const dropped = payload.acDropped ?? [];
52849
+ const droppedSummary = dropped.slice(0, 5);
52850
+ logger?.warn("review", `${title} failed: 0 findings \u2014 ${dropped.length > 0 ? `${dropped.length} blocking finding(s) dropped as ungrounded by AC-grounding filter` : "model emitted passed:false but produced no findings (likely empty output)"}`, {
52267
52851
  storyId,
52268
- findingsCount
52852
+ findingsCount,
52853
+ reason: dropped.length > 0 ? "ac-grounding-drop" : "passed-false-no-findings",
52854
+ droppedCount: dropped.length || undefined,
52855
+ droppedFindings: droppedSummary.length > 0 ? droppedSummary : undefined,
52856
+ droppedTruncated: dropped.length > droppedSummary.length || undefined
52269
52857
  });
52858
+ return;
52270
52859
  }
52860
+ const findingsSummary = payload.result.findings.slice(0, 5).map((f) => {
52861
+ const r = f ?? {};
52862
+ return {
52863
+ severity: typeof r.severity === "string" ? r.severity : undefined,
52864
+ file: typeof r.file === "string" ? r.file : undefined,
52865
+ line: typeof r.line === "number" ? r.line : undefined,
52866
+ rule: typeof r.rule === "string" ? r.rule : undefined,
52867
+ issue: typeof r.issue === "string" ? r.issue : typeof r.message === "string" ? r.message : undefined,
52868
+ acIndex: typeof r.acIndex === "number" ? r.acIndex : undefined
52869
+ };
52870
+ });
52871
+ logger?.warn("review", `${title} failed: ${findingsCount} findings`, {
52872
+ storyId,
52873
+ findingsCount,
52874
+ findings: findingsSummary,
52875
+ truncated: findingsCount > findingsSummary.length
52876
+ });
52271
52877
  }
52272
52878
  async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = false) {
52273
52879
  const logger = getSafeLogger();
@@ -52386,7 +52992,7 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52386
52992
  continue;
52387
52993
  findings.push(...extractPhaseFindings(phaseOutputs[phase.slot.op.name]));
52388
52994
  }
52389
- return findings;
52995
+ return rectification2.postValidate ? await rectification2.postValidate(findings, _validateCtx) : findings;
52390
52996
  }
52391
52997
  };
52392
52998
  const cycleResult = await _storyOrchestratorDeps.runFixCycle(cycle, ctx, "story-orchestrator-rectification", { callOp: wrappedCallOp });
@@ -52400,7 +53006,8 @@ async function runRectification(ctx, state, phaseCosts, phaseOutputs) {
52400
53006
  "max-attempts-total",
52401
53007
  "max-attempts-per-strategy",
52402
53008
  "bail-when",
52403
- "no-strategy"
53009
+ "no-strategy",
53010
+ "agent-gave-up"
52404
53011
  ]);
52405
53012
  if (exhaustedReasons.has(cycleResult.exitReason) && cycleResult.finalFindings.length > 0) {
52406
53013
  return { rectificationExhausted: true, unfixedFindings: cycleResult.finalFindings };
@@ -52585,6 +53192,7 @@ var init_story_orchestrator = __esm(() => {
52585
53192
  });
52586
53193
 
52587
53194
  // src/execution/build-plan-for-strategy.ts
53195
+ import { join as join46 } from "path";
52588
53196
  function isThreeSessionStrategy(strategy) {
52589
53197
  return THREE_SESSION_STRATEGIES2.has(strategy);
52590
53198
  }
@@ -52596,7 +53204,7 @@ function isFreshRun(story) {
52596
53204
  const hasReviewEscalation = (story.priorFailures ?? []).some((f) => f.stage === "review");
52597
53205
  return !hasAttempts && !hasReviewEscalation;
52598
53206
  }
52599
- function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
53207
+ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
52600
53208
  const isThreeSession = isThreeSessionStrategy(testStrategy);
52601
53209
  const freshRun = isFreshRun(story);
52602
53210
  const builder = new StoryOrchestratorBuilder;
@@ -52631,6 +53239,9 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
52631
53239
  builder.addAdversarialReview(inputs.adversarialReview);
52632
53240
  }
52633
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);
52634
53245
  const strategies = [];
52635
53246
  if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
52636
53247
  strategies.push(makeMechanicalLintFixStrategy());
@@ -52639,15 +53250,31 @@ function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
52639
53250
  strategies.push(makeMechanicalFormatFixStrategy());
52640
53251
  }
52641
53252
  if (isThreeSession && inputs.fullSuiteGate) {
52642
- strategies.push(makeFullSuiteRectifyStrategy(story));
53253
+ strategies.push(makeFullSuiteRectifyStrategy(story, config2));
52643
53254
  }
52644
53255
  if (config2.quality.autofix?.enabled !== false) {
52645
- strategies.push(makeAutofixImplementerStrategy(story));
52646
- strategies.push(makeAutofixTestWriterStrategy(story, config2));
52647
- }
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
+ };
52648
53274
  const rectOpts = {
52649
53275
  ...inputs.rectification,
52650
- strategies: [...strategies, ...inputs.rectification.strategies]
53276
+ strategies: [...strategies, ...inputs.rectification.strategies],
53277
+ postValidate
52651
53278
  };
52652
53279
  builder.addRectification(rectOpts);
52653
53280
  }
@@ -52658,6 +53285,7 @@ var init_build_plan_for_strategy = __esm(() => {
52658
53285
  init_operations();
52659
53286
  init_execution_gates();
52660
53287
  init_full_suite_rectify();
53288
+ init_test_runners();
52661
53289
  init_story_orchestrator();
52662
53290
  THREE_SESSION_STRATEGIES2 = new Set(["three-session-tdd", "three-session-tdd-lite"]);
52663
53291
  });
@@ -52783,9 +53411,9 @@ async function assemblePlanInputsFromCtx(ctx) {
52783
53411
  blockingThreshold: ctx.config.review.blockingThreshold
52784
53412
  } : undefined;
52785
53413
  const rectificationInput = ctx.config.execution?.rectification?.enabled === true ? {
52786
- maxAttempts: ctx.config.execution.rectification.maxRetries ?? 2,
53414
+ maxAttempts: ctx.config.execution.rectification.maxAttemptsTotal,
52787
53415
  strategies: [],
52788
- abortOnIncreasingFailures: ctx.config.execution.rectification.abortOnIncreasingFailures ?? true
53416
+ abortOnIncreasingFailures: ctx.config.execution.rectification.abortOnIncreasingFailures
52789
53417
  } : undefined;
52790
53418
  return {
52791
53419
  story,
@@ -53249,10 +53877,29 @@ Category: ${failureCategory ?? "unknown"}`,
53249
53877
  }
53250
53878
  }
53251
53879
  if (!planResult.success) {
53880
+ const failedPhases = {};
53881
+ for (const [name, output] of Object.entries(planResult.phaseOutputs)) {
53882
+ if (!output || typeof output !== "object")
53883
+ continue;
53884
+ const r = output;
53885
+ const passed = typeof r.passed === "boolean" ? r.passed : undefined;
53886
+ const success2 = typeof r.success === "boolean" ? r.success : undefined;
53887
+ const explicitFail = passed === false || success2 === false;
53888
+ if (!explicitFail)
53889
+ continue;
53890
+ const findings = Array.isArray(r.findings) ? r.findings.length : undefined;
53891
+ failedPhases[name] = { passed, success: success2, findingsCount: findings };
53892
+ }
53893
+ const stderrTail = (agentResult.stderr ?? "").slice(-500);
53894
+ const outputTail = (agentResult.output ?? "").slice(-500);
53252
53895
  logger.error("execution", "Agent session failed", {
53253
53896
  storyId: ctx.story.id,
53254
53897
  exitCode: agentResult.exitCode,
53255
- rateLimited: agentResult.rateLimited
53898
+ rateLimited: agentResult.rateLimited,
53899
+ failureCategory: failureCategory ?? "unknown",
53900
+ failedPhases: Object.keys(failedPhases).length > 0 ? failedPhases : undefined,
53901
+ stderrTail: stderrTail || undefined,
53902
+ outputTail: outputTail || undefined
53256
53903
  });
53257
53904
  if (agentResult.rateLimited) {
53258
53905
  logger.warn("execution", "Rate limited \u2014 will retry", { storyId: ctx.story.id });
@@ -53357,7 +54004,7 @@ var init_execution = __esm(() => {
53357
54004
  } : null;
53358
54005
  const initialRef = tddMode ? await _executionDeps.captureGitRef(ctx.workdir) ?? "HEAD" : null;
53359
54006
  const inputs = await _executionDeps.assemblePlanInputsFromCtx(ctx);
53360
- 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);
53361
54008
  let planResult;
53362
54009
  try {
53363
54010
  planResult = await plan.run();
@@ -53976,6 +54623,11 @@ class RegressionStrategy {
53976
54623
  const durationMs = Date.now() - start;
53977
54624
  if (result.success) {
53978
54625
  const parsed2 = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
54626
+ logger?.info("verify[regression]", "Full-suite regression gate passed", {
54627
+ storyId: ctx.storyId,
54628
+ passCount: parsed2.passed,
54629
+ durationMs
54630
+ });
53979
54631
  return makePassResult(ctx.storyId, "regression", {
53980
54632
  rawOutput: result.output,
53981
54633
  passCount: parsed2.passed,
@@ -53989,9 +54641,19 @@ class RegressionStrategy {
53989
54641
  return makePassResult(ctx.storyId, "regression", { durationMs });
53990
54642
  }
53991
54643
  if (result.status === "TIMEOUT") {
54644
+ logger?.warn("verify[regression]", "Full-suite regression gate timed out", {
54645
+ storyId: ctx.storyId,
54646
+ durationMs
54647
+ });
53992
54648
  return makeFailResult(ctx.storyId, "regression", "TIMEOUT", { rawOutput: result.output, durationMs });
53993
54649
  }
53994
54650
  const parsed = result.output ? parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
54651
+ logger?.warn("verify[regression]", "Full-suite regression gate failed", {
54652
+ storyId: ctx.storyId,
54653
+ passCount: parsed.passed,
54654
+ failCount: parsed.failed,
54655
+ durationMs
54656
+ });
53995
54657
  return makeFailResult(ctx.storyId, "regression", "TEST_FAILURE", {
53996
54658
  rawOutput: result.output,
53997
54659
  passCount: parsed.passed,
@@ -54289,7 +54951,7 @@ function buildFrontmatter(story, ctx, role) {
54289
54951
  }
54290
54952
 
54291
54953
  // src/cli/prompts-tdd.ts
54292
- import { join as join45 } from "path";
54954
+ import { join as join47 } from "path";
54293
54955
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
54294
54956
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
54295
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(),
@@ -54308,7 +54970,7 @@ ${frontmatter}---
54308
54970
 
54309
54971
  ${session.prompt}`;
54310
54972
  if (outputDir) {
54311
- const promptFile = join45(outputDir, `${story.id}.${session.role}.md`);
54973
+ const promptFile = join47(outputDir, `${story.id}.${session.role}.md`);
54312
54974
  await Bun.write(promptFile, fullOutput);
54313
54975
  logger.info("cli", "Written TDD prompt file", {
54314
54976
  storyId: story.id,
@@ -54324,7 +54986,7 @@ ${"=".repeat(80)}`);
54324
54986
  }
54325
54987
  }
54326
54988
  if (outputDir && ctx.contextMarkdown) {
54327
- const contextFile = join45(outputDir, `${story.id}.context.md`);
54989
+ const contextFile = join47(outputDir, `${story.id}.context.md`);
54328
54990
  const frontmatter = buildFrontmatter(story, ctx);
54329
54991
  const contextOutput = `---
54330
54992
  ${frontmatter}---
@@ -54339,16 +55001,16 @@ var init_prompts_tdd = __esm(() => {
54339
55001
 
54340
55002
  // src/cli/prompts-main.ts
54341
55003
  import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
54342
- import { join as join46 } from "path";
55004
+ import { join as join48 } from "path";
54343
55005
  async function promptsCommand(options) {
54344
55006
  const logger = getLogger();
54345
55007
  const { feature, workdir, config: config2, storyId, outputDir } = options;
54346
- const naxDir = join46(workdir, ".nax");
55008
+ const naxDir = join48(workdir, ".nax");
54347
55009
  if (!existsSync20(naxDir)) {
54348
55010
  throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
54349
55011
  }
54350
- const featureDir = join46(naxDir, "features", feature);
54351
- const prdPath = join46(featureDir, "prd.json");
55012
+ const featureDir = join48(naxDir, "features", feature);
55013
+ const prdPath = join48(featureDir, "prd.json");
54352
55014
  if (!existsSync20(prdPath)) {
54353
55015
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
54354
55016
  }
@@ -54415,10 +55077,10 @@ ${frontmatter}---
54415
55077
 
54416
55078
  ${ctx.prompt}`;
54417
55079
  if (outputDir) {
54418
- const promptFile = join46(outputDir, `${story.id}.prompt.md`);
55080
+ const promptFile = join48(outputDir, `${story.id}.prompt.md`);
54419
55081
  await Bun.write(promptFile, fullOutput);
54420
55082
  if (ctx.contextMarkdown) {
54421
- const contextFile = join46(outputDir, `${story.id}.context.md`);
55083
+ const contextFile = join48(outputDir, `${story.id}.context.md`);
54422
55084
  const contextOutput = `---
54423
55085
  ${frontmatter}---
54424
55086
 
@@ -54454,12 +55116,12 @@ var init_prompts_main = __esm(() => {
54454
55116
 
54455
55117
  // src/cli/prompts-init.ts
54456
55118
  import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
54457
- import { join as join47 } from "path";
55119
+ import { join as join49 } from "path";
54458
55120
  async function promptsInitCommand(options) {
54459
55121
  const { workdir, force = false, autoWireConfig = true } = options;
54460
- const templatesDir = join47(workdir, ".nax", "templates");
55122
+ const templatesDir = join49(workdir, ".nax", "templates");
54461
55123
  mkdirSync4(templatesDir, { recursive: true });
54462
- 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)));
54463
55125
  if (existingFiles.length > 0 && !force) {
54464
55126
  _promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
54465
55127
  Pass --force to overwrite existing templates.`);
@@ -54467,7 +55129,7 @@ async function promptsInitCommand(options) {
54467
55129
  }
54468
55130
  const written = [];
54469
55131
  for (const template of TEMPLATE_ROLES) {
54470
- const filePath = join47(templatesDir, template.file);
55132
+ const filePath = join49(templatesDir, template.file);
54471
55133
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
54472
55134
  const content = TEMPLATE_HEADER + roleBody;
54473
55135
  await Bun.write(filePath, content);
@@ -54483,7 +55145,7 @@ async function promptsInitCommand(options) {
54483
55145
  return written;
54484
55146
  }
54485
55147
  async function autoWirePromptsConfig(workdir) {
54486
- const configPath = join47(workdir, "nax.config.json");
55148
+ const configPath = join49(workdir, "nax.config.json");
54487
55149
  if (!existsSync21(configPath)) {
54488
55150
  const exampleConfig = JSON.stringify({
54489
55151
  prompts: {
@@ -54653,7 +55315,7 @@ __export(exports_init_context, {
54653
55315
  });
54654
55316
  import { existsSync as existsSync22 } from "fs";
54655
55317
  import { mkdir as mkdir8 } from "fs/promises";
54656
- import { basename as basename9, join as join48 } from "path";
55318
+ import { basename as basename9, join as join50 } from "path";
54657
55319
  async function findFiles(dir, maxFiles = 200) {
54658
55320
  try {
54659
55321
  const proc = Bun.spawnSync([
@@ -54681,7 +55343,7 @@ async function findFiles(dir, maxFiles = 200) {
54681
55343
  return [];
54682
55344
  }
54683
55345
  async function readPackageManifest(projectRoot) {
54684
- const packageJsonPath = join48(projectRoot, "package.json");
55346
+ const packageJsonPath = join50(projectRoot, "package.json");
54685
55347
  if (!existsSync22(packageJsonPath)) {
54686
55348
  return null;
54687
55349
  }
@@ -54699,7 +55361,7 @@ async function readPackageManifest(projectRoot) {
54699
55361
  }
54700
55362
  }
54701
55363
  async function readReadmeSnippet(projectRoot) {
54702
- const readmePath = join48(projectRoot, "README.md");
55364
+ const readmePath = join50(projectRoot, "README.md");
54703
55365
  if (!existsSync22(readmePath)) {
54704
55366
  return null;
54705
55367
  }
@@ -54717,7 +55379,7 @@ async function detectEntryPoints(projectRoot) {
54717
55379
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
54718
55380
  const found = [];
54719
55381
  for (const candidate of candidates) {
54720
- const path14 = join48(projectRoot, candidate);
55382
+ const path14 = join50(projectRoot, candidate);
54721
55383
  if (existsSync22(path14)) {
54722
55384
  found.push(candidate);
54723
55385
  }
@@ -54728,7 +55390,7 @@ async function detectConfigFiles(projectRoot) {
54728
55390
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
54729
55391
  const found = [];
54730
55392
  for (const candidate of candidates) {
54731
- const path14 = join48(projectRoot, candidate);
55393
+ const path14 = join50(projectRoot, candidate);
54732
55394
  if (existsSync22(path14)) {
54733
55395
  found.push(candidate);
54734
55396
  }
@@ -54889,8 +55551,8 @@ function generatePackageContextTemplate(packagePath) {
54889
55551
  }
54890
55552
  async function initPackage(repoRoot, packagePath, force = false) {
54891
55553
  const logger = getLogger();
54892
- const naxDir = join48(repoRoot, ".nax", "mono", packagePath);
54893
- const contextPath = join48(naxDir, "context.md");
55554
+ const naxDir = join50(repoRoot, ".nax", "mono", packagePath);
55555
+ const contextPath = join50(naxDir, "context.md");
54894
55556
  if (existsSync22(contextPath) && !force) {
54895
55557
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
54896
55558
  return;
@@ -54904,8 +55566,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
54904
55566
  }
54905
55567
  async function initContext(projectRoot, options = {}) {
54906
55568
  const logger = getLogger();
54907
- const naxDir = join48(projectRoot, ".nax");
54908
- const contextPath = join48(naxDir, "context.md");
55569
+ const naxDir = join50(projectRoot, ".nax");
55570
+ const contextPath = join50(naxDir, "context.md");
54909
55571
  if (existsSync22(contextPath) && !options.force) {
54910
55572
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
54911
55573
  return;
@@ -54935,9 +55597,9 @@ var init_init_context = __esm(() => {
54935
55597
 
54936
55598
  // src/cli/init-detect.ts
54937
55599
  import { existsSync as existsSync23, readFileSync } from "fs";
54938
- import { join as join49 } from "path";
55600
+ import { join as join51 } from "path";
54939
55601
  function readPackageJson(projectRoot) {
54940
- const pkgPath = join49(projectRoot, "package.json");
55602
+ const pkgPath = join51(projectRoot, "package.json");
54941
55603
  if (!existsSync23(pkgPath))
54942
55604
  return;
54943
55605
  try {
@@ -54980,41 +55642,41 @@ function detectStack(projectRoot) {
54980
55642
  };
54981
55643
  }
54982
55644
  function detectRuntime(projectRoot) {
54983
- if (existsSync23(join49(projectRoot, "bun.lockb")) || existsSync23(join49(projectRoot, "bunfig.toml"))) {
55645
+ if (existsSync23(join51(projectRoot, "bun.lockb")) || existsSync23(join51(projectRoot, "bunfig.toml"))) {
54984
55646
  return "bun";
54985
55647
  }
54986
- 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"))) {
54987
55649
  return "node";
54988
55650
  }
54989
55651
  return "unknown";
54990
55652
  }
54991
55653
  function detectLanguage2(projectRoot) {
54992
- if (existsSync23(join49(projectRoot, "tsconfig.json")))
55654
+ if (existsSync23(join51(projectRoot, "tsconfig.json")))
54993
55655
  return "typescript";
54994
- if (existsSync23(join49(projectRoot, "pyproject.toml")) || existsSync23(join49(projectRoot, "setup.py"))) {
55656
+ if (existsSync23(join51(projectRoot, "pyproject.toml")) || existsSync23(join51(projectRoot, "setup.py"))) {
54995
55657
  return "python";
54996
55658
  }
54997
- if (existsSync23(join49(projectRoot, "Cargo.toml")))
55659
+ if (existsSync23(join51(projectRoot, "Cargo.toml")))
54998
55660
  return "rust";
54999
- if (existsSync23(join49(projectRoot, "go.mod")))
55661
+ if (existsSync23(join51(projectRoot, "go.mod")))
55000
55662
  return "go";
55001
55663
  return "unknown";
55002
55664
  }
55003
55665
  function detectLinter(projectRoot) {
55004
- if (existsSync23(join49(projectRoot, "biome.json")) || existsSync23(join49(projectRoot, "biome.jsonc"))) {
55666
+ if (existsSync23(join51(projectRoot, "biome.json")) || existsSync23(join51(projectRoot, "biome.jsonc"))) {
55005
55667
  return "biome";
55006
55668
  }
55007
- 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"))) {
55008
55670
  return "eslint";
55009
55671
  }
55010
55672
  return "unknown";
55011
55673
  }
55012
55674
  function detectMonorepo(projectRoot) {
55013
- if (existsSync23(join49(projectRoot, "turbo.json")))
55675
+ if (existsSync23(join51(projectRoot, "turbo.json")))
55014
55676
  return "turborepo";
55015
- if (existsSync23(join49(projectRoot, "nx.json")))
55677
+ if (existsSync23(join51(projectRoot, "nx.json")))
55016
55678
  return "nx";
55017
- if (existsSync23(join49(projectRoot, "pnpm-workspace.yaml")))
55679
+ if (existsSync23(join51(projectRoot, "pnpm-workspace.yaml")))
55018
55680
  return "pnpm-workspaces";
55019
55681
  const pkg = readPackageJson(projectRoot);
55020
55682
  if (pkg?.workspaces)
@@ -55158,7 +55820,7 @@ __export(exports_init, {
55158
55820
  });
55159
55821
  import { existsSync as existsSync24 } from "fs";
55160
55822
  import { mkdir as mkdir9 } from "fs/promises";
55161
- import { join as join50 } from "path";
55823
+ import { join as join52 } from "path";
55162
55824
  function validateProjectName(name) {
55163
55825
  if (!name)
55164
55826
  return { valid: false, error: "name must be non-empty" };
@@ -55194,7 +55856,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
55194
55856
  }
55195
55857
  async function updateGitignore(projectRoot) {
55196
55858
  const logger = getLogger();
55197
- const gitignorePath = join50(projectRoot, ".gitignore");
55859
+ const gitignorePath = join52(projectRoot, ".gitignore");
55198
55860
  let existing = "";
55199
55861
  if (existsSync24(gitignorePath)) {
55200
55862
  existing = await Bun.file(gitignorePath).text();
@@ -55280,7 +55942,7 @@ async function initGlobal() {
55280
55942
  await mkdir9(globalDir, { recursive: true });
55281
55943
  logger.info("init", "Created global config directory", { path: globalDir });
55282
55944
  }
55283
- const configPath = join50(globalDir, "config.json");
55945
+ const configPath = join52(globalDir, "config.json");
55284
55946
  if (!existsSync24(configPath)) {
55285
55947
  await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
55286
55948
  `);
@@ -55288,14 +55950,14 @@ async function initGlobal() {
55288
55950
  } else {
55289
55951
  logger.info("init", "Global config already exists", { path: configPath });
55290
55952
  }
55291
- const constitutionPath = join50(globalDir, "constitution.md");
55953
+ const constitutionPath = join52(globalDir, "constitution.md");
55292
55954
  if (!existsSync24(constitutionPath)) {
55293
55955
  await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
55294
55956
  logger.info("init", "Created global constitution", { path: constitutionPath });
55295
55957
  } else {
55296
55958
  logger.info("init", "Global constitution already exists", { path: constitutionPath });
55297
55959
  }
55298
- const hooksDir = join50(globalDir, "hooks");
55960
+ const hooksDir = join52(globalDir, "hooks");
55299
55961
  if (!existsSync24(hooksDir)) {
55300
55962
  await mkdir9(hooksDir, { recursive: true });
55301
55963
  logger.info("init", "Created global hooks directory", { path: hooksDir });
@@ -55328,7 +55990,7 @@ async function initProject(projectRoot, options) {
55328
55990
  if (detectedName && !options?.force) {
55329
55991
  const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
55330
55992
  if (collision.collision && collision.existing) {
55331
- const configPath2 = join50(projectDir, "config.json");
55993
+ const configPath2 = join52(projectDir, "config.json");
55332
55994
  throw new NaxError([
55333
55995
  `Project name collision: "${detectedName}"`,
55334
55996
  ` This project: ${projectRoot}`,
@@ -55356,7 +56018,7 @@ async function initProject(projectRoot, options) {
55356
56018
  linter: stack.linter,
55357
56019
  monorepo: stack.monorepo
55358
56020
  });
55359
- const configPath = join50(projectDir, "config.json");
56021
+ const configPath = join52(projectDir, "config.json");
55360
56022
  if (!existsSync24(configPath)) {
55361
56023
  await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
55362
56024
  `);
@@ -55365,14 +56027,14 @@ async function initProject(projectRoot, options) {
55365
56027
  logger.info("init", "Project config already exists", { path: configPath });
55366
56028
  }
55367
56029
  await initContext(projectRoot, { ai: options?.ai, force: options?.force });
55368
- const constitutionPath = join50(projectDir, "constitution.md");
56030
+ const constitutionPath = join52(projectDir, "constitution.md");
55369
56031
  if (!existsSync24(constitutionPath) || options?.force) {
55370
56032
  await Bun.write(constitutionPath, buildConstitution(stack));
55371
56033
  logger.info("init", "Created project constitution", { path: constitutionPath });
55372
56034
  } else {
55373
56035
  logger.info("init", "Project constitution already exists", { path: constitutionPath });
55374
56036
  }
55375
- const hooksDir = join50(projectDir, "hooks");
56037
+ const hooksDir = join52(projectDir, "hooks");
55376
56038
  if (!existsSync24(hooksDir)) {
55377
56039
  await mkdir9(hooksDir, { recursive: true });
55378
56040
  logger.info("init", "Created project hooks directory", { path: hooksDir });
@@ -56807,12 +57469,12 @@ var init_loader4 = __esm(() => {
56807
57469
  });
56808
57470
 
56809
57471
  // src/utils/paths.ts
56810
- import { join as join62 } from "path";
57472
+ import { join as join64 } from "path";
56811
57473
  function getRunsDir() {
56812
- return process.env.NAX_RUNS_DIR ?? join62(globalConfigDir(), "runs");
57474
+ return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
56813
57475
  }
56814
57476
  function getEventsRootDir() {
56815
- return join62(globalConfigDir(), "events");
57477
+ return join64(globalConfigDir(), "events");
56816
57478
  }
56817
57479
  var init_paths3 = __esm(() => {
56818
57480
  init_paths();
@@ -56872,7 +57534,7 @@ var init_command_argv = __esm(() => {
56872
57534
  });
56873
57535
 
56874
57536
  // src/hooks/runner.ts
56875
- import { join as join69 } from "path";
57537
+ import { join as join71 } from "path";
56876
57538
  function createDrainDeadline2(deadlineMs) {
56877
57539
  let timeoutId;
56878
57540
  const promise2 = new Promise((resolve16) => {
@@ -56891,14 +57553,14 @@ async function loadHooksConfig(projectDir, globalDir) {
56891
57553
  let globalHooks = { hooks: {} };
56892
57554
  let projectHooks = { hooks: {} };
56893
57555
  let skipGlobal = false;
56894
- const projectPath = join69(projectDir, "hooks.json");
57556
+ const projectPath = join71(projectDir, "hooks.json");
56895
57557
  const projectData = await loadJsonFile(projectPath, "hooks");
56896
57558
  if (projectData) {
56897
57559
  projectHooks = projectData;
56898
57560
  skipGlobal = projectData.skipGlobal ?? false;
56899
57561
  }
56900
57562
  if (!skipGlobal && globalDir) {
56901
- const globalPath = join69(globalDir, "hooks.json");
57563
+ const globalPath = join71(globalDir, "hooks.json");
56902
57564
  const globalData = await loadJsonFile(globalPath, "hooks");
56903
57565
  if (globalData) {
56904
57566
  globalHooks = globalData;
@@ -57068,7 +57730,7 @@ var package_default;
57068
57730
  var init_package = __esm(() => {
57069
57731
  package_default = {
57070
57732
  name: "@nathapp/nax",
57071
- version: "0.67.10",
57733
+ version: "0.67.12",
57072
57734
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
57073
57735
  type: "module",
57074
57736
  bin: {
@@ -57163,8 +57825,8 @@ var init_version = __esm(() => {
57163
57825
  NAX_VERSION = package_default.version;
57164
57826
  NAX_COMMIT = (() => {
57165
57827
  try {
57166
- if (/^[0-9a-f]{6,10}$/.test("1d0ef5ac"))
57167
- return "1d0ef5ac";
57828
+ if (/^[0-9a-f]{6,10}$/.test("c747dea2"))
57829
+ return "c747dea2";
57168
57830
  } catch {}
57169
57831
  try {
57170
57832
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -58033,15 +58695,15 @@ var init_acceptance_loop = __esm(() => {
58033
58695
 
58034
58696
  // src/session/scratch-purge.ts
58035
58697
  import { mkdir as mkdir13, rename, rm } from "fs/promises";
58036
- import { dirname as dirname12, join as join70 } from "path";
58698
+ import { dirname as dirname12, join as join72 } from "path";
58037
58699
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
58038
- const sessionsDir = join70(projectDir, ".nax", "features", featureName, "sessions");
58700
+ const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
58039
58701
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
58040
58702
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
58041
58703
  let purged = 0;
58042
58704
  for (const sessionId of sessionIds) {
58043
- const sessionDir = join70(sessionsDir, sessionId);
58044
- const descriptorPath = join70(sessionDir, "descriptor.json");
58705
+ const sessionDir = join72(sessionsDir, sessionId);
58706
+ const descriptorPath = join72(sessionDir, "descriptor.json");
58045
58707
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
58046
58708
  continue;
58047
58709
  let lastActivityAt;
@@ -58057,7 +58719,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
58057
58719
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
58058
58720
  continue;
58059
58721
  if (archiveInsteadOfDelete) {
58060
- const archiveDest = join70(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
58722
+ const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
58061
58723
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
58062
58724
  } else {
58063
58725
  await _scratchPurgeDeps.remove(sessionDir);
@@ -58138,7 +58800,7 @@ async function runDeferredRegression(options) {
58138
58800
  }
58139
58801
  const testCommand = config2.quality.commands.test ?? "bun test";
58140
58802
  const timeoutSeconds = config2.execution.regressionGate?.timeoutSeconds ?? 120;
58141
- const maxRectificationAttempts = config2.execution.regressionGate?.maxRectificationAttempts ?? 2;
58803
+ const maxRectificationAttempts = config2.execution.rectification.maxAttemptsTotal;
58142
58804
  const acceptOnTimeout = config2.execution.regressionGate?.acceptOnTimeout ?? true;
58143
58805
  const verifyOpts = {
58144
58806
  workdir,
@@ -58302,7 +58964,7 @@ async function runDeferredRegression(options) {
58302
58964
  const cycle = {
58303
58965
  findings: initialFindings,
58304
58966
  iterations: [],
58305
- strategies: [makeFullSuiteRectifyStrategy(story)],
58967
+ strategies: [makeFullSuiteRectifyStrategy(story, config2)],
58306
58968
  config: { maxAttemptsTotal: maxRectificationAttempts, validatorRetries: 1 },
58307
58969
  validate: async (_cycleCtx, _opts) => {
58308
58970
  const verification = await _regressionDeps.runVerification(verifyOpts);
@@ -58766,12 +59428,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
58766
59428
 
58767
59429
  // src/pipeline/subscribers/events-writer.ts
58768
59430
  import { appendFile as appendFile5, mkdir as mkdir14 } from "fs/promises";
58769
- import { basename as basename14, join as join71 } from "path";
59431
+ import { basename as basename14, join as join73 } from "path";
58770
59432
  function wireEventsWriter(bus, feature, runId, workdir) {
58771
59433
  const logger = getSafeLogger();
58772
59434
  const project = basename14(workdir);
58773
- const eventsDir = join71(getEventsRootDir(), project);
58774
- const eventsFile = join71(eventsDir, "events.jsonl");
59435
+ const eventsDir = join73(getEventsRootDir(), project);
59436
+ const eventsFile = join73(eventsDir, "events.jsonl");
58775
59437
  let dirReady = false;
58776
59438
  const write = (line) => {
58777
59439
  return (async () => {
@@ -58952,12 +59614,12 @@ var init_interaction2 = __esm(() => {
58952
59614
 
58953
59615
  // src/pipeline/subscribers/registry.ts
58954
59616
  import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
58955
- import { basename as basename15, join as join72 } from "path";
59617
+ import { basename as basename15, join as join74 } from "path";
58956
59618
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
58957
59619
  const logger = getSafeLogger();
58958
59620
  const project = basename15(workdir);
58959
- const runDir = join72(getRunsDir(), `${project}-${feature}-${runId}`);
58960
- const metaFile = join72(runDir, "meta.json");
59621
+ const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
59622
+ const metaFile = join74(runDir, "meta.json");
58961
59623
  const unsub = bus.on("run:started", (_ev) => {
58962
59624
  return (async () => {
58963
59625
  try {
@@ -58967,8 +59629,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
58967
59629
  project,
58968
59630
  feature,
58969
59631
  workdir,
58970
- statusPath: join72(outputDir, "features", feature, "status.json"),
58971
- eventsDir: join72(outputDir, "features", feature, "runs"),
59632
+ statusPath: join74(outputDir, "features", feature, "status.json"),
59633
+ eventsDir: join74(outputDir, "features", feature, "runs"),
58972
59634
  registeredAt: new Date().toISOString()
58973
59635
  };
58974
59636
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -59214,7 +59876,7 @@ var init_types9 = __esm(() => {
59214
59876
 
59215
59877
  // src/worktree/dependencies.ts
59216
59878
  import { existsSync as existsSync32 } from "fs";
59217
- import { join as join73 } from "path";
59879
+ import { join as join75 } from "path";
59218
59880
  async function prepareWorktreeDependencies(options) {
59219
59881
  const mode = options.config.execution.worktreeDependencies.mode;
59220
59882
  const resolvedCwd = resolveDependencyCwd(options);
@@ -59228,7 +59890,7 @@ async function prepareWorktreeDependencies(options) {
59228
59890
  }
59229
59891
  }
59230
59892
  function resolveDependencyCwd(options) {
59231
- return options.storyWorkdir ? join73(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
59893
+ return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
59232
59894
  }
59233
59895
  function resolveInheritedDependencies(options, resolvedCwd) {
59234
59896
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -59238,7 +59900,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
59238
59900
  }
59239
59901
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
59240
59902
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
59241
- 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))));
59242
59904
  }
59243
59905
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
59244
59906
  const setupCommand = config2.execution.worktreeDependencies.setupCommand;
@@ -59302,13 +59964,13 @@ __export(exports_manager, {
59302
59964
  });
59303
59965
  import { existsSync as existsSync33, symlinkSync } from "fs";
59304
59966
  import { mkdir as mkdir16 } from "fs/promises";
59305
- import { join as join74 } from "path";
59967
+ import { join as join76 } from "path";
59306
59968
 
59307
59969
  class WorktreeManager {
59308
59970
  async ensureGitExcludes(projectRoot) {
59309
59971
  const logger = getSafeLogger();
59310
- const infoDir = join74(projectRoot, ".git", "info");
59311
- const excludePath = join74(infoDir, "exclude");
59972
+ const infoDir = join76(projectRoot, ".git", "info");
59973
+ const excludePath = join76(infoDir, "exclude");
59312
59974
  try {
59313
59975
  await mkdir16(infoDir, { recursive: true });
59314
59976
  let existing = "";
@@ -59335,7 +59997,7 @@ ${missing.join(`
59335
59997
  }
59336
59998
  async create(projectRoot, storyId) {
59337
59999
  validateStoryId(storyId);
59338
- const worktreePath = join74(projectRoot, ".nax-wt", storyId);
60000
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
59339
60001
  const branchName = `nax/${storyId}`;
59340
60002
  try {
59341
60003
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -59376,9 +60038,9 @@ ${missing.join(`
59376
60038
  }
59377
60039
  throw new Error(`Failed to create worktree: ${String(error48)}`);
59378
60040
  }
59379
- const envSource = join74(projectRoot, ".env");
60041
+ const envSource = join76(projectRoot, ".env");
59380
60042
  if (existsSync33(envSource)) {
59381
- const envTarget = join74(worktreePath, ".env");
60043
+ const envTarget = join76(worktreePath, ".env");
59382
60044
  try {
59383
60045
  symlinkSync(envSource, envTarget, "file");
59384
60046
  } catch (error48) {
@@ -59389,7 +60051,7 @@ ${missing.join(`
59389
60051
  }
59390
60052
  async remove(projectRoot, storyId) {
59391
60053
  validateStoryId(storyId);
59392
- const worktreePath = join74(projectRoot, ".nax-wt", storyId);
60054
+ const worktreePath = join76(projectRoot, ".nax-wt", storyId);
59393
60055
  const branchName = `nax/${storyId}`;
59394
60056
  try {
59395
60057
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -60189,10 +60851,10 @@ var init_merge_conflict_rectify = __esm(() => {
60189
60851
  });
60190
60852
 
60191
60853
  // src/execution/pipeline-result-handler.ts
60192
- import { join as join75 } from "path";
60854
+ import { join as join77 } from "path";
60193
60855
  async function removeWorktreeDirectory(projectRoot, storyId) {
60194
60856
  const logger = getSafeLogger();
60195
- const worktreePath = join75(projectRoot, ".nax-wt", storyId);
60857
+ const worktreePath = join77(projectRoot, ".nax-wt", storyId);
60196
60858
  try {
60197
60859
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
60198
60860
  cwd: projectRoot,
@@ -60348,7 +61010,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
60348
61010
  feature: ctx.feature,
60349
61011
  attempts: ctx.story.attempts
60350
61012
  });
60351
- if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxRetries) {
61013
+ if (ctx.story.attempts !== undefined && ctx.story.attempts >= ctx.config.execution.rectification.maxAttemptsTotal) {
60352
61014
  await pipelineEventBus.emitAsync({
60353
61015
  type: "human-review:requested",
60354
61016
  storyId: ctx.story.id,
@@ -60403,7 +61065,7 @@ var init_pipeline_result_handler = __esm(() => {
60403
61065
 
60404
61066
  // src/execution/iteration-runner.ts
60405
61067
  import { existsSync as existsSync34 } from "fs";
60406
- import { join as join76 } from "path";
61068
+ import { join as join78 } from "path";
60407
61069
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
60408
61070
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
60409
61071
  if (ctx.dryRun) {
@@ -60428,7 +61090,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60428
61090
  const storyStartTime = Date.now();
60429
61091
  let effectiveWorkdir = ctx.workdir;
60430
61092
  if (ctx.config.execution.storyIsolation === "worktree") {
60431
- const worktreePath = join76(ctx.workdir, ".nax-wt", story.id);
61093
+ const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
60432
61094
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
60433
61095
  if (!worktreeExists) {
60434
61096
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -60448,7 +61110,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60448
61110
  }
60449
61111
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
60450
61112
  const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
60451
- 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;
60452
61114
  let dependencyContext;
60453
61115
  if (ctx.config.execution.storyIsolation === "worktree") {
60454
61116
  try {
@@ -60475,7 +61137,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
60475
61137
  };
60476
61138
  }
60477
61139
  }
60478
- 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;
60479
61141
  const pipelineContext = {
60480
61142
  config: effectiveConfig,
60481
61143
  rootConfig: ctx.config,
@@ -60631,7 +61293,7 @@ function selectNextStories(prd, config2, batchPlan, currentBatchIndex, lastStory
60631
61293
  nextBatchIndex: currentBatchIndex + 1
60632
61294
  };
60633
61295
  }
60634
- const story = getNextStory(prd, lastStoryId, config2.execution.rectification?.maxRetries ?? 2);
61296
+ const story = getNextStory(prd, lastStoryId, config2.execution.rectification?.maxAttemptsTotal ?? 12);
60635
61297
  if (!story)
60636
61298
  return null;
60637
61299
  return {
@@ -60676,7 +61338,7 @@ __export(exports_parallel_worker, {
60676
61338
  executeParallelBatch: () => executeParallelBatch,
60677
61339
  _parallelWorkerDeps: () => _parallelWorkerDeps
60678
61340
  });
60679
- import { join as join77 } from "path";
61341
+ import { join as join79 } from "path";
60680
61342
  async function executeStoryInWorktree(story, worktreePath, dependencyContext, context, routing, eventEmitter) {
60681
61343
  const logger = getSafeLogger();
60682
61344
  try {
@@ -60696,7 +61358,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
60696
61358
  story,
60697
61359
  stories: [story],
60698
61360
  projectDir: context.projectDir,
60699
- workdir: dependencyContext.cwd ?? (story.workdir ? join77(worktreePath, story.workdir) : worktreePath),
61361
+ workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
60700
61362
  worktreeDependencyContext: dependencyContext,
60701
61363
  routing,
60702
61364
  storyGitRef: storyGitRef ?? undefined
@@ -61525,7 +62187,7 @@ async function writeStatusFile(filePath, status) {
61525
62187
  var init_status_file = () => {};
61526
62188
 
61527
62189
  // src/execution/status-writer.ts
61528
- import { join as join78 } from "path";
62190
+ import { join as join80 } from "path";
61529
62191
 
61530
62192
  class StatusWriter {
61531
62193
  statusFile;
@@ -61644,7 +62306,7 @@ class StatusWriter {
61644
62306
  if (!this._prd)
61645
62307
  return;
61646
62308
  const safeLogger = getSafeLogger();
61647
- const featureStatusPath = join78(featureDir, "status.json");
62309
+ const featureStatusPath = join80(featureDir, "status.json");
61648
62310
  const write = async () => {
61649
62311
  try {
61650
62312
  const base = this.getSnapshot(totalCost, iterations);
@@ -62078,7 +62740,7 @@ __export(exports_run_initialization, {
62078
62740
  initializeRun: () => initializeRun,
62079
62741
  _reconcileDeps: () => _reconcileDeps
62080
62742
  });
62081
- import { join as join79 } from "path";
62743
+ import { join as join81 } from "path";
62082
62744
  async function reconcileState(prd, prdPath, workdir, config2) {
62083
62745
  const logger = getSafeLogger();
62084
62746
  let reconciledCount = 0;
@@ -62095,7 +62757,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
62095
62757
  });
62096
62758
  continue;
62097
62759
  }
62098
- const effectiveWorkdir = story.workdir ? join79(workdir, story.workdir) : workdir;
62760
+ const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
62099
62761
  try {
62100
62762
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
62101
62763
  if (!reviewResult.success) {
@@ -93527,7 +94189,7 @@ __export(exports_curator, {
93527
94189
  });
93528
94190
  import { readdirSync as readdirSync9 } from "fs";
93529
94191
  import { unlink as unlink4 } from "fs/promises";
93530
- import { basename as basename16, join as join81 } from "path";
94192
+ import { basename as basename16, join as join83 } from "path";
93531
94193
  function getProjectKey(config2, projectDir) {
93532
94194
  return config2.name?.trim() || basename16(projectDir);
93533
94195
  }
@@ -93610,7 +94272,7 @@ async function curatorStatus(options) {
93610
94272
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93611
94273
  const projectKey = getProjectKey(config2, resolved.projectDir);
93612
94274
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93613
- const runsDir = join81(outputDir, "runs");
94275
+ const runsDir = join83(outputDir, "runs");
93614
94276
  const runIds = listRunIds(runsDir);
93615
94277
  let runId;
93616
94278
  if (options.run) {
@@ -93627,8 +94289,8 @@ async function curatorStatus(options) {
93627
94289
  runId = runIds[runIds.length - 1];
93628
94290
  }
93629
94291
  console.log(`Run: ${runId}`);
93630
- const runDir = join81(runsDir, runId);
93631
- const observationsPath = join81(runDir, "observations.jsonl");
94292
+ const runDir = join83(runsDir, runId);
94293
+ const observationsPath = join83(runDir, "observations.jsonl");
93632
94294
  const observations = await parseObservations(observationsPath);
93633
94295
  const counts = new Map;
93634
94296
  for (const obs of observations) {
@@ -93638,7 +94300,7 @@ async function curatorStatus(options) {
93638
94300
  for (const [kind, count] of counts.entries()) {
93639
94301
  console.log(` ${kind}: ${count}`);
93640
94302
  }
93641
- const proposalsPath = join81(runDir, "curator-proposals.md");
94303
+ const proposalsPath = join83(runDir, "curator-proposals.md");
93642
94304
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93643
94305
  if (proposalText !== null) {
93644
94306
  console.log("");
@@ -93652,8 +94314,8 @@ async function curatorCommit(options) {
93652
94314
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93653
94315
  const projectKey = getProjectKey(config2, resolved.projectDir);
93654
94316
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93655
- const runDir = join81(outputDir, "runs", options.runId);
93656
- const proposalsPath = join81(runDir, "curator-proposals.md");
94317
+ const runDir = join83(outputDir, "runs", options.runId);
94318
+ const proposalsPath = join83(runDir, "curator-proposals.md");
93657
94319
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
93658
94320
  if (proposalText === null) {
93659
94321
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -93669,7 +94331,7 @@ async function curatorCommit(options) {
93669
94331
  const dropFileState = new Map;
93670
94332
  const skippedDrops = new Set;
93671
94333
  for (const drop2 of drops) {
93672
- const targetPath = join81(resolved.projectDir, drop2.canonicalFile);
94334
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
93673
94335
  if (!dropFileState.has(targetPath)) {
93674
94336
  const fileExists2 = await Bun.file(targetPath).exists();
93675
94337
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -93703,7 +94365,7 @@ async function curatorCommit(options) {
93703
94365
  if (skippedDrops.has(drop2)) {
93704
94366
  continue;
93705
94367
  }
93706
- const targetPath = join81(resolved.projectDir, drop2.canonicalFile);
94368
+ const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
93707
94369
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
93708
94370
  const filtered = filterDropContent(existing, drop2.description);
93709
94371
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -93712,7 +94374,7 @@ async function curatorCommit(options) {
93712
94374
  }
93713
94375
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
93714
94376
  for (const add2 of adds) {
93715
- const targetPath = join81(resolved.projectDir, add2.canonicalFile);
94377
+ const targetPath = join83(resolved.projectDir, add2.canonicalFile);
93716
94378
  const content = buildAddContent(add2);
93717
94379
  await _curatorCmdDeps.appendFile(targetPath, content);
93718
94380
  modifiedFiles.add(targetPath);
@@ -93749,7 +94411,7 @@ async function curatorDryrun(options) {
93749
94411
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
93750
94412
  const projectKey = getProjectKey(config2, resolved.projectDir);
93751
94413
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93752
- const runsDir = join81(outputDir, "runs");
94414
+ const runsDir = join83(outputDir, "runs");
93753
94415
  const runIds = listRunIds(runsDir);
93754
94416
  if (runIds.length === 0) {
93755
94417
  console.log("No runs found.");
@@ -93760,7 +94422,7 @@ async function curatorDryrun(options) {
93760
94422
  console.log(`Run ${options.run} not found in ${runsDir}.`);
93761
94423
  return;
93762
94424
  }
93763
- const observationsPath = join81(runsDir, runId, "observations.jsonl");
94425
+ const observationsPath = join83(runsDir, runId, "observations.jsonl");
93764
94426
  const observations = await parseObservations(observationsPath);
93765
94427
  const thresholds = getThresholds(config2);
93766
94428
  const proposals = runHeuristics(observations, thresholds);
@@ -93801,12 +94463,12 @@ async function curatorGc(options) {
93801
94463
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
93802
94464
  const projectKey = getProjectKey(config2, resolved.projectDir);
93803
94465
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
93804
- const perRunsDir = join81(outputDir, "runs");
94466
+ const perRunsDir = join83(outputDir, "runs");
93805
94467
  for (const runId of uniqueRunIds) {
93806
94468
  if (!keepSet.has(runId)) {
93807
- const runDir = join81(perRunsDir, runId);
93808
- await _curatorCmdDeps.removeFile(join81(runDir, "observations.jsonl"));
93809
- 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"));
93810
94472
  }
93811
94473
  }
93812
94474
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -93851,7 +94513,7 @@ var init_curator2 = __esm(() => {
93851
94513
  init_source();
93852
94514
  import { existsSync as existsSync37, mkdirSync as mkdirSync7 } from "fs";
93853
94515
  import { homedir as homedir3 } from "os";
93854
- import { basename as basename17, join as join82 } from "path";
94516
+ import { basename as basename17, join as join84 } from "path";
93855
94517
 
93856
94518
  // node_modules/commander/esm.mjs
93857
94519
  var import__ = __toESM(require_commander(), 1);
@@ -93875,12 +94537,12 @@ init_errors();
93875
94537
  init_operations();
93876
94538
 
93877
94539
  // src/plan/strategies/context-builder.ts
93878
- import { join as join36 } from "path";
94540
+ import { join as join37 } from "path";
93879
94541
  init_config();
93880
94542
  init_errors();
93881
94543
  init_interaction();
93882
94544
  async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93883
- const naxDir = join36(workdir, ".nax");
94545
+ const naxDir = join37(workdir, ".nax");
93884
94546
  if (!deps.existsSync(naxDir)) {
93885
94547
  throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
93886
94548
  stage: "plan",
@@ -93888,8 +94550,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93888
94550
  });
93889
94551
  }
93890
94552
  validateFeatureName(options.feature);
93891
- const outputDir = join36(naxDir, "features", options.feature);
93892
- const outputPath = join36(outputDir, "prd.json");
94553
+ const outputDir = join37(naxDir, "features", options.feature);
94554
+ const outputPath = join37(outputDir, "prd.json");
93893
94555
  const [specContent, sourceRoots, pkg] = await Promise.all([
93894
94556
  deps.readFile(options.from),
93895
94557
  deps.scanSourceRoots(workdir),
@@ -93904,7 +94566,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
93904
94566
  ...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
93905
94567
  ];
93906
94568
  const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
93907
- const packageJson = await deps.readPackageJsonAt(join36(workdir, relativePath, "package.json"));
94569
+ const packageJson = await deps.readPackageJsonAt(join37(workdir, relativePath, "package.json"));
93908
94570
  return buildPackageSummary(relativePath, packageJson);
93909
94571
  }));
93910
94572
  const projectName = detectProjectName(workdir, pkg);
@@ -94505,7 +95167,7 @@ init_interaction();
94505
95167
  init_prd();
94506
95168
  init_runtime();
94507
95169
  import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
94508
- 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";
94509
95171
  var _statusFeaturesDeps = {
94510
95172
  projectOutputDir,
94511
95173
  loadConfig
@@ -94519,7 +95181,7 @@ function isPidAlive(pid) {
94519
95181
  }
94520
95182
  }
94521
95183
  async function loadStatusFile(featureDir) {
94522
- const statusPath = join41(featureDir, "status.json");
95184
+ const statusPath = join42(featureDir, "status.json");
94523
95185
  if (!existsSync17(statusPath)) {
94524
95186
  return null;
94525
95187
  }
@@ -94534,7 +95196,7 @@ async function loadProjectStatusFile(projectDir) {
94534
95196
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94535
95197
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94536
95198
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94537
- const statusPath = join41(outputDir, "status.json");
95199
+ const statusPath = join42(outputDir, "status.json");
94538
95200
  if (!existsSync17(statusPath)) {
94539
95201
  return null;
94540
95202
  }
@@ -94546,7 +95208,7 @@ async function loadProjectStatusFile(projectDir) {
94546
95208
  }
94547
95209
  }
94548
95210
  async function getFeatureSummary(featureName, featureDir) {
94549
- const prdPath = join41(featureDir, "prd.json");
95211
+ const prdPath = join42(featureDir, "prd.json");
94550
95212
  if (!existsSync17(prdPath)) {
94551
95213
  return {
94552
95214
  name: featureName,
@@ -94589,7 +95251,7 @@ async function getFeatureSummary(featureName, featureDir) {
94589
95251
  };
94590
95252
  }
94591
95253
  }
94592
- const runsDir = join41(featureDir, "runs");
95254
+ const runsDir = join42(featureDir, "runs");
94593
95255
  if (existsSync17(runsDir)) {
94594
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();
94595
95257
  if (runs.length > 0) {
@@ -94603,7 +95265,7 @@ async function displayAllFeatures(projectDir) {
94603
95265
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94604
95266
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94605
95267
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94606
- const featuresDir = join41(outputDir, "features");
95268
+ const featuresDir = join42(outputDir, "features");
94607
95269
  if (!existsSync17(featuresDir)) {
94608
95270
  console.log(source_default.dim("No features found."));
94609
95271
  return;
@@ -94644,7 +95306,7 @@ async function displayAllFeatures(projectDir) {
94644
95306
  console.log();
94645
95307
  }
94646
95308
  }
94647
- 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))));
94648
95310
  console.log(source_default.bold(`\uD83D\uDCCA Features
94649
95311
  `));
94650
95312
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -94670,7 +95332,7 @@ async function displayAllFeatures(projectDir) {
94670
95332
  console.log();
94671
95333
  }
94672
95334
  async function displayFeatureDetails(featureName, featureDir) {
94673
- const prdPath = join41(featureDir, "prd.json");
95335
+ const prdPath = join42(featureDir, "prd.json");
94674
95336
  if (!existsSync17(prdPath)) {
94675
95337
  console.log(source_default.bold(`
94676
95338
  \uD83D\uDCCA ${featureName}
@@ -94816,7 +95478,7 @@ async function displayFeatureStatus(options = {}) {
94816
95478
  const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
94817
95479
  const projectKey = config2?.name?.trim() || basename7(projectDir);
94818
95480
  const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
94819
- featureDir = join41(outputDir, "features", options.feature);
95481
+ featureDir = join42(outputDir, "features", options.feature);
94820
95482
  } else {
94821
95483
  const resolved = resolveProject({ feature: options.feature });
94822
95484
  if (!resolved.featureDir) {
@@ -94836,7 +95498,7 @@ init_errors();
94836
95498
  init_logger2();
94837
95499
  init_runtime();
94838
95500
  import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
94839
- import { basename as basename8, join as join42 } from "path";
95501
+ import { basename as basename8, join as join43 } from "path";
94840
95502
  async function resolveOutputDir2(workdir, override) {
94841
95503
  if (override)
94842
95504
  return override;
@@ -94860,7 +95522,7 @@ async function runsListCommand(options) {
94860
95522
  const logger = getLogger();
94861
95523
  const { feature, workdir } = options;
94862
95524
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
94863
- const runsDir = join42(outputDir, "features", feature, "runs");
95525
+ const runsDir = join43(outputDir, "features", feature, "runs");
94864
95526
  if (!existsSync18(runsDir)) {
94865
95527
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
94866
95528
  return;
@@ -94872,7 +95534,7 @@ async function runsListCommand(options) {
94872
95534
  }
94873
95535
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
94874
95536
  for (const file3 of files.sort().reverse()) {
94875
- const logPath = join42(runsDir, file3);
95537
+ const logPath = join43(runsDir, file3);
94876
95538
  const entries = await parseRunLog(logPath);
94877
95539
  const startEvent = entries.find((e) => e.message === "run.start");
94878
95540
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -94899,7 +95561,7 @@ async function runsShowCommand(options) {
94899
95561
  const logger = getLogger();
94900
95562
  const { runId, feature, workdir } = options;
94901
95563
  const outputDir = await resolveOutputDir2(workdir, options.outputDir);
94902
- const logPath = join42(outputDir, "features", feature, "runs", `${runId}.jsonl`);
95564
+ const logPath = join43(outputDir, "features", feature, "runs", `${runId}.jsonl`);
94903
95565
  if (!existsSync18(logPath)) {
94904
95566
  logger.error("cli", "Run not found", { runId, feature, logPath });
94905
95567
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
@@ -95012,7 +95674,7 @@ init_logger2();
95012
95674
  init_prd();
95013
95675
  init_runtime();
95014
95676
  import { existsSync as existsSync25, readdirSync as readdirSync5 } from "fs";
95015
- import { basename as basename12, join as join55 } from "path";
95677
+ import { basename as basename12, join as join57 } from "path";
95016
95678
 
95017
95679
  // src/cli/diagnose-analysis.ts
95018
95680
  function detectFailurePattern(story, _prd, status) {
@@ -95215,7 +95877,7 @@ function isProcessAlive2(pid) {
95215
95877
  }
95216
95878
  }
95217
95879
  async function loadStatusFile2(outputDir) {
95218
- const statusPath = join55(outputDir, "status.json");
95880
+ const statusPath = join57(outputDir, "status.json");
95219
95881
  if (!existsSync25(statusPath))
95220
95882
  return null;
95221
95883
  try {
@@ -95243,7 +95905,7 @@ async function countCommitsSince(workdir, since) {
95243
95905
  }
95244
95906
  }
95245
95907
  async function checkLock(workdir) {
95246
- const lockFile = Bun.file(join55(workdir, "nax.lock"));
95908
+ const lockFile = Bun.file(join57(workdir, "nax.lock"));
95247
95909
  if (!await lockFile.exists())
95248
95910
  return { lockPresent: false };
95249
95911
  try {
@@ -95261,8 +95923,8 @@ async function diagnoseCommand(options = {}) {
95261
95923
  const logger = getLogger();
95262
95924
  const workdir = options.workdir ?? process.cwd();
95263
95925
  const naxSubdir = findProjectDir(workdir);
95264
- let projectDir = naxSubdir ? join55(naxSubdir, "..") : null;
95265
- if (!projectDir && existsSync25(join55(workdir, ".nax"))) {
95926
+ let projectDir = naxSubdir ? join57(naxSubdir, "..") : null;
95927
+ if (!projectDir && existsSync25(join57(workdir, ".nax"))) {
95266
95928
  projectDir = workdir;
95267
95929
  }
95268
95930
  if (!projectDir)
@@ -95276,7 +95938,7 @@ async function diagnoseCommand(options = {}) {
95276
95938
  if (status2) {
95277
95939
  feature = status2.run.feature;
95278
95940
  } else {
95279
- const featuresDir = join55(outputDir, "features");
95941
+ const featuresDir = join57(outputDir, "features");
95280
95942
  if (!existsSync25(featuresDir))
95281
95943
  throw new Error("No features found in project");
95282
95944
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -95286,8 +95948,8 @@ async function diagnoseCommand(options = {}) {
95286
95948
  logger.info("diagnose", "No feature specified, using first found", { feature });
95287
95949
  }
95288
95950
  }
95289
- const featureDir = join55(outputDir, "features", feature);
95290
- const prdPath = join55(featureDir, "prd.json");
95951
+ const featureDir = join57(outputDir, "features", feature);
95952
+ const prdPath = join57(featureDir, "prd.json");
95291
95953
  if (!existsSync25(prdPath))
95292
95954
  throw new Error(`Feature not found: ${feature}`);
95293
95955
  const prd = await loadPRD(prdPath);
@@ -95332,7 +95994,7 @@ init_source();
95332
95994
  init_loader();
95333
95995
  init_generator2();
95334
95996
  import { existsSync as existsSync26 } from "fs";
95335
- import { join as join56 } from "path";
95997
+ import { join as join58 } from "path";
95336
95998
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
95337
95999
  async function generateCommand(options) {
95338
96000
  const workdir = options.dir ?? process.cwd();
@@ -95375,7 +96037,7 @@ async function generateCommand(options) {
95375
96037
  return;
95376
96038
  }
95377
96039
  if (options.package) {
95378
- const packageDir = join56(workdir, options.package);
96040
+ const packageDir = join58(workdir, options.package);
95379
96041
  if (dryRun) {
95380
96042
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
95381
96043
  }
@@ -95395,8 +96057,8 @@ async function generateCommand(options) {
95395
96057
  process.exit(1);
95396
96058
  return;
95397
96059
  }
95398
- const contextPath = options.context ? join56(workdir, options.context) : join56(workdir, ".nax/context.md");
95399
- 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;
95400
96062
  const autoInject = !options.noAutoInject;
95401
96063
  if (!existsSync26(contextPath)) {
95402
96064
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -95502,7 +96164,7 @@ async function generateCommand(options) {
95502
96164
  // src/cli/config-display.ts
95503
96165
  init_loader();
95504
96166
  import { existsSync as existsSync28 } from "fs";
95505
- import { join as join58 } from "path";
96167
+ import { join as join60 } from "path";
95506
96168
 
95507
96169
  // src/cli/config-descriptions.ts
95508
96170
  var FIELD_DESCRIPTIONS = {
@@ -95551,15 +96213,16 @@ var FIELD_DESCRIPTIONS = {
95551
96213
  "execution.contextProviderTokenBudget": "Token budget for plugin context providers",
95552
96214
  "execution.lintCommand": "Lint command override (null=disabled, undefined=auto-detect)",
95553
96215
  "execution.typecheckCommand": "Typecheck command override (null=disabled, undefined=auto-detect)",
95554
- "execution.rectification": "Rectification loop settings (retry failed tests)",
96216
+ "execution.rectification": "Unified fix-cycle settings \u2014 shared by story-orchestrator (semantic + adversarial + mechanical) and post-run regression cycles",
95555
96217
  "execution.rectification.enabled": "Enable rectification loop",
95556
- "execution.rectification.maxRetries": "Max retry attempts per story",
96218
+ "execution.rectification.maxAttemptsTotal": "Total iteration cap for the unified fix cycle (default: 12). Per-strategy caps are the granular bound.",
96219
+ "execution.rectification.maxAttemptsPerStrategy": "Default per-strategy cap for LLM-driven strategies \u2014 autofix-implementer / autofix-test-writer / full-suite-rectify (default: 3). Mechanical strategies stay at 1.",
95557
96220
  "execution.rectification.fullSuiteTimeoutSeconds": "Timeout for full test suite run in seconds",
95558
96221
  "execution.rectification.maxFailureSummaryChars": "Max characters in failure summary",
95559
96222
  "execution.rectification.abortOnIncreasingFailures": "Abort if failure count increases",
95560
- "execution.rectification.escalateOnExhaustion": "Enable model tier escalation when retries are exhausted with remaining failures",
95561
- "execution.rectification.rethinkAtAttempt": "Attempt number at which 'rethink your approach' language is injected into the prompt (default: 2, set >= maxRetries to disable)",
95562
- "execution.rectification.urgencyAtAttempt": "Attempt number at which 'final chance before escalation' urgency is added to the prompt (default: 3, set >= maxRetries to disable)",
96223
+ "execution.rectification.escalateOnExhaustion": "Enable model tier escalation when attempts are exhausted with remaining failures",
96224
+ "execution.rectification.rethinkAtAttempt": "Attempt number at which 'rethink your approach' language is injected into the prompt (default: 2)",
96225
+ "execution.rectification.urgencyAtAttempt": "Attempt number at which 'final chance before escalation' urgency is added (default: 3)",
95563
96226
  "execution.regressionGate": "Regression gate settings (full suite after scoped tests)",
95564
96227
  "execution.regressionGate.enabled": "Enable full-suite regression gate",
95565
96228
  "execution.regressionGate.timeoutSeconds": "Timeout for regression run in seconds",
@@ -95752,7 +96415,7 @@ function deepEqual(a, b) {
95752
96415
  init_defaults();
95753
96416
  init_loader();
95754
96417
  import { existsSync as existsSync27 } from "fs";
95755
- import { join as join57 } from "path";
96418
+ import { join as join59 } from "path";
95756
96419
  async function loadConfigFile(path19) {
95757
96420
  if (!existsSync27(path19))
95758
96421
  return null;
@@ -95774,7 +96437,7 @@ async function loadProjectConfig() {
95774
96437
  const projectDir = findProjectDir();
95775
96438
  if (!projectDir)
95776
96439
  return null;
95777
- const projectPath = join57(projectDir, "config.json");
96440
+ const projectPath = join59(projectDir, "config.json");
95778
96441
  return await loadConfigFile(projectPath);
95779
96442
  }
95780
96443
 
@@ -95834,7 +96497,7 @@ async function configCommand(config2, options = {}) {
95834
96497
  function determineConfigSources() {
95835
96498
  const globalPath = globalConfigPath();
95836
96499
  const projectDir = findProjectDir();
95837
- const projectPath = projectDir ? join58(projectDir, "config.json") : null;
96500
+ const projectPath = projectDir ? join60(projectDir, "config.json") : null;
95838
96501
  return {
95839
96502
  global: fileExists(globalPath) ? globalPath : null,
95840
96503
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -95983,15 +96646,15 @@ init_paths();
95983
96646
  init_profile();
95984
96647
  import { mkdirSync as mkdirSync5 } from "fs";
95985
96648
  import { readdirSync as readdirSync6 } from "fs";
95986
- import { join as join59 } from "path";
96649
+ import { join as join61 } from "path";
95987
96650
  var _profileCLIDeps = {
95988
96651
  env: process.env
95989
96652
  };
95990
96653
  var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
95991
96654
  var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
95992
96655
  async function profileListCommand(startDir) {
95993
- const globalProfilesDir = join59(globalConfigDir(), "profiles");
95994
- const projectProfilesDir = join59(projectConfigDir(startDir), "profiles");
96656
+ const globalProfilesDir = join61(globalConfigDir(), "profiles");
96657
+ const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
95995
96658
  const globalProfiles = scanProfileDir(globalProfilesDir);
95996
96659
  const projectProfiles = scanProfileDir(projectProfilesDir);
95997
96660
  const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
@@ -96050,7 +96713,7 @@ function maskProfileValues(obj) {
96050
96713
  return result;
96051
96714
  }
96052
96715
  async function profileUseCommand(profileName, startDir) {
96053
- const configPath = join59(projectConfigDir(startDir), "config.json");
96716
+ const configPath = join61(projectConfigDir(startDir), "config.json");
96054
96717
  const configFile = Bun.file(configPath);
96055
96718
  let existing = {};
96056
96719
  if (await configFile.exists()) {
@@ -96069,8 +96732,8 @@ async function profileCurrentCommand(startDir) {
96069
96732
  return resolveProfileName({}, _profileCLIDeps.env, startDir);
96070
96733
  }
96071
96734
  async function profileCreateCommand(profileName, startDir) {
96072
- const profilesDir = join59(projectConfigDir(startDir), "profiles");
96073
- const profilePath = join59(profilesDir, `${profileName}.json`);
96735
+ const profilesDir = join61(projectConfigDir(startDir), "profiles");
96736
+ const profilePath = join61(profilesDir, `${profileName}.json`);
96074
96737
  const profileFile = Bun.file(profilePath);
96075
96738
  if (await profileFile.exists()) {
96076
96739
  throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
@@ -96192,7 +96855,7 @@ async function contextInspectCommand(options) {
96192
96855
  init_canonical_loader();
96193
96856
  init_errors();
96194
96857
  import { mkdir as mkdir12 } from "fs/promises";
96195
- import { basename as basename13, join as join60 } from "path";
96858
+ import { basename as basename13, join as join62 } from "path";
96196
96859
  var _rulesCLIDeps = {
96197
96860
  readFile: async (path19) => Bun.file(path19).text(),
96198
96861
  writeFile: async (path19, content) => {
@@ -96201,7 +96864,7 @@ var _rulesCLIDeps = {
96201
96864
  fileExists: async (path19) => Bun.file(path19).exists(),
96202
96865
  globInDir: (dir) => {
96203
96866
  try {
96204
- 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));
96205
96868
  } catch {
96206
96869
  return [];
96207
96870
  }
@@ -96250,7 +96913,7 @@ ${r.content}`).join(`
96250
96913
  `);
96251
96914
  const shimContent = `${header + body}
96252
96915
  `;
96253
- const shimPath = join60(workdir, shimFileName);
96916
+ const shimPath = join62(workdir, shimFileName);
96254
96917
  if (options.dryRun) {
96255
96918
  console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
96256
96919
  return;
@@ -96279,14 +96942,14 @@ function neutralizeContent(content) {
96279
96942
  }
96280
96943
  async function collectMigrationSources(workdir) {
96281
96944
  const sources = [];
96282
- const claudeMdPath = join60(workdir, "CLAUDE.md");
96945
+ const claudeMdPath = join62(workdir, "CLAUDE.md");
96283
96946
  if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
96284
96947
  const content = await _rulesCLIDeps.readFile(claudeMdPath);
96285
96948
  if (content.trim()) {
96286
96949
  sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
96287
96950
  }
96288
96951
  }
96289
- const rulesDir = join60(workdir, ".claude", "rules");
96952
+ const rulesDir = join62(workdir, ".claude", "rules");
96290
96953
  const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
96291
96954
  for (const filePath of ruleFiles) {
96292
96955
  try {
@@ -96306,7 +96969,7 @@ async function rulesMigrateCommand(options) {
96306
96969
  console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
96307
96970
  return;
96308
96971
  }
96309
- const targetDir = join60(workdir, CANONICAL_RULES_DIR);
96972
+ const targetDir = join62(workdir, CANONICAL_RULES_DIR);
96310
96973
  if (!options.dryRun) {
96311
96974
  try {
96312
96975
  await _rulesCLIDeps.mkdir(targetDir);
@@ -96317,7 +96980,7 @@ async function rulesMigrateCommand(options) {
96317
96980
  let written = 0;
96318
96981
  let skipped = 0;
96319
96982
  for (const { sourcePath, targetFileName, content } of sources) {
96320
- const targetPath = join60(targetDir, targetFileName);
96983
+ const targetPath = join62(targetDir, targetFileName);
96321
96984
  if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
96322
96985
  console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
96323
96986
  skipped++;
@@ -96356,7 +97019,7 @@ function collectCanonicalRuleRoots(workdir) {
96356
97019
  const packageRel = normalized.slice(0, idx);
96357
97020
  if (!packageRel)
96358
97021
  continue;
96359
- roots.add(join60(workdir, packageRel));
97022
+ roots.add(join62(workdir, packageRel));
96360
97023
  }
96361
97024
  return [...roots].sort();
96362
97025
  }
@@ -96378,7 +97041,7 @@ init_logger2();
96378
97041
  init_detect2();
96379
97042
  init_workspace();
96380
97043
  init_common();
96381
- import { join as join61 } from "path";
97044
+ import { join as join63 } from "path";
96382
97045
  function resolveEffective(detected, configPatterns) {
96383
97046
  if (configPatterns !== undefined)
96384
97047
  return "config";
@@ -96463,7 +97126,7 @@ async function detectCommand(options) {
96463
97126
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
96464
97127
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
96465
97128
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
96466
- const pkgConfigPath = join61(workdir, ".nax", "mono", dir, "config.json");
97129
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96467
97130
  const pkgRaw = await loadRawConfig(pkgConfigPath);
96468
97131
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
96469
97132
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -96517,13 +97180,13 @@ async function detectCommand(options) {
96517
97180
  if (rootDetected.confidence === "empty") {
96518
97181
  console.log(source_default.yellow(" root: skipped (empty detection)"));
96519
97182
  } else {
96520
- const rootConfigPath = join61(workdir, ".nax", "config.json");
97183
+ const rootConfigPath = join63(workdir, ".nax", "config.json");
96521
97184
  try {
96522
97185
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
96523
97186
  if (status === "skipped") {
96524
97187
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
96525
97188
  } else {
96526
- 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")}`));
96527
97190
  }
96528
97191
  } catch (err) {
96529
97192
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -96536,13 +97199,13 @@ async function detectCommand(options) {
96536
97199
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
96537
97200
  continue;
96538
97201
  }
96539
- const pkgConfigPath = join61(workdir, ".nax", "mono", dir, "config.json");
97202
+ const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
96540
97203
  try {
96541
97204
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
96542
97205
  if (status === "skipped") {
96543
97206
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
96544
97207
  } else {
96545
- 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")}`));
96546
97209
  }
96547
97210
  } catch (err) {
96548
97211
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -96565,19 +97228,19 @@ async function diagnose(options) {
96565
97228
  // src/commands/logs.ts
96566
97229
  init_common();
96567
97230
  import { existsSync as existsSync30 } from "fs";
96568
- import { join as join65 } from "path";
97231
+ import { join as join67 } from "path";
96569
97232
 
96570
97233
  // src/commands/logs-formatter.ts
96571
97234
  init_source();
96572
97235
  init_formatter();
96573
97236
  import { readdirSync as readdirSync8 } from "fs";
96574
- import { join as join64 } from "path";
97237
+ import { join as join66 } from "path";
96575
97238
 
96576
97239
  // src/commands/logs-reader.ts
96577
97240
  init_paths3();
96578
97241
  import { existsSync as existsSync29, readdirSync as readdirSync7 } from "fs";
96579
97242
  import { readdir as readdir4 } from "fs/promises";
96580
- import { join as join63 } from "path";
97243
+ import { join as join65 } from "path";
96581
97244
  var _logsReaderDeps = {
96582
97245
  getRunsDir
96583
97246
  };
@@ -96591,7 +97254,7 @@ async function resolveRunFileFromRegistry(runId) {
96591
97254
  }
96592
97255
  let matched = null;
96593
97256
  for (const entry of entries) {
96594
- const metaPath = join63(runsDir, entry, "meta.json");
97257
+ const metaPath = join65(runsDir, entry, "meta.json");
96595
97258
  try {
96596
97259
  const meta3 = await Bun.file(metaPath).json();
96597
97260
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -96613,14 +97276,14 @@ async function resolveRunFileFromRegistry(runId) {
96613
97276
  return null;
96614
97277
  }
96615
97278
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
96616
- return join63(matched.eventsDir, specificFile ?? files[0]);
97279
+ return join65(matched.eventsDir, specificFile ?? files[0]);
96617
97280
  }
96618
97281
  async function selectRunFile(runsDir) {
96619
97282
  const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
96620
97283
  if (files.length === 0) {
96621
97284
  return null;
96622
97285
  }
96623
- return join63(runsDir, files[0]);
97286
+ return join65(runsDir, files[0]);
96624
97287
  }
96625
97288
  async function extractRunSummary(filePath) {
96626
97289
  const file3 = Bun.file(filePath);
@@ -96706,7 +97369,7 @@ Runs:
96706
97369
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
96707
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"));
96708
97371
  for (const file3 of files) {
96709
- const filePath = join64(runsDir, file3);
97372
+ const filePath = join66(runsDir, file3);
96710
97373
  const summary = await extractRunSummary(filePath);
96711
97374
  const timestamp = file3.replace(".jsonl", "");
96712
97375
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -96820,7 +97483,7 @@ async function logsCommand(options) {
96820
97483
  return;
96821
97484
  }
96822
97485
  const resolved = resolveProject({ dir: options.dir });
96823
- const naxDir = join65(resolved.projectDir, ".nax");
97486
+ const naxDir = join67(resolved.projectDir, ".nax");
96824
97487
  const configPath = resolved.configPath;
96825
97488
  const configFile = Bun.file(configPath);
96826
97489
  const config2 = await configFile.json();
@@ -96828,8 +97491,8 @@ async function logsCommand(options) {
96828
97491
  if (!featureName) {
96829
97492
  throw new Error("No feature specified in config.json");
96830
97493
  }
96831
- const featureDir = join65(naxDir, "features", featureName);
96832
- const runsDir = join65(featureDir, "runs");
97494
+ const featureDir = join67(naxDir, "features", featureName);
97495
+ const runsDir = join67(featureDir, "runs");
96833
97496
  if (!existsSync30(runsDir)) {
96834
97497
  throw new Error(`No runs directory found for feature: ${featureName}`);
96835
97498
  }
@@ -96855,7 +97518,7 @@ init_prd();
96855
97518
  init_precheck();
96856
97519
  init_common();
96857
97520
  import { existsSync as existsSync31 } from "fs";
96858
- import { join as join66 } from "path";
97521
+ import { join as join68 } from "path";
96859
97522
  async function precheckCommand(options) {
96860
97523
  const resolved = resolveProject({
96861
97524
  dir: options.dir,
@@ -96877,9 +97540,9 @@ async function precheckCommand(options) {
96877
97540
  process.exit(1);
96878
97541
  }
96879
97542
  }
96880
- const naxDir = join66(resolved.projectDir, ".nax");
96881
- const featureDir = join66(naxDir, "features", featureName);
96882
- 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");
96883
97546
  if (!existsSync31(featureDir)) {
96884
97547
  console.error(source_default.red(`Feature not found: ${featureName}`));
96885
97548
  process.exit(1);
@@ -96902,7 +97565,7 @@ async function precheckCommand(options) {
96902
97565
  init_source();
96903
97566
  init_paths3();
96904
97567
  import { readdir as readdir5 } from "fs/promises";
96905
- import { join as join67 } from "path";
97568
+ import { join as join69 } from "path";
96906
97569
  var DEFAULT_LIMIT = 20;
96907
97570
  var _runsCmdDeps = {
96908
97571
  getRunsDir
@@ -96957,7 +97620,7 @@ async function runsCommand(options = {}) {
96957
97620
  }
96958
97621
  const rows = [];
96959
97622
  for (const entry of entries) {
96960
- const metaPath = join67(runsDir, entry, "meta.json");
97623
+ const metaPath = join69(runsDir, entry, "meta.json");
96961
97624
  let meta3;
96962
97625
  try {
96963
97626
  meta3 = await Bun.file(metaPath).json();
@@ -97034,7 +97697,7 @@ async function runsCommand(options = {}) {
97034
97697
 
97035
97698
  // src/commands/unlock.ts
97036
97699
  init_source();
97037
- import { join as join68 } from "path";
97700
+ import { join as join70 } from "path";
97038
97701
  function isProcessAlive3(pid) {
97039
97702
  try {
97040
97703
  process.kill(pid, 0);
@@ -97049,7 +97712,7 @@ function formatLockAge(ageMs) {
97049
97712
  }
97050
97713
  async function unlockCommand(options) {
97051
97714
  const workdir = options.dir ?? process.cwd();
97052
- const lockPath = join68(workdir, "nax.lock");
97715
+ const lockPath = join70(workdir, "nax.lock");
97053
97716
  const lockFile = Bun.file(lockPath);
97054
97717
  const exists = await lockFile.exists();
97055
97718
  if (!exists) {
@@ -105122,7 +105785,7 @@ Next: nax generate --package ${options.package}`));
105122
105785
  }
105123
105786
  return;
105124
105787
  }
105125
- const naxDir = join82(workdir, ".nax");
105788
+ const naxDir = join84(workdir, ".nax");
105126
105789
  if (existsSync37(naxDir) && !options.force) {
105127
105790
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
105128
105791
  return;
@@ -105151,11 +105814,11 @@ Next: nax generate --package ${options.package}`));
105151
105814
  }
105152
105815
  }
105153
105816
  }
105154
- mkdirSync7(join82(naxDir, "features"), { recursive: true });
105155
- mkdirSync7(join82(naxDir, "hooks"), { recursive: true });
105817
+ mkdirSync7(join84(naxDir, "features"), { recursive: true });
105818
+ mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
105156
105819
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
105157
- await Bun.write(join82(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
105158
- 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({
105159
105822
  hooks: {
105160
105823
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
105161
105824
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -105163,12 +105826,12 @@ Next: nax generate --package ${options.package}`));
105163
105826
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
105164
105827
  }
105165
105828
  }, null, 2));
105166
- await Bun.write(join82(naxDir, ".gitignore"), `# nax temp files
105829
+ await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
105167
105830
  *.tmp
105168
105831
  .paused.json
105169
105832
  .nax-verifier-verdict.json
105170
105833
  `);
105171
- await Bun.write(join82(naxDir, "context.md"), `# Project Context
105834
+ await Bun.write(join84(naxDir, "context.md"), `# Project Context
105172
105835
 
105173
105836
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
105174
105837
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -105298,8 +105961,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
105298
105961
  console.error(source_default.red("nax not initialized. Run: nax init"));
105299
105962
  process.exit(1);
105300
105963
  }
105301
- const featureDir = join82(naxDir, "features", options.feature);
105302
- const prdPath = join82(featureDir, "prd.json");
105964
+ const featureDir = join84(naxDir, "features", options.feature);
105965
+ const prdPath = join84(featureDir, "prd.json");
105303
105966
  if (options.plan && options.from) {
105304
105967
  if (existsSync37(prdPath) && !options.force) {
105305
105968
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -105321,10 +105984,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105321
105984
  }
105322
105985
  }
105323
105986
  try {
105324
- const planLogDir = join82(featureDir, "plan");
105987
+ const planLogDir = join84(featureDir, "plan");
105325
105988
  mkdirSync7(planLogDir, { recursive: true });
105326
105989
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105327
- const planLogPath = join82(planLogDir, `${planLogId}.jsonl`);
105990
+ const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
105328
105991
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105329
105992
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105330
105993
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -105370,10 +106033,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
105370
106033
  resetLogger();
105371
106034
  const projectKey = config2.name?.trim() || basename17(workdir);
105372
106035
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
105373
- const runsDir = join82(outputDir, "features", options.feature, "runs");
106036
+ const runsDir = join84(outputDir, "features", options.feature, "runs");
105374
106037
  mkdirSync7(runsDir, { recursive: true });
105375
106038
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105376
- const logFilePath = join82(runsDir, `${runId}.jsonl`);
106039
+ const logFilePath = join84(runsDir, `${runId}.jsonl`);
105377
106040
  const isTTY = process.stdout.isTTY ?? false;
105378
106041
  const headlessFlag = options.headless ?? false;
105379
106042
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -105390,7 +106053,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105390
106053
  config2.agent.default = options.agent;
105391
106054
  }
105392
106055
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
105393
- const globalNaxDir = join82(homedir3(), ".nax");
106056
+ const globalNaxDir = join84(homedir3(), ".nax");
105394
106057
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
105395
106058
  const eventEmitter = new PipelineEventEmitter;
105396
106059
  let tuiInstance;
@@ -105413,7 +106076,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105413
106076
  } else {
105414
106077
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
105415
106078
  }
105416
- const statusFilePath = join82(outputDir, "status.json");
106079
+ const statusFilePath = join84(outputDir, "status.json");
105417
106080
  let parallel;
105418
106081
  if (options.parallel !== undefined) {
105419
106082
  parallel = Number.parseInt(options.parallel, 10);
@@ -105439,7 +106102,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
105439
106102
  headless: useHeadless,
105440
106103
  skipPrecheck: options.skipPrecheck ?? false
105441
106104
  });
105442
- const latestSymlink = join82(runsDir, "latest.jsonl");
106105
+ const latestSymlink = join84(runsDir, "latest.jsonl");
105443
106106
  try {
105444
106107
  if (existsSync37(latestSymlink)) {
105445
106108
  Bun.spawnSync(["rm", latestSymlink]);
@@ -105500,9 +106163,9 @@ features.command("create <name>").description("Create a new feature").option("-d
105500
106163
  console.error(source_default.red("nax not initialized. Run: nax init"));
105501
106164
  process.exit(1);
105502
106165
  }
105503
- const featureDir = join82(naxDir, "features", name);
106166
+ const featureDir = join84(naxDir, "features", name);
105504
106167
  mkdirSync7(featureDir, { recursive: true });
105505
- await Bun.write(join82(featureDir, "spec.md"), `# Feature: ${name}
106168
+ await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
105506
106169
 
105507
106170
  ## Overview
105508
106171
 
@@ -105535,7 +106198,7 @@ features.command("create <name>").description("Create a new feature").option("-d
105535
106198
 
105536
106199
  <!-- What this feature explicitly does NOT cover. -->
105537
106200
  `);
105538
- await Bun.write(join82(featureDir, "progress.txt"), `# Progress: ${name}
106201
+ await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
105539
106202
 
105540
106203
  Created: ${new Date().toISOString()}
105541
106204
 
@@ -105561,7 +106224,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
105561
106224
  console.error(source_default.red("nax not initialized."));
105562
106225
  process.exit(1);
105563
106226
  }
105564
- const featuresDir = join82(naxDir, "features");
106227
+ const featuresDir = join84(naxDir, "features");
105565
106228
  if (!existsSync37(featuresDir)) {
105566
106229
  console.log(source_default.dim("No features yet."));
105567
106230
  return;
@@ -105576,7 +106239,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
105576
106239
  Features:
105577
106240
  `));
105578
106241
  for (const name of entries) {
105579
- const prdPath = join82(featuresDir, name, "prd.json");
106242
+ const prdPath = join84(featuresDir, name, "prd.json");
105580
106243
  if (existsSync37(prdPath)) {
105581
106244
  const prd = await loadPRD(prdPath);
105582
106245
  const c = countStories(prd);
@@ -105611,10 +106274,10 @@ Use: nax plan -f <feature> --from <spec>`));
105611
106274
  cliOverrides.profile = options.profile;
105612
106275
  }
105613
106276
  const config2 = await loadConfig(workdir, cliOverrides);
105614
- const featureLogDir = join82(naxDir, "features", options.feature, "plan");
106277
+ const featureLogDir = join84(naxDir, "features", options.feature, "plan");
105615
106278
  mkdirSync7(featureLogDir, { recursive: true });
105616
106279
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
105617
- const planLogPath = join82(featureLogDir, `${planLogId}.jsonl`);
106280
+ const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
105618
106281
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
105619
106282
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
105620
106283
  try {