@nathapp/nax 0.70.0-canary.3 → 0.70.0-canary.4

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 +109 -29
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -32092,6 +32092,19 @@ ${STEP3_SHARED_RULES}
32092
32092
  - **File output (REQUIRED)**: Write the acceptance test file DIRECTLY to the path shown below. Do NOT output the test code in your response. After writing the file, reply with a brief confirmation.
32093
32093
  - **Path anchor (CRITICAL \u2014 do NOT deviate)**: Write the test file to this exact path: \`${p.targetTestFilePath}\`. This path is intentional and computed by the orchestrator \u2014 do not change it based on what you observe in the project. In particular: if you see a \`.nax/features/\` directory at the repo root, that is for stories scoped to the repo root. When a story belongs to a specific package (e.g. \`packages/core\`), its acceptance test lives inside that package's \`.nax/features/\` directory so the test runner can resolve the package's imports correctly. The package root is 3 levels above the test file (\`../../../\` relative to the test file).
32094
32094
  - **Process cwd**: When spawning child processes to invoke a CLI or binary, set the working directory to the **package root** (\`join(import.meta.dir, "../../..")\`) as your default \u2014 unless your Step 2 exploration reveals the CLI uses a different working directory convention (e.g. reads config from \`~/.config/\`, or resolves paths relative to a flag value). Always check how the CLI resolves file paths before assuming.${implSection}`;
32095
+ }
32096
+ buildPathCorrection(targetTestFilePath) {
32097
+ return `The acceptance test file was NOT found at the required path. You likely wrote it to a different filename or directory (for example, by renaming a dotfile or replacing dashes with underscores).
32098
+
32099
+ Move (or re-write) the acceptance test you just created so it lives at EXACTLY this path:
32100
+ ${targetTestFilePath}
32101
+
32102
+ Requirements:
32103
+ - The file must be at that exact path \u2014 same directory and same filename, including any leading dot and dashes. Do NOT sanitize, rename, or relocate it.
32104
+ - Preserve the test content you already wrote. Do not regenerate, weaken, or stub the assertions.
32105
+ - If you wrote it somewhere else, delete the misplaced copy after moving it so only the canonical path remains.
32106
+
32107
+ After writing the file to the exact path above, reply with a brief confirmation only.`;
32095
32108
  }
32096
32109
  buildGeneratorFromSpecPrompt(p) {
32097
32110
  return `You are a senior test engineer. Your task is to generate a complete acceptance test file for the "${p.featureName}" feature.
@@ -34759,6 +34772,40 @@ ${outputFormat}`, overridable: false }
34759
34772
  };
34760
34773
  });
34761
34774
 
34775
+ // src/operations/self-heal.ts
34776
+ function makeSelfHealStep(spec) {
34777
+ return {
34778
+ async run(ctx) {
34779
+ const deviations = await spec.detect(ctx.input);
34780
+ if (deviations.length === 0)
34781
+ return null;
34782
+ if (spec.log) {
34783
+ getSafeLogger()?.info(spec.log.kind, spec.log.message, spec.log.meta?.(ctx.input, deviations) ?? {});
34784
+ }
34785
+ return ctx.send(spec.buildRepair(deviations, ctx.input));
34786
+ }
34787
+ };
34788
+ }
34789
+ async function runSelfHealChain(ctx, seed, steps) {
34790
+ let last = seed;
34791
+ let totalCost = seed.estimatedCostUsd ?? 0;
34792
+ for (const step of steps) {
34793
+ try {
34794
+ const turn = await step.run(ctx);
34795
+ if (turn) {
34796
+ totalCost += turn.estimatedCostUsd ?? 0;
34797
+ last = turn;
34798
+ }
34799
+ } catch (err) {
34800
+ getSafeLogger()?.warn("self-heal", "step threw \u2014 skipping", { error: errorMessage(err) });
34801
+ }
34802
+ }
34803
+ return { ...last, estimatedCostUsd: totalCost };
34804
+ }
34805
+ var init_self_heal = __esm(() => {
34806
+ init_logger2();
34807
+ });
34808
+
34762
34809
  // src/operations/plan-refine.ts
34763
34810
  import { join as join21 } from "path";
34764
34811
  function hasToken(text, tokens) {
@@ -34897,6 +34944,28 @@ async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
34897
34944
  return prd;
34898
34945
  return { ...prd, userStories: results.map((r) => r.story) };
34899
34946
  }
34947
+ function verbatimSelfHealStep(builder) {
34948
+ return makeSelfHealStep({
34949
+ detect: (input) => readMissingVerbatimAcs(input),
34950
+ buildRepair: (missing, input) => builder.buildVerbatimRepair(missing, input.outputPath),
34951
+ log: {
34952
+ kind: "plan",
34953
+ message: "Refine dropped [verbatim] spec ACs \u2014 issuing one repair turn",
34954
+ meta: (input, missing) => ({ featureName: input.featureName, missingCount: missing.length })
34955
+ }
34956
+ });
34957
+ }
34958
+ function specDriftSelfHealStep(builder) {
34959
+ return makeSelfHealStep({
34960
+ detect: (input) => readSpecDriftViolations(input),
34961
+ buildRepair: (drifted, input) => builder.buildSpecDriftRepair(drifted, input.outputPath),
34962
+ log: {
34963
+ kind: "plan",
34964
+ message: "specGuard: spec-drift violations found \u2014 issuing one repair turn",
34965
+ meta: (input, drifted) => ({ featureName: input.featureName, violationCount: drifted.length })
34966
+ }
34967
+ });
34968
+ }
34900
34969
  var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
34901
34970
  var init_plan_refine = __esm(() => {
34902
34971
  init_retry();
@@ -34906,6 +34975,7 @@ var init_plan_refine = __esm(() => {
34906
34975
  init_prd();
34907
34976
  init_schema2();
34908
34977
  init_prompts();
34978
+ init_self_heal();
34909
34979
  init_verbatim_warn();
34910
34980
  _planRefineDeps = {
34911
34981
  readFile: async (path3) => {
@@ -34985,31 +35055,15 @@ ${outputFormat}`,
34985
35055
  const specGuard = ctx.input.specGuard ?? false;
34986
35056
  const turn1 = await ctx.sendWithParseRetry(initialPrompt);
34987
35057
  const turn2 = await ctx.send(builder.buildRefineContinuation(ctx.input.outputPath, specGuard));
34988
- let totalCost = (turn1.estimatedCostUsd ?? 0) + (turn2.estimatedCostUsd ?? 0);
34989
- let last = turn2;
34990
- const missing = await readMissingVerbatimAcs(ctx.input);
34991
- if (missing.length > 0) {
34992
- getSafeLogger()?.info("plan", "Refine dropped [verbatim] spec ACs \u2014 issuing one repair turn", {
34993
- featureName: ctx.input.featureName,
34994
- missingCount: missing.length
34995
- });
34996
- const turn3 = await ctx.send(builder.buildVerbatimRepair(missing, ctx.input.outputPath));
34997
- totalCost += turn3.estimatedCostUsd ?? 0;
34998
- last = turn3;
34999
- }
35000
- if (specGuard) {
35001
- const drifted = await readSpecDriftViolations(ctx.input);
35002
- if (drifted.length > 0) {
35003
- getSafeLogger()?.info("plan", "specGuard: spec-drift violations found \u2014 issuing one repair turn", {
35004
- featureName: ctx.input.featureName,
35005
- violationCount: drifted.length
35006
- });
35007
- const turn4 = await ctx.send(builder.buildSpecDriftRepair(drifted, ctx.input.outputPath));
35008
- totalCost += turn4.estimatedCostUsd ?? 0;
35009
- last = turn4;
35010
- }
35011
- }
35012
- return { ...last, estimatedCostUsd: totalCost };
35058
+ const seed = {
35059
+ ...turn2,
35060
+ estimatedCostUsd: (turn1.estimatedCostUsd ?? 0) + (turn2.estimatedCostUsd ?? 0)
35061
+ };
35062
+ const steps = [
35063
+ verbatimSelfHealStep(builder),
35064
+ ...specGuard ? [specDriftSelfHealStep(builder)] : []
35065
+ ];
35066
+ return runSelfHealChain(ctx, seed, steps);
35013
35067
  },
35014
35068
  parse(output, input) {
35015
35069
  return validatePlanOutput(output, input.featureName, input.branchName);
@@ -36127,11 +36181,32 @@ function isStubTestContent(content) {
36127
36181
  }
36128
36182
 
36129
36183
  // src/operations/acceptance-generate.ts
36130
- var acceptanceGenerateOp;
36184
+ function pathCorrectionStep() {
36185
+ return makeSelfHealStep({
36186
+ detect: async (input) => await _acceptanceGenerateDeps.fileExists(input.targetTestFilePath) ? [] : [input.targetTestFilePath],
36187
+ buildRepair: (_deviations, input) => new AcceptancePromptBuilder().buildPathCorrection(input.targetTestFilePath),
36188
+ log: {
36189
+ kind: "acceptance",
36190
+ message: "Acceptance test not found at target path \u2014 issuing one corrective turn",
36191
+ meta: (input) => ({ targetTestFilePath: input.targetTestFilePath })
36192
+ }
36193
+ });
36194
+ }
36195
+ var _acceptanceGenerateDeps, acceptanceGenerateOp;
36131
36196
  var init_acceptance_generate = __esm(() => {
36132
36197
  init_generator();
36133
36198
  init_config();
36134
36199
  init_prompts();
36200
+ init_self_heal();
36201
+ _acceptanceGenerateDeps = {
36202
+ fileExists: async (path4) => {
36203
+ try {
36204
+ return await Bun.file(path4).exists();
36205
+ } catch {
36206
+ return false;
36207
+ }
36208
+ }
36209
+ };
36135
36210
  acceptanceGenerateOp = {
36136
36211
  kind: "run",
36137
36212
  name: "acceptance-generate",
@@ -36153,6 +36228,10 @@ var init_acceptance_generate = __esm(() => {
36153
36228
  task: { id: "task", content: prompt, overridable: false }
36154
36229
  };
36155
36230
  },
36231
+ async hopBody(initialPrompt, ctx) {
36232
+ const turn1 = await ctx.sendWithParseRetry(initialPrompt);
36233
+ return runSelfHealChain(ctx, turn1, [pathCorrectionStep()]);
36234
+ },
36156
36235
  parse(output, _input, _ctx) {
36157
36236
  return { testCode: extractTestCode(output) };
36158
36237
  },
@@ -40163,6 +40242,7 @@ var init_operations = __esm(() => {
40163
40242
  init_call();
40164
40243
  init_plan();
40165
40244
  init_plan_refine();
40245
+ init_self_heal();
40166
40246
  init_verbatim_warn();
40167
40247
  init_decompose2();
40168
40248
  init_build_hop_callback();
@@ -60245,7 +60325,7 @@ var package_default;
60245
60325
  var init_package = __esm(() => {
60246
60326
  package_default = {
60247
60327
  name: "@nathapp/nax",
60248
- version: "0.70.0-canary.3",
60328
+ version: "0.70.0-canary.4",
60249
60329
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
60250
60330
  type: "module",
60251
60331
  bin: {
@@ -60340,8 +60420,8 @@ var init_version = __esm(() => {
60340
60420
  NAX_VERSION = package_default.version;
60341
60421
  NAX_COMMIT = (() => {
60342
60422
  try {
60343
- if (/^[0-9a-f]{6,10}$/.test("905b80cf"))
60344
- return "905b80cf";
60423
+ if (/^[0-9a-f]{6,10}$/.test("e2a854e7"))
60424
+ return "e2a854e7";
60345
60425
  } catch {}
60346
60426
  try {
60347
60427
  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.70.0-canary.3",
3
+ "version": "0.70.0-canary.4",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {