@nathapp/nax 0.73.0 → 0.73.1

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 +93 -59
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -36839,8 +36839,7 @@ function resolvePytestBin(packageDir) {
36839
36839
  }
36840
36840
  function buildAcceptanceRunCommand(testPath, testFramework, commandOverride, packageDir) {
36841
36841
  if (commandOverride) {
36842
- const resolved = commandOverride.replace(/\{\{files\}\}/g, testPath).replace(/\{\{file\}\}/g, testPath).replace(/\{\{FILE\}\}/g, testPath);
36843
- return resolved.trim().split(/\s+/);
36842
+ return commandOverride.trim().split(/\s+/).map((part) => part.replace(/\{\{files\}\}/g, testPath).replace(/\{\{file\}\}/g, testPath).replace(/\{\{FILE\}\}/g, testPath));
36844
36843
  }
36845
36844
  switch (testFramework?.toLowerCase()) {
36846
36845
  case "vitest":
@@ -40291,14 +40290,21 @@ var init_autofix_test_writer_strategy = __esm(() => {
40291
40290
  });
40292
40291
 
40293
40292
  // src/operations/apply-test-edit-declarations.ts
40294
- function applyTestEditDeclarations(findings, declarations, story, invalidMockStructure) {
40293
+ function applyTestEditDeclarations(findings, declarations, story, opts = {}) {
40294
+ const { invalidMockStructure, allowTestRetag = true } = opts;
40295
40295
  let result = [...findings];
40296
- const advisories = [];
40296
+ const diagnostics = [];
40297
40297
  for (const d of declarations) {
40298
40298
  if (d.reason === "prd_contract") {
40299
40299
  const prdQuote = d.prdQuote ?? "";
40300
40300
  const valid = validatePrdQuote(prdQuote, story);
40301
- if (valid) {
40301
+ if (!valid) {
40302
+ diagnostics.push({
40303
+ reason: "prd_quote_mismatch",
40304
+ file: d.file,
40305
+ detail: `PRD quote not found verbatim in story text for file: ${d.file}`
40306
+ });
40307
+ } else if (allowTestRetag) {
40302
40308
  result = result.map((f) => {
40303
40309
  const eligible = f.file === d.file && (f.fixTarget === "source" || f.fixTarget == null && f.source === "test-runner");
40304
40310
  if (eligible) {
@@ -40313,31 +40319,18 @@ function applyTestEditDeclarations(findings, declarations, story, invalidMockStr
40313
40319
  }
40314
40320
  return f;
40315
40321
  });
40316
- } else {
40317
- advisories.push({
40318
- source: "autofix",
40319
- severity: "warning",
40320
- category: "prd_quote_mismatch",
40321
- message: `PRD quote not found verbatim in story text for file: ${d.file}`,
40322
- file: d.file,
40323
- fixTarget: "source"
40324
- });
40325
40322
  }
40326
40323
  }
40327
40324
  }
40328
- if (invalidMockStructure && invalidMockStructure.length > 0) {
40329
- for (const d of invalidMockStructure) {
40330
- const fileList = (d.files ?? [d.file]).join(", ");
40331
- advisories.push({
40332
- source: "autofix",
40333
- severity: "warning",
40334
- category: "mock_structure_invalid_files",
40335
- message: `Mock structure handoff references file that does not exist or is not a test file: ${fileList}`,
40336
- fixTarget: "source"
40337
- });
40338
- }
40325
+ for (const d of invalidMockStructure ?? []) {
40326
+ const fileList = (d.files ?? [d.file]).join(", ");
40327
+ diagnostics.push({
40328
+ reason: "mock_structure_invalid_files",
40329
+ file: d.file,
40330
+ detail: `Mock structure handoff references file that does not exist or is not a test file: ${fileList}`
40331
+ });
40339
40332
  }
40340
- return [...result, ...advisories];
40333
+ return { findings: result, diagnostics };
40341
40334
  }
40342
40335
  var init_apply_test_edit_declarations = __esm(() => {
40343
40336
  init_test_edit_declaration();
@@ -41637,12 +41630,14 @@ async function runFixCycle(cycle, ctx, cycleName, _deps = {}) {
41637
41630
  }
41638
41631
  const active = selectActiveStrategies(cycle.strategies, cycle.findings, cycle.verdict);
41639
41632
  if (active.length === 0) {
41640
- logger?.info("findings.cycle", "cycle exited \u2014 no matching strategy", {
41633
+ const orphanSources = [...new Set(cycle.findings.map((f) => f.source))];
41634
+ logger?.warn("findings.cycle", "cycle exited \u2014 no matching strategy (orphaned findings)", {
41641
41635
  storyId,
41642
41636
  packageDir,
41643
41637
  cycleName,
41644
41638
  reason: "no-strategy",
41645
- findingsCount: cycle.findings.length
41639
+ findingsCount: cycle.findings.length,
41640
+ orphanSources
41646
41641
  });
41647
41642
  return {
41648
41643
  iterations: cycle.iterations,
@@ -58427,6 +58422,20 @@ import { join as join50 } from "path";
58427
58422
  function requiresInitialRefCapture(strategy) {
58428
58423
  return isThreeSessionStrategy(strategy);
58429
58424
  }
58425
+ function logRejectedDeclarations(diagnostics, ctx) {
58426
+ if (diagnostics.length === 0)
58427
+ return;
58428
+ const logger = getSafeLogger();
58429
+ for (const d of diagnostics) {
58430
+ logger?.warn("findings.cycle", "test-edit declaration rejected \u2014 ignoring declaration", {
58431
+ storyId: ctx.storyId,
58432
+ packageDir: ctx.packageDir,
58433
+ reason: d.reason,
58434
+ file: d.file,
58435
+ detail: d.detail
58436
+ });
58437
+ }
58438
+ }
58430
58439
  function isFreshRun(story) {
58431
58440
  const hasAttempts = (story.attempts ?? 0) > 0;
58432
58441
  const hasReviewEscalation = (story.priorFailures ?? []).some((f) => f.stage === "review");
@@ -58498,7 +58507,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
58498
58507
  strategies.push(makeAutofixTestWriterStrategy(story, config2, sink));
58499
58508
  }
58500
58509
  }
58501
- const postValidate = async (findings, _validateCtx) => {
58510
+ const postValidate = async (findings, validateCtx) => {
58502
58511
  if (sink.testEdits.length === 0 && sink.mockHandoffs.length === 0)
58503
58512
  return findings;
58504
58513
  const pendingMock = sink.mockHandoffs.map((h) => ({
@@ -58511,7 +58520,12 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
58511
58520
  sink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
58512
58521
  const allDeclarations = [...sink.testEdits, ...valid];
58513
58522
  sink.testEdits = [];
58514
- return applyTestEditDeclarations(findings, allDeclarations, story, invalid);
58523
+ const applied = applyTestEditDeclarations(findings, allDeclarations, story, {
58524
+ invalidMockStructure: invalid,
58525
+ allowTestRetag: isThreeSession
58526
+ });
58527
+ logRejectedDeclarations(applied.diagnostics, validateCtx);
58528
+ return applied.findings;
58515
58529
  };
58516
58530
  const rectOpts = {
58517
58531
  ...inputs.rectification,
@@ -58551,7 +58565,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
58551
58565
  }));
58552
58566
  }
58553
58567
  nbStrategies.push(makeFullSuiteRectifyStrategy(story, config2, nbSink));
58554
- const nbPostValidate = async (findings, _validateCtx) => {
58568
+ const nbPostValidate = async (findings, validateCtx) => {
58555
58569
  if (nbSink.testEdits.length === 0 && nbSink.mockHandoffs.length === 0)
58556
58570
  return findings;
58557
58571
  const pendingMock = nbSink.mockHandoffs.map((h) => ({
@@ -58564,7 +58578,12 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
58564
58578
  nbSink.mockHandoffs = valid.map((d) => ({ files: d.files ?? [], reasonDetail: d.reasonDetail ?? "" }));
58565
58579
  const allDeclarations = [...nbSink.testEdits, ...valid];
58566
58580
  nbSink.testEdits = [];
58567
- return applyTestEditDeclarations(findings, allDeclarations, story, invalid);
58581
+ const applied = applyTestEditDeclarations(findings, allDeclarations, story, {
58582
+ invalidMockStructure: invalid,
58583
+ allowTestRetag: isThreeSession
58584
+ });
58585
+ logRejectedDeclarations(applied.diagnostics, validateCtx);
58586
+ return applied.findings;
58568
58587
  };
58569
58588
  builder.addNonBlockingFix(nbf, nbStrategies, nbPostValidate);
58570
58589
  }
@@ -58572,6 +58591,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
58572
58591
  }
58573
58592
  var init_build_plan_for_strategy = __esm(() => {
58574
58593
  init_config();
58594
+ init_logger2();
58575
58595
  init_operations();
58576
58596
  init_execution_gates();
58577
58597
  init_full_suite_rectify();
@@ -59183,6 +59203,17 @@ async function decideStageAction(ctx, planResult, inspection, opts) {
59183
59203
  ctx.tddFailureCategory = failureCategory;
59184
59204
  }
59185
59205
  if (planResult.rectificationExhausted && planResult.unfixedFindings && planResult.unfixedFindings.length > 0) {
59206
+ const blockingThreshold = ctx.config?.review?.blockingThreshold ?? "error";
59207
+ const blockingUnfixed = planResult.unfixedFindings.filter((f) => isBlockingSeverity(f.severity ?? "error", blockingThreshold));
59208
+ if (blockingUnfixed.length === 0) {
59209
+ logger.warn("execution", "Rectification exhausted but all unfixed findings are advisory (below blocking threshold) \u2014 proceeding", {
59210
+ storyId: ctx.story.id,
59211
+ blockingThreshold,
59212
+ unfixedCount: planResult.unfixedFindings.length,
59213
+ unfixedSources: [...new Set(planResult.unfixedFindings.map((f) => f.source))]
59214
+ });
59215
+ return { action: "continue" };
59216
+ }
59186
59217
  const sources = new Set(planResult.unfixedFindings.map((f) => f.source));
59187
59218
  const allMechanical = [...sources].every((s) => s === "lint" || s === "typecheck");
59188
59219
  if (allMechanical) {
@@ -59344,6 +59375,7 @@ var init_post_run = __esm(() => {
59344
59375
  init_operations();
59345
59376
  init_execution_helpers();
59346
59377
  init_quality();
59378
+ init_severity();
59347
59379
  init_scratch_writer();
59348
59380
  init_rollback();
59349
59381
  init_git();
@@ -59925,6 +59957,8 @@ var init_queue_handler = __esm(() => {
59925
59957
  import path15 from "path";
59926
59958
  var queueCheckStage;
59927
59959
  var init_queue_check = __esm(() => {
59960
+ init_config();
59961
+ init_errors();
59928
59962
  init_queue_handler();
59929
59963
  init_logger2();
59930
59964
  init_prd();
@@ -59973,8 +60007,11 @@ var init_queue_check = __esm(() => {
59973
60007
  continue;
59974
60008
  }
59975
60009
  if (cmd.type === "INJECT") {
59976
- const storyFilePath = path15.isAbsolute(cmd.storyFile) ? cmd.storyFile : path15.join(ctx.workdir, cmd.storyFile);
59977
60010
  try {
60011
+ if (path15.isAbsolute(cmd.storyFile)) {
60012
+ throw new NaxError(`INJECT storyFile must be a relative path within the workspace: ${cmd.storyFile}`, "INJECT_PATH_ABSOLUTE", { stage: "queue-check", storyId: ctx.story?.id ?? "unknown", storyFile: cmd.storyFile });
60013
+ }
60014
+ const storyFilePath = validateFilePath(path15.join(ctx.workdir, cmd.storyFile), ctx.workdir);
59978
60015
  const raw = await Bun.file(storyFilePath).json();
59979
60016
  const existingIds = new Set(ctx.prd.userStories.map((s) => s.id));
59980
60017
  const story = validateInjectedStory(raw, existingIds);
@@ -63447,13 +63484,9 @@ var init_loader4 = __esm(() => {
63447
63484
  });
63448
63485
 
63449
63486
  // src/execution/checkpoint/writer.ts
63450
- import { rename } from "fs/promises";
63487
+ import { appendFile as appendFile4 } from "fs/promises";
63451
63488
  async function defaultAppend(filePath, line) {
63452
- const file3 = Bun.file(filePath);
63453
- const existing = await file3.exists() ? await file3.text() : "";
63454
- const tmpPath = `${filePath}.tmp`;
63455
- await Bun.write(tmpPath, existing + line);
63456
- await rename(tmpPath, filePath);
63489
+ await appendFile4(filePath, line, "utf8");
63457
63490
  }
63458
63491
  function createCheckpointWriter(filePath, runId) {
63459
63492
  return new CheckpointWriter({ filePath, runId, _deps: { append: defaultAppend } });
@@ -63554,15 +63587,8 @@ async function loadCheckpoints(featureDir, options = { _deps: { read: defaultRea
63554
63587
  break;
63555
63588
  }
63556
63589
  }
63557
- let latestRunId;
63558
- for (const r of validRecords) {
63559
- latestRunId = maxRunId(latestRunId, r.runId);
63560
- }
63561
- if (latestRunId === undefined)
63562
- return new Map;
63563
- const latest = validRecords.filter((r) => r.runId === latestRunId);
63564
63590
  const grouped = new Map;
63565
- for (const r of latest) {
63591
+ for (const r of validRecords) {
63566
63592
  const list = grouped.get(r.storyId);
63567
63593
  if (list) {
63568
63594
  list.push(r);
@@ -63570,6 +63596,13 @@ async function loadCheckpoints(featureDir, options = { _deps: { read: defaultRea
63570
63596
  grouped.set(r.storyId, [r]);
63571
63597
  }
63572
63598
  }
63599
+ for (const [storyId, records] of grouped) {
63600
+ let latestRunId;
63601
+ for (const r of records) {
63602
+ latestRunId = maxRunId(latestRunId, r.runId);
63603
+ }
63604
+ grouped.set(storyId, records.filter((r) => r.runId === latestRunId));
63605
+ }
63573
63606
  const result = new Map;
63574
63607
  for (const [storyId, records] of grouped) {
63575
63608
  const seen = new Set;
@@ -63842,7 +63875,7 @@ var package_default;
63842
63875
  var init_package = __esm(() => {
63843
63876
  package_default = {
63844
63877
  name: "@nathapp/nax",
63845
- version: "0.73.0",
63878
+ version: "0.73.1",
63846
63879
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
63847
63880
  type: "module",
63848
63881
  bin: {
@@ -63943,8 +63976,8 @@ var init_version = __esm(() => {
63943
63976
  NAX_VERSION = package_default.version;
63944
63977
  NAX_COMMIT = (() => {
63945
63978
  try {
63946
- if (/^[0-9a-f]{6,10}$/.test("ca202ba0"))
63947
- return "ca202ba0";
63979
+ if (/^[0-9a-f]{6,10}$/.test("73c95c44"))
63980
+ return "73c95c44";
63948
63981
  } catch {}
63949
63982
  try {
63950
63983
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -64850,7 +64883,7 @@ var init_acceptance_loop = __esm(() => {
64850
64883
  });
64851
64884
 
64852
64885
  // src/session/scratch-purge.ts
64853
- import { mkdir as mkdir12, rename as rename2, rm } from "fs/promises";
64886
+ import { mkdir as mkdir12, rename, rm } from "fs/promises";
64854
64887
  import { dirname as dirname13, join as join84 } from "path";
64855
64888
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
64856
64889
  const sessionsDir = join84(projectDir, ".nax", "features", featureName, "sessions");
@@ -64903,7 +64936,7 @@ var init_scratch_purge = __esm(() => {
64903
64936
  remove: (path24) => rm(path24, { recursive: true, force: true }),
64904
64937
  move: async (src, dest) => {
64905
64938
  await mkdir12(dirname13(dest), { recursive: true });
64906
- await rename2(src, dest);
64939
+ await rename(src, dest);
64907
64940
  },
64908
64941
  now: () => Date.now()
64909
64942
  };
@@ -66053,7 +66086,7 @@ var init_ensure_package_dirs = __esm(() => {
66053
66086
  });
66054
66087
 
66055
66088
  // src/pipeline/subscribers/events-writer.ts
66056
- import { appendFile as appendFile4, mkdir as mkdir13 } from "fs/promises";
66089
+ import { appendFile as appendFile5, mkdir as mkdir13 } from "fs/promises";
66057
66090
  import { basename as basename15, join as join85 } from "path";
66058
66091
  function wireEventsWriter(bus, feature, runId, workdir) {
66059
66092
  const logger = getSafeLogger();
@@ -66068,7 +66101,7 @@ function wireEventsWriter(bus, feature, runId, workdir) {
66068
66101
  await mkdir13(eventsDir, { recursive: true });
66069
66102
  dirReady = true;
66070
66103
  }
66071
- await appendFile4(eventsFile, `${JSON.stringify(line)}
66104
+ await appendFile5(eventsFile, `${JSON.stringify(line)}
66072
66105
  `);
66073
66106
  } catch (err) {
66074
66107
  logger?.warn("events-writer", "Failed to write event line (non-fatal)", {
@@ -69031,7 +69064,7 @@ var init_runner_execution = __esm(() => {
69031
69064
  });
69032
69065
 
69033
69066
  // src/execution/status-file.ts
69034
- import { rename as rename3, unlink as unlink3 } from "fs/promises";
69067
+ import { rename as rename2, unlink as unlink3 } from "fs/promises";
69035
69068
  import { resolve as resolve16 } from "path";
69036
69069
  function countProgress(prd) {
69037
69070
  const stories = prd.userStories;
@@ -69086,7 +69119,7 @@ async function writeStatusFile(filePath, status) {
69086
69119
  await unlink3(tmpPath);
69087
69120
  } catch {}
69088
69121
  await Bun.write(tmpPath, JSON.stringify(status, null, 2));
69089
- await rename3(tmpPath, resolvedPath);
69122
+ await rename2(tmpPath, resolvedPath);
69090
69123
  }
69091
69124
  var init_status_file = () => {};
69092
69125
 
@@ -69242,7 +69275,7 @@ __export(exports_migrate, {
69242
69275
  detectGeneratedContent: () => detectGeneratedContent
69243
69276
  });
69244
69277
  import { existsSync as existsSync36 } from "fs";
69245
- import { mkdir as mkdir16, readdir as readdir5, rename as rename4 } from "fs/promises";
69278
+ import { mkdir as mkdir16, readdir as readdir5, rename as rename3 } from "fs/promises";
69246
69279
  import path28 from "path";
69247
69280
  async function detectGeneratedContent(naxDir) {
69248
69281
  if (!existsSync36(naxDir))
@@ -69331,7 +69364,7 @@ async function migrateCommand(options) {
69331
69364
  const archiveBase = path28.join(globalConfigDir(), "_archive");
69332
69365
  const archiveDest = path28.join(archiveBase, `${options.reclaim}-${Date.now()}`);
69333
69366
  await mkdir16(archiveBase, { recursive: true });
69334
- await rename4(src, archiveDest);
69367
+ await rename3(src, archiveDest);
69335
69368
  logger.info("migrate", `Reclaimed: archived to ${archiveDest}`, { storyId: "_migrate" });
69336
69369
  return;
69337
69370
  }
@@ -69410,7 +69443,7 @@ async function migrateCommand(options) {
69410
69443
  Remove the destination or run nax migrate --dry-run to inspect.`, "MIGRATE_CONFLICT", { stage: "migrate", src: candidate.srcPath, dest });
69411
69444
  }
69412
69445
  try {
69413
- await rename4(candidate.srcPath, dest);
69446
+ await rename3(candidate.srcPath, dest);
69414
69447
  } catch (err) {
69415
69448
  const isXdev = err instanceof Error && "code" in err && err.code === "EXDEV";
69416
69449
  if (isXdev) {
@@ -102467,6 +102500,7 @@ async function resolveFeatureAcceptance(featureName, workdir) {
102467
102500
  testPath: relative15(repoRoot, g.testPath),
102468
102501
  exists: await Bun.file(g.testPath).exists(),
102469
102502
  command,
102503
+ cwd: packageDir,
102470
102504
  language: g.language
102471
102505
  };
102472
102506
  }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.73.0",
3
+ "version": "0.73.1",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {