@nathapp/nax 0.70.5 → 0.70.6

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 +218 -161
  2. package/package.json +1 -1
package/dist/nax.js CHANGED
@@ -60861,7 +60861,7 @@ var init_loader4 = __esm(() => {
60861
60861
  });
60862
60862
 
60863
60863
  // src/hooks/runner.ts
60864
- import { join as join77 } from "path";
60864
+ import { join as join78 } from "path";
60865
60865
  function createDrainDeadline2(deadlineMs) {
60866
60866
  let timeoutId;
60867
60867
  const promise2 = new Promise((resolve16) => {
@@ -60880,14 +60880,14 @@ async function loadHooksConfig(projectDir, globalDir) {
60880
60880
  let globalHooks = { hooks: {} };
60881
60881
  let projectHooks = { hooks: {} };
60882
60882
  let skipGlobal = false;
60883
- const projectPath = join77(projectDir, "hooks.json");
60883
+ const projectPath = join78(projectDir, "hooks.json");
60884
60884
  const projectData = await loadJsonFile(projectPath, "hooks");
60885
60885
  if (projectData) {
60886
60886
  projectHooks = projectData;
60887
60887
  skipGlobal = projectData.skipGlobal ?? false;
60888
60888
  }
60889
60889
  if (!skipGlobal && globalDir) {
60890
- const globalPath = join77(globalDir, "hooks.json");
60890
+ const globalPath = join78(globalDir, "hooks.json");
60891
60891
  const globalData = await loadJsonFile(globalPath, "hooks");
60892
60892
  if (globalData) {
60893
60893
  globalHooks = globalData;
@@ -61057,7 +61057,7 @@ var package_default;
61057
61057
  var init_package = __esm(() => {
61058
61058
  package_default = {
61059
61059
  name: "@nathapp/nax",
61060
- version: "0.70.5",
61060
+ version: "0.70.6",
61061
61061
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
61062
61062
  type: "module",
61063
61063
  bin: {
@@ -61157,8 +61157,8 @@ var init_version = __esm(() => {
61157
61157
  NAX_VERSION = package_default.version;
61158
61158
  NAX_COMMIT = (() => {
61159
61159
  try {
61160
- if (/^[0-9a-f]{6,10}$/.test("0e9abf26"))
61161
- return "0e9abf26";
61160
+ if (/^[0-9a-f]{6,10}$/.test("eb4e2b89"))
61161
+ return "eb4e2b89";
61162
61162
  } catch {}
61163
61163
  try {
61164
61164
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -62036,15 +62036,15 @@ var init_acceptance_loop = __esm(() => {
62036
62036
 
62037
62037
  // src/session/scratch-purge.ts
62038
62038
  import { mkdir as mkdir12, rename, rm } from "fs/promises";
62039
- import { dirname as dirname12, join as join78 } from "path";
62039
+ import { dirname as dirname12, join as join79 } from "path";
62040
62040
  async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
62041
- const sessionsDir = join78(projectDir, ".nax", "features", featureName, "sessions");
62041
+ const sessionsDir = join79(projectDir, ".nax", "features", featureName, "sessions");
62042
62042
  const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
62043
62043
  const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
62044
62044
  let purged = 0;
62045
62045
  for (const sessionId of sessionIds) {
62046
- const sessionDir = join78(sessionsDir, sessionId);
62047
- const descriptorPath = join78(sessionDir, "descriptor.json");
62046
+ const sessionDir = join79(sessionsDir, sessionId);
62047
+ const descriptorPath = join79(sessionDir, "descriptor.json");
62048
62048
  if (!await _scratchPurgeDeps.fileExists(descriptorPath))
62049
62049
  continue;
62050
62050
  let lastActivityAt;
@@ -62060,7 +62060,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
62060
62060
  if (new Date(lastActivityAt).getTime() >= cutoffMs)
62061
62061
  continue;
62062
62062
  if (archiveInsteadOfDelete) {
62063
- const archiveDest = join78(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
62063
+ const archiveDest = join79(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
62064
62064
  await _scratchPurgeDeps.move(sessionDir, archiveDest);
62065
62065
  } else {
62066
62066
  await _scratchPurgeDeps.remove(sessionDir);
@@ -62811,12 +62811,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
62811
62811
 
62812
62812
  // src/pipeline/subscribers/events-writer.ts
62813
62813
  import { appendFile as appendFile4, mkdir as mkdir13 } from "fs/promises";
62814
- import { basename as basename13, join as join79 } from "path";
62814
+ import { basename as basename13, join as join80 } from "path";
62815
62815
  function wireEventsWriter(bus, feature, runId, workdir) {
62816
62816
  const logger = getSafeLogger();
62817
62817
  const project = basename13(workdir);
62818
- const eventsDir = join79(getEventsRootDir(), project);
62819
- const eventsFile = join79(eventsDir, "events.jsonl");
62818
+ const eventsDir = join80(getEventsRootDir(), project);
62819
+ const eventsFile = join80(eventsDir, "events.jsonl");
62820
62820
  let dirReady = false;
62821
62821
  const write = (line) => {
62822
62822
  return (async () => {
@@ -62997,12 +62997,12 @@ var init_interaction2 = __esm(() => {
62997
62997
 
62998
62998
  // src/pipeline/subscribers/registry.ts
62999
62999
  import { mkdir as mkdir14, writeFile as writeFile2 } from "fs/promises";
63000
- import { basename as basename14, join as join80 } from "path";
63000
+ import { basename as basename14, join as join81 } from "path";
63001
63001
  function wireRegistry(bus, feature, runId, workdir, outputDir) {
63002
63002
  const logger = getSafeLogger();
63003
63003
  const project = basename14(workdir);
63004
- const runDir = join80(getRunsDir(), `${project}-${feature}-${runId}`);
63005
- const metaFile = join80(runDir, "meta.json");
63004
+ const runDir = join81(getRunsDir(), `${project}-${feature}-${runId}`);
63005
+ const metaFile = join81(runDir, "meta.json");
63006
63006
  const unsub = bus.on("run:started", (_ev) => {
63007
63007
  return (async () => {
63008
63008
  try {
@@ -63012,8 +63012,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
63012
63012
  project,
63013
63013
  feature,
63014
63014
  workdir,
63015
- statusPath: join80(outputDir, "features", feature, "status.json"),
63016
- eventsDir: join80(outputDir, "features", feature, "runs"),
63015
+ statusPath: join81(outputDir, "features", feature, "status.json"),
63016
+ eventsDir: join81(outputDir, "features", feature, "runs"),
63017
63017
  registeredAt: new Date().toISOString()
63018
63018
  };
63019
63019
  await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
@@ -63258,8 +63258,8 @@ var init_types10 = __esm(() => {
63258
63258
  });
63259
63259
 
63260
63260
  // src/worktree/dependencies.ts
63261
- import { existsSync as existsSync31 } from "fs";
63262
- import { join as join81 } from "path";
63261
+ import { existsSync as existsSync32 } from "fs";
63262
+ import { join as join82 } from "path";
63263
63263
  async function prepareWorktreeDependencies(options) {
63264
63264
  const mode = options.config.execution.worktreeDependencies.mode;
63265
63265
  const resolvedCwd = resolveDependencyCwd(options);
@@ -63273,7 +63273,7 @@ async function prepareWorktreeDependencies(options) {
63273
63273
  }
63274
63274
  }
63275
63275
  function resolveDependencyCwd(options) {
63276
- return options.storyWorkdir ? join81(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
63276
+ return options.storyWorkdir ? join82(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
63277
63277
  }
63278
63278
  function resolveInheritedDependencies(options, resolvedCwd) {
63279
63279
  if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
@@ -63283,7 +63283,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
63283
63283
  }
63284
63284
  function hasDependencyManifests(worktreeRoot, resolvedCwd) {
63285
63285
  const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
63286
- return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join81(directory, filename))));
63286
+ return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join82(directory, filename))));
63287
63287
  }
63288
63288
  async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
63289
63289
  const setupCommand2 = config2.execution.worktreeDependencies.setupCommand;
@@ -63334,7 +63334,7 @@ var init_dependencies = __esm(() => {
63334
63334
  "build.gradle.kts"
63335
63335
  ];
63336
63336
  _worktreeDependencyDeps = {
63337
- existsSync: existsSync31,
63337
+ existsSync: existsSync32,
63338
63338
  spawn
63339
63339
  };
63340
63340
  });
@@ -63345,19 +63345,19 @@ __export(exports_manager, {
63345
63345
  _managerDeps: () => _managerDeps,
63346
63346
  WorktreeManager: () => WorktreeManager
63347
63347
  });
63348
- import { existsSync as existsSync32, symlinkSync } from "fs";
63348
+ import { existsSync as existsSync33, symlinkSync } from "fs";
63349
63349
  import { mkdir as mkdir15 } from "fs/promises";
63350
- import { join as join82 } from "path";
63350
+ import { join as join83 } from "path";
63351
63351
 
63352
63352
  class WorktreeManager {
63353
63353
  async ensureGitExcludes(projectRoot) {
63354
63354
  const logger = getSafeLogger();
63355
- const infoDir = join82(projectRoot, ".git", "info");
63356
- const excludePath = join82(infoDir, "exclude");
63355
+ const infoDir = join83(projectRoot, ".git", "info");
63356
+ const excludePath = join83(infoDir, "exclude");
63357
63357
  try {
63358
63358
  await mkdir15(infoDir, { recursive: true });
63359
63359
  let existing = "";
63360
- if (existsSync32(excludePath)) {
63360
+ if (existsSync33(excludePath)) {
63361
63361
  existing = await Bun.file(excludePath).text();
63362
63362
  }
63363
63363
  const missing = NAX_GITIGNORE_ENTRIES.filter((entry) => !existing.includes(entry));
@@ -63380,7 +63380,7 @@ ${missing.join(`
63380
63380
  }
63381
63381
  async create(projectRoot, storyId) {
63382
63382
  validateStoryId(storyId);
63383
- const worktreePath = join82(projectRoot, ".nax-wt", storyId);
63383
+ const worktreePath = join83(projectRoot, ".nax-wt", storyId);
63384
63384
  const branchName = `nax/${storyId}`;
63385
63385
  try {
63386
63386
  const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
@@ -63441,9 +63441,9 @@ ${missing.join(`
63441
63441
  projectRoot
63442
63442
  });
63443
63443
  }
63444
- const envSource = join82(projectRoot, ".env");
63445
- if (existsSync32(envSource)) {
63446
- const envTarget = join82(worktreePath, ".env");
63444
+ const envSource = join83(projectRoot, ".env");
63445
+ if (existsSync33(envSource)) {
63446
+ const envTarget = join83(worktreePath, ".env");
63447
63447
  try {
63448
63448
  symlinkSync(envSource, envTarget, "file");
63449
63449
  } catch (error48) {
@@ -63459,7 +63459,7 @@ ${missing.join(`
63459
63459
  }
63460
63460
  async remove(projectRoot, storyId) {
63461
63461
  validateStoryId(storyId);
63462
- const worktreePath = join82(projectRoot, ".nax-wt", storyId);
63462
+ const worktreePath = join83(projectRoot, ".nax-wt", storyId);
63463
63463
  const branchName = `nax/${storyId}`;
63464
63464
  try {
63465
63465
  const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -64306,10 +64306,10 @@ var init_merge_conflict_rectify = __esm(() => {
64306
64306
  });
64307
64307
 
64308
64308
  // src/execution/pipeline-result-handler.ts
64309
- import { join as join83 } from "path";
64309
+ import { join as join84 } from "path";
64310
64310
  async function removeWorktreeDirectory(projectRoot, storyId) {
64311
64311
  const logger = getSafeLogger();
64312
- const worktreePath = join83(projectRoot, ".nax-wt", storyId);
64312
+ const worktreePath = join84(projectRoot, ".nax-wt", storyId);
64313
64313
  try {
64314
64314
  const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
64315
64315
  cwd: projectRoot,
@@ -64525,8 +64525,8 @@ var init_pipeline_result_handler = __esm(() => {
64525
64525
  });
64526
64526
 
64527
64527
  // src/execution/iteration-runner.ts
64528
- import { existsSync as existsSync33 } from "fs";
64529
- import { join as join84 } from "path";
64528
+ import { existsSync as existsSync34 } from "fs";
64529
+ import { join as join85 } from "path";
64530
64530
  async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
64531
64531
  const { story, storiesToExecute, routing, isBatchExecution } = selection;
64532
64532
  if (ctx.dryRun) {
@@ -64551,7 +64551,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
64551
64551
  const storyStartTime = Date.now();
64552
64552
  let effectiveWorkdir = ctx.workdir;
64553
64553
  if (ctx.config.execution.storyIsolation === "worktree") {
64554
- const worktreePath = join84(ctx.workdir, ".nax-wt", story.id);
64554
+ const worktreePath = join85(ctx.workdir, ".nax-wt", story.id);
64555
64555
  const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
64556
64556
  if (!worktreeExists) {
64557
64557
  await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
@@ -64571,7 +64571,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
64571
64571
  }
64572
64572
  const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
64573
64573
  const profileOverride = profileOverrideFromConfig(ctx.config);
64574
- const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join84(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
64574
+ const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join85(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
64575
64575
  let dependencyContext;
64576
64576
  if (ctx.config.execution.storyIsolation === "worktree") {
64577
64577
  try {
@@ -64598,7 +64598,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
64598
64598
  };
64599
64599
  }
64600
64600
  }
64601
- const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join84(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join84(ctx.workdir, story.workdir) : ctx.workdir;
64601
+ const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join85(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join85(ctx.workdir, story.workdir) : ctx.workdir;
64602
64602
  const pipelineContext = {
64603
64603
  config: effectiveConfig,
64604
64604
  rootConfig: ctx.config,
@@ -64731,7 +64731,7 @@ var init_iteration_runner = __esm(() => {
64731
64731
  loadConfigForWorkdir,
64732
64732
  prepareWorktreeDependencies,
64733
64733
  runPipeline,
64734
- existsSync: existsSync33,
64734
+ existsSync: existsSync34,
64735
64735
  worktreeManager: new WorktreeManager
64736
64736
  };
64737
64737
  });
@@ -64801,7 +64801,7 @@ __export(exports_parallel_worker, {
64801
64801
  buildWorktreePipelineContext: () => buildWorktreePipelineContext,
64802
64802
  _parallelWorkerDeps: () => _parallelWorkerDeps
64803
64803
  });
64804
- import { join as join85 } from "path";
64804
+ import { join as join86 } from "path";
64805
64805
  function buildWorktreePipelineContext(base, _story) {
64806
64806
  return { ...base, prd: structuredClone(base.prd) };
64807
64807
  }
@@ -64824,7 +64824,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
64824
64824
  story,
64825
64825
  stories: [story],
64826
64826
  projectDir: context.projectDir,
64827
- workdir: dependencyContext.cwd ?? (story.workdir ? join85(worktreePath, story.workdir) : worktreePath),
64827
+ workdir: dependencyContext.cwd ?? (story.workdir ? join86(worktreePath, story.workdir) : worktreePath),
64828
64828
  worktreeDependencyContext: dependencyContext,
64829
64829
  routing,
64830
64830
  storyGitRef: storyGitRef ?? undefined
@@ -65724,7 +65724,7 @@ async function writeStatusFile(filePath, status) {
65724
65724
  var init_status_file = () => {};
65725
65725
 
65726
65726
  // src/execution/status-writer.ts
65727
- import { join as join86 } from "path";
65727
+ import { join as join87 } from "path";
65728
65728
 
65729
65729
  class StatusWriter {
65730
65730
  statusFile;
@@ -65843,7 +65843,7 @@ class StatusWriter {
65843
65843
  if (!this._prd)
65844
65844
  return;
65845
65845
  const safeLogger = getSafeLogger();
65846
- const featureStatusPath = join86(featureDir, "status.json");
65846
+ const featureStatusPath = join87(featureDir, "status.json");
65847
65847
  const write = async () => {
65848
65848
  try {
65849
65849
  const base = this.getSnapshot(totalCost, iterations);
@@ -65874,11 +65874,11 @@ __export(exports_migrate, {
65874
65874
  migrateCommand: () => migrateCommand,
65875
65875
  detectGeneratedContent: () => detectGeneratedContent
65876
65876
  });
65877
- import { existsSync as existsSync34 } from "fs";
65877
+ import { existsSync as existsSync35 } from "fs";
65878
65878
  import { mkdir as mkdir16, readdir as readdir5, rename as rename3 } from "fs/promises";
65879
65879
  import path25 from "path";
65880
65880
  async function detectGeneratedContent(naxDir) {
65881
- if (!existsSync34(naxDir))
65881
+ if (!existsSync35(naxDir))
65882
65882
  return [];
65883
65883
  const candidates = [];
65884
65884
  let entries = [];
@@ -65893,7 +65893,7 @@ async function detectGeneratedContent(naxDir) {
65893
65893
  }
65894
65894
  }
65895
65895
  const featuresDir = path25.join(naxDir, "features");
65896
- if (existsSync34(featuresDir)) {
65896
+ if (existsSync35(featuresDir)) {
65897
65897
  let featureDirs = [];
65898
65898
  try {
65899
65899
  featureDirs = await readdir5(featuresDir);
@@ -65955,7 +65955,7 @@ async function migrateCommand(options) {
65955
65955
  });
65956
65956
  }
65957
65957
  const src = path25.join(globalConfigDir(), options.reclaim);
65958
- if (!existsSync34(src)) {
65958
+ if (!existsSync35(src)) {
65959
65959
  throw new NaxError(`Nothing to reclaim: ~/.nax/${options.reclaim} does not exist`, "MIGRATE_RECLAIM_NOT_FOUND", {
65960
65960
  stage: "migrate",
65961
65961
  name: options.reclaim
@@ -66001,7 +66001,7 @@ async function migrateCommand(options) {
66001
66001
  }
66002
66002
  const naxDir = path25.join(options.workdir, ".nax");
66003
66003
  const configPath = path25.join(naxDir, "config.json");
66004
- if (!existsSync34(configPath)) {
66004
+ if (!existsSync35(configPath)) {
66005
66005
  throw new NaxError("No .nax/config.json found \u2014 run nax init first", "MIGRATE_NO_CONFIG", {
66006
66006
  stage: "migrate",
66007
66007
  workdir: options.workdir
@@ -66036,7 +66036,7 @@ async function migrateCommand(options) {
66036
66036
  for (const candidate of candidates) {
66037
66037
  const dest = path25.join(destBase, candidate.name);
66038
66038
  await mkdir16(path25.dirname(dest), { recursive: true });
66039
- if (existsSync34(dest)) {
66039
+ if (existsSync35(dest)) {
66040
66040
  throw new NaxError(`Migration conflict: destination already exists.
66041
66041
  Source: ${candidate.srcPath}
66042
66042
  Destination: ${dest}
@@ -66287,7 +66287,7 @@ __export(exports_run_initialization, {
66287
66287
  initializeRun: () => initializeRun,
66288
66288
  _reconcileDeps: () => _reconcileDeps
66289
66289
  });
66290
- import { join as join87 } from "path";
66290
+ import { join as join88 } from "path";
66291
66291
  async function reconcileState(prd, prdPath, workdir, config2) {
66292
66292
  const logger = getSafeLogger();
66293
66293
  let reconciledCount = 0;
@@ -66304,7 +66304,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
66304
66304
  });
66305
66305
  continue;
66306
66306
  }
66307
- const effectiveWorkdir = story.workdir ? join87(workdir, story.workdir) : workdir;
66307
+ const effectiveWorkdir = story.workdir ? join88(workdir, story.workdir) : workdir;
66308
66308
  try {
66309
66309
  const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
66310
66310
  if (!reviewResult.success) {
@@ -96135,7 +96135,7 @@ __export(exports_curator, {
96135
96135
  });
96136
96136
  import { readdirSync as readdirSync9 } from "fs";
96137
96137
  import { unlink as unlink4 } from "fs/promises";
96138
- import { basename as basename15, join as join89 } from "path";
96138
+ import { basename as basename15, join as join90 } from "path";
96139
96139
  function getProjectKey(config2, projectDir) {
96140
96140
  return config2.name?.trim() || basename15(projectDir);
96141
96141
  }
@@ -96218,7 +96218,7 @@ async function curatorStatus(options) {
96218
96218
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
96219
96219
  const projectKey = getProjectKey(config2, resolved.projectDir);
96220
96220
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
96221
- const runsDir = join89(outputDir, "runs");
96221
+ const runsDir = join90(outputDir, "runs");
96222
96222
  const runIds = listRunIds(runsDir);
96223
96223
  let runId;
96224
96224
  if (options.run) {
@@ -96235,8 +96235,8 @@ async function curatorStatus(options) {
96235
96235
  runId = runIds[runIds.length - 1];
96236
96236
  }
96237
96237
  console.log(`Run: ${runId}`);
96238
- const runDir = join89(runsDir, runId);
96239
- const observationsPath = join89(runDir, "observations.jsonl");
96238
+ const runDir = join90(runsDir, runId);
96239
+ const observationsPath = join90(runDir, "observations.jsonl");
96240
96240
  const observations = await parseObservations(observationsPath);
96241
96241
  const counts = new Map;
96242
96242
  for (const obs of observations) {
@@ -96246,7 +96246,7 @@ async function curatorStatus(options) {
96246
96246
  for (const [kind, count] of counts.entries()) {
96247
96247
  console.log(` ${kind}: ${count}`);
96248
96248
  }
96249
- const proposalsPath = join89(runDir, "curator-proposals.md");
96249
+ const proposalsPath = join90(runDir, "curator-proposals.md");
96250
96250
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
96251
96251
  if (proposalText !== null) {
96252
96252
  console.log("");
@@ -96260,8 +96260,8 @@ async function curatorCommit(options) {
96260
96260
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
96261
96261
  const projectKey = getProjectKey(config2, resolved.projectDir);
96262
96262
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
96263
- const runDir = join89(outputDir, "runs", options.runId);
96264
- const proposalsPath = join89(runDir, "curator-proposals.md");
96263
+ const runDir = join90(outputDir, "runs", options.runId);
96264
+ const proposalsPath = join90(runDir, "curator-proposals.md");
96265
96265
  const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
96266
96266
  if (proposalText === null) {
96267
96267
  console.log(`curator-proposals.md not found for run ${options.runId}.`);
@@ -96277,7 +96277,7 @@ async function curatorCommit(options) {
96277
96277
  const dropFileState = new Map;
96278
96278
  const skippedDrops = new Set;
96279
96279
  for (const drop2 of drops) {
96280
- const targetPath = join89(resolved.projectDir, drop2.canonicalFile);
96280
+ const targetPath = join90(resolved.projectDir, drop2.canonicalFile);
96281
96281
  if (!dropFileState.has(targetPath)) {
96282
96282
  const fileExists2 = await Bun.file(targetPath).exists();
96283
96283
  const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
@@ -96311,7 +96311,7 @@ async function curatorCommit(options) {
96311
96311
  if (skippedDrops.has(drop2)) {
96312
96312
  continue;
96313
96313
  }
96314
- const targetPath = join89(resolved.projectDir, drop2.canonicalFile);
96314
+ const targetPath = join90(resolved.projectDir, drop2.canonicalFile);
96315
96315
  const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
96316
96316
  const filtered = filterDropContent(existing, drop2.description);
96317
96317
  await _curatorCmdDeps.writeFile(targetPath, filtered);
@@ -96320,7 +96320,7 @@ async function curatorCommit(options) {
96320
96320
  }
96321
96321
  const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
96322
96322
  for (const add2 of adds) {
96323
- const targetPath = join89(resolved.projectDir, add2.canonicalFile);
96323
+ const targetPath = join90(resolved.projectDir, add2.canonicalFile);
96324
96324
  const content = buildAddContent(add2);
96325
96325
  await _curatorCmdDeps.appendFile(targetPath, content);
96326
96326
  modifiedFiles.add(targetPath);
@@ -96357,7 +96357,7 @@ async function curatorDryrun(options) {
96357
96357
  const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
96358
96358
  const projectKey = getProjectKey(config2, resolved.projectDir);
96359
96359
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
96360
- const runsDir = join89(outputDir, "runs");
96360
+ const runsDir = join90(outputDir, "runs");
96361
96361
  const runIds = listRunIds(runsDir);
96362
96362
  if (runIds.length === 0) {
96363
96363
  console.log("No runs found.");
@@ -96368,7 +96368,7 @@ async function curatorDryrun(options) {
96368
96368
  console.log(`Run ${options.run} not found in ${runsDir}.`);
96369
96369
  return;
96370
96370
  }
96371
- const observationsPath = join89(runsDir, runId, "observations.jsonl");
96371
+ const observationsPath = join90(runsDir, runId, "observations.jsonl");
96372
96372
  const observations = await parseObservations(observationsPath);
96373
96373
  const thresholds = getThresholds(config2);
96374
96374
  const proposals = runHeuristics(observations, thresholds);
@@ -96409,12 +96409,12 @@ async function curatorGc(options) {
96409
96409
  await _curatorCmdDeps.writeFile(rollupPath, newContent);
96410
96410
  const projectKey = getProjectKey(config2, resolved.projectDir);
96411
96411
  const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
96412
- const perRunsDir = join89(outputDir, "runs");
96412
+ const perRunsDir = join90(outputDir, "runs");
96413
96413
  for (const runId of uniqueRunIds) {
96414
96414
  if (!keepSet.has(runId)) {
96415
- const runDir = join89(perRunsDir, runId);
96416
- await _curatorCmdDeps.removeFile(join89(runDir, "observations.jsonl"));
96417
- await _curatorCmdDeps.removeFile(join89(runDir, "curator-proposals.md"));
96415
+ const runDir = join90(perRunsDir, runId);
96416
+ await _curatorCmdDeps.removeFile(join90(runDir, "observations.jsonl"));
96417
+ await _curatorCmdDeps.removeFile(join90(runDir, "curator-proposals.md"));
96418
96418
  }
96419
96419
  }
96420
96420
  console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
@@ -96457,9 +96457,9 @@ var init_curator2 = __esm(() => {
96457
96457
 
96458
96458
  // bin/nax.ts
96459
96459
  init_source();
96460
- import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
96460
+ import { existsSync as existsSync37, mkdirSync as mkdirSync7 } from "fs";
96461
96461
  import { homedir as homedir3 } from "os";
96462
- import { basename as basename16, join as join90 } from "path";
96462
+ import { basename as basename16, join as join91 } from "path";
96463
96463
 
96464
96464
  // node_modules/commander/esm.mjs
96465
96465
  var import__ = __toESM(require_commander(), 1);
@@ -98733,64 +98733,119 @@ async function resolveRunProfileOverride(opts) {
98733
98733
  }
98734
98734
  // src/cli/features-resolve.ts
98735
98735
  init_config();
98736
- import { existsSync as existsSync27, readdirSync as readdirSync6 } from "fs";
98736
+ import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
98737
+ import { join as join70, relative as relative15 } from "path";
98738
+
98739
+ // src/cli/features-acceptance.ts
98740
+ init_acceptance2();
98741
+ init_config();
98742
+ init_logger2();
98743
+ init_prd();
98744
+ import { existsSync as existsSync27 } from "fs";
98737
98745
  import { join as join69, relative as relative14 } from "path";
98746
+ async function resolveFeatureAcceptance(featureName, workdir) {
98747
+ let enabled = true;
98748
+ try {
98749
+ const naxDir = findProjectDir(workdir);
98750
+ if (!naxDir) {
98751
+ return { status: "no-prd", enabled, groups: [] };
98752
+ }
98753
+ const repoRoot = join69(naxDir, "..");
98754
+ const config2 = await loadConfig(workdir);
98755
+ enabled = config2.acceptance?.enabled ?? true;
98756
+ if (!enabled) {
98757
+ return { status: "disabled", enabled: false, groups: [] };
98758
+ }
98759
+ const prdPath = join69(naxDir, "features", featureName, "prd.json");
98760
+ if (!existsSync27(prdPath)) {
98761
+ return { status: "no-prd", enabled, groups: [] };
98762
+ }
98763
+ const prd = await loadPRD(prdPath);
98764
+ const testGroups = await groupStoriesByPackage(prd, repoRoot, featureName, config2.acceptance?.testPath, config2.project?.language);
98765
+ const groups = await Promise.all(testGroups.map(async (g) => {
98766
+ const packageDir = relative14(repoRoot, g.packageDir);
98767
+ const command = await resolveGroupCommand(repoRoot, packageDir, config2.acceptance?.command);
98768
+ return {
98769
+ packageDir,
98770
+ testPath: relative14(repoRoot, g.testPath),
98771
+ exists: await Bun.file(g.testPath).exists(),
98772
+ command,
98773
+ language: g.language
98774
+ };
98775
+ }));
98776
+ return { status: "ok", enabled, groups };
98777
+ } catch (err) {
98778
+ getSafeLogger()?.warn("acceptance", "Failed to resolve feature acceptance targets", {
98779
+ featureName,
98780
+ cause: errorMessage(err)
98781
+ });
98782
+ return { status: "no-prd", enabled, groups: [] };
98783
+ }
98784
+ }
98785
+ async function resolveGroupCommand(repoRoot, packageDir, rootCommand) {
98786
+ if (packageDir === "")
98787
+ return rootCommand;
98788
+ const override = await loadPackageOverride(repoRoot, packageDir);
98789
+ return override?.acceptance?.command ?? rootCommand;
98790
+ }
98791
+
98792
+ // src/cli/features-resolve.ts
98738
98793
  async function isNonEmptyFile(absolutePath) {
98739
- if (!existsSync27(absolutePath))
98794
+ if (!existsSync28(absolutePath))
98740
98795
  return false;
98741
98796
  const content = await Bun.file(absolutePath).text();
98742
98797
  return content.trim().length > 0;
98743
98798
  }
98744
98799
  async function searchSpecSource(naxDir, repoRoot, name) {
98745
98800
  const candidates = [
98746
- { abs: join69(naxDir, "features", name, "spec.md"), kind: "markdown" },
98747
- { abs: join69(naxDir, "specs", `${name}.md`), kind: "markdown" }
98801
+ { abs: join70(naxDir, "features", name, "spec.md"), kind: "markdown" },
98802
+ { abs: join70(naxDir, "specs", `${name}.md`), kind: "markdown" }
98748
98803
  ];
98749
- const docsSpecExact = join69(repoRoot, "docs", "specs", `SPEC-${name}.md`);
98804
+ const docsSpecExact = join70(repoRoot, "docs", "specs", `SPEC-${name}.md`);
98750
98805
  candidates.push({ abs: docsSpecExact, kind: "markdown" });
98751
- const checked = candidates.map((c) => relative14(repoRoot, c.abs));
98806
+ const checked = candidates.map((c) => relative15(repoRoot, c.abs));
98752
98807
  for (const { abs, kind } of candidates.slice(0, 2)) {
98753
98808
  if (kind === "markdown") {
98754
98809
  const nonEmpty = await isNonEmptyFile(abs);
98755
98810
  if (nonEmpty) {
98756
- return { source: { kind, path: relative14(repoRoot, abs) }, checked };
98811
+ return { source: { kind, path: relative15(repoRoot, abs) }, checked };
98757
98812
  }
98758
98813
  }
98759
98814
  }
98760
98815
  if (await isNonEmptyFile(docsSpecExact)) {
98761
- return { source: { kind: "markdown", path: relative14(repoRoot, docsSpecExact) }, checked };
98816
+ return { source: { kind: "markdown", path: relative15(repoRoot, docsSpecExact) }, checked };
98762
98817
  }
98763
- const docsSpecsDir = join69(repoRoot, "docs", "specs");
98764
- if (existsSync27(docsSpecsDir)) {
98818
+ const docsSpecsDir = join70(repoRoot, "docs", "specs");
98819
+ if (existsSync28(docsSpecsDir)) {
98765
98820
  const glob = new Bun.Glob(`*${name}*.md`);
98766
98821
  for (const match of glob.scanSync({ cwd: docsSpecsDir, absolute: false })) {
98767
- const abs = join69(docsSpecsDir, match);
98822
+ const abs = join70(docsSpecsDir, match);
98768
98823
  if (await isNonEmptyFile(abs)) {
98769
- const relPath = relative14(repoRoot, abs);
98824
+ const relPath = relative15(repoRoot, abs);
98770
98825
  if (!checked.includes(relPath))
98771
98826
  checked.push(relPath);
98772
98827
  return { source: { kind: "markdown", path: relPath }, checked };
98773
98828
  }
98774
98829
  }
98775
98830
  }
98776
- const prdAbs = join69(naxDir, "features", name, "prd.json");
98777
- const prdRel = relative14(repoRoot, prdAbs);
98831
+ const prdAbs = join70(naxDir, "features", name, "prd.json");
98832
+ const prdRel = relative15(repoRoot, prdAbs);
98778
98833
  if (!checked.includes(prdRel))
98779
98834
  checked.push(prdRel);
98780
- if (existsSync27(prdAbs)) {
98835
+ if (existsSync28(prdAbs)) {
98781
98836
  return { source: { kind: "prd", path: prdRel }, checked };
98782
98837
  }
98783
98838
  return { source: null, checked };
98784
98839
  }
98785
98840
  function discoverCandidates(naxDir) {
98786
- const featuresDir = join69(naxDir, "features");
98787
- if (!existsSync27(featuresDir))
98841
+ const featuresDir = join70(naxDir, "features");
98842
+ if (!existsSync28(featuresDir))
98788
98843
  return [];
98789
98844
  return readdirSync6(featuresDir, { withFileTypes: true }).filter((e) => {
98790
98845
  if (!e.isDirectory())
98791
98846
  return false;
98792
- const dir = join69(featuresDir, e.name);
98793
- return existsSync27(join69(dir, "prd.json")) || existsSync27(join69(dir, "spec.md"));
98847
+ const dir = join70(featuresDir, e.name);
98848
+ return existsSync28(join70(dir, "prd.json")) || existsSync28(join70(dir, "spec.md"));
98794
98849
  }).map((e) => e.name).sort();
98795
98850
  }
98796
98851
  async function resolveFeatureSpec(name, workdir) {
@@ -98801,10 +98856,10 @@ async function resolveFeatureSpec(name, workdir) {
98801
98856
  message: `not a nax repo: no .nax/config.json found from ${workdir}`
98802
98857
  };
98803
98858
  }
98804
- const repoRoot = join69(naxDir, "..");
98859
+ const repoRoot = join70(naxDir, "..");
98805
98860
  if (name !== undefined && (name.startsWith("./") || name.startsWith("/") || name.endsWith(".md"))) {
98806
- const abs = name.startsWith("/") ? name : join69(workdir, name);
98807
- if (!existsSync27(abs)) {
98861
+ const abs = name.startsWith("/") ? name : join70(workdir, name);
98862
+ if (!existsSync28(abs)) {
98808
98863
  return {
98809
98864
  status: "missing",
98810
98865
  featureName: null,
@@ -98826,8 +98881,8 @@ async function resolveFeatureSpec(name, workdir) {
98826
98881
  return {
98827
98882
  status: "ok",
98828
98883
  featureName: null,
98829
- specSource: { kind: "markdown", path: relative14(repoRoot, abs) },
98830
- message: `resolved spec: ${relative14(repoRoot, abs)}`
98884
+ specSource: { kind: "markdown", path: relative15(repoRoot, abs) },
98885
+ message: `resolved spec: ${relative15(repoRoot, abs)}`
98831
98886
  };
98832
98887
  }
98833
98888
  if (name !== undefined && name.trim() !== "") {
@@ -98838,11 +98893,12 @@ async function resolveFeatureSpec(name, workdir) {
98838
98893
  status: "ok",
98839
98894
  featureName: name,
98840
98895
  specSource: source2,
98896
+ acceptance: await resolveFeatureAcceptance(name, workdir),
98841
98897
  message: `resolved spec: ${source2.path}`
98842
98898
  };
98843
98899
  }
98844
- const featureDir = join69(naxDir, "features", name);
98845
- if (existsSync27(featureDir)) {
98900
+ const featureDir = join70(naxDir, "features", name);
98901
+ if (existsSync28(featureDir)) {
98846
98902
  return {
98847
98903
  status: "missing",
98848
98904
  featureName: name,
@@ -98883,6 +98939,7 @@ async function resolveFeatureSpec(name, workdir) {
98883
98939
  status: "ok",
98884
98940
  featureName: onlyName,
98885
98941
  specSource: source,
98942
+ acceptance: await resolveFeatureAcceptance(onlyName, workdir),
98886
98943
  message: `resolved spec: ${source.path}`
98887
98944
  };
98888
98945
  }
@@ -98901,7 +98958,7 @@ init_logger2();
98901
98958
  init_detect2();
98902
98959
  init_workspace();
98903
98960
  init_common();
98904
- import { join as join70 } from "path";
98961
+ import { join as join71 } from "path";
98905
98962
  function resolveEffective(detected, configPatterns) {
98906
98963
  if (configPatterns !== undefined)
98907
98964
  return "config";
@@ -98986,7 +99043,7 @@ async function detectCommand(options) {
98986
99043
  const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
98987
99044
  const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
98988
99045
  const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
98989
- const pkgConfigPath = join70(workdir, ".nax", "mono", dir, "config.json");
99046
+ const pkgConfigPath = join71(workdir, ".nax", "mono", dir, "config.json");
98990
99047
  const pkgRaw = await loadRawConfig(pkgConfigPath);
98991
99048
  const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
98992
99049
  const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
@@ -99040,13 +99097,13 @@ async function detectCommand(options) {
99040
99097
  if (rootDetected.confidence === "empty") {
99041
99098
  console.log(source_default.yellow(" root: skipped (empty detection)"));
99042
99099
  } else {
99043
- const rootConfigPath = join70(workdir, ".nax", "config.json");
99100
+ const rootConfigPath = join71(workdir, ".nax", "config.json");
99044
99101
  try {
99045
99102
  const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
99046
99103
  if (status === "skipped") {
99047
99104
  console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
99048
99105
  } else {
99049
- console.log(source_default.green(` root: ${status} \u2192 ${join70(".nax", "config.json")}`));
99106
+ console.log(source_default.green(` root: ${status} \u2192 ${join71(".nax", "config.json")}`));
99050
99107
  }
99051
99108
  } catch (err) {
99052
99109
  console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
@@ -99059,13 +99116,13 @@ async function detectCommand(options) {
99059
99116
  console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
99060
99117
  continue;
99061
99118
  }
99062
- const pkgConfigPath = join70(workdir, ".nax", "mono", dir, "config.json");
99119
+ const pkgConfigPath = join71(workdir, ".nax", "mono", dir, "config.json");
99063
99120
  try {
99064
99121
  const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
99065
99122
  if (status === "skipped") {
99066
99123
  console.log(source_default.dim(` ${dir}: skipped (already set)`));
99067
99124
  } else {
99068
- console.log(source_default.green(` ${dir}: ${status} \u2192 ${join70(".nax", "mono", dir, "config.json")}`));
99125
+ console.log(source_default.green(` ${dir}: ${status} \u2192 ${join71(".nax", "mono", dir, "config.json")}`));
99069
99126
  }
99070
99127
  } catch (err) {
99071
99128
  console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
@@ -99082,20 +99139,20 @@ async function detectCommand(options) {
99082
99139
 
99083
99140
  // src/commands/logs.ts
99084
99141
  init_common();
99085
- import { existsSync as existsSync29 } from "fs";
99086
- import { join as join73 } from "path";
99142
+ import { existsSync as existsSync30 } from "fs";
99143
+ import { join as join74 } from "path";
99087
99144
 
99088
99145
  // src/commands/logs-formatter.ts
99089
99146
  init_source();
99090
99147
  init_formatter();
99091
99148
  import { readdirSync as readdirSync8 } from "fs";
99092
- import { join as join72 } from "path";
99149
+ import { join as join73 } from "path";
99093
99150
 
99094
99151
  // src/commands/logs-reader.ts
99095
99152
  init_paths3();
99096
- import { existsSync as existsSync28, readdirSync as readdirSync7 } from "fs";
99153
+ import { existsSync as existsSync29, readdirSync as readdirSync7 } from "fs";
99097
99154
  import { readdir as readdir3 } from "fs/promises";
99098
- import { join as join71 } from "path";
99155
+ import { join as join72 } from "path";
99099
99156
  var _logsReaderDeps = {
99100
99157
  getRunsDir
99101
99158
  };
@@ -99109,7 +99166,7 @@ async function resolveRunFileFromRegistry(runId) {
99109
99166
  }
99110
99167
  let matched = null;
99111
99168
  for (const entry of entries) {
99112
- const metaPath = join71(runsDir, entry, "meta.json");
99169
+ const metaPath = join72(runsDir, entry, "meta.json");
99113
99170
  try {
99114
99171
  const meta3 = await Bun.file(metaPath).json();
99115
99172
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -99121,7 +99178,7 @@ async function resolveRunFileFromRegistry(runId) {
99121
99178
  if (!matched) {
99122
99179
  throw new Error(`Run not found in registry: ${runId}`);
99123
99180
  }
99124
- if (!existsSync28(matched.eventsDir)) {
99181
+ if (!existsSync29(matched.eventsDir)) {
99125
99182
  console.log(`Log directory unavailable for run: ${runId}`);
99126
99183
  return null;
99127
99184
  }
@@ -99131,14 +99188,14 @@ async function resolveRunFileFromRegistry(runId) {
99131
99188
  return null;
99132
99189
  }
99133
99190
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
99134
- return join71(matched.eventsDir, specificFile ?? files[0]);
99191
+ return join72(matched.eventsDir, specificFile ?? files[0]);
99135
99192
  }
99136
99193
  async function selectRunFile(runsDir) {
99137
99194
  const files = readdirSync7(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
99138
99195
  if (files.length === 0) {
99139
99196
  return null;
99140
99197
  }
99141
- return join71(runsDir, files[0]);
99198
+ return join72(runsDir, files[0]);
99142
99199
  }
99143
99200
  async function extractRunSummary(filePath) {
99144
99201
  const file3 = Bun.file(filePath);
@@ -99224,7 +99281,7 @@ Runs:
99224
99281
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
99225
99282
  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"));
99226
99283
  for (const file3 of files) {
99227
- const filePath = join72(runsDir, file3);
99284
+ const filePath = join73(runsDir, file3);
99228
99285
  const summary = await extractRunSummary(filePath);
99229
99286
  const timestamp = file3.replace(".jsonl", "");
99230
99287
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -99338,7 +99395,7 @@ async function logsCommand(options) {
99338
99395
  return;
99339
99396
  }
99340
99397
  const resolved = resolveProject({ dir: options.dir });
99341
- const naxDir = join73(resolved.projectDir, ".nax");
99398
+ const naxDir = join74(resolved.projectDir, ".nax");
99342
99399
  const configPath = resolved.configPath;
99343
99400
  const configFile = Bun.file(configPath);
99344
99401
  const config2 = await configFile.json();
@@ -99346,9 +99403,9 @@ async function logsCommand(options) {
99346
99403
  if (!featureName) {
99347
99404
  throw new Error("No feature specified in config.json");
99348
99405
  }
99349
- const featureDir = join73(naxDir, "features", featureName);
99350
- const runsDir = join73(featureDir, "runs");
99351
- if (!existsSync29(runsDir)) {
99406
+ const featureDir = join74(naxDir, "features", featureName);
99407
+ const runsDir = join74(featureDir, "runs");
99408
+ if (!existsSync30(runsDir)) {
99352
99409
  throw new Error(`No runs directory found for feature: ${featureName}`);
99353
99410
  }
99354
99411
  if (options.list) {
@@ -99372,8 +99429,8 @@ init_config();
99372
99429
  init_prd();
99373
99430
  init_precheck();
99374
99431
  init_common();
99375
- import { existsSync as existsSync30 } from "fs";
99376
- import { join as join74 } from "path";
99432
+ import { existsSync as existsSync31 } from "fs";
99433
+ import { join as join75 } from "path";
99377
99434
  async function precheckCommand(options) {
99378
99435
  const resolved = resolveProject({
99379
99436
  dir: options.dir,
@@ -99395,14 +99452,14 @@ async function precheckCommand(options) {
99395
99452
  process.exit(1);
99396
99453
  }
99397
99454
  }
99398
- const naxDir = join74(resolved.projectDir, ".nax");
99399
- const featureDir = join74(naxDir, "features", featureName);
99400
- const prdPath = join74(featureDir, "prd.json");
99401
- if (!existsSync30(featureDir)) {
99455
+ const naxDir = join75(resolved.projectDir, ".nax");
99456
+ const featureDir = join75(naxDir, "features", featureName);
99457
+ const prdPath = join75(featureDir, "prd.json");
99458
+ if (!existsSync31(featureDir)) {
99402
99459
  console.error(source_default.red(`Feature not found: ${featureName}`));
99403
99460
  process.exit(1);
99404
99461
  }
99405
- if (!existsSync30(prdPath)) {
99462
+ if (!existsSync31(prdPath)) {
99406
99463
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
99407
99464
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
99408
99465
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -99420,7 +99477,7 @@ async function precheckCommand(options) {
99420
99477
  init_source();
99421
99478
  init_paths3();
99422
99479
  import { readdir as readdir4 } from "fs/promises";
99423
- import { join as join75 } from "path";
99480
+ import { join as join76 } from "path";
99424
99481
  var DEFAULT_LIMIT = 20;
99425
99482
  var _runsCmdDeps = {
99426
99483
  getRunsDir
@@ -99475,7 +99532,7 @@ async function runsCommand(options = {}) {
99475
99532
  }
99476
99533
  const rows = [];
99477
99534
  for (const entry of entries) {
99478
- const metaPath = join75(runsDir, entry, "meta.json");
99535
+ const metaPath = join76(runsDir, entry, "meta.json");
99479
99536
  let meta3;
99480
99537
  try {
99481
99538
  meta3 = await Bun.file(metaPath).json();
@@ -99552,7 +99609,7 @@ async function runsCommand(options = {}) {
99552
99609
 
99553
99610
  // src/commands/unlock.ts
99554
99611
  init_source();
99555
- import { join as join76 } from "path";
99612
+ import { join as join77 } from "path";
99556
99613
  function isProcessAlive2(pid) {
99557
99614
  try {
99558
99615
  process.kill(pid, 0);
@@ -99567,7 +99624,7 @@ function formatLockAge(ageMs) {
99567
99624
  }
99568
99625
  async function unlockCommand(options) {
99569
99626
  const workdir = options.dir ?? process.cwd();
99570
- const lockPath = join76(workdir, "nax.lock");
99627
+ const lockPath = join77(workdir, "nax.lock");
99571
99628
  const lockFile = Bun.file(lockPath);
99572
99629
  const exists = await lockFile.exists();
99573
99630
  if (!exists) {
@@ -108077,8 +108134,8 @@ Next: nax generate --package ${options.package}`));
108077
108134
  }
108078
108135
  return;
108079
108136
  }
108080
- const naxDir = join90(workdir, ".nax");
108081
- if (existsSync36(naxDir) && !options.force) {
108137
+ const naxDir = join91(workdir, ".nax");
108138
+ if (existsSync37(naxDir) && !options.force) {
108082
108139
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
108083
108140
  return;
108084
108141
  }
@@ -108106,11 +108163,11 @@ Next: nax generate --package ${options.package}`));
108106
108163
  }
108107
108164
  }
108108
108165
  }
108109
- mkdirSync7(join90(naxDir, "features"), { recursive: true });
108110
- mkdirSync7(join90(naxDir, "hooks"), { recursive: true });
108166
+ mkdirSync7(join91(naxDir, "features"), { recursive: true });
108167
+ mkdirSync7(join91(naxDir, "hooks"), { recursive: true });
108111
108168
  const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
108112
- await Bun.write(join90(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
108113
- await Bun.write(join90(naxDir, "hooks.json"), JSON.stringify({
108169
+ await Bun.write(join91(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
108170
+ await Bun.write(join91(naxDir, "hooks.json"), JSON.stringify({
108114
108171
  hooks: {
108115
108172
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
108116
108173
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -108118,12 +108175,12 @@ Next: nax generate --package ${options.package}`));
108118
108175
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
108119
108176
  }
108120
108177
  }, null, 2));
108121
- await Bun.write(join90(naxDir, ".gitignore"), `# nax temp files
108178
+ await Bun.write(join91(naxDir, ".gitignore"), `# nax temp files
108122
108179
  *.tmp
108123
108180
  .paused.json
108124
108181
  .nax-verifier-verdict.json
108125
108182
  `);
108126
- await Bun.write(join90(naxDir, "context.md"), `# Project Context
108183
+ await Bun.write(join91(naxDir, "context.md"), `# Project Context
108127
108184
 
108128
108185
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
108129
108186
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -108238,7 +108295,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
108238
108295
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
108239
108296
  process.exit(1);
108240
108297
  }
108241
- if (options.from && !existsSync36(options.from)) {
108298
+ if (options.from && !existsSync37(options.from)) {
108242
108299
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
108243
108300
  process.exit(1);
108244
108301
  }
@@ -108265,7 +108322,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
108265
108322
  const cliOverrides = {};
108266
108323
  const cliProfiles = options.profile ?? [];
108267
108324
  const profileOverride = naxDir ? await resolveRunProfileOverride({
108268
- prdPath: join90(naxDir, "features", options.feature, "prd.json"),
108325
+ prdPath: join91(naxDir, "features", options.feature, "prd.json"),
108269
108326
  projectRoot: workdir,
108270
108327
  cliProfile: cliProfiles,
108271
108328
  envProfile: process.env.NAX_PROFILE
@@ -108278,10 +108335,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
108278
108335
  console.error(source_default.red("nax not initialized. Run: nax init"));
108279
108336
  process.exit(1);
108280
108337
  }
108281
- const featureDir = join90(naxDir, "features", options.feature);
108282
- const prdPath = join90(featureDir, "prd.json");
108338
+ const featureDir = join91(naxDir, "features", options.feature);
108339
+ const prdPath = join91(featureDir, "prd.json");
108283
108340
  if (options.plan && options.from) {
108284
- if (existsSync36(prdPath) && !options.force) {
108341
+ if (existsSync37(prdPath) && !options.force) {
108285
108342
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
108286
108343
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
108287
108344
  process.exit(1);
@@ -108301,10 +108358,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
108301
108358
  }
108302
108359
  }
108303
108360
  try {
108304
- const planLogDir = join90(featureDir, "plan");
108361
+ const planLogDir = join91(featureDir, "plan");
108305
108362
  mkdirSync7(planLogDir, { recursive: true });
108306
108363
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
108307
- const planLogPath = join90(planLogDir, `${planLogId}.jsonl`);
108364
+ const planLogPath = join91(planLogDir, `${planLogId}.jsonl`);
108308
108365
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
108309
108366
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
108310
108367
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -108343,17 +108400,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
108343
108400
  process.exit(1);
108344
108401
  }
108345
108402
  }
108346
- if (!existsSync36(prdPath)) {
108403
+ if (!existsSync37(prdPath)) {
108347
108404
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
108348
108405
  process.exit(1);
108349
108406
  }
108350
108407
  resetLogger();
108351
108408
  const projectKey = config2.name?.trim() || basename16(workdir);
108352
108409
  const outputDir = projectOutputDir(projectKey, config2.outputDir);
108353
- const runsDir = join90(outputDir, "features", options.feature, "runs");
108410
+ const runsDir = join91(outputDir, "features", options.feature, "runs");
108354
108411
  mkdirSync7(runsDir, { recursive: true });
108355
108412
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
108356
- const logFilePath = join90(runsDir, `${runId}.jsonl`);
108413
+ const logFilePath = join91(runsDir, `${runId}.jsonl`);
108357
108414
  const isTTY = process.stdout.isTTY ?? false;
108358
108415
  const headlessFlag = options.headless ?? false;
108359
108416
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -108371,7 +108428,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
108371
108428
  config2.agent.default = options.agent;
108372
108429
  }
108373
108430
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
108374
- const globalNaxDir = join90(homedir3(), ".nax");
108431
+ const globalNaxDir = join91(homedir3(), ".nax");
108375
108432
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
108376
108433
  const eventEmitter = new PipelineEventEmitter;
108377
108434
  const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
@@ -108391,12 +108448,12 @@ program2.command("run").description("Run the orchestration loop for a feature").
108391
108448
  events: eventEmitter,
108392
108449
  ptyOptions: null,
108393
108450
  agentStreamEvents,
108394
- queueFilePath: join90(workdir, ".queue.txt")
108451
+ queueFilePath: join91(workdir, ".queue.txt")
108395
108452
  });
108396
108453
  } else {
108397
108454
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
108398
108455
  }
108399
- const statusFilePath = join90(outputDir, "status.json");
108456
+ const statusFilePath = join91(outputDir, "status.json");
108400
108457
  let parallel;
108401
108458
  if (options.parallel !== undefined) {
108402
108459
  parallel = Number.parseInt(options.parallel, 10);
@@ -108423,9 +108480,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
108423
108480
  skipPrecheck: options.skipPrecheck ?? false,
108424
108481
  agentStreamEvents
108425
108482
  });
108426
- const latestSymlink = join90(runsDir, "latest.jsonl");
108483
+ const latestSymlink = join91(runsDir, "latest.jsonl");
108427
108484
  try {
108428
- if (existsSync36(latestSymlink)) {
108485
+ if (existsSync37(latestSymlink)) {
108429
108486
  Bun.spawnSync(["rm", latestSymlink]);
108430
108487
  }
108431
108488
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -108520,9 +108577,9 @@ features.command("create <name>").description("Create a new feature").option("-d
108520
108577
  console.error(source_default.red("nax not initialized. Run: nax init"));
108521
108578
  process.exit(1);
108522
108579
  }
108523
- const featureDir = join90(naxDir, "features", name);
108580
+ const featureDir = join91(naxDir, "features", name);
108524
108581
  mkdirSync7(featureDir, { recursive: true });
108525
- await Bun.write(join90(featureDir, "spec.md"), `# Feature: ${name}
108582
+ await Bun.write(join91(featureDir, "spec.md"), `# Feature: ${name}
108526
108583
 
108527
108584
  ## Overview
108528
108585
 
@@ -108555,7 +108612,7 @@ features.command("create <name>").description("Create a new feature").option("-d
108555
108612
 
108556
108613
  <!-- What this feature explicitly does NOT cover. -->
108557
108614
  `);
108558
- await Bun.write(join90(featureDir, "progress.txt"), `# Progress: ${name}
108615
+ await Bun.write(join91(featureDir, "progress.txt"), `# Progress: ${name}
108559
108616
 
108560
108617
  Created: ${new Date().toISOString()}
108561
108618
 
@@ -108581,8 +108638,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
108581
108638
  console.error(source_default.red("nax not initialized."));
108582
108639
  process.exit(1);
108583
108640
  }
108584
- const featuresDir = join90(naxDir, "features");
108585
- if (!existsSync36(featuresDir)) {
108641
+ const featuresDir = join91(naxDir, "features");
108642
+ if (!existsSync37(featuresDir)) {
108586
108643
  console.log(source_default.dim("No features yet."));
108587
108644
  return;
108588
108645
  }
@@ -108596,8 +108653,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
108596
108653
  Features:
108597
108654
  `));
108598
108655
  for (const name of entries) {
108599
- const prdPath = join90(featuresDir, name, "prd.json");
108600
- if (existsSync36(prdPath)) {
108656
+ const prdPath = join91(featuresDir, name, "prd.json");
108657
+ if (existsSync37(prdPath)) {
108601
108658
  const prd = await loadPRD(prdPath);
108602
108659
  const c = countStories(prd);
108603
108660
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -108669,10 +108726,10 @@ Use: nax plan -f <feature> --from <spec>`));
108669
108726
  cliOverrides.profile = cliProfiles;
108670
108727
  }
108671
108728
  const config2 = await loadConfig(workdir, cliOverrides);
108672
- const featureLogDir = join90(naxDir, "features", options.feature, "plan");
108729
+ const featureLogDir = join91(naxDir, "features", options.feature, "plan");
108673
108730
  mkdirSync7(featureLogDir, { recursive: true });
108674
108731
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
108675
- const planLogPath = join90(featureLogDir, `${planLogId}.jsonl`);
108732
+ const planLogPath = join91(featureLogDir, `${planLogId}.jsonl`);
108676
108733
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
108677
108734
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
108678
108735
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.70.5",
3
+ "version": "0.70.6",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {