@nathapp/nax 0.69.5 → 0.69.7

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 +190 -78
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -18041,7 +18041,7 @@ function redactSecrets(input) {
18041
18041
  }
18042
18042
  var SECRET_KEY_PATTERN, SECRET_VALUE_PATTERNS, REDACTED = "[REDACTED]";
18043
18043
  var init_redact = __esm(() => {
18044
- SECRET_KEY_PATTERN = /(SECRET|TOKEN|API_?KEY|PASSWORD|PRIVATE_?KEY|ACCESS_?KEY|WEBHOOK)/i;
18044
+ SECRET_KEY_PATTERN = /(SECRET|TOKEN(?!s\b)|API_?KEY|PASSWORD|PRIVATE_?KEY|ACCESS_?KEY|WEBHOOK)/i;
18045
18045
  SECRET_VALUE_PATTERNS = [
18046
18046
  /sk-[A-Za-z0-9_-]{16,}/g,
18047
18047
  /ghp_[A-Za-z0-9]{16,}/g,
@@ -24993,12 +24993,15 @@ async function getGitRootMemo(workdir) {
24993
24993
  }
24994
24994
  return result ?? null;
24995
24995
  }
24996
- function extractPatternSuffix(pattern) {
24997
- const lastStar = pattern.lastIndexOf("*");
24998
- if (lastStar === -1)
24996
+ function extractBasenamePattern(pattern) {
24997
+ const basename4 = pattern.slice(pattern.lastIndexOf("/") + 1);
24998
+ const parts = basename4.split("*");
24999
+ if (parts.length !== 2)
24999
25000
  return null;
25000
- const suffix = pattern.slice(lastStar + 1);
25001
- return suffix.length > 0 ? suffix : null;
25001
+ const [prefix, suffix] = parts;
25002
+ if (!prefix && !suffix)
25003
+ return null;
25004
+ return { prefix, suffix };
25002
25005
  }
25003
25006
  function extractSearchTerms(sourceFile) {
25004
25007
  const withoutPrefix = sourceFile.replace(/^(?:.*\/)?src\//, "");
@@ -25036,28 +25039,11 @@ async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
25036
25039
  return results.filter((p) => p !== null);
25037
25040
  }
25038
25041
  async function mapSourceToTests(sourceFiles, workdir, packagePrefix, testFilePatterns = [...DEFAULT_TEST_FILE_PATTERNS]) {
25039
- const testSuffixes = [...new Set(testFilePatterns.map(extractPatternSuffix).filter((s) => s !== null))];
25042
+ const shapes = dedupeBasenamePatterns(testFilePatterns);
25043
+ const testDirs = [...new Set([...DEFAULT_SEPARATED_TEST_DIRS, ...extractTestDirs(testFilePatterns)])];
25040
25044
  const result = [];
25041
25045
  for (const sourceFile of sourceFiles) {
25042
- const sourceWithoutExt = sourceFile.replace(/\.[^.]+$/, "");
25043
- let innerRelative;
25044
- let testBase;
25045
- if (packagePrefix) {
25046
- const srcRoot = `${packagePrefix}/src/`;
25047
- const inner = sourceFile.startsWith(srcRoot) ? sourceFile.slice(srcRoot.length) : sourceFile.replace(/^.*\/src\//, "");
25048
- innerRelative = inner.replace(/\.[^.]+$/, "");
25049
- testBase = `${workdir}/${packagePrefix}`;
25050
- } else {
25051
- innerRelative = sourceFile.replace(/^src\//, "").replace(/\.[^.]+$/, "");
25052
- testBase = workdir;
25053
- }
25054
- const candidates = [];
25055
- for (const suffix of testSuffixes) {
25056
- for (const testDir of DEFAULT_SEPARATED_TEST_DIRS) {
25057
- candidates.push(`${testBase}/${testDir}/${innerRelative}${suffix}`);
25058
- }
25059
- candidates.push(`${workdir}/${sourceWithoutExt}${suffix}`);
25060
- }
25046
+ const candidates = buildTestCandidates(sourceFile, workdir, packagePrefix, shapes, testDirs);
25061
25047
  const existsFlags = await Promise.all(candidates.map((c) => _bunDeps.file(c).exists()));
25062
25048
  candidates.forEach((c, i) => {
25063
25049
  if (existsFlags[i])
@@ -25066,6 +25052,58 @@ async function mapSourceToTests(sourceFiles, workdir, packagePrefix, testFilePat
25066
25052
  }
25067
25053
  return result;
25068
25054
  }
25055
+ function dedupeBasenamePatterns(testFilePatterns) {
25056
+ const seen = new Set;
25057
+ const shapes = [];
25058
+ for (const pattern of testFilePatterns) {
25059
+ const shape = extractBasenamePattern(pattern);
25060
+ if (!shape)
25061
+ continue;
25062
+ const key = `${shape.prefix}\x00${shape.suffix}`;
25063
+ if (seen.has(key))
25064
+ continue;
25065
+ seen.add(key);
25066
+ shapes.push(shape);
25067
+ }
25068
+ return shapes;
25069
+ }
25070
+ function buildTestCandidates(sourceFile, workdir, packagePrefix, shapes, testDirs) {
25071
+ const sourceWithoutExt = sourceFile.replace(/\.[^.]+$/, "");
25072
+ let innerRelative;
25073
+ let testBase;
25074
+ if (packagePrefix) {
25075
+ const srcRoot = `${packagePrefix}/src/`;
25076
+ const inner = sourceFile.startsWith(srcRoot) ? sourceFile.slice(srcRoot.length) : sourceFile.replace(/^.*\/src\//, "");
25077
+ innerRelative = inner.replace(/\.[^.]+$/, "");
25078
+ testBase = `${workdir}/${packagePrefix}`;
25079
+ } else {
25080
+ innerRelative = sourceFile.replace(/^src\//, "").replace(/\.[^.]+$/, "");
25081
+ testBase = workdir;
25082
+ }
25083
+ const lastSlash = innerRelative.lastIndexOf("/");
25084
+ const innerDir = lastSlash === -1 ? "" : innerRelative.slice(0, lastSlash);
25085
+ const baseName = lastSlash === -1 ? innerRelative : innerRelative.slice(lastSlash + 1);
25086
+ const sourceDirAbs = sourceWithoutExt.includes("/") ? `${workdir}/${sourceWithoutExt.slice(0, sourceWithoutExt.lastIndexOf("/"))}` : workdir;
25087
+ const candidates = [];
25088
+ for (const { prefix, suffix } of shapes) {
25089
+ if (prefix === "") {
25090
+ for (const testDir of testDirs) {
25091
+ candidates.push(`${testBase}/${testDir}/${innerRelative}${suffix}`);
25092
+ }
25093
+ candidates.push(`${workdir}/${sourceWithoutExt}${suffix}`);
25094
+ } else {
25095
+ const named = `${prefix}${baseName}${suffix}`;
25096
+ for (const testDir of testDirs) {
25097
+ candidates.push(`${testBase}/${testDir}/${innerDir ? `${innerDir}/` : ""}${named}`);
25098
+ if (innerDir)
25099
+ candidates.push(`${testBase}/${testDir}/${named}`);
25100
+ }
25101
+ candidates.push(`${sourceDirAbs}/${named}`);
25102
+ }
25103
+ }
25104
+ const sourceAbs = `${workdir}/${sourceFile}`;
25105
+ return [...new Set(candidates)].filter((c) => c !== sourceAbs);
25106
+ }
25069
25107
  function buildSmartTestCommand(testFiles, baseCommand) {
25070
25108
  if (testFiles.length === 0) {
25071
25109
  return baseCommand;
@@ -39763,28 +39801,47 @@ var init_verify_scoped = __esm(() => {
39763
39801
  timeoutSeconds: scopedTimeout,
39764
39802
  isFullSuite: selection.isFullSuite
39765
39803
  });
39804
+ const runTests = async (command) => {
39805
+ const result2 = await deps.regression({
39806
+ workdir: cmdWorkdir,
39807
+ command,
39808
+ timeoutSeconds: scopedTimeout,
39809
+ forceExit: quality.quality?.forceExit,
39810
+ detectOpenHandles: quality.quality?.detectOpenHandles,
39811
+ detectOpenHandlesRetries: quality.quality?.detectOpenHandlesRetries,
39812
+ gracePeriodMs: quality.quality?.gracePeriodMs,
39813
+ drainTimeoutMs: quality.quality?.drainTimeoutMs,
39814
+ shell: quality.quality?.shell,
39815
+ stripEnvVars: quality.quality?.stripEnvVars
39816
+ });
39817
+ const parsed2 = result2.output ? deps.parseTestOutput(result2.output) : { passed: 0, failed: 0, failures: [] };
39818
+ return { result: result2, parsed: parsed2 };
39819
+ };
39766
39820
  const start = Date.now();
39767
- const result = await deps.regression({
39768
- workdir: cmdWorkdir,
39769
- command: selection.effectiveCommand,
39770
- timeoutSeconds: quality.execution?.regressionGate?.timeoutSeconds ?? 600,
39771
- forceExit: quality.quality?.forceExit,
39772
- detectOpenHandles: quality.quality?.detectOpenHandles,
39773
- detectOpenHandlesRetries: quality.quality?.detectOpenHandlesRetries,
39774
- gracePeriodMs: quality.quality?.gracePeriodMs,
39775
- drainTimeoutMs: quality.quality?.drainTimeoutMs,
39776
- shell: quality.quality?.shell,
39777
- stripEnvVars: quality.quality?.stripEnvVars
39778
- });
39821
+ let effectiveCommand = selection.effectiveCommand;
39822
+ let isFullSuite = selection.isFullSuite;
39823
+ let scopeTestFallback = selection.scopeTestFallback;
39824
+ let { result, parsed } = await runTests(effectiveCommand);
39825
+ const ranNoTests = parsed.passed === 0 && parsed.failed === 0 && parsed.failures.length === 0;
39826
+ if (!result.success && result.status !== "TIMEOUT" && !isFullSuite && ranNoTests) {
39827
+ logger.warn("verify[scoped]", "Scoped run executed no tests \u2014 falling back to full suite", {
39828
+ storyId: input.storyId,
39829
+ command: effectiveCommand,
39830
+ exitCode: result.exitCode
39831
+ });
39832
+ effectiveCommand = baseCommand;
39833
+ isFullSuite = true;
39834
+ scopeTestFallback = true;
39835
+ ({ result, parsed } = await runTests(effectiveCommand));
39836
+ }
39779
39837
  const durationMs = Date.now() - start;
39780
- const parsed = result.output ? deps.parseTestOutput(result.output) : { passed: 0, failed: 0, failures: [] };
39781
39838
  if (result.success) {
39782
39839
  logger.info("verify[scoped]", "Scoped tests passed", {
39783
39840
  storyId: input.storyId,
39784
39841
  passCount: parsed.passed,
39785
39842
  durationMs,
39786
- scopeTestFallback: selection.scopeTestFallback ?? false,
39787
- isFullSuite: selection.isFullSuite
39843
+ scopeTestFallback: scopeTestFallback ?? false,
39844
+ isFullSuite
39788
39845
  });
39789
39846
  return {
39790
39847
  success: true,
@@ -39792,16 +39849,16 @@ var init_verify_scoped = __esm(() => {
39792
39849
  findings: [],
39793
39850
  durationMs,
39794
39851
  passCount: parsed.passed,
39795
- isFullSuite: selection.isFullSuite,
39796
- scopeTestFallback: selection.scopeTestFallback
39852
+ isFullSuite,
39853
+ scopeTestFallback
39797
39854
  };
39798
39855
  }
39799
39856
  if (result.status === "TIMEOUT") {
39800
39857
  logger.warn("verify[scoped]", "Scoped tests timed out", {
39801
39858
  storyId: input.storyId,
39802
39859
  durationMs,
39803
- scopeTestFallback: selection.scopeTestFallback ?? false,
39804
- isFullSuite: selection.isFullSuite
39860
+ scopeTestFallback: scopeTestFallback ?? false,
39861
+ isFullSuite
39805
39862
  });
39806
39863
  return {
39807
39864
  success: false,
@@ -39809,8 +39866,8 @@ var init_verify_scoped = __esm(() => {
39809
39866
  findings: [],
39810
39867
  durationMs,
39811
39868
  passCount: parsed.passed,
39812
- isFullSuite: selection.isFullSuite,
39813
- scopeTestFallback: selection.scopeTestFallback
39869
+ isFullSuite,
39870
+ scopeTestFallback
39814
39871
  };
39815
39872
  }
39816
39873
  logger.warn("verify[scoped]", "Scoped tests failed", {
@@ -39818,17 +39875,35 @@ var init_verify_scoped = __esm(() => {
39818
39875
  passCount: parsed.passed,
39819
39876
  failCount: parsed.failed,
39820
39877
  durationMs,
39821
- scopeTestFallback: selection.scopeTestFallback ?? false,
39822
- isFullSuite: selection.isFullSuite
39878
+ scopeTestFallback: scopeTestFallback ?? false,
39879
+ isFullSuite
39823
39880
  });
39881
+ let findings = deps.testSummaryToFindings(parsed);
39882
+ if (findings.length === 0) {
39883
+ logger.warn("verify[scoped]", "Scoped verify execution-failed \u2014 emitting synth finding", {
39884
+ storyId: input.storyId,
39885
+ command: effectiveCommand,
39886
+ exitCode: result.exitCode,
39887
+ cwd: cmdWorkdir
39888
+ });
39889
+ findings = [
39890
+ executionFailureToFinding({
39891
+ command: result.command ?? effectiveCommand,
39892
+ exitCode: result.exitCode,
39893
+ output: result.output ?? "",
39894
+ packageDir: input.packagePrefix,
39895
+ cwd: cmdWorkdir
39896
+ })
39897
+ ];
39898
+ }
39824
39899
  return {
39825
39900
  success: false,
39826
39901
  status: "failed",
39827
- findings: deps.testSummaryToFindings(parsed),
39902
+ findings,
39828
39903
  durationMs,
39829
39904
  passCount: parsed.passed,
39830
- isFullSuite: selection.isFullSuite,
39831
- scopeTestFallback: selection.scopeTestFallback
39905
+ isFullSuite,
39906
+ scopeTestFallback
39832
39907
  };
39833
39908
  }
39834
39909
  };
@@ -39973,26 +40048,23 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
39973
40048
  costUsd: totalCostUsd
39974
40049
  };
39975
40050
  }
39976
- for (const strategy of active) {
39977
- const attempts = countStrategyAttempts(cycle.iterations, strategy.name);
39978
- if (attempts >= strategy.maxAttempts) {
39979
- logger?.info("findings.cycle", "cycle exited \u2014 strategy attempt cap reached", {
39980
- storyId,
39981
- packageDir,
39982
- cycleName,
39983
- reason: "max-attempts-per-strategy",
39984
- exhaustedStrategy: strategy.name,
39985
- attempts,
39986
- maxAttempts: strategy.maxAttempts
39987
- });
39988
- return {
39989
- iterations: cycle.iterations,
39990
- finalFindings: cycle.findings,
39991
- exitReason: "max-attempts-per-strategy",
39992
- exhaustedStrategy: strategy.name,
39993
- costUsd: totalCostUsd
39994
- };
39995
- }
40051
+ const uncappedActive = active.filter((s) => countStrategyAttempts(cycle.iterations, s.name) < s.maxAttempts);
40052
+ if (uncappedActive.length === 0) {
40053
+ const exhaustedStrategy = active.find((s) => countStrategyAttempts(cycle.iterations, s.name) >= s.maxAttempts);
40054
+ logger?.info("findings.cycle", "cycle exited \u2014 all active strategies exhausted", {
40055
+ storyId,
40056
+ packageDir,
40057
+ cycleName,
40058
+ reason: "max-attempts-per-strategy",
40059
+ exhaustedStrategy: exhaustedStrategy?.name
40060
+ });
40061
+ return {
40062
+ iterations: cycle.iterations,
40063
+ finalFindings: cycle.findings,
40064
+ exitReason: "max-attempts-per-strategy",
40065
+ exhaustedStrategy: exhaustedStrategy?.name,
40066
+ costUsd: totalCostUsd
40067
+ };
39996
40068
  }
39997
40069
  const totalAttempts = countTotalAttempts(cycle.iterations);
39998
40070
  if (totalAttempts >= cycle.config.maxAttemptsTotal) {
@@ -40011,7 +40083,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
40011
40083
  costUsd: totalCostUsd
40012
40084
  };
40013
40085
  }
40014
- for (const strategy of active) {
40086
+ for (const strategy of uncappedActive) {
40015
40087
  const bailReason = strategy.bailWhen?.(cycle.iterations) ?? null;
40016
40088
  if (bailReason !== null) {
40017
40089
  logger?.info("findings.cycle", "cycle exited \u2014 bail predicate fired", {
@@ -40031,7 +40103,7 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
40031
40103
  };
40032
40104
  }
40033
40105
  }
40034
- const group = selectExecutionGroup(active);
40106
+ const group = selectExecutionGroup(uncappedActive);
40035
40107
  const startedAt = now();
40036
40108
  const findingsBefore = [...cycle.findings];
40037
40109
  const fixesApplied = [];
@@ -40142,6 +40214,19 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
40142
40214
  };
40143
40215
  }
40144
40216
  if (liteShortCircuited) {
40217
+ const companions = uncappedActive.filter((s) => !group.includes(s));
40218
+ if (companions.length > 0) {
40219
+ const iterCostUsd = fixesApplied.reduce((sum, fa) => sum + (fa.costUsd ?? 0), 0);
40220
+ totalCostUsd += iterCostUsd;
40221
+ logger?.info("findings.cycle", "exclusive strategy exhausted \u2014 continuing to companion strategies", {
40222
+ storyId,
40223
+ packageDir,
40224
+ cycleName,
40225
+ exhaustedStrategies: group.map((s) => s.name),
40226
+ remainingStrategies: companions.map((s) => s.name)
40227
+ });
40228
+ continue;
40229
+ }
40145
40230
  logger?.info("findings.cycle", "cycle exited \u2014 validate short-circuited", {
40146
40231
  storyId,
40147
40232
  packageDir,
@@ -54803,6 +54888,33 @@ class ExecutionPlan {
54803
54888
  }
54804
54889
  }
54805
54890
  }
54891
+ if (this.state.rectification && rectResult.rectificationExhausted) {
54892
+ const mechanicalOnly = !!rectResult.unfixedFindings?.length && rectResult.unfixedFindings.every((f) => f.source === "lint" || f.source === "typecheck");
54893
+ if (mechanicalOnly) {
54894
+ for (const phase of collectOrderedPhases(this.state)) {
54895
+ const name = phase.slot.op.name;
54896
+ if (name in phaseOutputs)
54897
+ continue;
54898
+ try {
54899
+ await runPhase(this.ctx, phase.slot, phaseCosts, phaseOutputs, this.isThreeSession);
54900
+ } catch (error48) {
54901
+ logger?.error("story-orchestrator", "Phase threw unexpected error (mechanical-only resume)", {
54902
+ storyId: this.ctx.storyId,
54903
+ phase: name,
54904
+ error: errorMessage(error48)
54905
+ });
54906
+ throw error48;
54907
+ }
54908
+ if (!phasePassed(name, phaseOutputs[name], this.ctx.storyId)) {
54909
+ logger?.warn("story-orchestrator", "Phase failed in mechanical-only resume", {
54910
+ storyId: this.ctx.storyId,
54911
+ phase: name
54912
+ });
54913
+ break;
54914
+ }
54915
+ }
54916
+ }
54917
+ }
54806
54918
  const advCfg = this.state.adversarialReview ? this.state.nonBlockingFix : undefined;
54807
54919
  const advisoryOut = phaseOutputs["adversarial-review"];
54808
54920
  const advisoryFindings = advisoryOut?.advisoryFindings ?? [];
@@ -55663,7 +55775,7 @@ async function decideStageAction(ctx, planResult, inspection, opts) {
55663
55775
  const sources = new Set(planResult.unfixedFindings.map((f) => f.source));
55664
55776
  const allMechanical = [...sources].every((s) => s === "lint" || s === "typecheck");
55665
55777
  if (allMechanical) {
55666
- logger.warn("execution", "Mechanical-only failure unfixable \u2014 proceeding (LLM review passed)", {
55778
+ logger.warn("execution", "Mechanical-only failure unfixable \u2014 proceeding (style-only errors remain)", {
55667
55779
  storyId: ctx.story.id
55668
55780
  });
55669
55781
  return { action: "continue" };
@@ -59658,7 +59770,7 @@ var package_default;
59658
59770
  var init_package = __esm(() => {
59659
59771
  package_default = {
59660
59772
  name: "@nathapp/nax",
59661
- version: "0.69.5",
59773
+ version: "0.69.7",
59662
59774
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
59663
59775
  type: "module",
59664
59776
  bin: {
@@ -59753,8 +59865,8 @@ var init_version = __esm(() => {
59753
59865
  NAX_VERSION = package_default.version;
59754
59866
  NAX_COMMIT = (() => {
59755
59867
  try {
59756
- if (/^[0-9a-f]{6,10}$/.test("f09a75e2"))
59757
- return "f09a75e2";
59868
+ if (/^[0-9a-f]{6,10}$/.test("d524317e"))
59869
+ return "d524317e";
59758
59870
  } catch {}
59759
59871
  try {
59760
59872
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.69.5",
3
+ "version": "0.69.7",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {