@nathapp/nax 0.48.0 → 0.48.2

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.
package/dist/nax.js CHANGED
@@ -19241,7 +19241,7 @@ class SpawnAcpSession {
19241
19241
  } catch {}
19242
19242
  this.activeProc = null;
19243
19243
  }
19244
- const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
19244
+ const cmd = ["acpx", "--cwd", this.cwd, this.agentName, "sessions", "close", this.sessionName];
19245
19245
  getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
19246
19246
  const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
19247
19247
  const exitCode = await proc.exited;
@@ -22210,7 +22210,7 @@ var package_default;
22210
22210
  var init_package = __esm(() => {
22211
22211
  package_default = {
22212
22212
  name: "@nathapp/nax",
22213
- version: "0.48.0",
22213
+ version: "0.48.2",
22214
22214
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22215
22215
  type: "module",
22216
22216
  bin: {
@@ -22283,8 +22283,8 @@ var init_version = __esm(() => {
22283
22283
  NAX_VERSION = package_default.version;
22284
22284
  NAX_COMMIT = (() => {
22285
22285
  try {
22286
- if (/^[0-9a-f]{6,10}$/.test("3188738"))
22287
- return "3188738";
22286
+ if (/^[0-9a-f]{6,10}$/.test("c1ac720"))
22287
+ return "c1ac720";
22288
22288
  } catch {}
22289
22289
  try {
22290
22290
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -29366,6 +29366,7 @@ var init_regression2 = __esm(() => {
29366
29366
  });
29367
29367
 
29368
29368
  // src/pipeline/stages/routing.ts
29369
+ import { join as join25 } from "path";
29369
29370
  async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
29370
29371
  const naxDecompose = config2.decompose;
29371
29372
  const builderConfig = {
@@ -29439,11 +29440,13 @@ var init_routing2 = __esm(() => {
29439
29440
  }
29440
29441
  const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
29441
29442
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
29442
- const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, ctx.workdir);
29443
+ const greenfieldScanDir = ctx.story.workdir ? join25(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29444
+ const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
29443
29445
  if (isGreenfield) {
29444
29446
  logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
29445
29447
  storyId: ctx.story.id,
29446
- originalStrategy: routing.testStrategy
29448
+ originalStrategy: routing.testStrategy,
29449
+ scanDir: greenfieldScanDir
29447
29450
  });
29448
29451
  routing.testStrategy = "test-after";
29449
29452
  routing.reasoning = `${routing.reasoning} [GREENFIELD OVERRIDE: No test files exist, using test-after instead of TDD]`;
@@ -29534,7 +29537,7 @@ var init_crash_detector = __esm(() => {
29534
29537
  });
29535
29538
 
29536
29539
  // src/pipeline/stages/verify.ts
29537
- import { join as join25 } from "path";
29540
+ import { join as join26 } from "path";
29538
29541
  function coerceSmartTestRunner(val) {
29539
29542
  if (val === undefined || val === true)
29540
29543
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -29566,7 +29569,7 @@ var init_verify = __esm(() => {
29566
29569
  skipReason: () => "not needed (full-suite gate already passed)",
29567
29570
  async execute(ctx) {
29568
29571
  const logger = getLogger();
29569
- const effectiveConfig = ctx.story.workdir ? await _verifyDeps.loadConfigForWorkdir(join25(ctx.workdir, "nax", "config.json"), ctx.story.workdir) : ctx.config;
29572
+ const effectiveConfig = ctx.story.workdir ? await _verifyDeps.loadConfigForWorkdir(join26(ctx.workdir, "nax", "config.json"), ctx.story.workdir) : ctx.config;
29570
29573
  if (!effectiveConfig.quality.requireTests) {
29571
29574
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
29572
29575
  return { action: "continue" };
@@ -29578,7 +29581,7 @@ var init_verify = __esm(() => {
29578
29581
  return { action: "continue" };
29579
29582
  }
29580
29583
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
29581
- const effectiveWorkdir = ctx.story.workdir ? join25(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29584
+ const effectiveWorkdir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29582
29585
  let effectiveCommand = testCommand;
29583
29586
  let isFullSuite = true;
29584
29587
  const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
@@ -29752,7 +29755,7 @@ __export(exports_init_context, {
29752
29755
  });
29753
29756
  import { existsSync as existsSync20 } from "fs";
29754
29757
  import { mkdir } from "fs/promises";
29755
- import { basename, join as join29 } from "path";
29758
+ import { basename, join as join30 } from "path";
29756
29759
  async function findFiles(dir, maxFiles = 200) {
29757
29760
  try {
29758
29761
  const proc = Bun.spawnSync([
@@ -29780,7 +29783,7 @@ async function findFiles(dir, maxFiles = 200) {
29780
29783
  return [];
29781
29784
  }
29782
29785
  async function readPackageManifest(projectRoot) {
29783
- const packageJsonPath = join29(projectRoot, "package.json");
29786
+ const packageJsonPath = join30(projectRoot, "package.json");
29784
29787
  if (!existsSync20(packageJsonPath)) {
29785
29788
  return null;
29786
29789
  }
@@ -29798,7 +29801,7 @@ async function readPackageManifest(projectRoot) {
29798
29801
  }
29799
29802
  }
29800
29803
  async function readReadmeSnippet(projectRoot) {
29801
- const readmePath = join29(projectRoot, "README.md");
29804
+ const readmePath = join30(projectRoot, "README.md");
29802
29805
  if (!existsSync20(readmePath)) {
29803
29806
  return null;
29804
29807
  }
@@ -29816,7 +29819,7 @@ async function detectEntryPoints(projectRoot) {
29816
29819
  const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
29817
29820
  const found = [];
29818
29821
  for (const candidate of candidates) {
29819
- const path12 = join29(projectRoot, candidate);
29822
+ const path12 = join30(projectRoot, candidate);
29820
29823
  if (existsSync20(path12)) {
29821
29824
  found.push(candidate);
29822
29825
  }
@@ -29827,7 +29830,7 @@ async function detectConfigFiles(projectRoot) {
29827
29830
  const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
29828
29831
  const found = [];
29829
29832
  for (const candidate of candidates) {
29830
- const path12 = join29(projectRoot, candidate);
29833
+ const path12 = join30(projectRoot, candidate);
29831
29834
  if (existsSync20(path12)) {
29832
29835
  found.push(candidate);
29833
29836
  }
@@ -29988,9 +29991,9 @@ function generatePackageContextTemplate(packagePath) {
29988
29991
  }
29989
29992
  async function initPackage(repoRoot, packagePath, force = false) {
29990
29993
  const logger = getLogger();
29991
- const packageDir = join29(repoRoot, packagePath);
29992
- const naxDir = join29(packageDir, "nax");
29993
- const contextPath = join29(naxDir, "context.md");
29994
+ const packageDir = join30(repoRoot, packagePath);
29995
+ const naxDir = join30(packageDir, "nax");
29996
+ const contextPath = join30(naxDir, "context.md");
29994
29997
  if (existsSync20(contextPath) && !force) {
29995
29998
  logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
29996
29999
  return;
@@ -30004,8 +30007,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
30004
30007
  }
30005
30008
  async function initContext(projectRoot, options = {}) {
30006
30009
  const logger = getLogger();
30007
- const naxDir = join29(projectRoot, "nax");
30008
- const contextPath = join29(naxDir, "context.md");
30010
+ const naxDir = join30(projectRoot, "nax");
30011
+ const contextPath = join30(naxDir, "context.md");
30009
30012
  if (existsSync20(contextPath) && !options.force) {
30010
30013
  logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
30011
30014
  return;
@@ -31312,19 +31315,19 @@ var init_precheck = __esm(() => {
31312
31315
  });
31313
31316
 
31314
31317
  // src/hooks/runner.ts
31315
- import { join as join42 } from "path";
31318
+ import { join as join43 } from "path";
31316
31319
  async function loadHooksConfig(projectDir, globalDir) {
31317
31320
  let globalHooks = { hooks: {} };
31318
31321
  let projectHooks = { hooks: {} };
31319
31322
  let skipGlobal = false;
31320
- const projectPath = join42(projectDir, "hooks.json");
31323
+ const projectPath = join43(projectDir, "hooks.json");
31321
31324
  const projectData = await loadJsonFile(projectPath, "hooks");
31322
31325
  if (projectData) {
31323
31326
  projectHooks = projectData;
31324
31327
  skipGlobal = projectData.skipGlobal ?? false;
31325
31328
  }
31326
31329
  if (!skipGlobal && globalDir) {
31327
- const globalPath = join42(globalDir, "hooks.json");
31330
+ const globalPath = join43(globalDir, "hooks.json");
31328
31331
  const globalData = await loadJsonFile(globalPath, "hooks");
31329
31332
  if (globalData) {
31330
31333
  globalHooks = globalData;
@@ -32347,12 +32350,12 @@ __export(exports_manager, {
32347
32350
  WorktreeManager: () => WorktreeManager
32348
32351
  });
32349
32352
  import { existsSync as existsSync32, symlinkSync } from "fs";
32350
- import { join as join43 } from "path";
32353
+ import { join as join44 } from "path";
32351
32354
 
32352
32355
  class WorktreeManager {
32353
32356
  async create(projectRoot, storyId) {
32354
32357
  validateStoryId(storyId);
32355
- const worktreePath = join43(projectRoot, ".nax-wt", storyId);
32358
+ const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32356
32359
  const branchName = `nax/${storyId}`;
32357
32360
  try {
32358
32361
  const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
@@ -32377,9 +32380,9 @@ class WorktreeManager {
32377
32380
  }
32378
32381
  throw new Error(`Failed to create worktree: ${String(error48)}`);
32379
32382
  }
32380
- const nodeModulesSource = join43(projectRoot, "node_modules");
32383
+ const nodeModulesSource = join44(projectRoot, "node_modules");
32381
32384
  if (existsSync32(nodeModulesSource)) {
32382
- const nodeModulesTarget = join43(worktreePath, "node_modules");
32385
+ const nodeModulesTarget = join44(worktreePath, "node_modules");
32383
32386
  try {
32384
32387
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
32385
32388
  } catch (error48) {
@@ -32387,9 +32390,9 @@ class WorktreeManager {
32387
32390
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
32388
32391
  }
32389
32392
  }
32390
- const envSource = join43(projectRoot, ".env");
32393
+ const envSource = join44(projectRoot, ".env");
32391
32394
  if (existsSync32(envSource)) {
32392
- const envTarget = join43(worktreePath, ".env");
32395
+ const envTarget = join44(worktreePath, ".env");
32393
32396
  try {
32394
32397
  symlinkSync(envSource, envTarget, "file");
32395
32398
  } catch (error48) {
@@ -32400,7 +32403,7 @@ class WorktreeManager {
32400
32403
  }
32401
32404
  async remove(projectRoot, storyId) {
32402
32405
  validateStoryId(storyId);
32403
- const worktreePath = join43(projectRoot, ".nax-wt", storyId);
32406
+ const worktreePath = join44(projectRoot, ".nax-wt", storyId);
32404
32407
  const branchName = `nax/${storyId}`;
32405
32408
  try {
32406
32409
  const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -32790,7 +32793,7 @@ var init_parallel_worker = __esm(() => {
32790
32793
 
32791
32794
  // src/execution/parallel-coordinator.ts
32792
32795
  import os3 from "os";
32793
- import { join as join44 } from "path";
32796
+ import { join as join45 } from "path";
32794
32797
  function groupStoriesByDependencies(stories) {
32795
32798
  const batches = [];
32796
32799
  const processed = new Set;
@@ -32868,7 +32871,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32868
32871
  };
32869
32872
  const worktreePaths = new Map;
32870
32873
  for (const story of batch) {
32871
- const worktreePath = join44(projectRoot, ".nax-wt", story.id);
32874
+ const worktreePath = join45(projectRoot, ".nax-wt", story.id);
32872
32875
  try {
32873
32876
  await worktreeManager.create(projectRoot, story.id);
32874
32877
  worktreePaths.set(story.id, worktreePath);
@@ -32917,7 +32920,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32917
32920
  });
32918
32921
  logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
32919
32922
  storyId: mergeResult.storyId,
32920
- worktreePath: join44(projectRoot, ".nax-wt", mergeResult.storyId)
32923
+ worktreePath: join45(projectRoot, ".nax-wt", mergeResult.storyId)
32921
32924
  });
32922
32925
  }
32923
32926
  }
@@ -33376,12 +33379,12 @@ var init_parallel_executor = __esm(() => {
33376
33379
  // src/pipeline/subscribers/events-writer.ts
33377
33380
  import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
33378
33381
  import { homedir as homedir7 } from "os";
33379
- import { basename as basename4, join as join45 } from "path";
33382
+ import { basename as basename4, join as join46 } from "path";
33380
33383
  function wireEventsWriter(bus, feature, runId, workdir) {
33381
33384
  const logger = getSafeLogger();
33382
33385
  const project = basename4(workdir);
33383
- const eventsDir = join45(homedir7(), ".nax", "events", project);
33384
- const eventsFile = join45(eventsDir, "events.jsonl");
33386
+ const eventsDir = join46(homedir7(), ".nax", "events", project);
33387
+ const eventsFile = join46(eventsDir, "events.jsonl");
33385
33388
  let dirReady = false;
33386
33389
  const write = (line) => {
33387
33390
  (async () => {
@@ -33541,12 +33544,12 @@ var init_interaction2 = __esm(() => {
33541
33544
  // src/pipeline/subscribers/registry.ts
33542
33545
  import { mkdir as mkdir3, writeFile } from "fs/promises";
33543
33546
  import { homedir as homedir8 } from "os";
33544
- import { basename as basename5, join as join46 } from "path";
33547
+ import { basename as basename5, join as join47 } from "path";
33545
33548
  function wireRegistry(bus, feature, runId, workdir) {
33546
33549
  const logger = getSafeLogger();
33547
33550
  const project = basename5(workdir);
33548
- const runDir = join46(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33549
- const metaFile = join46(runDir, "meta.json");
33551
+ const runDir = join47(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33552
+ const metaFile = join47(runDir, "meta.json");
33550
33553
  const unsub = bus.on("run:started", (_ev) => {
33551
33554
  (async () => {
33552
33555
  try {
@@ -33556,8 +33559,8 @@ function wireRegistry(bus, feature, runId, workdir) {
33556
33559
  project,
33557
33560
  feature,
33558
33561
  workdir,
33559
- statusPath: join46(workdir, "nax", "features", feature, "status.json"),
33560
- eventsDir: join46(workdir, "nax", "features", feature, "runs"),
33562
+ statusPath: join47(workdir, "nax", "features", feature, "status.json"),
33563
+ eventsDir: join47(workdir, "nax", "features", feature, "runs"),
33561
33564
  registeredAt: new Date().toISOString()
33562
33565
  };
33563
33566
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -34553,7 +34556,7 @@ async function writeStatusFile(filePath, status) {
34553
34556
  var init_status_file = () => {};
34554
34557
 
34555
34558
  // src/execution/status-writer.ts
34556
- import { join as join47 } from "path";
34559
+ import { join as join48 } from "path";
34557
34560
 
34558
34561
  class StatusWriter {
34559
34562
  statusFile;
@@ -34621,7 +34624,7 @@ class StatusWriter {
34621
34624
  if (!this._prd)
34622
34625
  return;
34623
34626
  const safeLogger = getSafeLogger();
34624
- const featureStatusPath = join47(featureDir, "status.json");
34627
+ const featureStatusPath = join48(featureDir, "status.json");
34625
34628
  try {
34626
34629
  const base = this.getSnapshot(totalCost, iterations);
34627
34630
  if (!base) {
@@ -65946,7 +65949,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
65946
65949
  init_source();
65947
65950
  import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
65948
65951
  import { homedir as homedir10 } from "os";
65949
- import { join as join48 } from "path";
65952
+ import { join as join49 } from "path";
65950
65953
 
65951
65954
  // node_modules/commander/esm.mjs
65952
65955
  var import__ = __toESM(require_commander(), 1);
@@ -68006,7 +68009,7 @@ async function runsShowCommand(options) {
68006
68009
  // src/cli/prompts-main.ts
68007
68010
  init_logger2();
68008
68011
  import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
68009
- import { join as join27 } from "path";
68012
+ import { join as join28 } from "path";
68010
68013
 
68011
68014
  // src/pipeline/index.ts
68012
68015
  init_runner();
@@ -68042,7 +68045,7 @@ init_prd();
68042
68045
 
68043
68046
  // src/cli/prompts-tdd.ts
68044
68047
  init_prompts2();
68045
- import { join as join26 } from "path";
68048
+ import { join as join27 } from "path";
68046
68049
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
68047
68050
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
68048
68051
  PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
@@ -68061,7 +68064,7 @@ ${frontmatter}---
68061
68064
 
68062
68065
  ${session.prompt}`;
68063
68066
  if (outputDir) {
68064
- const promptFile = join26(outputDir, `${story.id}.${session.role}.md`);
68067
+ const promptFile = join27(outputDir, `${story.id}.${session.role}.md`);
68065
68068
  await Bun.write(promptFile, fullOutput);
68066
68069
  logger.info("cli", "Written TDD prompt file", {
68067
68070
  storyId: story.id,
@@ -68077,7 +68080,7 @@ ${"=".repeat(80)}`);
68077
68080
  }
68078
68081
  }
68079
68082
  if (outputDir && ctx.contextMarkdown) {
68080
- const contextFile = join26(outputDir, `${story.id}.context.md`);
68083
+ const contextFile = join27(outputDir, `${story.id}.context.md`);
68081
68084
  const frontmatter = buildFrontmatter(story, ctx);
68082
68085
  const contextOutput = `---
68083
68086
  ${frontmatter}---
@@ -68091,12 +68094,12 @@ ${ctx.contextMarkdown}`;
68091
68094
  async function promptsCommand(options) {
68092
68095
  const logger = getLogger();
68093
68096
  const { feature, workdir, config: config2, storyId, outputDir } = options;
68094
- const naxDir = join27(workdir, "nax");
68097
+ const naxDir = join28(workdir, "nax");
68095
68098
  if (!existsSync18(naxDir)) {
68096
68099
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
68097
68100
  }
68098
- const featureDir = join27(naxDir, "features", feature);
68099
- const prdPath = join27(featureDir, "prd.json");
68101
+ const featureDir = join28(naxDir, "features", feature);
68102
+ const prdPath = join28(featureDir, "prd.json");
68100
68103
  if (!existsSync18(prdPath)) {
68101
68104
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
68102
68105
  }
@@ -68156,10 +68159,10 @@ ${frontmatter}---
68156
68159
 
68157
68160
  ${ctx.prompt}`;
68158
68161
  if (outputDir) {
68159
- const promptFile = join27(outputDir, `${story.id}.prompt.md`);
68162
+ const promptFile = join28(outputDir, `${story.id}.prompt.md`);
68160
68163
  await Bun.write(promptFile, fullOutput);
68161
68164
  if (ctx.contextMarkdown) {
68162
- const contextFile = join27(outputDir, `${story.id}.context.md`);
68165
+ const contextFile = join28(outputDir, `${story.id}.context.md`);
68163
68166
  const contextOutput = `---
68164
68167
  ${frontmatter}---
68165
68168
 
@@ -68223,7 +68226,7 @@ function buildFrontmatter(story, ctx, role) {
68223
68226
  }
68224
68227
  // src/cli/prompts-init.ts
68225
68228
  import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
68226
- import { join as join28 } from "path";
68229
+ import { join as join29 } from "path";
68227
68230
  var TEMPLATE_ROLES = [
68228
68231
  { file: "test-writer.md", role: "test-writer" },
68229
68232
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -68247,9 +68250,9 @@ var TEMPLATE_HEADER = `<!--
68247
68250
  `;
68248
68251
  async function promptsInitCommand(options) {
68249
68252
  const { workdir, force = false, autoWireConfig = true } = options;
68250
- const templatesDir = join28(workdir, "nax", "templates");
68253
+ const templatesDir = join29(workdir, "nax", "templates");
68251
68254
  mkdirSync4(templatesDir, { recursive: true });
68252
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join28(templatesDir, f)));
68255
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join29(templatesDir, f)));
68253
68256
  if (existingFiles.length > 0 && !force) {
68254
68257
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
68255
68258
  Pass --force to overwrite existing templates.`);
@@ -68257,7 +68260,7 @@ async function promptsInitCommand(options) {
68257
68260
  }
68258
68261
  const written = [];
68259
68262
  for (const template of TEMPLATE_ROLES) {
68260
- const filePath = join28(templatesDir, template.file);
68263
+ const filePath = join29(templatesDir, template.file);
68261
68264
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
68262
68265
  const content = TEMPLATE_HEADER + roleBody;
68263
68266
  await Bun.write(filePath, content);
@@ -68273,7 +68276,7 @@ async function promptsInitCommand(options) {
68273
68276
  return written;
68274
68277
  }
68275
68278
  async function autoWirePromptsConfig(workdir) {
68276
- const configPath = join28(workdir, "nax.config.json");
68279
+ const configPath = join29(workdir, "nax.config.json");
68277
68280
  if (!existsSync19(configPath)) {
68278
68281
  const exampleConfig = JSON.stringify({
68279
68282
  prompts: {
@@ -68438,7 +68441,7 @@ init_config();
68438
68441
  init_logger2();
68439
68442
  init_prd();
68440
68443
  import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
68441
- import { join as join32 } from "path";
68444
+ import { join as join33 } from "path";
68442
68445
 
68443
68446
  // src/cli/diagnose-analysis.ts
68444
68447
  function detectFailurePattern(story, prd, status) {
@@ -68637,7 +68640,7 @@ function isProcessAlive2(pid) {
68637
68640
  }
68638
68641
  }
68639
68642
  async function loadStatusFile2(workdir) {
68640
- const statusPath = join32(workdir, "nax", "status.json");
68643
+ const statusPath = join33(workdir, "nax", "status.json");
68641
68644
  if (!existsSync21(statusPath))
68642
68645
  return null;
68643
68646
  try {
@@ -68665,7 +68668,7 @@ async function countCommitsSince(workdir, since) {
68665
68668
  }
68666
68669
  }
68667
68670
  async function checkLock(workdir) {
68668
- const lockFile = Bun.file(join32(workdir, "nax.lock"));
68671
+ const lockFile = Bun.file(join33(workdir, "nax.lock"));
68669
68672
  if (!await lockFile.exists())
68670
68673
  return { lockPresent: false };
68671
68674
  try {
@@ -68683,8 +68686,8 @@ async function diagnoseCommand(options = {}) {
68683
68686
  const logger = getLogger();
68684
68687
  const workdir = options.workdir ?? process.cwd();
68685
68688
  const naxSubdir = findProjectDir(workdir);
68686
- let projectDir = naxSubdir ? join32(naxSubdir, "..") : null;
68687
- if (!projectDir && existsSync21(join32(workdir, "nax"))) {
68689
+ let projectDir = naxSubdir ? join33(naxSubdir, "..") : null;
68690
+ if (!projectDir && existsSync21(join33(workdir, "nax"))) {
68688
68691
  projectDir = workdir;
68689
68692
  }
68690
68693
  if (!projectDir)
@@ -68695,7 +68698,7 @@ async function diagnoseCommand(options = {}) {
68695
68698
  if (status2) {
68696
68699
  feature = status2.run.feature;
68697
68700
  } else {
68698
- const featuresDir = join32(projectDir, "nax", "features");
68701
+ const featuresDir = join33(projectDir, "nax", "features");
68699
68702
  if (!existsSync21(featuresDir))
68700
68703
  throw new Error("No features found in project");
68701
68704
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
@@ -68705,8 +68708,8 @@ async function diagnoseCommand(options = {}) {
68705
68708
  logger.info("diagnose", "No feature specified, using first found", { feature });
68706
68709
  }
68707
68710
  }
68708
- const featureDir = join32(projectDir, "nax", "features", feature);
68709
- const prdPath = join32(featureDir, "prd.json");
68711
+ const featureDir = join33(projectDir, "nax", "features", feature);
68712
+ const prdPath = join33(featureDir, "prd.json");
68710
68713
  if (!existsSync21(prdPath))
68711
68714
  throw new Error(`Feature not found: ${feature}`);
68712
68715
  const prd = await loadPRD(prdPath);
@@ -68749,7 +68752,7 @@ init_interaction();
68749
68752
  init_source();
68750
68753
  init_loader2();
68751
68754
  import { existsSync as existsSync22 } from "fs";
68752
- import { join as join33 } from "path";
68755
+ import { join as join34 } from "path";
68753
68756
  var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68754
68757
  async function generateCommand(options) {
68755
68758
  const workdir = process.cwd();
@@ -68790,7 +68793,7 @@ async function generateCommand(options) {
68790
68793
  return;
68791
68794
  }
68792
68795
  if (options.package) {
68793
- const packageDir = join33(workdir, options.package);
68796
+ const packageDir = join34(workdir, options.package);
68794
68797
  if (dryRun) {
68795
68798
  console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
68796
68799
  }
@@ -68804,8 +68807,8 @@ async function generateCommand(options) {
68804
68807
  console.log(source_default.green(`\u2713 ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
68805
68808
  return;
68806
68809
  }
68807
- const contextPath = options.context ? join33(workdir, options.context) : join33(workdir, "nax/context.md");
68808
- const outputDir = options.output ? join33(workdir, options.output) : workdir;
68810
+ const contextPath = options.context ? join34(workdir, options.context) : join34(workdir, "nax/context.md");
68811
+ const outputDir = options.output ? join34(workdir, options.output) : workdir;
68809
68812
  const autoInject = !options.noAutoInject;
68810
68813
  if (!existsSync22(contextPath)) {
68811
68814
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
@@ -68843,7 +68846,13 @@ async function generateCommand(options) {
68843
68846
  const suffix = dryRun ? " (dry run)" : "";
68844
68847
  console.log(source_default.green(`\u2713 ${agent} \u2192 ${result.outputFile} (${result.content.length} bytes${suffix})`));
68845
68848
  } else {
68846
- const configAgents = config2?.generate?.agents;
68849
+ let configAgents = config2?.generate?.agents;
68850
+ const misplacedAgents = config2?.autoMode?.generate;
68851
+ if (!configAgents && misplacedAgents?.agents && misplacedAgents.agents.length > 0) {
68852
+ console.warn(source_default.yellow('\u26A0 Warning: "generate.agents" is nested under "autoMode" in your config \u2014 it should be at the top level.'));
68853
+ console.warn(source_default.yellow(' Move it to: { "generate": { "agents": [...] } }'));
68854
+ configAgents = misplacedAgents.agents;
68855
+ }
68847
68856
  const agentFilter = configAgents && configAgents.length > 0 ? configAgents : null;
68848
68857
  if (agentFilter) {
68849
68858
  console.log(source_default.blue(`\u2192 Generating configs for: ${agentFilter.join(", ")} (from config)...`));
@@ -68867,6 +68876,28 @@ async function generateCommand(options) {
68867
68876
  \u2717 ${errorCount} generation(s) failed`));
68868
68877
  process.exit(1);
68869
68878
  }
68879
+ const packages = await discoverPackages(workdir);
68880
+ if (packages.length > 0) {
68881
+ console.log(source_default.blue(`
68882
+ \u2192 Discovered ${packages.length} package(s) with nax/context.md \u2014 generating CLAUDE.md...`));
68883
+ let pkgErrorCount = 0;
68884
+ for (const pkgDir of packages) {
68885
+ const result = await generateForPackage(pkgDir, config2, dryRun);
68886
+ if (result.error) {
68887
+ console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
68888
+ pkgErrorCount++;
68889
+ } else {
68890
+ const suffix = dryRun ? " (dry run)" : "";
68891
+ const rel = pkgDir.startsWith(workdir) ? pkgDir.slice(workdir.length + 1) : pkgDir;
68892
+ console.log(source_default.green(`\u2713 ${rel}/${result.outputFile} (${result.content.length} bytes${suffix})`));
68893
+ }
68894
+ }
68895
+ if (pkgErrorCount > 0) {
68896
+ console.error(source_default.red(`
68897
+ \u2717 ${pkgErrorCount} package generation(s) failed`));
68898
+ process.exit(1);
68899
+ }
68900
+ }
68870
68901
  }
68871
68902
  if (!dryRun) {
68872
68903
  console.log(source_default.green(`
@@ -68881,7 +68912,7 @@ async function generateCommand(options) {
68881
68912
  // src/cli/config-display.ts
68882
68913
  init_loader2();
68883
68914
  import { existsSync as existsSync24 } from "fs";
68884
- import { join as join35 } from "path";
68915
+ import { join as join36 } from "path";
68885
68916
 
68886
68917
  // src/cli/config-descriptions.ts
68887
68918
  var FIELD_DESCRIPTIONS = {
@@ -69090,7 +69121,7 @@ function deepEqual(a, b) {
69090
69121
  init_defaults();
69091
69122
  init_loader2();
69092
69123
  import { existsSync as existsSync23 } from "fs";
69093
- import { join as join34 } from "path";
69124
+ import { join as join35 } from "path";
69094
69125
  async function loadConfigFile(path14) {
69095
69126
  if (!existsSync23(path14))
69096
69127
  return null;
@@ -69112,7 +69143,7 @@ async function loadProjectConfig() {
69112
69143
  const projectDir = findProjectDir();
69113
69144
  if (!projectDir)
69114
69145
  return null;
69115
- const projectPath = join34(projectDir, "config.json");
69146
+ const projectPath = join35(projectDir, "config.json");
69116
69147
  return await loadConfigFile(projectPath);
69117
69148
  }
69118
69149
 
@@ -69172,7 +69203,7 @@ async function configCommand(config2, options = {}) {
69172
69203
  function determineConfigSources() {
69173
69204
  const globalPath = globalConfigPath();
69174
69205
  const projectDir = findProjectDir();
69175
- const projectPath = projectDir ? join35(projectDir, "config.json") : null;
69206
+ const projectPath = projectDir ? join36(projectDir, "config.json") : null;
69176
69207
  return {
69177
69208
  global: fileExists(globalPath) ? globalPath : null,
69178
69209
  project: projectPath && fileExists(projectPath) ? projectPath : null
@@ -69352,21 +69383,21 @@ async function diagnose(options) {
69352
69383
 
69353
69384
  // src/commands/logs.ts
69354
69385
  import { existsSync as existsSync26 } from "fs";
69355
- import { join as join38 } from "path";
69386
+ import { join as join39 } from "path";
69356
69387
 
69357
69388
  // src/commands/logs-formatter.ts
69358
69389
  init_source();
69359
69390
  init_formatter();
69360
69391
  import { readdirSync as readdirSync7 } from "fs";
69361
- import { join as join37 } from "path";
69392
+ import { join as join38 } from "path";
69362
69393
 
69363
69394
  // src/commands/logs-reader.ts
69364
69395
  import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
69365
69396
  import { readdir as readdir3 } from "fs/promises";
69366
69397
  import { homedir as homedir5 } from "os";
69367
- import { join as join36 } from "path";
69398
+ import { join as join37 } from "path";
69368
69399
  var _deps7 = {
69369
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join36(homedir5(), ".nax", "runs")
69400
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join37(homedir5(), ".nax", "runs")
69370
69401
  };
69371
69402
  async function resolveRunFileFromRegistry(runId) {
69372
69403
  const runsDir = _deps7.getRunsDir();
@@ -69378,7 +69409,7 @@ async function resolveRunFileFromRegistry(runId) {
69378
69409
  }
69379
69410
  let matched = null;
69380
69411
  for (const entry of entries) {
69381
- const metaPath = join36(runsDir, entry, "meta.json");
69412
+ const metaPath = join37(runsDir, entry, "meta.json");
69382
69413
  try {
69383
69414
  const meta3 = await Bun.file(metaPath).json();
69384
69415
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -69400,14 +69431,14 @@ async function resolveRunFileFromRegistry(runId) {
69400
69431
  return null;
69401
69432
  }
69402
69433
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
69403
- return join36(matched.eventsDir, specificFile ?? files[0]);
69434
+ return join37(matched.eventsDir, specificFile ?? files[0]);
69404
69435
  }
69405
69436
  async function selectRunFile(runsDir) {
69406
69437
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
69407
69438
  if (files.length === 0) {
69408
69439
  return null;
69409
69440
  }
69410
- return join36(runsDir, files[0]);
69441
+ return join37(runsDir, files[0]);
69411
69442
  }
69412
69443
  async function extractRunSummary(filePath) {
69413
69444
  const file2 = Bun.file(filePath);
@@ -69492,7 +69523,7 @@ Runs:
69492
69523
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
69493
69524
  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"));
69494
69525
  for (const file2 of files) {
69495
- const filePath = join37(runsDir, file2);
69526
+ const filePath = join38(runsDir, file2);
69496
69527
  const summary = await extractRunSummary(filePath);
69497
69528
  const timestamp = file2.replace(".jsonl", "");
69498
69529
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -69617,7 +69648,7 @@ async function logsCommand(options) {
69617
69648
  return;
69618
69649
  }
69619
69650
  const resolved = resolveProject({ dir: options.dir });
69620
- const naxDir = join38(resolved.projectDir, "nax");
69651
+ const naxDir = join39(resolved.projectDir, "nax");
69621
69652
  const configPath = resolved.configPath;
69622
69653
  const configFile = Bun.file(configPath);
69623
69654
  const config2 = await configFile.json();
@@ -69625,8 +69656,8 @@ async function logsCommand(options) {
69625
69656
  if (!featureName) {
69626
69657
  throw new Error("No feature specified in config.json");
69627
69658
  }
69628
- const featureDir = join38(naxDir, "features", featureName);
69629
- const runsDir = join38(featureDir, "runs");
69659
+ const featureDir = join39(naxDir, "features", featureName);
69660
+ const runsDir = join39(featureDir, "runs");
69630
69661
  if (!existsSync26(runsDir)) {
69631
69662
  throw new Error(`No runs directory found for feature: ${featureName}`);
69632
69663
  }
@@ -69651,7 +69682,7 @@ init_config();
69651
69682
  init_prd();
69652
69683
  init_precheck();
69653
69684
  import { existsSync as existsSync31 } from "fs";
69654
- import { join as join39 } from "path";
69685
+ import { join as join40 } from "path";
69655
69686
  async function precheckCommand(options) {
69656
69687
  const resolved = resolveProject({
69657
69688
  dir: options.dir,
@@ -69667,9 +69698,9 @@ async function precheckCommand(options) {
69667
69698
  process.exit(1);
69668
69699
  }
69669
69700
  }
69670
- const naxDir = join39(resolved.projectDir, "nax");
69671
- const featureDir = join39(naxDir, "features", featureName);
69672
- const prdPath = join39(featureDir, "prd.json");
69701
+ const naxDir = join40(resolved.projectDir, "nax");
69702
+ const featureDir = join40(naxDir, "features", featureName);
69703
+ const prdPath = join40(featureDir, "prd.json");
69673
69704
  if (!existsSync31(featureDir)) {
69674
69705
  console.error(source_default.red(`Feature not found: ${featureName}`));
69675
69706
  process.exit(1);
@@ -69693,10 +69724,10 @@ async function precheckCommand(options) {
69693
69724
  init_source();
69694
69725
  import { readdir as readdir4 } from "fs/promises";
69695
69726
  import { homedir as homedir6 } from "os";
69696
- import { join as join40 } from "path";
69727
+ import { join as join41 } from "path";
69697
69728
  var DEFAULT_LIMIT = 20;
69698
69729
  var _deps9 = {
69699
- getRunsDir: () => join40(homedir6(), ".nax", "runs")
69730
+ getRunsDir: () => join41(homedir6(), ".nax", "runs")
69700
69731
  };
69701
69732
  function formatDuration3(ms) {
69702
69733
  if (ms <= 0)
@@ -69748,7 +69779,7 @@ async function runsCommand(options = {}) {
69748
69779
  }
69749
69780
  const rows = [];
69750
69781
  for (const entry of entries) {
69751
- const metaPath = join40(runsDir, entry, "meta.json");
69782
+ const metaPath = join41(runsDir, entry, "meta.json");
69752
69783
  let meta3;
69753
69784
  try {
69754
69785
  meta3 = await Bun.file(metaPath).json();
@@ -69825,7 +69856,7 @@ async function runsCommand(options = {}) {
69825
69856
 
69826
69857
  // src/commands/unlock.ts
69827
69858
  init_source();
69828
- import { join as join41 } from "path";
69859
+ import { join as join42 } from "path";
69829
69860
  function isProcessAlive3(pid) {
69830
69861
  try {
69831
69862
  process.kill(pid, 0);
@@ -69840,7 +69871,7 @@ function formatLockAge(ageMs) {
69840
69871
  }
69841
69872
  async function unlockCommand(options) {
69842
69873
  const workdir = options.dir ?? process.cwd();
69843
- const lockPath = join41(workdir, "nax.lock");
69874
+ const lockPath = join42(workdir, "nax.lock");
69844
69875
  const lockFile = Bun.file(lockPath);
69845
69876
  const exists = await lockFile.exists();
69846
69877
  if (!exists) {
@@ -77675,15 +77706,15 @@ Next: nax generate --package ${options.package}`));
77675
77706
  }
77676
77707
  return;
77677
77708
  }
77678
- const naxDir = join48(workdir, "nax");
77709
+ const naxDir = join49(workdir, "nax");
77679
77710
  if (existsSync34(naxDir) && !options.force) {
77680
77711
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
77681
77712
  return;
77682
77713
  }
77683
- mkdirSync6(join48(naxDir, "features"), { recursive: true });
77684
- mkdirSync6(join48(naxDir, "hooks"), { recursive: true });
77685
- await Bun.write(join48(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77686
- await Bun.write(join48(naxDir, "hooks.json"), JSON.stringify({
77714
+ mkdirSync6(join49(naxDir, "features"), { recursive: true });
77715
+ mkdirSync6(join49(naxDir, "hooks"), { recursive: true });
77716
+ await Bun.write(join49(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77717
+ await Bun.write(join49(naxDir, "hooks.json"), JSON.stringify({
77687
77718
  hooks: {
77688
77719
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
77689
77720
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -77691,12 +77722,12 @@ Next: nax generate --package ${options.package}`));
77691
77722
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
77692
77723
  }
77693
77724
  }, null, 2));
77694
- await Bun.write(join48(naxDir, ".gitignore"), `# nax temp files
77725
+ await Bun.write(join49(naxDir, ".gitignore"), `# nax temp files
77695
77726
  *.tmp
77696
77727
  .paused.json
77697
77728
  .nax-verifier-verdict.json
77698
77729
  `);
77699
- await Bun.write(join48(naxDir, "context.md"), `# Project Context
77730
+ await Bun.write(join49(naxDir, "context.md"), `# Project Context
77700
77731
 
77701
77732
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
77702
77733
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -77822,8 +77853,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
77822
77853
  console.error(source_default.red("nax not initialized. Run: nax init"));
77823
77854
  process.exit(1);
77824
77855
  }
77825
- const featureDir = join48(naxDir, "features", options.feature);
77826
- const prdPath = join48(featureDir, "prd.json");
77856
+ const featureDir = join49(naxDir, "features", options.feature);
77857
+ const prdPath = join49(featureDir, "prd.json");
77827
77858
  if (options.plan && options.from) {
77828
77859
  if (existsSync34(prdPath) && !options.force) {
77829
77860
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
@@ -77845,10 +77876,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77845
77876
  }
77846
77877
  }
77847
77878
  try {
77848
- const planLogDir = join48(featureDir, "plan");
77879
+ const planLogDir = join49(featureDir, "plan");
77849
77880
  mkdirSync6(planLogDir, { recursive: true });
77850
77881
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77851
- const planLogPath = join48(planLogDir, `${planLogId}.jsonl`);
77882
+ const planLogPath = join49(planLogDir, `${planLogId}.jsonl`);
77852
77883
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77853
77884
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77854
77885
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -77886,10 +77917,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77886
77917
  process.exit(1);
77887
77918
  }
77888
77919
  resetLogger();
77889
- const runsDir = join48(featureDir, "runs");
77920
+ const runsDir = join49(featureDir, "runs");
77890
77921
  mkdirSync6(runsDir, { recursive: true });
77891
77922
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77892
- const logFilePath = join48(runsDir, `${runId}.jsonl`);
77923
+ const logFilePath = join49(runsDir, `${runId}.jsonl`);
77893
77924
  const isTTY = process.stdout.isTTY ?? false;
77894
77925
  const headlessFlag = options.headless ?? false;
77895
77926
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -77905,7 +77936,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77905
77936
  config2.autoMode.defaultAgent = options.agent;
77906
77937
  }
77907
77938
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
77908
- const globalNaxDir = join48(homedir10(), ".nax");
77939
+ const globalNaxDir = join49(homedir10(), ".nax");
77909
77940
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
77910
77941
  const eventEmitter = new PipelineEventEmitter;
77911
77942
  let tuiInstance;
@@ -77928,7 +77959,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77928
77959
  } else {
77929
77960
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
77930
77961
  }
77931
- const statusFilePath = join48(workdir, "nax", "status.json");
77962
+ const statusFilePath = join49(workdir, "nax", "status.json");
77932
77963
  let parallel;
77933
77964
  if (options.parallel !== undefined) {
77934
77965
  parallel = Number.parseInt(options.parallel, 10);
@@ -77954,7 +77985,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77954
77985
  headless: useHeadless,
77955
77986
  skipPrecheck: options.skipPrecheck ?? false
77956
77987
  });
77957
- const latestSymlink = join48(runsDir, "latest.jsonl");
77988
+ const latestSymlink = join49(runsDir, "latest.jsonl");
77958
77989
  try {
77959
77990
  if (existsSync34(latestSymlink)) {
77960
77991
  Bun.spawnSync(["rm", latestSymlink]);
@@ -77992,9 +78023,9 @@ features.command("create <name>").description("Create a new feature").option("-d
77992
78023
  console.error(source_default.red("nax not initialized. Run: nax init"));
77993
78024
  process.exit(1);
77994
78025
  }
77995
- const featureDir = join48(naxDir, "features", name);
78026
+ const featureDir = join49(naxDir, "features", name);
77996
78027
  mkdirSync6(featureDir, { recursive: true });
77997
- await Bun.write(join48(featureDir, "spec.md"), `# Feature: ${name}
78028
+ await Bun.write(join49(featureDir, "spec.md"), `# Feature: ${name}
77998
78029
 
77999
78030
  ## Overview
78000
78031
 
@@ -78002,7 +78033,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78002
78033
 
78003
78034
  ## Acceptance Criteria
78004
78035
  `);
78005
- await Bun.write(join48(featureDir, "plan.md"), `# Plan: ${name}
78036
+ await Bun.write(join49(featureDir, "plan.md"), `# Plan: ${name}
78006
78037
 
78007
78038
  ## Architecture
78008
78039
 
@@ -78010,7 +78041,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78010
78041
 
78011
78042
  ## Dependencies
78012
78043
  `);
78013
- await Bun.write(join48(featureDir, "tasks.md"), `# Tasks: ${name}
78044
+ await Bun.write(join49(featureDir, "tasks.md"), `# Tasks: ${name}
78014
78045
 
78015
78046
  ## US-001: [Title]
78016
78047
 
@@ -78019,7 +78050,7 @@ features.command("create <name>").description("Create a new feature").option("-d
78019
78050
  ### Acceptance Criteria
78020
78051
  - [ ] Criterion 1
78021
78052
  `);
78022
- await Bun.write(join48(featureDir, "progress.txt"), `# Progress: ${name}
78053
+ await Bun.write(join49(featureDir, "progress.txt"), `# Progress: ${name}
78023
78054
 
78024
78055
  Created: ${new Date().toISOString()}
78025
78056
 
@@ -78047,7 +78078,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78047
78078
  console.error(source_default.red("nax not initialized."));
78048
78079
  process.exit(1);
78049
78080
  }
78050
- const featuresDir = join48(naxDir, "features");
78081
+ const featuresDir = join49(naxDir, "features");
78051
78082
  if (!existsSync34(featuresDir)) {
78052
78083
  console.log(source_default.dim("No features yet."));
78053
78084
  return;
@@ -78062,7 +78093,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
78062
78093
  Features:
78063
78094
  `));
78064
78095
  for (const name of entries) {
78065
- const prdPath = join48(featuresDir, name, "prd.json");
78096
+ const prdPath = join49(featuresDir, name, "prd.json");
78066
78097
  if (existsSync34(prdPath)) {
78067
78098
  const prd = await loadPRD(prdPath);
78068
78099
  const c = countStories(prd);
@@ -78093,10 +78124,10 @@ Use: nax plan -f <feature> --from <spec>`));
78093
78124
  process.exit(1);
78094
78125
  }
78095
78126
  const config2 = await loadConfig(workdir);
78096
- const featureLogDir = join48(naxDir, "features", options.feature, "plan");
78127
+ const featureLogDir = join49(naxDir, "features", options.feature, "plan");
78097
78128
  mkdirSync6(featureLogDir, { recursive: true });
78098
78129
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
78099
- const planLogPath = join48(featureLogDir, `${planLogId}.jsonl`);
78130
+ const planLogPath = join49(featureLogDir, `${planLogId}.jsonl`);
78100
78131
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
78101
78132
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
78102
78133
  try {
@@ -78133,7 +78164,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78133
78164
  console.error(source_default.red("nax not initialized. Run: nax init"));
78134
78165
  process.exit(1);
78135
78166
  }
78136
- const featureDir = join48(naxDir, "features", options.feature);
78167
+ const featureDir = join49(naxDir, "features", options.feature);
78137
78168
  if (!existsSync34(featureDir)) {
78138
78169
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
78139
78170
  process.exit(1);
@@ -78149,7 +78180,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
78149
78180
  specPath: options.from,
78150
78181
  reclassify: options.reclassify
78151
78182
  });
78152
- const prdPath = join48(featureDir, "prd.json");
78183
+ const prdPath = join49(featureDir, "prd.json");
78153
78184
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
78154
78185
  const c = countStories(prd);
78155
78186
  console.log(source_default.green(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.48.0",
3
+ "version": "0.48.2",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {
@@ -218,7 +218,7 @@ class SpawnAcpSession implements AcpSession {
218
218
  this.activeProc = null;
219
219
  }
220
220
 
221
- const cmd = ["acpx", this.agentName, "sessions", "close", this.sessionName];
221
+ const cmd = ["acpx", "--cwd", this.cwd, this.agentName, "sessions", "close", this.sessionName];
222
222
  getSafeLogger()?.debug("acp-adapter", `Closing session: ${this.sessionName}`);
223
223
 
224
224
  const proc = _spawnClientDeps.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
@@ -157,7 +157,24 @@ export async function generateCommand(options: GenerateCommandOptions): Promise<
157
157
  console.log(chalk.green(`✓ ${agent} → ${result.outputFile} (${result.content.length} bytes${suffix})`));
158
158
  } else {
159
159
  // No --agent flag: use config.generate.agents filter, or generate all
160
- const configAgents = config?.generate?.agents;
160
+ let configAgents = config?.generate?.agents;
161
+
162
+ // Detect misplaced generate config (autoMode.generate.agents) and warn
163
+ const misplacedAgents = (config?.autoMode as unknown as Record<string, unknown> | undefined)?.generate as
164
+ | { agents?: string[] }
165
+ | undefined;
166
+ if (!configAgents && misplacedAgents?.agents && misplacedAgents.agents.length > 0) {
167
+ console.warn(
168
+ chalk.yellow(
169
+ '⚠ Warning: "generate.agents" is nested under "autoMode" in your config — it should be at the top level.',
170
+ ),
171
+ );
172
+ console.warn(chalk.yellow(' Move it to: { "generate": { "agents": [...] } }'));
173
+ configAgents = misplacedAgents.agents as Array<
174
+ "claude" | "codex" | "opencode" | "cursor" | "windsurf" | "aider" | "gemini"
175
+ >;
176
+ }
177
+
161
178
  const agentFilter = configAgents && configAgents.length > 0 ? configAgents : null;
162
179
 
163
180
  if (agentFilter) {
@@ -187,6 +204,30 @@ export async function generateCommand(options: GenerateCommandOptions): Promise<
187
204
  console.error(chalk.red(`\n✗ ${errorCount} generation(s) failed`));
188
205
  process.exit(1);
189
206
  }
207
+
208
+ // Auto-generate per-package CLAUDE.md when packages with nax/context.md are discovered
209
+ const packages = await discoverPackages(workdir);
210
+ if (packages.length > 0) {
211
+ console.log(
212
+ chalk.blue(`\n→ Discovered ${packages.length} package(s) with nax/context.md — generating CLAUDE.md...`),
213
+ );
214
+ let pkgErrorCount = 0;
215
+ for (const pkgDir of packages) {
216
+ const result = await generateForPackage(pkgDir, config, dryRun);
217
+ if (result.error) {
218
+ console.error(chalk.red(`✗ ${pkgDir}: ${result.error}`));
219
+ pkgErrorCount++;
220
+ } else {
221
+ const suffix = dryRun ? " (dry run)" : "";
222
+ const rel = pkgDir.startsWith(workdir) ? pkgDir.slice(workdir.length + 1) : pkgDir;
223
+ console.log(chalk.green(`✓ ${rel}/${result.outputFile} (${result.content.length} bytes${suffix})`));
224
+ }
225
+ }
226
+ if (pkgErrorCount > 0) {
227
+ console.error(chalk.red(`\n✗ ${pkgErrorCount} package generation(s) failed`));
228
+ process.exit(1);
229
+ }
230
+ }
190
231
  }
191
232
 
192
233
  if (!dryRun) {
@@ -25,6 +25,7 @@
25
25
  * ```
26
26
  */
27
27
 
28
+ import { join } from "node:path";
28
29
  import { getAgent } from "../../agents/registry";
29
30
  import type { NaxConfig } from "../../config";
30
31
  import { isGreenfieldStory } from "../../context/greenfield";
@@ -140,13 +141,18 @@ export const routingStage: PipelineStage = {
140
141
  }
141
142
 
142
143
  // BUG-010: Greenfield detection — force test-after if no test files exist
144
+ // MW-011: For monorepo stories, scan the story's package workdir (story.workdir), not the
145
+ // repo root. Scanning the repo root would find tests in OTHER packages and incorrectly
146
+ // classify the story as non-greenfield even when the target package has zero tests.
143
147
  const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
144
148
  if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
145
- const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, ctx.workdir);
149
+ const greenfieldScanDir = ctx.story.workdir ? join(ctx.workdir, ctx.story.workdir) : ctx.workdir;
150
+ const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
146
151
  if (isGreenfield) {
147
152
  logger.info("routing", "Greenfield detected — forcing test-after strategy", {
148
153
  storyId: ctx.story.id,
149
154
  originalStrategy: routing.testStrategy,
155
+ scanDir: greenfieldScanDir,
150
156
  });
151
157
  routing.testStrategy = "test-after";
152
158
  routing.reasoning = `${routing.reasoning} [GREENFIELD OVERRIDE: No test files exist, using test-after instead of TDD]`;