@nathapp/nax 0.46.2 → 0.47.0

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
@@ -18812,7 +18812,7 @@ Generate a complete acceptance.test.ts file using bun:test framework. Follow the
18812
18812
  1. **One test per AC**: Each acceptance criterion maps to exactly one test
18813
18813
  2. **Test observable behavior only**: No implementation details, only user-facing behavior
18814
18814
  3. **Independent tests**: No shared state between tests
18815
- 4. **Integration-level**: Tests should be runnable without mocking (use real implementations)
18815
+ 4. **Real-implementation**: Tests should use real implementations without mocking (test observable behavior, not internal units)
18816
18816
  5. **Clear test names**: Use format "AC-N: <description>" for test names
18817
18817
  6. **Async where needed**: Use async/await for operations that may be asynchronous
18818
18818
 
@@ -20695,6 +20695,24 @@ var init_json_file = __esm(() => {
20695
20695
  init_logger2();
20696
20696
  });
20697
20697
 
20698
+ // src/config/merge.ts
20699
+ function mergePackageConfig(root, packageOverride) {
20700
+ const packageCommands = packageOverride.quality?.commands;
20701
+ if (!packageCommands) {
20702
+ return root;
20703
+ }
20704
+ return {
20705
+ ...root,
20706
+ quality: {
20707
+ ...root.quality,
20708
+ commands: {
20709
+ ...root.quality.commands,
20710
+ ...packageCommands
20711
+ }
20712
+ }
20713
+ };
20714
+ }
20715
+
20698
20716
  // src/config/merger.ts
20699
20717
  function deepMergeConfig(base, override) {
20700
20718
  const result = { ...base };
@@ -20853,7 +20871,7 @@ var init_paths = () => {};
20853
20871
 
20854
20872
  // src/config/loader.ts
20855
20873
  import { existsSync as existsSync5 } from "fs";
20856
- import { join as join7, resolve as resolve5 } from "path";
20874
+ import { dirname as dirname2, join as join7, resolve as resolve5 } from "path";
20857
20875
  function globalConfigPath() {
20858
20876
  return join7(globalConfigDir(), "config.json");
20859
20877
  }
@@ -20924,6 +20942,20 @@ ${errors3.join(`
20924
20942
  }
20925
20943
  return result.data;
20926
20944
  }
20945
+ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
20946
+ const rootNaxDir = dirname2(rootConfigPath);
20947
+ const rootConfig = await loadConfig(rootNaxDir);
20948
+ if (!packageDir) {
20949
+ return rootConfig;
20950
+ }
20951
+ const repoRoot = dirname2(rootNaxDir);
20952
+ const packageConfigPath = join7(repoRoot, packageDir, "nax", "config.json");
20953
+ const packageOverride = await loadJsonFile(packageConfigPath, "config");
20954
+ if (!packageOverride) {
20955
+ return rootConfig;
20956
+ }
20957
+ return mergePackageConfig(rootConfig, packageOverride);
20958
+ }
20927
20959
  var init_loader2 = __esm(() => {
20928
20960
  init_logger2();
20929
20961
  init_json_file();
@@ -22178,7 +22210,7 @@ var package_default;
22178
22210
  var init_package = __esm(() => {
22179
22211
  package_default = {
22180
22212
  name: "@nathapp/nax",
22181
- version: "0.46.2",
22213
+ version: "0.47.0",
22182
22214
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
22183
22215
  type: "module",
22184
22216
  bin: {
@@ -22251,8 +22283,8 @@ var init_version = __esm(() => {
22251
22283
  NAX_VERSION = package_default.version;
22252
22284
  NAX_COMMIT = (() => {
22253
22285
  try {
22254
- if (/^[0-9a-f]{6,10}$/.test("506ad27"))
22255
- return "506ad27";
22286
+ if (/^[0-9a-f]{6,10}$/.test("ed0a660"))
22287
+ return "ed0a660";
22256
22288
  } catch {}
22257
22289
  try {
22258
22290
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -24444,7 +24476,7 @@ async function getChangedFiles(workdir, baseRef) {
24444
24476
  }
24445
24477
 
24446
24478
  class ReviewOrchestrator {
24447
- async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef) {
24479
+ async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix) {
24448
24480
  const logger = getSafeLogger();
24449
24481
  const builtIn = await runReview(reviewConfig, workdir, executionConfig);
24450
24482
  if (!builtIn.success) {
@@ -24459,13 +24491,14 @@ class ReviewOrchestrator {
24459
24491
  if (reviewers.length > 0) {
24460
24492
  const baseRef = storyGitRef ?? executionConfig?.storyGitRef;
24461
24493
  const changedFiles = await getChangedFiles(workdir, baseRef);
24494
+ const scopedFiles = scopePrefix ? changedFiles.filter((f) => f === scopePrefix || f.startsWith(`${scopePrefix}/`)) : changedFiles;
24462
24495
  const pluginResults = [];
24463
24496
  for (const reviewer of reviewers) {
24464
24497
  logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
24465
- changedFiles: changedFiles.length
24498
+ changedFiles: scopedFiles.length
24466
24499
  });
24467
24500
  try {
24468
- const result = await reviewer.check(workdir, changedFiles);
24501
+ const result = await reviewer.check(workdir, scopedFiles);
24469
24502
  logger?.info("review", `Plugin reviewer result: ${reviewer.name}`, {
24470
24503
  passed: result.passed,
24471
24504
  exitCode: result.exitCode,
@@ -24521,6 +24554,7 @@ __export(exports_review, {
24521
24554
  reviewStage: () => reviewStage,
24522
24555
  _reviewDeps: () => _reviewDeps
24523
24556
  });
24557
+ import { join as join17 } from "path";
24524
24558
  var reviewStage, _reviewDeps;
24525
24559
  var init_review = __esm(() => {
24526
24560
  init_triggers();
@@ -24532,7 +24566,8 @@ var init_review = __esm(() => {
24532
24566
  async execute(ctx) {
24533
24567
  const logger = getLogger();
24534
24568
  logger.info("review", "Running review phase", { storyId: ctx.story.id });
24535
- const result = await reviewOrchestrator.review(ctx.config.review, ctx.workdir, ctx.config.execution, ctx.plugins, ctx.storyGitRef);
24569
+ const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
24570
+ const result = await reviewOrchestrator.review(ctx.config.review, effectiveWorkdir, ctx.config.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
24536
24571
  ctx.reviewResult = result.builtIn;
24537
24572
  if (!result.success) {
24538
24573
  const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
@@ -24743,10 +24778,10 @@ var init_autofix = __esm(() => {
24743
24778
 
24744
24779
  // src/execution/progress.ts
24745
24780
  import { mkdirSync as mkdirSync2 } from "fs";
24746
- import { join as join15 } from "path";
24781
+ import { join as join18 } from "path";
24747
24782
  async function appendProgress(featureDir, storyId, status, message) {
24748
24783
  mkdirSync2(featureDir, { recursive: true });
24749
- const progressPath = join15(featureDir, "progress.txt");
24784
+ const progressPath = join18(featureDir, "progress.txt");
24750
24785
  const timestamp = new Date().toISOString();
24751
24786
  const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
24752
24787
  `;
@@ -24829,8 +24864,8 @@ function estimateTokens(text) {
24829
24864
  }
24830
24865
 
24831
24866
  // src/constitution/loader.ts
24832
- import { existsSync as existsSync13 } from "fs";
24833
- import { join as join16 } from "path";
24867
+ import { existsSync as existsSync15 } from "fs";
24868
+ import { join as join19 } from "path";
24834
24869
  function truncateToTokens(text, maxTokens) {
24835
24870
  const maxChars = maxTokens * 3;
24836
24871
  if (text.length <= maxChars) {
@@ -24852,8 +24887,8 @@ async function loadConstitution(projectDir, config2) {
24852
24887
  }
24853
24888
  let combinedContent = "";
24854
24889
  if (!config2.skipGlobal) {
24855
- const globalPath = join16(globalConfigDir(), config2.path);
24856
- if (existsSync13(globalPath)) {
24890
+ const globalPath = join19(globalConfigDir(), config2.path);
24891
+ if (existsSync15(globalPath)) {
24857
24892
  const validatedPath = validateFilePath(globalPath, globalConfigDir());
24858
24893
  const globalFile = Bun.file(validatedPath);
24859
24894
  const globalContent = await globalFile.text();
@@ -24862,8 +24897,8 @@ async function loadConstitution(projectDir, config2) {
24862
24897
  }
24863
24898
  }
24864
24899
  }
24865
- const projectPath = join16(projectDir, config2.path);
24866
- if (existsSync13(projectPath)) {
24900
+ const projectPath = join19(projectDir, config2.path);
24901
+ if (existsSync15(projectPath)) {
24867
24902
  const validatedPath = validateFilePath(projectPath, projectDir);
24868
24903
  const projectFile = Bun.file(validatedPath);
24869
24904
  const projectContent = await projectFile.text();
@@ -24910,7 +24945,7 @@ var init_constitution = __esm(() => {
24910
24945
  });
24911
24946
 
24912
24947
  // src/pipeline/stages/constitution.ts
24913
- import { dirname as dirname2 } from "path";
24948
+ import { dirname as dirname3 } from "path";
24914
24949
  var constitutionStage;
24915
24950
  var init_constitution2 = __esm(() => {
24916
24951
  init_constitution();
@@ -24920,7 +24955,7 @@ var init_constitution2 = __esm(() => {
24920
24955
  enabled: (ctx) => ctx.config.constitution.enabled,
24921
24956
  async execute(ctx) {
24922
24957
  const logger = getLogger();
24923
- const ngentDir = ctx.featureDir ? dirname2(dirname2(ctx.featureDir)) : `${ctx.workdir}/nax`;
24958
+ const ngentDir = ctx.featureDir ? dirname3(dirname3(ctx.featureDir)) : `${ctx.workdir}/nax`;
24924
24959
  const result = await loadConstitution(ngentDir, ctx.config.constitution);
24925
24960
  if (result) {
24926
24961
  ctx.constitution = result;
@@ -25680,7 +25715,14 @@ function hookCtx(feature, opts) {
25680
25715
  ...opts
25681
25716
  };
25682
25717
  }
25683
- async function buildStoryContextFull(prd, story, config2) {
25718
+ async function loadPackageContextMd(packageWorkdir) {
25719
+ const contextPath = `${packageWorkdir}/nax/context.md`;
25720
+ const file2 = Bun.file(contextPath);
25721
+ if (!await file2.exists())
25722
+ return null;
25723
+ return file2.text();
25724
+ }
25725
+ async function buildStoryContextFull(prd, story, config2, packageWorkdir) {
25684
25726
  try {
25685
25727
  const storyContext = {
25686
25728
  prd,
@@ -25694,10 +25736,22 @@ async function buildStoryContextFull(prd, story, config2) {
25694
25736
  availableForContext: CONTEXT_MAX_TOKENS - CONTEXT_RESERVED_TOKENS
25695
25737
  };
25696
25738
  const built = await buildContext(storyContext, budget);
25697
- if (built.elements.length === 0) {
25739
+ let packageSection = "";
25740
+ if (packageWorkdir) {
25741
+ const pkgContent = await loadPackageContextMd(packageWorkdir);
25742
+ if (pkgContent) {
25743
+ packageSection = `
25744
+ ---
25745
+
25746
+ ${pkgContent.trim()}`;
25747
+ }
25748
+ }
25749
+ if (built.elements.length === 0 && !packageSection) {
25698
25750
  return;
25699
25751
  }
25700
- return { markdown: formatContextAsMarkdown(built), builtContext: built };
25752
+ const baseMarkdown = built.elements.length > 0 ? formatContextAsMarkdown(built) : "";
25753
+ const markdown = packageSection ? `${baseMarkdown}${packageSection}` : baseMarkdown;
25754
+ return { markdown, builtContext: built };
25701
25755
  } catch (error48) {
25702
25756
  const logger = getSafeLogger2();
25703
25757
  logger?.warn("context", "Context builder failed", {
@@ -25815,6 +25869,7 @@ var init_helpers = __esm(() => {
25815
25869
  });
25816
25870
 
25817
25871
  // src/pipeline/stages/context.ts
25872
+ import { join as join20 } from "path";
25818
25873
  var contextStage;
25819
25874
  var init_context2 = __esm(() => {
25820
25875
  init_helpers();
@@ -25824,7 +25879,8 @@ var init_context2 = __esm(() => {
25824
25879
  enabled: () => true,
25825
25880
  async execute(ctx) {
25826
25881
  const logger = getLogger();
25827
- const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config);
25882
+ const packageWorkdir = ctx.story.workdir ? join20(ctx.workdir, ctx.story.workdir) : undefined;
25883
+ const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
25828
25884
  if (result) {
25829
25885
  ctx.contextMarkdown = result.markdown;
25830
25886
  ctx.builtContext = result.builtContext;
@@ -25955,14 +26011,14 @@ var init_isolation = __esm(() => {
25955
26011
 
25956
26012
  // src/context/greenfield.ts
25957
26013
  import { readdir } from "fs/promises";
25958
- import { join as join17 } from "path";
26014
+ import { join as join21 } from "path";
25959
26015
  async function scanForTestFiles(dir, testPattern, isRootCall = true) {
25960
26016
  const results = [];
25961
26017
  const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
25962
26018
  try {
25963
26019
  const entries = await readdir(dir, { withFileTypes: true });
25964
26020
  for (const entry of entries) {
25965
- const fullPath = join17(dir, entry.name);
26021
+ const fullPath = join21(dir, entry.name);
25966
26022
  if (entry.isDirectory()) {
25967
26023
  if (ignoreDirs.has(entry.name))
25968
26024
  continue;
@@ -26373,14 +26429,14 @@ function parseTestOutput(output, exitCode) {
26373
26429
  }
26374
26430
 
26375
26431
  // src/verification/runners.ts
26376
- import { existsSync as existsSync14 } from "fs";
26377
- import { join as join18 } from "path";
26432
+ import { existsSync as existsSync16 } from "fs";
26433
+ import { join as join22 } from "path";
26378
26434
  async function verifyAssets(workingDirectory, expectedFiles) {
26379
26435
  if (!expectedFiles || expectedFiles.length === 0)
26380
26436
  return { success: true, missingFiles: [] };
26381
26437
  const missingFiles = [];
26382
26438
  for (const file2 of expectedFiles) {
26383
- if (!existsSync14(join18(workingDirectory, file2)))
26439
+ if (!existsSync16(join22(workingDirectory, file2)))
26384
26440
  missingFiles.push(file2);
26385
26441
  }
26386
26442
  if (missingFiles.length > 0) {
@@ -27065,13 +27121,13 @@ var exports_loader = {};
27065
27121
  __export(exports_loader, {
27066
27122
  loadOverride: () => loadOverride
27067
27123
  });
27068
- import { join as join19 } from "path";
27124
+ import { join as join23 } from "path";
27069
27125
  async function loadOverride(role, workdir, config2) {
27070
27126
  const overridePath = config2.prompts?.overrides?.[role];
27071
27127
  if (!overridePath) {
27072
27128
  return null;
27073
27129
  }
27074
- const absolutePath = join19(workdir, overridePath);
27130
+ const absolutePath = join23(workdir, overridePath);
27075
27131
  const file2 = Bun.file(absolutePath);
27076
27132
  if (!await file2.exists()) {
27077
27133
  return null;
@@ -27719,8 +27775,8 @@ async function runThreeSessionTdd(options) {
27719
27775
  let hasPreExistingTests = false;
27720
27776
  try {
27721
27777
  hasPreExistingTests = !await isGreenfieldStory(story, workdir, testPatternGlob);
27722
- const { existsSync: existsSync15 } = await import("fs");
27723
- if (!existsSync15(workdir)) {
27778
+ const { existsSync: existsSync17 } = await import("fs");
27779
+ if (!existsSync17(workdir)) {
27724
27780
  hasPreExistingTests = false;
27725
27781
  }
27726
27782
  } catch {
@@ -27892,6 +27948,17 @@ var init_tdd = __esm(() => {
27892
27948
  });
27893
27949
 
27894
27950
  // src/pipeline/stages/execution.ts
27951
+ import { existsSync as existsSync17 } from "fs";
27952
+ import { join as join24 } from "path";
27953
+ function resolveStoryWorkdir(repoRoot, storyWorkdir) {
27954
+ if (!storyWorkdir)
27955
+ return repoRoot;
27956
+ const resolved = join24(repoRoot, storyWorkdir);
27957
+ if (!existsSync17(resolved)) {
27958
+ throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
27959
+ }
27960
+ return resolved;
27961
+ }
27895
27962
  function isAmbiguousOutput(output) {
27896
27963
  if (!output)
27897
27964
  return false;
@@ -27948,11 +28015,12 @@ var init_execution2 = __esm(() => {
27948
28015
  storyId: ctx.story.id,
27949
28016
  lite: isLiteMode
27950
28017
  });
28018
+ const effectiveWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
27951
28019
  const tddResult = await runThreeSessionTdd({
27952
28020
  agent,
27953
28021
  story: ctx.story,
27954
28022
  config: ctx.config,
27955
- workdir: ctx.workdir,
28023
+ workdir: effectiveWorkdir,
27956
28024
  modelTier: ctx.routing.modelTier,
27957
28025
  featureName: ctx.prd.feature,
27958
28026
  contextMarkdown: ctx.contextMarkdown,
@@ -28018,9 +28086,10 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
28018
28086
  supportedTiers: agent.capabilities.supportedTiers
28019
28087
  });
28020
28088
  }
28089
+ const storyWorkdir = _executionDeps.resolveStoryWorkdir(ctx.workdir, ctx.story.workdir);
28021
28090
  const result = await agent.run({
28022
28091
  prompt: ctx.prompt,
28023
- workdir: ctx.workdir,
28092
+ workdir: storyWorkdir,
28024
28093
  modelTier: ctx.routing.modelTier,
28025
28094
  modelDef: resolveModel(ctx.config.models[ctx.routing.modelTier]),
28026
28095
  timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
@@ -28061,7 +28130,7 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
28061
28130
  })()
28062
28131
  });
28063
28132
  ctx.agentResult = result;
28064
- await autoCommitIfDirty(ctx.workdir, "execution", "single-session", ctx.story.id);
28133
+ await autoCommitIfDirty(storyWorkdir, "execution", "single-session", ctx.story.id);
28065
28134
  const combinedOutput = (result.output ?? "") + (result.stderr ?? "");
28066
28135
  if (_executionDeps.detectMergeConflict(combinedOutput) && ctx.interaction && isTriggerEnabled("merge-conflict", ctx.config)) {
28067
28136
  const shouldProceed = await _executionDeps.checkMergeConflict({ featureName: ctx.prd.feature, storyId: ctx.story.id }, ctx.config, ctx.interaction);
@@ -28102,7 +28171,8 @@ Category: ${tddResult.failureCategory ?? "unknown"}`,
28102
28171
  detectMergeConflict,
28103
28172
  checkMergeConflict,
28104
28173
  isAmbiguousOutput,
28105
- checkStoryAmbiguity
28174
+ checkStoryAmbiguity,
28175
+ resolveStoryWorkdir
28106
28176
  };
28107
28177
  });
28108
28178
 
@@ -29021,7 +29091,7 @@ function buildSmartTestCommand(testFiles, baseCommand) {
29021
29091
  const newParts = [...beforePath, ...testFiles, ...afterPath];
29022
29092
  return newParts.join(" ");
29023
29093
  }
29024
- async function getChangedSourceFiles(workdir, baseRef) {
29094
+ async function getChangedSourceFiles(workdir, baseRef, packagePrefix) {
29025
29095
  try {
29026
29096
  const ref = baseRef ?? "HEAD~1";
29027
29097
  const { stdout, exitCode } = await gitWithTimeout(["diff", "--name-only", ref], workdir);
@@ -29029,7 +29099,8 @@ async function getChangedSourceFiles(workdir, baseRef) {
29029
29099
  return [];
29030
29100
  const lines = stdout.trim().split(`
29031
29101
  `).filter(Boolean);
29032
- return lines.filter((f) => f.startsWith("src/") && f.endsWith(".ts"));
29102
+ const srcPrefix = packagePrefix ? `${packagePrefix}/src/` : "src/";
29103
+ return lines.filter((f) => f.startsWith(srcPrefix) && f.endsWith(".ts"));
29033
29104
  } catch {
29034
29105
  return [];
29035
29106
  }
@@ -29463,6 +29534,7 @@ var init_crash_detector = __esm(() => {
29463
29534
  });
29464
29535
 
29465
29536
  // src/pipeline/stages/verify.ts
29537
+ import { join as join25 } from "path";
29466
29538
  function coerceSmartTestRunner(val) {
29467
29539
  if (val === undefined || val === true)
29468
29540
  return DEFAULT_SMART_RUNNER_CONFIG2;
@@ -29478,6 +29550,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
29478
29550
  }
29479
29551
  var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
29480
29552
  var init_verify = __esm(() => {
29553
+ init_loader2();
29481
29554
  init_logger2();
29482
29555
  init_crash_detector();
29483
29556
  init_runners();
@@ -29493,24 +29566,26 @@ var init_verify = __esm(() => {
29493
29566
  skipReason: () => "not needed (full-suite gate already passed)",
29494
29567
  async execute(ctx) {
29495
29568
  const logger = getLogger();
29496
- if (!ctx.config.quality.requireTests) {
29569
+ const effectiveConfig = ctx.story.workdir ? await _verifyDeps.loadConfigForWorkdir(join25(ctx.workdir, "nax", "config.json"), ctx.story.workdir) : ctx.config;
29570
+ if (!effectiveConfig.quality.requireTests) {
29497
29571
  logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
29498
29572
  return { action: "continue" };
29499
29573
  }
29500
- const testCommand = ctx.config.review?.commands?.test ?? ctx.config.quality.commands.test;
29501
- const testScopedTemplate = ctx.config.quality.commands.testScoped;
29574
+ const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test;
29575
+ const testScopedTemplate = effectiveConfig.quality.commands.testScoped;
29502
29576
  if (!testCommand) {
29503
29577
  logger.debug("verify", "Skipping verification (no test command configured)", { storyId: ctx.story.id });
29504
29578
  return { action: "continue" };
29505
29579
  }
29506
29580
  logger.info("verify", "Running verification", { storyId: ctx.story.id });
29581
+ const effectiveWorkdir = ctx.story.workdir ? join25(ctx.workdir, ctx.story.workdir) : ctx.workdir;
29507
29582
  let effectiveCommand = testCommand;
29508
29583
  let isFullSuite = true;
29509
29584
  const smartRunnerConfig = coerceSmartTestRunner(ctx.config.execution.smartTestRunner);
29510
29585
  const regressionMode = ctx.config.execution.regressionGate?.mode ?? "deferred";
29511
29586
  if (smartRunnerConfig.enabled) {
29512
- const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(ctx.workdir, ctx.storyGitRef);
29513
- const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, ctx.workdir);
29587
+ const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(effectiveWorkdir, ctx.storyGitRef, ctx.story.workdir);
29588
+ const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, effectiveWorkdir);
29514
29589
  if (pass1Files.length > 0) {
29515
29590
  logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
29516
29591
  storyId: ctx.story.id
@@ -29518,7 +29593,7 @@ var init_verify = __esm(() => {
29518
29593
  effectiveCommand = buildScopedCommand2(pass1Files, testCommand, testScopedTemplate);
29519
29594
  isFullSuite = false;
29520
29595
  } else if (smartRunnerConfig.fallback === "import-grep") {
29521
- const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, ctx.workdir, smartRunnerConfig.testFilePatterns);
29596
+ const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, effectiveWorkdir, smartRunnerConfig.testFilePatterns);
29522
29597
  if (pass2Files.length > 0) {
29523
29598
  logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
29524
29599
  storyId: ctx.story.id
@@ -29544,7 +29619,7 @@ var init_verify = __esm(() => {
29544
29619
  command: effectiveCommand
29545
29620
  });
29546
29621
  const result = await _verifyDeps.regression({
29547
- workdir: ctx.workdir,
29622
+ workdir: effectiveWorkdir,
29548
29623
  command: effectiveCommand,
29549
29624
  timeoutSeconds: ctx.config.execution.verificationTimeoutSeconds,
29550
29625
  acceptOnTimeout: ctx.config.execution.regressionGate?.acceptOnTimeout ?? true
@@ -29589,7 +29664,8 @@ var init_verify = __esm(() => {
29589
29664
  }
29590
29665
  };
29591
29666
  _verifyDeps = {
29592
- regression
29667
+ regression,
29668
+ loadConfigForWorkdir
29593
29669
  };
29594
29670
  });
29595
29671
 
@@ -29664,6 +29740,299 @@ var init_stages = __esm(() => {
29664
29740
  preRunPipeline = [acceptanceSetupStage];
29665
29741
  });
29666
29742
 
29743
+ // src/cli/init-context.ts
29744
+ var exports_init_context = {};
29745
+ __export(exports_init_context, {
29746
+ scanProject: () => scanProject,
29747
+ initPackage: () => initPackage,
29748
+ initContext: () => initContext,
29749
+ generatePackageContextTemplate: () => generatePackageContextTemplate,
29750
+ generateContextTemplate: () => generateContextTemplate,
29751
+ _deps: () => _deps6
29752
+ });
29753
+ import { existsSync as existsSync20 } from "fs";
29754
+ import { mkdir } from "fs/promises";
29755
+ import { basename, join as join29 } from "path";
29756
+ async function findFiles(dir, maxFiles = 200) {
29757
+ try {
29758
+ const proc = Bun.spawnSync([
29759
+ "find",
29760
+ dir,
29761
+ "-type",
29762
+ "f",
29763
+ "-not",
29764
+ "-path",
29765
+ "*/node_modules/*",
29766
+ "-not",
29767
+ "-path",
29768
+ "*/.git/*",
29769
+ "-not",
29770
+ "-path",
29771
+ "*/dist/*"
29772
+ ], { stdio: ["pipe", "pipe", "pipe"] });
29773
+ if (proc.success) {
29774
+ const output = new TextDecoder().decode(proc.stdout);
29775
+ const files = output.trim().split(`
29776
+ `).filter((f) => f.length > 0).map((f) => f.replace(`${dir}/`, "")).slice(0, maxFiles);
29777
+ return files;
29778
+ }
29779
+ } catch {}
29780
+ return [];
29781
+ }
29782
+ async function readPackageManifest(projectRoot) {
29783
+ const packageJsonPath = join29(projectRoot, "package.json");
29784
+ if (!existsSync20(packageJsonPath)) {
29785
+ return null;
29786
+ }
29787
+ try {
29788
+ const content = await Bun.file(packageJsonPath).text();
29789
+ const manifest = JSON.parse(content);
29790
+ return {
29791
+ name: manifest.name,
29792
+ description: manifest.description,
29793
+ scripts: manifest.scripts,
29794
+ dependencies: manifest.dependencies
29795
+ };
29796
+ } catch {
29797
+ return null;
29798
+ }
29799
+ }
29800
+ async function readReadmeSnippet(projectRoot) {
29801
+ const readmePath = join29(projectRoot, "README.md");
29802
+ if (!existsSync20(readmePath)) {
29803
+ return null;
29804
+ }
29805
+ try {
29806
+ const content = await Bun.file(readmePath).text();
29807
+ const lines = content.split(`
29808
+ `);
29809
+ return lines.slice(0, 100).join(`
29810
+ `);
29811
+ } catch {
29812
+ return null;
29813
+ }
29814
+ }
29815
+ async function detectEntryPoints(projectRoot) {
29816
+ const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
29817
+ const found = [];
29818
+ for (const candidate of candidates) {
29819
+ const path12 = join29(projectRoot, candidate);
29820
+ if (existsSync20(path12)) {
29821
+ found.push(candidate);
29822
+ }
29823
+ }
29824
+ return found;
29825
+ }
29826
+ async function detectConfigFiles(projectRoot) {
29827
+ const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
29828
+ const found = [];
29829
+ for (const candidate of candidates) {
29830
+ const path12 = join29(projectRoot, candidate);
29831
+ if (existsSync20(path12)) {
29832
+ found.push(candidate);
29833
+ }
29834
+ }
29835
+ return found;
29836
+ }
29837
+ async function scanProject(projectRoot) {
29838
+ const fileTree = await findFiles(projectRoot, 200);
29839
+ const packageManifest = await readPackageManifest(projectRoot);
29840
+ const readmeSnippet = await readReadmeSnippet(projectRoot);
29841
+ const entryPoints = await detectEntryPoints(projectRoot);
29842
+ const configFiles = await detectConfigFiles(projectRoot);
29843
+ const projectName = packageManifest?.name || basename(projectRoot);
29844
+ return {
29845
+ projectName,
29846
+ fileTree,
29847
+ packageManifest,
29848
+ readmeSnippet,
29849
+ entryPoints,
29850
+ configFiles
29851
+ };
29852
+ }
29853
+ function generateContextTemplate(scan) {
29854
+ const lines = [];
29855
+ lines.push(`# ${scan.projectName}
29856
+ `);
29857
+ if (scan.packageManifest?.description) {
29858
+ lines.push(`${scan.packageManifest.description}
29859
+ `);
29860
+ } else {
29861
+ lines.push(`<!-- TODO: Add project description -->
29862
+ `);
29863
+ }
29864
+ if (scan.entryPoints.length > 0) {
29865
+ lines.push(`## Entry Points
29866
+ `);
29867
+ for (const ep of scan.entryPoints) {
29868
+ lines.push(`- ${ep}`);
29869
+ }
29870
+ lines.push("");
29871
+ } else {
29872
+ lines.push(`## Entry Points
29873
+ `);
29874
+ lines.push(`<!-- TODO: Document entry points -->
29875
+ `);
29876
+ }
29877
+ if (scan.fileTree.length > 0) {
29878
+ lines.push(`## Project Structure
29879
+ `);
29880
+ lines.push("```");
29881
+ for (const file2 of scan.fileTree.slice(0, 20)) {
29882
+ lines.push(file2);
29883
+ }
29884
+ if (scan.fileTree.length > 20) {
29885
+ lines.push(`... and ${scan.fileTree.length - 20} more files`);
29886
+ }
29887
+ lines.push("```\n");
29888
+ } else {
29889
+ lines.push(`## Project Structure
29890
+ `);
29891
+ lines.push(`<!-- TODO: Document project structure -->
29892
+ `);
29893
+ }
29894
+ if (scan.configFiles.length > 0) {
29895
+ lines.push(`## Configuration Files
29896
+ `);
29897
+ for (const cf of scan.configFiles) {
29898
+ lines.push(`- ${cf}`);
29899
+ }
29900
+ lines.push("");
29901
+ } else {
29902
+ lines.push(`## Configuration Files
29903
+ `);
29904
+ lines.push(`<!-- TODO: Document configuration files -->
29905
+ `);
29906
+ }
29907
+ if (scan.packageManifest?.scripts) {
29908
+ const hasScripts = Object.keys(scan.packageManifest.scripts).length > 0;
29909
+ if (hasScripts) {
29910
+ lines.push(`## Scripts
29911
+ `);
29912
+ for (const [name, command] of Object.entries(scan.packageManifest.scripts)) {
29913
+ lines.push(`- **${name}**: \`${command}\``);
29914
+ }
29915
+ lines.push("");
29916
+ }
29917
+ }
29918
+ if (scan.packageManifest?.dependencies) {
29919
+ const deps = Object.keys(scan.packageManifest.dependencies);
29920
+ if (deps.length > 0) {
29921
+ lines.push(`## Dependencies
29922
+ `);
29923
+ lines.push(`<!-- TODO: Document key dependencies and their purpose -->
29924
+ `);
29925
+ }
29926
+ }
29927
+ lines.push(`## Development Guidelines
29928
+ `);
29929
+ lines.push(`<!-- TODO: Document development guidelines and conventions -->
29930
+ `);
29931
+ return `${lines.join(`
29932
+ `).trim()}
29933
+ `;
29934
+ }
29935
+ async function generateContextWithLLM(scan) {
29936
+ const logger = getLogger();
29937
+ const scanSummary = `
29938
+ Project: ${scan.projectName}
29939
+ Entry Points: ${scan.entryPoints.join(", ") || "None detected"}
29940
+ Config Files: ${scan.configFiles.join(", ") || "None detected"}
29941
+ Total Files: ${scan.fileTree.length}
29942
+ Description: ${scan.packageManifest?.description || "Not provided"}
29943
+ `;
29944
+ const prompt = `
29945
+ You are a technical documentation expert. Generate a concise, well-structured context.md file for a software project based on this scan:
29946
+
29947
+ ${scanSummary}
29948
+
29949
+ The context.md should include:
29950
+ 1. Project overview (name, purpose, key technologies)
29951
+ 2. Entry points and main modules
29952
+ 3. Key dependencies and why they're used
29953
+ 4. Development setup and common commands
29954
+ 5. Architecture overview (brief)
29955
+ 6. Development guidelines
29956
+
29957
+ Keep it under 2000 tokens. Use markdown formatting. Be specific to the detected stack and structure.
29958
+ `;
29959
+ try {
29960
+ const result = await _deps6.callLLM(prompt);
29961
+ logger.info("init", "Generated context.md with LLM");
29962
+ return result;
29963
+ } catch (err) {
29964
+ logger.warn("init", `LLM context generation failed, falling back to template: ${err instanceof Error ? err.message : String(err)}`);
29965
+ return generateContextTemplate(scan);
29966
+ }
29967
+ }
29968
+ function generatePackageContextTemplate(packagePath) {
29969
+ const packageName = packagePath.split("/").pop() ?? packagePath;
29970
+ return `# ${packageName} \u2014 Context
29971
+
29972
+ <!-- Package-specific conventions. Root context.md provides shared rules. -->
29973
+
29974
+ ## Tech Stack
29975
+
29976
+ <!-- TODO: Document this package's tech stack -->
29977
+
29978
+ ## Commands
29979
+
29980
+ | Command | Purpose |
29981
+ |:--------|:--------|
29982
+ | \`bun test\` | Unit tests |
29983
+
29984
+ ## Development Guidelines
29985
+
29986
+ <!-- TODO: Document package-specific guidelines -->
29987
+ `;
29988
+ }
29989
+ async function initPackage(repoRoot, packagePath, force = false) {
29990
+ const logger = getLogger();
29991
+ const packageDir = join29(repoRoot, packagePath);
29992
+ const naxDir = join29(packageDir, "nax");
29993
+ const contextPath = join29(naxDir, "context.md");
29994
+ if (existsSync20(contextPath) && !force) {
29995
+ logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
29996
+ return;
29997
+ }
29998
+ if (!existsSync20(naxDir)) {
29999
+ await mkdir(naxDir, { recursive: true });
30000
+ }
30001
+ const content = generatePackageContextTemplate(packagePath);
30002
+ await Bun.write(contextPath, content);
30003
+ logger.info("init", "Created package context.md", { path: contextPath });
30004
+ }
30005
+ async function initContext(projectRoot, options = {}) {
30006
+ const logger = getLogger();
30007
+ const naxDir = join29(projectRoot, "nax");
30008
+ const contextPath = join29(naxDir, "context.md");
30009
+ if (existsSync20(contextPath) && !options.force) {
30010
+ logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
30011
+ return;
30012
+ }
30013
+ if (!existsSync20(naxDir)) {
30014
+ await mkdir(naxDir, { recursive: true });
30015
+ }
30016
+ const scan = await scanProject(projectRoot);
30017
+ let content;
30018
+ if (options.ai) {
30019
+ content = await generateContextWithLLM(scan);
30020
+ } else {
30021
+ content = generateContextTemplate(scan);
30022
+ }
30023
+ await Bun.write(contextPath, content);
30024
+ logger.info("init", "Generated nax/context.md template from project scan", { path: contextPath });
30025
+ }
30026
+ var _deps6;
30027
+ var init_init_context = __esm(() => {
30028
+ init_logger2();
30029
+ _deps6 = {
30030
+ callLLM: async (_prompt) => {
30031
+ throw new Error("callLLM not implemented");
30032
+ }
30033
+ };
30034
+ });
30035
+
29667
30036
  // src/plugins/plugin-logger.ts
29668
30037
  function createPluginLogger(pluginName) {
29669
30038
  const stage = `plugin:${pluginName}`;
@@ -29980,11 +30349,11 @@ function getSafeLogger6() {
29980
30349
  return getSafeLogger();
29981
30350
  }
29982
30351
  function extractPluginName(pluginPath) {
29983
- const basename2 = path12.basename(pluginPath);
29984
- if (basename2 === "index.ts" || basename2 === "index.js" || basename2 === "index.mjs") {
30352
+ const basename3 = path12.basename(pluginPath);
30353
+ if (basename3 === "index.ts" || basename3 === "index.js" || basename3 === "index.mjs") {
29985
30354
  return path12.basename(path12.dirname(pluginPath));
29986
30355
  }
29987
- return basename2.replace(/\.(ts|js|mjs)$/, "");
30356
+ return basename3.replace(/\.(ts|js|mjs)$/, "");
29988
30357
  }
29989
30358
  async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
29990
30359
  const loadedPlugins = [];
@@ -30155,7 +30524,7 @@ var init_loader5 = __esm(() => {
30155
30524
  });
30156
30525
 
30157
30526
  // src/precheck/checks-git.ts
30158
- import { existsSync as existsSync25, statSync as statSync2 } from "fs";
30527
+ import { existsSync as existsSync27, statSync as statSync2 } from "fs";
30159
30528
  async function checkGitRepoExists(workdir) {
30160
30529
  const proc = Bun.spawn(["git", "rev-parse", "--git-dir"], {
30161
30530
  cwd: workdir,
@@ -30166,7 +30535,7 @@ async function checkGitRepoExists(workdir) {
30166
30535
  let passed = exitCode === 0;
30167
30536
  if (!passed) {
30168
30537
  const gitDir = `${workdir}/.git`;
30169
- if (existsSync25(gitDir)) {
30538
+ if (existsSync27(gitDir)) {
30170
30539
  const stats = statSync2(gitDir);
30171
30540
  passed = stats.isDirectory();
30172
30541
  }
@@ -30238,10 +30607,10 @@ var init_checks_git = __esm(() => {
30238
30607
  });
30239
30608
 
30240
30609
  // src/precheck/checks-config.ts
30241
- import { existsSync as existsSync26, statSync as statSync3 } from "fs";
30610
+ import { existsSync as existsSync28, statSync as statSync3 } from "fs";
30242
30611
  async function checkStaleLock(workdir) {
30243
30612
  const lockPath = `${workdir}/nax.lock`;
30244
- const exists = existsSync26(lockPath);
30613
+ const exists = existsSync28(lockPath);
30245
30614
  if (!exists) {
30246
30615
  return {
30247
30616
  name: "no-stale-lock",
@@ -30326,7 +30695,7 @@ var init_checks_config = () => {};
30326
30695
  async function checkAgentCLI(config2) {
30327
30696
  const agent = config2.execution?.agent || "claude";
30328
30697
  try {
30329
- const proc = _deps7.spawn([agent, "--version"], {
30698
+ const proc = _deps8.spawn([agent, "--version"], {
30330
30699
  stdout: "pipe",
30331
30700
  stderr: "pipe"
30332
30701
  });
@@ -30347,15 +30716,15 @@ async function checkAgentCLI(config2) {
30347
30716
  };
30348
30717
  }
30349
30718
  }
30350
- var _deps7;
30719
+ var _deps8;
30351
30720
  var init_checks_cli = __esm(() => {
30352
- _deps7 = {
30721
+ _deps8 = {
30353
30722
  spawn: Bun.spawn
30354
30723
  };
30355
30724
  });
30356
30725
 
30357
30726
  // src/precheck/checks-system.ts
30358
- import { existsSync as existsSync27, statSync as statSync4 } from "fs";
30727
+ import { existsSync as existsSync29, statSync as statSync4 } from "fs";
30359
30728
  async function checkDependenciesInstalled(workdir) {
30360
30729
  const depPaths = [
30361
30730
  { path: "node_modules" },
@@ -30367,7 +30736,7 @@ async function checkDependenciesInstalled(workdir) {
30367
30736
  const found = [];
30368
30737
  for (const { path: path14 } of depPaths) {
30369
30738
  const fullPath = `${workdir}/${path14}`;
30370
- if (existsSync27(fullPath)) {
30739
+ if (existsSync29(fullPath)) {
30371
30740
  const stats = statSync4(fullPath);
30372
30741
  if (stats.isDirectory()) {
30373
30742
  found.push(path14);
@@ -30387,102 +30756,51 @@ async function checkTestCommand(config2) {
30387
30756
  if (!testCommand || testCommand === null) {
30388
30757
  return {
30389
30758
  name: "test-command-works",
30390
- tier: "blocker",
30759
+ tier: "warning",
30391
30760
  passed: true,
30392
- message: "Test command not configured (skipped)"
30393
- };
30394
- }
30395
- const parts = testCommand.split(" ");
30396
- const [cmd, ...args] = parts;
30397
- try {
30398
- const proc = Bun.spawn([cmd, ...args, "--help"], {
30399
- stdout: "pipe",
30400
- stderr: "pipe"
30401
- });
30402
- const exitCode = await proc.exited;
30403
- const passed = exitCode === 0;
30404
- return {
30405
- name: "test-command-works",
30406
- tier: "blocker",
30407
- passed,
30408
- message: passed ? "Test command is available" : `Test command failed: ${testCommand}`
30409
- };
30410
- } catch {
30411
- return {
30412
- name: "test-command-works",
30413
- tier: "blocker",
30414
- passed: false,
30415
- message: `Test command failed: ${testCommand}`
30761
+ message: "Test command not configured (will use default: bun test)"
30416
30762
  };
30417
30763
  }
30764
+ return {
30765
+ name: "test-command-works",
30766
+ tier: "warning",
30767
+ passed: true,
30768
+ message: `Test command configured: ${testCommand}`
30769
+ };
30418
30770
  }
30419
30771
  async function checkLintCommand(config2) {
30420
30772
  const lintCommand = config2.execution.lintCommand;
30421
30773
  if (!lintCommand || lintCommand === null) {
30422
30774
  return {
30423
30775
  name: "lint-command-works",
30424
- tier: "blocker",
30776
+ tier: "warning",
30425
30777
  passed: true,
30426
30778
  message: "Lint command not configured (skipped)"
30427
30779
  };
30428
30780
  }
30429
- const parts = lintCommand.split(" ");
30430
- const [cmd, ...args] = parts;
30431
- try {
30432
- const proc = Bun.spawn([cmd, ...args, "--help"], {
30433
- stdout: "pipe",
30434
- stderr: "pipe"
30435
- });
30436
- const exitCode = await proc.exited;
30437
- const passed = exitCode === 0;
30438
- return {
30439
- name: "lint-command-works",
30440
- tier: "blocker",
30441
- passed,
30442
- message: passed ? "Lint command is available" : `Lint command failed: ${lintCommand}`
30443
- };
30444
- } catch {
30445
- return {
30446
- name: "lint-command-works",
30447
- tier: "blocker",
30448
- passed: false,
30449
- message: `Lint command failed: ${lintCommand}`
30450
- };
30451
- }
30781
+ return {
30782
+ name: "lint-command-works",
30783
+ tier: "warning",
30784
+ passed: true,
30785
+ message: `Lint command configured: ${lintCommand}`
30786
+ };
30452
30787
  }
30453
30788
  async function checkTypecheckCommand(config2) {
30454
30789
  const typecheckCommand = config2.execution.typecheckCommand;
30455
30790
  if (!typecheckCommand || typecheckCommand === null) {
30456
30791
  return {
30457
30792
  name: "typecheck-command-works",
30458
- tier: "blocker",
30793
+ tier: "warning",
30459
30794
  passed: true,
30460
30795
  message: "Typecheck command not configured (skipped)"
30461
30796
  };
30462
30797
  }
30463
- const parts = typecheckCommand.split(" ");
30464
- const [cmd, ...args] = parts;
30465
- try {
30466
- const proc = Bun.spawn([cmd, ...args, "--help"], {
30467
- stdout: "pipe",
30468
- stderr: "pipe"
30469
- });
30470
- const exitCode = await proc.exited;
30471
- const passed = exitCode === 0;
30472
- return {
30473
- name: "typecheck-command-works",
30474
- tier: "blocker",
30475
- passed,
30476
- message: passed ? `Typecheck command is available: ${typecheckCommand}` : `Typecheck command failed: ${typecheckCommand}`
30477
- };
30478
- } catch {
30479
- return {
30480
- name: "typecheck-command-works",
30481
- tier: "blocker",
30482
- passed: false,
30483
- message: `Typecheck command failed: ${typecheckCommand}`
30484
- };
30485
- }
30798
+ return {
30799
+ name: "typecheck-command-works",
30800
+ tier: "warning",
30801
+ passed: true,
30802
+ message: `Typecheck command configured: ${typecheckCommand}`
30803
+ };
30486
30804
  }
30487
30805
  var init_checks_system = () => {};
30488
30806
 
@@ -30495,11 +30813,11 @@ var init_checks_blockers = __esm(() => {
30495
30813
  });
30496
30814
 
30497
30815
  // src/precheck/checks-warnings.ts
30498
- import { existsSync as existsSync28 } from "fs";
30816
+ import { existsSync as existsSync30 } from "fs";
30499
30817
  import { isAbsolute as isAbsolute6 } from "path";
30500
30818
  async function checkClaudeMdExists(workdir) {
30501
30819
  const claudeMdPath = `${workdir}/CLAUDE.md`;
30502
- const passed = existsSync28(claudeMdPath);
30820
+ const passed = existsSync30(claudeMdPath);
30503
30821
  return {
30504
30822
  name: "claude-md-exists",
30505
30823
  tier: "warning",
@@ -30579,7 +30897,7 @@ async function hasPackageScript(workdir, name) {
30579
30897
  }
30580
30898
  async function checkGitignoreCoversNax(workdir) {
30581
30899
  const gitignorePath = `${workdir}/.gitignore`;
30582
- const exists = existsSync28(gitignorePath);
30900
+ const exists = existsSync30(gitignorePath);
30583
30901
  if (!exists) {
30584
30902
  return {
30585
30903
  name: "gitignore-covers-nax",
@@ -30614,7 +30932,7 @@ async function checkPromptOverrideFiles(config2, workdir) {
30614
30932
  const checks3 = [];
30615
30933
  for (const [role, relativePath] of Object.entries(config2.prompts.overrides)) {
30616
30934
  const resolvedPath = `${workdir}/${relativePath}`;
30617
- const exists = existsSync28(resolvedPath);
30935
+ const exists = existsSync30(resolvedPath);
30618
30936
  if (!exists) {
30619
30937
  checks3.push({
30620
30938
  name: `prompt-override-${role}`,
@@ -30994,19 +31312,19 @@ var init_precheck = __esm(() => {
30994
31312
  });
30995
31313
 
30996
31314
  // src/hooks/runner.ts
30997
- import { join as join37 } from "path";
31315
+ import { join as join42 } from "path";
30998
31316
  async function loadHooksConfig(projectDir, globalDir) {
30999
31317
  let globalHooks = { hooks: {} };
31000
31318
  let projectHooks = { hooks: {} };
31001
31319
  let skipGlobal = false;
31002
- const projectPath = join37(projectDir, "hooks.json");
31320
+ const projectPath = join42(projectDir, "hooks.json");
31003
31321
  const projectData = await loadJsonFile(projectPath, "hooks");
31004
31322
  if (projectData) {
31005
31323
  projectHooks = projectData;
31006
31324
  skipGlobal = projectData.skipGlobal ?? false;
31007
31325
  }
31008
31326
  if (!skipGlobal && globalDir) {
31009
- const globalPath = join37(globalDir, "hooks.json");
31327
+ const globalPath = join42(globalDir, "hooks.json");
31010
31328
  const globalData = await loadJsonFile(globalPath, "hooks");
31011
31329
  if (globalData) {
31012
31330
  globalHooks = globalData;
@@ -32028,13 +32346,13 @@ var exports_manager = {};
32028
32346
  __export(exports_manager, {
32029
32347
  WorktreeManager: () => WorktreeManager
32030
32348
  });
32031
- import { existsSync as existsSync30, symlinkSync } from "fs";
32032
- import { join as join38 } from "path";
32349
+ import { existsSync as existsSync32, symlinkSync } from "fs";
32350
+ import { join as join43 } from "path";
32033
32351
 
32034
32352
  class WorktreeManager {
32035
32353
  async create(projectRoot, storyId) {
32036
32354
  validateStoryId(storyId);
32037
- const worktreePath = join38(projectRoot, ".nax-wt", storyId);
32355
+ const worktreePath = join43(projectRoot, ".nax-wt", storyId);
32038
32356
  const branchName = `nax/${storyId}`;
32039
32357
  try {
32040
32358
  const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
@@ -32059,9 +32377,9 @@ class WorktreeManager {
32059
32377
  }
32060
32378
  throw new Error(`Failed to create worktree: ${String(error48)}`);
32061
32379
  }
32062
- const nodeModulesSource = join38(projectRoot, "node_modules");
32063
- if (existsSync30(nodeModulesSource)) {
32064
- const nodeModulesTarget = join38(worktreePath, "node_modules");
32380
+ const nodeModulesSource = join43(projectRoot, "node_modules");
32381
+ if (existsSync32(nodeModulesSource)) {
32382
+ const nodeModulesTarget = join43(worktreePath, "node_modules");
32065
32383
  try {
32066
32384
  symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
32067
32385
  } catch (error48) {
@@ -32069,9 +32387,9 @@ class WorktreeManager {
32069
32387
  throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
32070
32388
  }
32071
32389
  }
32072
- const envSource = join38(projectRoot, ".env");
32073
- if (existsSync30(envSource)) {
32074
- const envTarget = join38(worktreePath, ".env");
32390
+ const envSource = join43(projectRoot, ".env");
32391
+ if (existsSync32(envSource)) {
32392
+ const envTarget = join43(worktreePath, ".env");
32075
32393
  try {
32076
32394
  symlinkSync(envSource, envTarget, "file");
32077
32395
  } catch (error48) {
@@ -32082,7 +32400,7 @@ class WorktreeManager {
32082
32400
  }
32083
32401
  async remove(projectRoot, storyId) {
32084
32402
  validateStoryId(storyId);
32085
- const worktreePath = join38(projectRoot, ".nax-wt", storyId);
32403
+ const worktreePath = join43(projectRoot, ".nax-wt", storyId);
32086
32404
  const branchName = `nax/${storyId}`;
32087
32405
  try {
32088
32406
  const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
@@ -32472,7 +32790,7 @@ var init_parallel_worker = __esm(() => {
32472
32790
 
32473
32791
  // src/execution/parallel-coordinator.ts
32474
32792
  import os3 from "os";
32475
- import { join as join39 } from "path";
32793
+ import { join as join44 } from "path";
32476
32794
  function groupStoriesByDependencies(stories) {
32477
32795
  const batches = [];
32478
32796
  const processed = new Set;
@@ -32550,7 +32868,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32550
32868
  };
32551
32869
  const worktreePaths = new Map;
32552
32870
  for (const story of batch) {
32553
- const worktreePath = join39(projectRoot, ".nax-wt", story.id);
32871
+ const worktreePath = join44(projectRoot, ".nax-wt", story.id);
32554
32872
  try {
32555
32873
  await worktreeManager.create(projectRoot, story.id);
32556
32874
  worktreePaths.set(story.id, worktreePath);
@@ -32599,7 +32917,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
32599
32917
  });
32600
32918
  logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
32601
32919
  storyId: mergeResult.storyId,
32602
- worktreePath: join39(projectRoot, ".nax-wt", mergeResult.storyId)
32920
+ worktreePath: join44(projectRoot, ".nax-wt", mergeResult.storyId)
32603
32921
  });
32604
32922
  }
32605
32923
  }
@@ -33056,20 +33374,20 @@ var init_parallel_executor = __esm(() => {
33056
33374
  });
33057
33375
 
33058
33376
  // src/pipeline/subscribers/events-writer.ts
33059
- import { appendFile as appendFile2, mkdir } from "fs/promises";
33377
+ import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
33060
33378
  import { homedir as homedir7 } from "os";
33061
- import { basename as basename3, join as join40 } from "path";
33379
+ import { basename as basename4, join as join45 } from "path";
33062
33380
  function wireEventsWriter(bus, feature, runId, workdir) {
33063
33381
  const logger = getSafeLogger();
33064
- const project = basename3(workdir);
33065
- const eventsDir = join40(homedir7(), ".nax", "events", project);
33066
- const eventsFile = join40(eventsDir, "events.jsonl");
33382
+ const project = basename4(workdir);
33383
+ const eventsDir = join45(homedir7(), ".nax", "events", project);
33384
+ const eventsFile = join45(eventsDir, "events.jsonl");
33067
33385
  let dirReady = false;
33068
33386
  const write = (line) => {
33069
33387
  (async () => {
33070
33388
  try {
33071
33389
  if (!dirReady) {
33072
- await mkdir(eventsDir, { recursive: true });
33390
+ await mkdir2(eventsDir, { recursive: true });
33073
33391
  dirReady = true;
33074
33392
  }
33075
33393
  await appendFile2(eventsFile, `${JSON.stringify(line)}
@@ -33221,25 +33539,25 @@ var init_interaction2 = __esm(() => {
33221
33539
  });
33222
33540
 
33223
33541
  // src/pipeline/subscribers/registry.ts
33224
- import { mkdir as mkdir2, writeFile } from "fs/promises";
33542
+ import { mkdir as mkdir3, writeFile } from "fs/promises";
33225
33543
  import { homedir as homedir8 } from "os";
33226
- import { basename as basename4, join as join41 } from "path";
33544
+ import { basename as basename5, join as join46 } from "path";
33227
33545
  function wireRegistry(bus, feature, runId, workdir) {
33228
33546
  const logger = getSafeLogger();
33229
- const project = basename4(workdir);
33230
- const runDir = join41(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33231
- const metaFile = join41(runDir, "meta.json");
33547
+ const project = basename5(workdir);
33548
+ const runDir = join46(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
33549
+ const metaFile = join46(runDir, "meta.json");
33232
33550
  const unsub = bus.on("run:started", (_ev) => {
33233
33551
  (async () => {
33234
33552
  try {
33235
- await mkdir2(runDir, { recursive: true });
33553
+ await mkdir3(runDir, { recursive: true });
33236
33554
  const meta3 = {
33237
33555
  runId,
33238
33556
  project,
33239
33557
  feature,
33240
33558
  workdir,
33241
- statusPath: join41(workdir, "nax", "features", feature, "status.json"),
33242
- eventsDir: join41(workdir, "nax", "features", feature, "runs"),
33559
+ statusPath: join46(workdir, "nax", "features", feature, "status.json"),
33560
+ eventsDir: join46(workdir, "nax", "features", feature, "runs"),
33243
33561
  registeredAt: new Date().toISOString()
33244
33562
  };
33245
33563
  await writeFile(metaFile, JSON.stringify(meta3, null, 2));
@@ -34235,7 +34553,7 @@ async function writeStatusFile(filePath, status) {
34235
34553
  var init_status_file = () => {};
34236
34554
 
34237
34555
  // src/execution/status-writer.ts
34238
- import { join as join42 } from "path";
34556
+ import { join as join47 } from "path";
34239
34557
 
34240
34558
  class StatusWriter {
34241
34559
  statusFile;
@@ -34303,7 +34621,7 @@ class StatusWriter {
34303
34621
  if (!this._prd)
34304
34622
  return;
34305
34623
  const safeLogger = getSafeLogger();
34306
- const featureStatusPath = join42(featureDir, "status.json");
34624
+ const featureStatusPath = join47(featureDir, "status.json");
34307
34625
  try {
34308
34626
  const base = this.getSnapshot(totalCost, iterations);
34309
34627
  if (!base) {
@@ -65626,9 +65944,9 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
65626
65944
 
65627
65945
  // bin/nax.ts
65628
65946
  init_source();
65629
- import { existsSync as existsSync32, mkdirSync as mkdirSync6 } from "fs";
65947
+ import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
65630
65948
  import { homedir as homedir10 } from "os";
65631
- import { join as join43 } from "path";
65949
+ import { join as join48 } from "path";
65632
65950
 
65633
65951
  // node_modules/commander/esm.mjs
65634
65952
  var import__ = __toESM(require_commander(), 1);
@@ -66113,10 +66431,461 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
66113
66431
  }
66114
66432
  // src/cli/plan.ts
66115
66433
  init_registry();
66116
- import { existsSync as existsSync9 } from "fs";
66117
- import { join as join10 } from "path";
66434
+ import { existsSync as existsSync11 } from "fs";
66435
+ import { join as join12 } from "path";
66118
66436
  import { createInterface } from "readline";
66119
66437
  init_test_strategy();
66438
+
66439
+ // src/context/generator.ts
66440
+ init_path_security2();
66441
+ import { existsSync as existsSync10 } from "fs";
66442
+ import { join as join11 } from "path";
66443
+
66444
+ // src/context/injector.ts
66445
+ import { existsSync as existsSync9 } from "fs";
66446
+ import { join as join10 } from "path";
66447
+ var NOTABLE_NODE_DEPS = [
66448
+ "@nestjs",
66449
+ "express",
66450
+ "fastify",
66451
+ "koa",
66452
+ "hono",
66453
+ "next",
66454
+ "nuxt",
66455
+ "react",
66456
+ "vue",
66457
+ "svelte",
66458
+ "solid",
66459
+ "prisma",
66460
+ "typeorm",
66461
+ "mongoose",
66462
+ "drizzle",
66463
+ "sequelize",
66464
+ "jest",
66465
+ "vitest",
66466
+ "mocha",
66467
+ "bun",
66468
+ "zod",
66469
+ "typescript",
66470
+ "graphql",
66471
+ "trpc",
66472
+ "bull",
66473
+ "ioredis"
66474
+ ];
66475
+ async function detectNode(workdir) {
66476
+ const pkgPath = join10(workdir, "package.json");
66477
+ if (!existsSync9(pkgPath))
66478
+ return null;
66479
+ try {
66480
+ const file2 = Bun.file(pkgPath);
66481
+ const pkg = await file2.json();
66482
+ const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
66483
+ const notable = [
66484
+ ...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
66485
+ ].slice(0, 10);
66486
+ const lang = pkg.devDependencies?.typescript || pkg.dependencies?.typescript ? "TypeScript" : "JavaScript";
66487
+ return { name: pkg.name, lang, dependencies: notable };
66488
+ } catch {
66489
+ return null;
66490
+ }
66491
+ }
66492
+ async function detectGo(workdir) {
66493
+ const goMod = join10(workdir, "go.mod");
66494
+ if (!existsSync9(goMod))
66495
+ return null;
66496
+ try {
66497
+ const content = await Bun.file(goMod).text();
66498
+ const moduleMatch = content.match(/^module\s+(\S+)/m);
66499
+ const name = moduleMatch?.[1];
66500
+ const requires = [];
66501
+ const requireBlock = content.match(/require\s*\(([^)]+)\)/s)?.[1] ?? "";
66502
+ for (const line of requireBlock.split(`
66503
+ `)) {
66504
+ const trimmed = line.trim();
66505
+ if (trimmed && !trimmed.startsWith("//") && !trimmed.includes("// indirect")) {
66506
+ const dep = trimmed.split(/\s+/)[0];
66507
+ if (dep)
66508
+ requires.push(dep.split("/").slice(-1)[0]);
66509
+ }
66510
+ }
66511
+ return { name, lang: "Go", dependencies: requires.slice(0, 10) };
66512
+ } catch {
66513
+ return null;
66514
+ }
66515
+ }
66516
+ async function detectRust(workdir) {
66517
+ const cargoPath = join10(workdir, "Cargo.toml");
66518
+ if (!existsSync9(cargoPath))
66519
+ return null;
66520
+ try {
66521
+ const content = await Bun.file(cargoPath).text();
66522
+ const nameMatch = content.match(/^\[package\][^[]*name\s*=\s*"([^"]+)"/ms);
66523
+ const name = nameMatch?.[1];
66524
+ const depsSection = content.match(/^\[dependencies\]([^[]*)/ms)?.[1] ?? "";
66525
+ const deps = depsSection.split(`
66526
+ `).map((l) => l.split("=")[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
66527
+ return { name, lang: "Rust", dependencies: deps };
66528
+ } catch {
66529
+ return null;
66530
+ }
66531
+ }
66532
+ async function detectPython(workdir) {
66533
+ const pyproject = join10(workdir, "pyproject.toml");
66534
+ const requirements = join10(workdir, "requirements.txt");
66535
+ if (!existsSync9(pyproject) && !existsSync9(requirements))
66536
+ return null;
66537
+ try {
66538
+ if (existsSync9(pyproject)) {
66539
+ const content = await Bun.file(pyproject).text();
66540
+ const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
66541
+ const depsSection = content.match(/^\[project\][^[]*dependencies\s*=\s*\[([^\]]*)\]/ms)?.[1] ?? "";
66542
+ const deps = depsSection.split(",").map((d) => d.trim().replace(/["'\s>=<!^~].*/g, "")).filter(Boolean).slice(0, 10);
66543
+ return { name: nameMatch?.[1], lang: "Python", dependencies: deps };
66544
+ }
66545
+ const lines = (await Bun.file(requirements).text()).split(`
66546
+ `).map((l) => l.split(/[>=<!]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
66547
+ return { lang: "Python", dependencies: lines };
66548
+ } catch {
66549
+ return null;
66550
+ }
66551
+ }
66552
+ async function detectPhp(workdir) {
66553
+ const composerPath = join10(workdir, "composer.json");
66554
+ if (!existsSync9(composerPath))
66555
+ return null;
66556
+ try {
66557
+ const file2 = Bun.file(composerPath);
66558
+ const composer = await file2.json();
66559
+ const deps = Object.keys({ ...composer.require ?? {}, ...composer["require-dev"] ?? {} }).filter((d) => d !== "php").map((d) => d.split("/").pop() ?? d).slice(0, 10);
66560
+ return { name: composer.name, lang: "PHP", dependencies: deps };
66561
+ } catch {
66562
+ return null;
66563
+ }
66564
+ }
66565
+ async function detectRuby(workdir) {
66566
+ const gemfile = join10(workdir, "Gemfile");
66567
+ if (!existsSync9(gemfile))
66568
+ return null;
66569
+ try {
66570
+ const content = await Bun.file(gemfile).text();
66571
+ const gems = [...content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)].map((m) => m[1]).slice(0, 10);
66572
+ return { lang: "Ruby", dependencies: gems };
66573
+ } catch {
66574
+ return null;
66575
+ }
66576
+ }
66577
+ async function detectJvm(workdir) {
66578
+ const pom = join10(workdir, "pom.xml");
66579
+ const gradle = join10(workdir, "build.gradle");
66580
+ const gradleKts = join10(workdir, "build.gradle.kts");
66581
+ if (!existsSync9(pom) && !existsSync9(gradle) && !existsSync9(gradleKts))
66582
+ return null;
66583
+ try {
66584
+ if (existsSync9(pom)) {
66585
+ const content2 = await Bun.file(pom).text();
66586
+ const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
66587
+ const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
66588
+ const lang2 = existsSync9(join10(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
66589
+ return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
66590
+ }
66591
+ const gradleFile = existsSync9(gradleKts) ? gradleKts : gradle;
66592
+ const content = await Bun.file(gradleFile).text();
66593
+ const lang = gradleFile.endsWith(".kts") ? "Kotlin" : "Java";
66594
+ const deps = [...content.matchAll(/implementation[^'"]*['"]([^:'"]+:[^:'"]+)[^'"]*['"]/g)].map((m) => m[1].split(":").pop() ?? m[1]).slice(0, 10);
66595
+ return { lang, dependencies: deps };
66596
+ } catch {
66597
+ return null;
66598
+ }
66599
+ }
66600
+ async function buildProjectMetadata(workdir, config2) {
66601
+ const detected = await detectGo(workdir) ?? await detectRust(workdir) ?? await detectPython(workdir) ?? await detectPhp(workdir) ?? await detectRuby(workdir) ?? await detectJvm(workdir) ?? await detectNode(workdir);
66602
+ return {
66603
+ name: detected?.name,
66604
+ language: detected?.lang,
66605
+ dependencies: detected?.dependencies ?? [],
66606
+ testCommand: config2.execution?.testCommand ?? undefined,
66607
+ lintCommand: config2.execution?.lintCommand ?? undefined,
66608
+ typecheckCommand: config2.execution?.typecheckCommand ?? undefined
66609
+ };
66610
+ }
66611
+ function formatMetadataSection(metadata) {
66612
+ const lines = ["## Project Metadata", "", "> Auto-injected by `nax generate`", ""];
66613
+ if (metadata.name) {
66614
+ lines.push(`**Project:** \`${metadata.name}\``);
66615
+ lines.push("");
66616
+ }
66617
+ if (metadata.language) {
66618
+ lines.push(`**Language:** ${metadata.language}`);
66619
+ lines.push("");
66620
+ }
66621
+ if (metadata.dependencies.length > 0) {
66622
+ lines.push(`**Key dependencies:** ${metadata.dependencies.join(", ")}`);
66623
+ lines.push("");
66624
+ }
66625
+ const commands = [];
66626
+ if (metadata.testCommand)
66627
+ commands.push(`test: \`${metadata.testCommand}\``);
66628
+ if (metadata.lintCommand)
66629
+ commands.push(`lint: \`${metadata.lintCommand}\``);
66630
+ if (metadata.typecheckCommand)
66631
+ commands.push(`typecheck: \`${metadata.typecheckCommand}\``);
66632
+ if (commands.length > 0) {
66633
+ lines.push(`**Commands:** ${commands.join(" | ")}`);
66634
+ lines.push("");
66635
+ }
66636
+ lines.push("---");
66637
+ lines.push("");
66638
+ return lines.join(`
66639
+ `);
66640
+ }
66641
+
66642
+ // src/context/generators/aider.ts
66643
+ function generateAiderConfig(context) {
66644
+ const header = `# Aider Configuration
66645
+ # Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
66646
+ # DO NOT EDIT MANUALLY
66647
+
66648
+ # Project instructions
66649
+ instructions: |
66650
+ `;
66651
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66652
+ const combined = metaSection + context.markdown;
66653
+ const indented = combined.split(`
66654
+ `).map((line) => ` ${line}`).join(`
66655
+ `);
66656
+ return `${header}${indented}
66657
+ `;
66658
+ }
66659
+ var aiderGenerator = {
66660
+ name: "aider",
66661
+ outputFile: ".aider.conf.yml",
66662
+ generate: generateAiderConfig
66663
+ };
66664
+
66665
+ // src/context/generators/claude.ts
66666
+ function generateClaudeConfig(context) {
66667
+ const header = `# Project Context
66668
+
66669
+ This file is auto-generated from \`nax/context.md\`.
66670
+ DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
66671
+
66672
+ ---
66673
+
66674
+ `;
66675
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66676
+ return header + metaSection + context.markdown;
66677
+ }
66678
+ var claudeGenerator = {
66679
+ name: "claude",
66680
+ outputFile: "CLAUDE.md",
66681
+ generate: generateClaudeConfig
66682
+ };
66683
+
66684
+ // src/context/generators/codex.ts
66685
+ function generateCodexConfig(context) {
66686
+ const header = `# Codex Instructions
66687
+
66688
+ This file is auto-generated from \`nax/context.md\`.
66689
+ DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
66690
+
66691
+ ---
66692
+
66693
+ `;
66694
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66695
+ return header + metaSection + context.markdown;
66696
+ }
66697
+ var codexGenerator = {
66698
+ name: "codex",
66699
+ outputFile: "codex.md",
66700
+ generate: generateCodexConfig
66701
+ };
66702
+
66703
+ // src/context/generators/cursor.ts
66704
+ function generateCursorRules(context) {
66705
+ const header = `# Project Rules
66706
+
66707
+ Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
66708
+ DO NOT EDIT MANUALLY
66709
+
66710
+ ---
66711
+
66712
+ `;
66713
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66714
+ return header + metaSection + context.markdown;
66715
+ }
66716
+ var cursorGenerator = {
66717
+ name: "cursor",
66718
+ outputFile: ".cursorrules",
66719
+ generate: generateCursorRules
66720
+ };
66721
+
66722
+ // src/context/generators/gemini.ts
66723
+ function generateGeminiConfig(context) {
66724
+ const header = `# Gemini CLI Context
66725
+
66726
+ This file is auto-generated from \`nax/context.md\`.
66727
+ DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
66728
+
66729
+ ---
66730
+
66731
+ `;
66732
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66733
+ return header + metaSection + context.markdown;
66734
+ }
66735
+ var geminiGenerator = {
66736
+ name: "gemini",
66737
+ outputFile: "GEMINI.md",
66738
+ generate: generateGeminiConfig
66739
+ };
66740
+
66741
+ // src/context/generators/opencode.ts
66742
+ function generateOpencodeConfig(context) {
66743
+ const header = `# Agent Instructions
66744
+
66745
+ This file is auto-generated from \`nax/context.md\`.
66746
+ DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
66747
+
66748
+ These instructions apply to all AI coding agents in this project.
66749
+
66750
+ ---
66751
+
66752
+ `;
66753
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66754
+ return header + metaSection + context.markdown;
66755
+ }
66756
+ var opencodeGenerator = {
66757
+ name: "opencode",
66758
+ outputFile: "AGENTS.md",
66759
+ generate: generateOpencodeConfig
66760
+ };
66761
+
66762
+ // src/context/generators/windsurf.ts
66763
+ function generateWindsurfRules(context) {
66764
+ const header = `# Windsurf Project Rules
66765
+
66766
+ Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
66767
+ DO NOT EDIT MANUALLY
66768
+
66769
+ ---
66770
+
66771
+ `;
66772
+ const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
66773
+ return header + metaSection + context.markdown;
66774
+ }
66775
+ var windsurfGenerator = {
66776
+ name: "windsurf",
66777
+ outputFile: ".windsurfrules",
66778
+ generate: generateWindsurfRules
66779
+ };
66780
+
66781
+ // src/context/generator.ts
66782
+ var GENERATORS = {
66783
+ claude: claudeGenerator,
66784
+ codex: codexGenerator,
66785
+ opencode: opencodeGenerator,
66786
+ cursor: cursorGenerator,
66787
+ windsurf: windsurfGenerator,
66788
+ aider: aiderGenerator,
66789
+ gemini: geminiGenerator
66790
+ };
66791
+ async function loadContextContent(options, config2) {
66792
+ if (!existsSync10(options.contextPath)) {
66793
+ throw new Error(`Context file not found: ${options.contextPath}`);
66794
+ }
66795
+ const file2 = Bun.file(options.contextPath);
66796
+ const markdown = await file2.text();
66797
+ const autoInject = options.autoInject ?? true;
66798
+ const metadata = autoInject ? await buildProjectMetadata(options.workdir, config2) : undefined;
66799
+ return { markdown, metadata };
66800
+ }
66801
+ async function generateFor(agent, options, config2) {
66802
+ const generator = GENERATORS[agent];
66803
+ if (!generator) {
66804
+ return { agent, outputFile: "", content: "", written: false, error: `Unknown agent: ${agent}` };
66805
+ }
66806
+ try {
66807
+ const context = await loadContextContent(options, config2);
66808
+ const content = generator.generate(context);
66809
+ const outputPath = join11(options.outputDir, generator.outputFile);
66810
+ validateFilePath(outputPath, options.outputDir);
66811
+ if (!options.dryRun) {
66812
+ await Bun.write(outputPath, content);
66813
+ }
66814
+ return { agent, outputFile: generator.outputFile, content, written: !options.dryRun };
66815
+ } catch (err) {
66816
+ const error48 = err instanceof Error ? err.message : String(err);
66817
+ return { agent, outputFile: generator.outputFile, content: "", written: false, error: error48 };
66818
+ }
66819
+ }
66820
+ async function generateAll(options, config2) {
66821
+ const context = await loadContextContent(options, config2);
66822
+ const results = [];
66823
+ for (const [agentKey, generator] of Object.entries(GENERATORS)) {
66824
+ try {
66825
+ const content = generator.generate(context);
66826
+ const outputPath = join11(options.outputDir, generator.outputFile);
66827
+ validateFilePath(outputPath, options.outputDir);
66828
+ if (!options.dryRun) {
66829
+ await Bun.write(outputPath, content);
66830
+ }
66831
+ results.push({ agent: agentKey, outputFile: generator.outputFile, content, written: !options.dryRun });
66832
+ } catch (err) {
66833
+ const error48 = err instanceof Error ? err.message : String(err);
66834
+ results.push({ agent: agentKey, outputFile: generator.outputFile, content: "", written: false, error: error48 });
66835
+ }
66836
+ }
66837
+ return results;
66838
+ }
66839
+ async function discoverPackages(repoRoot) {
66840
+ const packages = [];
66841
+ const seen = new Set;
66842
+ for (const pattern of ["*/nax/context.md", "*/*/nax/context.md"]) {
66843
+ const glob = new Bun.Glob(pattern);
66844
+ for await (const match of glob.scan(repoRoot)) {
66845
+ const pkgRelative = match.replace(/\/nax\/context\.md$/, "");
66846
+ const pkgAbsolute = join11(repoRoot, pkgRelative);
66847
+ if (!seen.has(pkgAbsolute)) {
66848
+ seen.add(pkgAbsolute);
66849
+ packages.push(pkgAbsolute);
66850
+ }
66851
+ }
66852
+ }
66853
+ return packages;
66854
+ }
66855
+ async function generateForPackage(packageDir, config2, dryRun = false) {
66856
+ const contextPath = join11(packageDir, "nax", "context.md");
66857
+ if (!existsSync10(contextPath)) {
66858
+ return {
66859
+ packageDir,
66860
+ outputFile: "CLAUDE.md",
66861
+ content: "",
66862
+ written: false,
66863
+ error: `context.md not found: ${contextPath}`
66864
+ };
66865
+ }
66866
+ try {
66867
+ const options = {
66868
+ contextPath,
66869
+ outputDir: packageDir,
66870
+ workdir: packageDir,
66871
+ dryRun,
66872
+ autoInject: true
66873
+ };
66874
+ const result = await generateFor("claude", options, config2);
66875
+ return {
66876
+ packageDir,
66877
+ outputFile: result.outputFile,
66878
+ content: result.content,
66879
+ written: result.written,
66880
+ error: result.error
66881
+ };
66882
+ } catch (err) {
66883
+ const error48 = err instanceof Error ? err.message : String(err);
66884
+ return { packageDir, outputFile: "CLAUDE.md", content: "", written: false, error: error48 };
66885
+ }
66886
+ }
66887
+
66888
+ // src/cli/plan.ts
66120
66889
  init_pid_registry();
66121
66890
  init_logger2();
66122
66891
 
@@ -66202,6 +66971,20 @@ function validateStory(raw, index, allIds) {
66202
66971
  }
66203
66972
  const rawTags = s.tags;
66204
66973
  const tags = Array.isArray(rawTags) ? rawTags : [];
66974
+ const rawWorkdir = s.workdir;
66975
+ let workdir;
66976
+ if (rawWorkdir !== undefined && rawWorkdir !== null) {
66977
+ if (typeof rawWorkdir !== "string") {
66978
+ throw new Error(`[schema] story[${index}].workdir must be a string`);
66979
+ }
66980
+ if (rawWorkdir.startsWith("/")) {
66981
+ throw new Error(`[schema] story[${index}].workdir must be relative (no leading /): "${rawWorkdir}"`);
66982
+ }
66983
+ if (rawWorkdir.includes("..")) {
66984
+ throw new Error(`[schema] story[${index}].workdir must not contain '..': "${rawWorkdir}"`);
66985
+ }
66986
+ workdir = rawWorkdir;
66987
+ }
66205
66988
  return {
66206
66989
  id,
66207
66990
  title: title.trim(),
@@ -66217,7 +67000,8 @@ function validateStory(raw, index, allIds) {
66217
67000
  complexity,
66218
67001
  testStrategy,
66219
67002
  reasoning: "validated from LLM output"
66220
- }
67003
+ },
67004
+ ...workdir !== undefined ? { workdir } : {}
66221
67005
  };
66222
67006
  }
66223
67007
  function parseRawString(text) {
@@ -66268,36 +67052,41 @@ var _deps2 = {
66268
67052
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
66269
67053
  scanCodebase: (workdir) => scanCodebase(workdir),
66270
67054
  getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
66271
- readPackageJson: (workdir) => Bun.file(join10(workdir, "package.json")).json().catch(() => null),
67055
+ readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
66272
67056
  spawnSync: (cmd, opts) => {
66273
67057
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
66274
67058
  return { stdout: result.stdout, exitCode: result.exitCode };
66275
67059
  },
66276
67060
  mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {}),
66277
- existsSync: (path) => existsSync9(path)
67061
+ existsSync: (path) => existsSync11(path),
67062
+ discoverPackages: (repoRoot) => discoverPackages(repoRoot)
66278
67063
  };
66279
67064
  async function planCommand(workdir, config2, options) {
66280
- const naxDir = join10(workdir, "nax");
66281
- if (!existsSync9(naxDir)) {
67065
+ const naxDir = join12(workdir, "nax");
67066
+ if (!existsSync11(naxDir)) {
66282
67067
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
66283
67068
  }
66284
67069
  const logger = getLogger();
66285
67070
  logger?.info("plan", "Reading spec", { from: options.from });
66286
67071
  const specContent = await _deps2.readFile(options.from);
66287
67072
  logger?.info("plan", "Scanning codebase...");
66288
- const scan = await _deps2.scanCodebase(workdir);
67073
+ const [scan, discoveredPackages, pkg] = await Promise.all([
67074
+ _deps2.scanCodebase(workdir),
67075
+ _deps2.discoverPackages(workdir),
67076
+ _deps2.readPackageJson(workdir)
67077
+ ]);
66289
67078
  const codebaseContext = buildCodebaseContext2(scan);
66290
- const pkg = await _deps2.readPackageJson(workdir);
67079
+ const relativePackages = discoveredPackages.map((p) => p.replace(`${workdir}/`, ""));
66291
67080
  const projectName = detectProjectName(workdir, pkg);
66292
67081
  const branchName = options.branch ?? `feat/${options.feature}`;
66293
- const outputDir = join10(naxDir, "features", options.feature);
66294
- const outputPath = join10(outputDir, "prd.json");
67082
+ const outputDir = join12(naxDir, "features", options.feature);
67083
+ const outputPath = join12(outputDir, "prd.json");
66295
67084
  await _deps2.mkdirp(outputDir);
66296
67085
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
66297
67086
  const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
66298
67087
  let rawResponse;
66299
67088
  if (options.auto) {
66300
- const prompt = buildPlanningPrompt(specContent, codebaseContext);
67089
+ const prompt = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages);
66301
67090
  const cliAdapter = _deps2.getAgent(agentName);
66302
67091
  if (!cliAdapter)
66303
67092
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
@@ -66309,7 +67098,7 @@ async function planCommand(workdir, config2, options) {
66309
67098
  }
66310
67099
  } catch {}
66311
67100
  } else {
66312
- const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath);
67101
+ const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages);
66313
67102
  const adapter = _deps2.getAgent(agentName, config2);
66314
67103
  if (!adapter)
66315
67104
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
@@ -66417,7 +67206,18 @@ function buildCodebaseContext2(scan) {
66417
67206
  return sections.join(`
66418
67207
  `);
66419
67208
  }
66420
- function buildPlanningPrompt(specContent, codebaseContext, outputFilePath) {
67209
+ function buildPlanningPrompt(specContent, codebaseContext, outputFilePath, packages) {
67210
+ const isMonorepo = packages && packages.length > 0;
67211
+ const monorepoHint = isMonorepo ? `
67212
+ ## Monorepo Context
67213
+
67214
+ This is a monorepo. Detected packages:
67215
+ ${packages.map((p) => `- ${p}`).join(`
67216
+ `)}
67217
+
67218
+ For each user story, set the "workdir" field to the relevant package path (e.g. "packages/api"). Stories that span the root should omit "workdir".` : "";
67219
+ const workdirField = isMonorepo ? `
67220
+ "workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
66421
67221
  return `You are a senior software architect generating a product requirements document (PRD) as JSON.
66422
67222
 
66423
67223
  ## Spec
@@ -66426,7 +67226,7 @@ ${specContent}
66426
67226
 
66427
67227
  ## Codebase Context
66428
67228
 
66429
- ${codebaseContext}
67229
+ ${codebaseContext}${monorepoHint}
66430
67230
 
66431
67231
  ## Output Schema
66432
67232
 
@@ -66445,7 +67245,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
66445
67245
  "description": "string \u2014 detailed description of the story",
66446
67246
  "acceptanceCriteria": ["string \u2014 each AC line"],
66447
67247
  "tags": ["string \u2014 routing tags, e.g. feature, security, api"],
66448
- "dependencies": ["string \u2014 story IDs this story depends on"],
67248
+ "dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
66449
67249
  "status": "pending",
66450
67250
  "passes": false,
66451
67251
  "routing": {
@@ -66615,14 +67415,14 @@ async function displayModelEfficiency(workdir) {
66615
67415
  }
66616
67416
  // src/cli/status-features.ts
66617
67417
  init_source();
66618
- import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
66619
- import { join as join13 } from "path";
67418
+ import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
67419
+ import { join as join15 } from "path";
66620
67420
 
66621
67421
  // src/commands/common.ts
66622
67422
  init_path_security2();
66623
67423
  init_errors3();
66624
- import { existsSync as existsSync10, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
66625
- import { join as join11, resolve as resolve6 } from "path";
67424
+ import { existsSync as existsSync12, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
67425
+ import { join as join13, resolve as resolve6 } from "path";
66626
67426
  function resolveProject(options = {}) {
66627
67427
  const { dir, feature } = options;
66628
67428
  let projectRoot;
@@ -66630,37 +67430,37 @@ function resolveProject(options = {}) {
66630
67430
  let configPath;
66631
67431
  if (dir) {
66632
67432
  projectRoot = realpathSync3(resolve6(dir));
66633
- naxDir = join11(projectRoot, "nax");
66634
- if (!existsSync10(naxDir)) {
67433
+ naxDir = join13(projectRoot, "nax");
67434
+ if (!existsSync12(naxDir)) {
66635
67435
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
66636
67436
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
66637
67437
  }
66638
- configPath = join11(naxDir, "config.json");
66639
- if (!existsSync10(configPath)) {
67438
+ configPath = join13(naxDir, "config.json");
67439
+ if (!existsSync12(configPath)) {
66640
67440
  throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
66641
67441
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
66642
67442
  }
66643
67443
  } else {
66644
67444
  const found = findProjectRoot(process.cwd());
66645
67445
  if (!found) {
66646
- const cwdNaxDir = join11(process.cwd(), "nax");
66647
- if (existsSync10(cwdNaxDir)) {
66648
- const cwdConfigPath = join11(cwdNaxDir, "config.json");
67446
+ const cwdNaxDir = join13(process.cwd(), "nax");
67447
+ if (existsSync12(cwdNaxDir)) {
67448
+ const cwdConfigPath = join13(cwdNaxDir, "config.json");
66649
67449
  throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
66650
67450
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
66651
67451
  }
66652
67452
  throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
66653
67453
  }
66654
67454
  projectRoot = found;
66655
- naxDir = join11(projectRoot, "nax");
66656
- configPath = join11(naxDir, "config.json");
67455
+ naxDir = join13(projectRoot, "nax");
67456
+ configPath = join13(naxDir, "config.json");
66657
67457
  }
66658
67458
  let featureDir;
66659
67459
  if (feature) {
66660
- const featuresDir = join11(naxDir, "features");
66661
- featureDir = join11(featuresDir, feature);
66662
- if (!existsSync10(featureDir)) {
66663
- const availableFeatures = existsSync10(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
67460
+ const featuresDir = join13(naxDir, "features");
67461
+ featureDir = join13(featuresDir, feature);
67462
+ if (!existsSync12(featureDir)) {
67463
+ const availableFeatures = existsSync12(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
66664
67464
  const availableMsg = availableFeatures.length > 0 ? `
66665
67465
 
66666
67466
  Available features:
@@ -66685,12 +67485,12 @@ function findProjectRoot(startDir) {
66685
67485
  let current = resolve6(startDir);
66686
67486
  let depth = 0;
66687
67487
  while (depth < MAX_DIRECTORY_DEPTH) {
66688
- const naxDir = join11(current, "nax");
66689
- const configPath = join11(naxDir, "config.json");
66690
- if (existsSync10(configPath)) {
67488
+ const naxDir = join13(current, "nax");
67489
+ const configPath = join13(naxDir, "config.json");
67490
+ if (existsSync12(configPath)) {
66691
67491
  return realpathSync3(current);
66692
67492
  }
66693
- const parent = join11(current, "..");
67493
+ const parent = join13(current, "..");
66694
67494
  if (parent === current) {
66695
67495
  break;
66696
67496
  }
@@ -66712,8 +67512,8 @@ function isPidAlive(pid) {
66712
67512
  }
66713
67513
  }
66714
67514
  async function loadStatusFile(featureDir) {
66715
- const statusPath = join13(featureDir, "status.json");
66716
- if (!existsSync11(statusPath)) {
67515
+ const statusPath = join15(featureDir, "status.json");
67516
+ if (!existsSync13(statusPath)) {
66717
67517
  return null;
66718
67518
  }
66719
67519
  try {
@@ -66724,8 +67524,8 @@ async function loadStatusFile(featureDir) {
66724
67524
  }
66725
67525
  }
66726
67526
  async function loadProjectStatusFile(projectDir) {
66727
- const statusPath = join13(projectDir, "nax", "status.json");
66728
- if (!existsSync11(statusPath)) {
67527
+ const statusPath = join15(projectDir, "nax", "status.json");
67528
+ if (!existsSync13(statusPath)) {
66729
67529
  return null;
66730
67530
  }
66731
67531
  try {
@@ -66736,8 +67536,8 @@ async function loadProjectStatusFile(projectDir) {
66736
67536
  }
66737
67537
  }
66738
67538
  async function getFeatureSummary(featureName, featureDir) {
66739
- const prdPath = join13(featureDir, "prd.json");
66740
- if (!existsSync11(prdPath)) {
67539
+ const prdPath = join15(featureDir, "prd.json");
67540
+ if (!existsSync13(prdPath)) {
66741
67541
  return {
66742
67542
  name: featureName,
66743
67543
  done: 0,
@@ -66779,8 +67579,8 @@ async function getFeatureSummary(featureName, featureDir) {
66779
67579
  };
66780
67580
  }
66781
67581
  }
66782
- const runsDir = join13(featureDir, "runs");
66783
- if (existsSync11(runsDir)) {
67582
+ const runsDir = join15(featureDir, "runs");
67583
+ if (existsSync13(runsDir)) {
66784
67584
  const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
66785
67585
  if (runs.length > 0) {
66786
67586
  const latestRun = runs[0].replace(".jsonl", "");
@@ -66790,8 +67590,8 @@ async function getFeatureSummary(featureName, featureDir) {
66790
67590
  return summary;
66791
67591
  }
66792
67592
  async function displayAllFeatures(projectDir) {
66793
- const featuresDir = join13(projectDir, "nax", "features");
66794
- if (!existsSync11(featuresDir)) {
67593
+ const featuresDir = join15(projectDir, "nax", "features");
67594
+ if (!existsSync13(featuresDir)) {
66795
67595
  console.log(source_default.dim("No features found."));
66796
67596
  return;
66797
67597
  }
@@ -66831,7 +67631,7 @@ async function displayAllFeatures(projectDir) {
66831
67631
  console.log();
66832
67632
  }
66833
67633
  }
66834
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join13(featuresDir, name))));
67634
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join15(featuresDir, name))));
66835
67635
  console.log(source_default.bold(`\uD83D\uDCCA Features
66836
67636
  `));
66837
67637
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -66857,8 +67657,8 @@ async function displayAllFeatures(projectDir) {
66857
67657
  console.log();
66858
67658
  }
66859
67659
  async function displayFeatureDetails(featureName, featureDir) {
66860
- const prdPath = join13(featureDir, "prd.json");
66861
- if (!existsSync11(prdPath)) {
67660
+ const prdPath = join15(featureDir, "prd.json");
67661
+ if (!existsSync13(prdPath)) {
66862
67662
  console.log(source_default.bold(`
66863
67663
  \uD83D\uDCCA ${featureName}
66864
67664
  `));
@@ -66978,8 +67778,8 @@ async function displayFeatureStatus(options = {}) {
66978
67778
  // src/cli/runs.ts
66979
67779
  init_errors3();
66980
67780
  init_logger2();
66981
- import { existsSync as existsSync12, readdirSync as readdirSync4 } from "fs";
66982
- import { join as join14 } from "path";
67781
+ import { existsSync as existsSync14, readdirSync as readdirSync4 } from "fs";
67782
+ import { join as join16 } from "path";
66983
67783
  async function parseRunLog(logPath) {
66984
67784
  const logger = getLogger();
66985
67785
  try {
@@ -66995,8 +67795,8 @@ async function parseRunLog(logPath) {
66995
67795
  async function runsListCommand(options) {
66996
67796
  const logger = getLogger();
66997
67797
  const { feature, workdir } = options;
66998
- const runsDir = join14(workdir, "nax", "features", feature, "runs");
66999
- if (!existsSync12(runsDir)) {
67798
+ const runsDir = join16(workdir, "nax", "features", feature, "runs");
67799
+ if (!existsSync14(runsDir)) {
67000
67800
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
67001
67801
  return;
67002
67802
  }
@@ -67007,7 +67807,7 @@ async function runsListCommand(options) {
67007
67807
  }
67008
67808
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
67009
67809
  for (const file2 of files.sort().reverse()) {
67010
- const logPath = join14(runsDir, file2);
67810
+ const logPath = join16(runsDir, file2);
67011
67811
  const entries = await parseRunLog(logPath);
67012
67812
  const startEvent = entries.find((e) => e.message === "run.start");
67013
67813
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -67033,8 +67833,8 @@ async function runsListCommand(options) {
67033
67833
  async function runsShowCommand(options) {
67034
67834
  const logger = getLogger();
67035
67835
  const { runId, feature, workdir } = options;
67036
- const logPath = join14(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
67037
- if (!existsSync12(logPath)) {
67836
+ const logPath = join16(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
67837
+ if (!existsSync14(logPath)) {
67038
67838
  logger.error("cli", "Run not found", { runId, feature, logPath });
67039
67839
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
67040
67840
  }
@@ -67071,8 +67871,8 @@ async function runsShowCommand(options) {
67071
67871
  }
67072
67872
  // src/cli/prompts-main.ts
67073
67873
  init_logger2();
67074
- import { existsSync as existsSync15, mkdirSync as mkdirSync3 } from "fs";
67075
- import { join as join21 } from "path";
67874
+ import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
67875
+ import { join as join27 } from "path";
67076
67876
 
67077
67877
  // src/pipeline/index.ts
67078
67878
  init_runner();
@@ -67108,7 +67908,7 @@ init_prd();
67108
67908
 
67109
67909
  // src/cli/prompts-tdd.ts
67110
67910
  init_prompts2();
67111
- import { join as join20 } from "path";
67911
+ import { join as join26 } from "path";
67112
67912
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
67113
67913
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
67114
67914
  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(),
@@ -67127,7 +67927,7 @@ ${frontmatter}---
67127
67927
 
67128
67928
  ${session.prompt}`;
67129
67929
  if (outputDir) {
67130
- const promptFile = join20(outputDir, `${story.id}.${session.role}.md`);
67930
+ const promptFile = join26(outputDir, `${story.id}.${session.role}.md`);
67131
67931
  await Bun.write(promptFile, fullOutput);
67132
67932
  logger.info("cli", "Written TDD prompt file", {
67133
67933
  storyId: story.id,
@@ -67143,7 +67943,7 @@ ${"=".repeat(80)}`);
67143
67943
  }
67144
67944
  }
67145
67945
  if (outputDir && ctx.contextMarkdown) {
67146
- const contextFile = join20(outputDir, `${story.id}.context.md`);
67946
+ const contextFile = join26(outputDir, `${story.id}.context.md`);
67147
67947
  const frontmatter = buildFrontmatter(story, ctx);
67148
67948
  const contextOutput = `---
67149
67949
  ${frontmatter}---
@@ -67157,13 +67957,13 @@ ${ctx.contextMarkdown}`;
67157
67957
  async function promptsCommand(options) {
67158
67958
  const logger = getLogger();
67159
67959
  const { feature, workdir, config: config2, storyId, outputDir } = options;
67160
- const naxDir = join21(workdir, "nax");
67161
- if (!existsSync15(naxDir)) {
67960
+ const naxDir = join27(workdir, "nax");
67961
+ if (!existsSync18(naxDir)) {
67162
67962
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
67163
67963
  }
67164
- const featureDir = join21(naxDir, "features", feature);
67165
- const prdPath = join21(featureDir, "prd.json");
67166
- if (!existsSync15(prdPath)) {
67964
+ const featureDir = join27(naxDir, "features", feature);
67965
+ const prdPath = join27(featureDir, "prd.json");
67966
+ if (!existsSync18(prdPath)) {
67167
67967
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
67168
67968
  }
67169
67969
  const prd = await loadPRD(prdPath);
@@ -67222,10 +68022,10 @@ ${frontmatter}---
67222
68022
 
67223
68023
  ${ctx.prompt}`;
67224
68024
  if (outputDir) {
67225
- const promptFile = join21(outputDir, `${story.id}.prompt.md`);
68025
+ const promptFile = join27(outputDir, `${story.id}.prompt.md`);
67226
68026
  await Bun.write(promptFile, fullOutput);
67227
68027
  if (ctx.contextMarkdown) {
67228
- const contextFile = join21(outputDir, `${story.id}.context.md`);
68028
+ const contextFile = join27(outputDir, `${story.id}.context.md`);
67229
68029
  const contextOutput = `---
67230
68030
  ${frontmatter}---
67231
68031
 
@@ -67288,8 +68088,8 @@ function buildFrontmatter(story, ctx, role) {
67288
68088
  `;
67289
68089
  }
67290
68090
  // src/cli/prompts-init.ts
67291
- import { existsSync as existsSync16, mkdirSync as mkdirSync4 } from "fs";
67292
- import { join as join22 } from "path";
68091
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
68092
+ import { join as join28 } from "path";
67293
68093
  var TEMPLATE_ROLES = [
67294
68094
  { file: "test-writer.md", role: "test-writer" },
67295
68095
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -67313,9 +68113,9 @@ var TEMPLATE_HEADER = `<!--
67313
68113
  `;
67314
68114
  async function promptsInitCommand(options) {
67315
68115
  const { workdir, force = false, autoWireConfig = true } = options;
67316
- const templatesDir = join22(workdir, "nax", "templates");
68116
+ const templatesDir = join28(workdir, "nax", "templates");
67317
68117
  mkdirSync4(templatesDir, { recursive: true });
67318
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(join22(templatesDir, f)));
68118
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join28(templatesDir, f)));
67319
68119
  if (existingFiles.length > 0 && !force) {
67320
68120
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
67321
68121
  Pass --force to overwrite existing templates.`);
@@ -67323,7 +68123,7 @@ async function promptsInitCommand(options) {
67323
68123
  }
67324
68124
  const written = [];
67325
68125
  for (const template of TEMPLATE_ROLES) {
67326
- const filePath = join22(templatesDir, template.file);
68126
+ const filePath = join28(templatesDir, template.file);
67327
68127
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
67328
68128
  const content = TEMPLATE_HEADER + roleBody;
67329
68129
  await Bun.write(filePath, content);
@@ -67339,8 +68139,8 @@ async function promptsInitCommand(options) {
67339
68139
  return written;
67340
68140
  }
67341
68141
  async function autoWirePromptsConfig(workdir) {
67342
- const configPath = join22(workdir, "nax.config.json");
67343
- if (!existsSync16(configPath)) {
68142
+ const configPath = join28(workdir, "nax.config.json");
68143
+ if (!existsSync19(configPath)) {
67344
68144
  const exampleConfig = JSON.stringify({
67345
68145
  prompts: {
67346
68146
  overrides: {
@@ -67435,9 +68235,7 @@ async function exportPromptCommand(options) {
67435
68235
  // src/cli/init.ts
67436
68236
  init_paths();
67437
68237
  init_logger2();
67438
-
67439
- // src/cli/init-context.ts
67440
- init_logger2();
68238
+ init_init_context();
67441
68239
  // src/cli/plugins.ts
67442
68240
  init_loader5();
67443
68241
  import * as os2 from "os";
@@ -67505,8 +68303,8 @@ function pad(str, width) {
67505
68303
  init_config();
67506
68304
  init_logger2();
67507
68305
  init_prd();
67508
- import { existsSync as existsSync17, readdirSync as readdirSync5 } from "fs";
67509
- import { join as join25 } from "path";
68306
+ import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
68307
+ import { join as join32 } from "path";
67510
68308
 
67511
68309
  // src/cli/diagnose-analysis.ts
67512
68310
  function detectFailurePattern(story, prd, status) {
@@ -67705,8 +68503,8 @@ function isProcessAlive2(pid) {
67705
68503
  }
67706
68504
  }
67707
68505
  async function loadStatusFile2(workdir) {
67708
- const statusPath = join25(workdir, "nax", "status.json");
67709
- if (!existsSync17(statusPath))
68506
+ const statusPath = join32(workdir, "nax", "status.json");
68507
+ if (!existsSync21(statusPath))
67710
68508
  return null;
67711
68509
  try {
67712
68510
  return await Bun.file(statusPath).json();
@@ -67733,7 +68531,7 @@ async function countCommitsSince(workdir, since) {
67733
68531
  }
67734
68532
  }
67735
68533
  async function checkLock(workdir) {
67736
- const lockFile = Bun.file(join25(workdir, "nax.lock"));
68534
+ const lockFile = Bun.file(join32(workdir, "nax.lock"));
67737
68535
  if (!await lockFile.exists())
67738
68536
  return { lockPresent: false };
67739
68537
  try {
@@ -67751,8 +68549,8 @@ async function diagnoseCommand(options = {}) {
67751
68549
  const logger = getLogger();
67752
68550
  const workdir = options.workdir ?? process.cwd();
67753
68551
  const naxSubdir = findProjectDir(workdir);
67754
- let projectDir = naxSubdir ? join25(naxSubdir, "..") : null;
67755
- if (!projectDir && existsSync17(join25(workdir, "nax"))) {
68552
+ let projectDir = naxSubdir ? join32(naxSubdir, "..") : null;
68553
+ if (!projectDir && existsSync21(join32(workdir, "nax"))) {
67756
68554
  projectDir = workdir;
67757
68555
  }
67758
68556
  if (!projectDir)
@@ -67763,8 +68561,8 @@ async function diagnoseCommand(options = {}) {
67763
68561
  if (status2) {
67764
68562
  feature = status2.run.feature;
67765
68563
  } else {
67766
- const featuresDir = join25(projectDir, "nax", "features");
67767
- if (!existsSync17(featuresDir))
68564
+ const featuresDir = join32(projectDir, "nax", "features");
68565
+ if (!existsSync21(featuresDir))
67768
68566
  throw new Error("No features found in project");
67769
68567
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
67770
68568
  if (features.length === 0)
@@ -67773,9 +68571,9 @@ async function diagnoseCommand(options = {}) {
67773
68571
  logger.info("diagnose", "No feature specified, using first found", { feature });
67774
68572
  }
67775
68573
  }
67776
- const featureDir = join25(projectDir, "nax", "features", feature);
67777
- const prdPath = join25(featureDir, "prd.json");
67778
- if (!existsSync17(prdPath))
68574
+ const featureDir = join32(projectDir, "nax", "features", feature);
68575
+ const prdPath = join32(featureDir, "prd.json");
68576
+ if (!existsSync21(prdPath))
67779
68577
  throw new Error(`Feature not found: ${feature}`);
67780
68578
  const prd = await loadPRD(prdPath);
67781
68579
  const status = await loadStatusFile2(projectDir);
@@ -67816,419 +68614,66 @@ init_interaction();
67816
68614
  // src/cli/generate.ts
67817
68615
  init_source();
67818
68616
  init_loader2();
67819
- import { existsSync as existsSync20 } from "fs";
67820
- import { join as join28 } from "path";
67821
-
67822
- // src/context/generator.ts
67823
- init_path_security2();
67824
- import { existsSync as existsSync19 } from "fs";
67825
- import { join as join27 } from "path";
67826
-
67827
- // src/context/injector.ts
67828
- import { existsSync as existsSync18 } from "fs";
67829
- import { join as join26 } from "path";
67830
- var NOTABLE_NODE_DEPS = [
67831
- "@nestjs",
67832
- "express",
67833
- "fastify",
67834
- "koa",
67835
- "hono",
67836
- "next",
67837
- "nuxt",
67838
- "react",
67839
- "vue",
67840
- "svelte",
67841
- "solid",
67842
- "prisma",
67843
- "typeorm",
67844
- "mongoose",
67845
- "drizzle",
67846
- "sequelize",
67847
- "jest",
67848
- "vitest",
67849
- "mocha",
67850
- "bun",
67851
- "zod",
67852
- "typescript",
67853
- "graphql",
67854
- "trpc",
67855
- "bull",
67856
- "ioredis"
67857
- ];
67858
- async function detectNode(workdir) {
67859
- const pkgPath = join26(workdir, "package.json");
67860
- if (!existsSync18(pkgPath))
67861
- return null;
68617
+ import { existsSync as existsSync22 } from "fs";
68618
+ import { join as join33 } from "path";
68619
+ var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68620
+ async function generateCommand(options) {
68621
+ const workdir = process.cwd();
68622
+ const dryRun = options.dryRun ?? false;
68623
+ let config2;
67862
68624
  try {
67863
- const file2 = Bun.file(pkgPath);
67864
- const pkg = await file2.json();
67865
- const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
67866
- const notable = [
67867
- ...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
67868
- ].slice(0, 10);
67869
- const lang = pkg.devDependencies?.typescript || pkg.dependencies?.typescript ? "TypeScript" : "JavaScript";
67870
- return { name: pkg.name, lang, dependencies: notable };
68625
+ config2 = await loadConfig(workdir);
67871
68626
  } catch {
67872
- return null;
68627
+ config2 = {};
67873
68628
  }
67874
- }
67875
- async function detectGo(workdir) {
67876
- const goMod = join26(workdir, "go.mod");
67877
- if (!existsSync18(goMod))
67878
- return null;
67879
- try {
67880
- const content = await Bun.file(goMod).text();
67881
- const moduleMatch = content.match(/^module\s+(\S+)/m);
67882
- const name = moduleMatch?.[1];
67883
- const requires = [];
67884
- const requireBlock = content.match(/require\s*\(([^)]+)\)/s)?.[1] ?? "";
67885
- for (const line of requireBlock.split(`
67886
- `)) {
67887
- const trimmed = line.trim();
67888
- if (trimmed && !trimmed.startsWith("//") && !trimmed.includes("// indirect")) {
67889
- const dep = trimmed.split(/\s+/)[0];
67890
- if (dep)
67891
- requires.push(dep.split("/").slice(-1)[0]);
67892
- }
68629
+ if (options.allPackages) {
68630
+ if (dryRun) {
68631
+ console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
67893
68632
  }
67894
- return { name, lang: "Go", dependencies: requires.slice(0, 10) };
67895
- } catch {
67896
- return null;
67897
- }
67898
- }
67899
- async function detectRust(workdir) {
67900
- const cargoPath = join26(workdir, "Cargo.toml");
67901
- if (!existsSync18(cargoPath))
67902
- return null;
67903
- try {
67904
- const content = await Bun.file(cargoPath).text();
67905
- const nameMatch = content.match(/^\[package\][^[]*name\s*=\s*"([^"]+)"/ms);
67906
- const name = nameMatch?.[1];
67907
- const depsSection = content.match(/^\[dependencies\]([^[]*)/ms)?.[1] ?? "";
67908
- const deps = depsSection.split(`
67909
- `).map((l) => l.split("=")[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
67910
- return { name, lang: "Rust", dependencies: deps };
67911
- } catch {
67912
- return null;
67913
- }
67914
- }
67915
- async function detectPython(workdir) {
67916
- const pyproject = join26(workdir, "pyproject.toml");
67917
- const requirements = join26(workdir, "requirements.txt");
67918
- if (!existsSync18(pyproject) && !existsSync18(requirements))
67919
- return null;
67920
- try {
67921
- if (existsSync18(pyproject)) {
67922
- const content = await Bun.file(pyproject).text();
67923
- const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
67924
- const depsSection = content.match(/^\[project\][^[]*dependencies\s*=\s*\[([^\]]*)\]/ms)?.[1] ?? "";
67925
- const deps = depsSection.split(",").map((d) => d.trim().replace(/["'\s>=<!^~].*/g, "")).filter(Boolean).slice(0, 10);
67926
- return { name: nameMatch?.[1], lang: "Python", dependencies: deps };
68633
+ console.log(source_default.blue("\u2192 Discovering packages with nax/context.md..."));
68634
+ const packages = await discoverPackages(workdir);
68635
+ if (packages.length === 0) {
68636
+ console.log(source_default.yellow(" No packages found (no */nax/context.md or */*/nax/context.md)"));
68637
+ return;
67927
68638
  }
67928
- const lines = (await Bun.file(requirements).text()).split(`
67929
- `).map((l) => l.split(/[>=<!]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
67930
- return { lang: "Python", dependencies: lines };
67931
- } catch {
67932
- return null;
67933
- }
67934
- }
67935
- async function detectPhp(workdir) {
67936
- const composerPath = join26(workdir, "composer.json");
67937
- if (!existsSync18(composerPath))
67938
- return null;
67939
- try {
67940
- const file2 = Bun.file(composerPath);
67941
- const composer = await file2.json();
67942
- const deps = Object.keys({ ...composer.require ?? {}, ...composer["require-dev"] ?? {} }).filter((d) => d !== "php").map((d) => d.split("/").pop() ?? d).slice(0, 10);
67943
- return { name: composer.name, lang: "PHP", dependencies: deps };
67944
- } catch {
67945
- return null;
67946
- }
67947
- }
67948
- async function detectRuby(workdir) {
67949
- const gemfile = join26(workdir, "Gemfile");
67950
- if (!existsSync18(gemfile))
67951
- return null;
67952
- try {
67953
- const content = await Bun.file(gemfile).text();
67954
- const gems = [...content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)].map((m) => m[1]).slice(0, 10);
67955
- return { lang: "Ruby", dependencies: gems };
67956
- } catch {
67957
- return null;
67958
- }
67959
- }
67960
- async function detectJvm(workdir) {
67961
- const pom = join26(workdir, "pom.xml");
67962
- const gradle = join26(workdir, "build.gradle");
67963
- const gradleKts = join26(workdir, "build.gradle.kts");
67964
- if (!existsSync18(pom) && !existsSync18(gradle) && !existsSync18(gradleKts))
67965
- return null;
67966
- try {
67967
- if (existsSync18(pom)) {
67968
- const content2 = await Bun.file(pom).text();
67969
- const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
67970
- const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
67971
- const lang2 = existsSync18(join26(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
67972
- return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
68639
+ console.log(source_default.blue(`\u2192 Generating CLAUDE.md for ${packages.length} package(s)...`));
68640
+ let errorCount = 0;
68641
+ for (const pkgDir of packages) {
68642
+ const result = await generateForPackage(pkgDir, config2, dryRun);
68643
+ if (result.error) {
68644
+ console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
68645
+ errorCount++;
68646
+ } else {
68647
+ const suffix = dryRun ? " (dry run)" : "";
68648
+ console.log(source_default.green(`\u2713 ${pkgDir}/${result.outputFile} (${result.content.length} bytes${suffix})`));
68649
+ }
67973
68650
  }
67974
- const gradleFile = existsSync18(gradleKts) ? gradleKts : gradle;
67975
- const content = await Bun.file(gradleFile).text();
67976
- const lang = gradleFile.endsWith(".kts") ? "Kotlin" : "Java";
67977
- const deps = [...content.matchAll(/implementation[^'"]*['"]([^:'"]+:[^:'"]+)[^'"]*['"]/g)].map((m) => m[1].split(":").pop() ?? m[1]).slice(0, 10);
67978
- return { lang, dependencies: deps };
67979
- } catch {
67980
- return null;
67981
- }
67982
- }
67983
- async function buildProjectMetadata(workdir, config2) {
67984
- const detected = await detectGo(workdir) ?? await detectRust(workdir) ?? await detectPython(workdir) ?? await detectPhp(workdir) ?? await detectRuby(workdir) ?? await detectJvm(workdir) ?? await detectNode(workdir);
67985
- return {
67986
- name: detected?.name,
67987
- language: detected?.lang,
67988
- dependencies: detected?.dependencies ?? [],
67989
- testCommand: config2.execution?.testCommand ?? undefined,
67990
- lintCommand: config2.execution?.lintCommand ?? undefined,
67991
- typecheckCommand: config2.execution?.typecheckCommand ?? undefined
67992
- };
67993
- }
67994
- function formatMetadataSection(metadata) {
67995
- const lines = ["## Project Metadata", "", "> Auto-injected by `nax generate`", ""];
67996
- if (metadata.name) {
67997
- lines.push(`**Project:** \`${metadata.name}\``);
67998
- lines.push("");
67999
- }
68000
- if (metadata.language) {
68001
- lines.push(`**Language:** ${metadata.language}`);
68002
- lines.push("");
68003
- }
68004
- if (metadata.dependencies.length > 0) {
68005
- lines.push(`**Key dependencies:** ${metadata.dependencies.join(", ")}`);
68006
- lines.push("");
68007
- }
68008
- const commands = [];
68009
- if (metadata.testCommand)
68010
- commands.push(`test: \`${metadata.testCommand}\``);
68011
- if (metadata.lintCommand)
68012
- commands.push(`lint: \`${metadata.lintCommand}\``);
68013
- if (metadata.typecheckCommand)
68014
- commands.push(`typecheck: \`${metadata.typecheckCommand}\``);
68015
- if (commands.length > 0) {
68016
- lines.push(`**Commands:** ${commands.join(" | ")}`);
68017
- lines.push("");
68018
- }
68019
- lines.push("---");
68020
- lines.push("");
68021
- return lines.join(`
68022
- `);
68023
- }
68024
-
68025
- // src/context/generators/aider.ts
68026
- function generateAiderConfig(context) {
68027
- const header = `# Aider Configuration
68028
- # Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
68029
- # DO NOT EDIT MANUALLY
68030
-
68031
- # Project instructions
68032
- instructions: |
68033
- `;
68034
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68035
- const combined = metaSection + context.markdown;
68036
- const indented = combined.split(`
68037
- `).map((line) => ` ${line}`).join(`
68038
- `);
68039
- return `${header}${indented}
68040
- `;
68041
- }
68042
- var aiderGenerator = {
68043
- name: "aider",
68044
- outputFile: ".aider.conf.yml",
68045
- generate: generateAiderConfig
68046
- };
68047
-
68048
- // src/context/generators/claude.ts
68049
- function generateClaudeConfig(context) {
68050
- const header = `# Project Context
68051
-
68052
- This file is auto-generated from \`nax/context.md\`.
68053
- DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
68054
-
68055
- ---
68056
-
68057
- `;
68058
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68059
- return header + metaSection + context.markdown;
68060
- }
68061
- var claudeGenerator = {
68062
- name: "claude",
68063
- outputFile: "CLAUDE.md",
68064
- generate: generateClaudeConfig
68065
- };
68066
-
68067
- // src/context/generators/codex.ts
68068
- function generateCodexConfig(context) {
68069
- const header = `# Codex Instructions
68070
-
68071
- This file is auto-generated from \`nax/context.md\`.
68072
- DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
68073
-
68074
- ---
68075
-
68076
- `;
68077
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68078
- return header + metaSection + context.markdown;
68079
- }
68080
- var codexGenerator = {
68081
- name: "codex",
68082
- outputFile: "codex.md",
68083
- generate: generateCodexConfig
68084
- };
68085
-
68086
- // src/context/generators/cursor.ts
68087
- function generateCursorRules(context) {
68088
- const header = `# Project Rules
68089
-
68090
- Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
68091
- DO NOT EDIT MANUALLY
68092
-
68093
- ---
68094
-
68095
- `;
68096
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68097
- return header + metaSection + context.markdown;
68098
- }
68099
- var cursorGenerator = {
68100
- name: "cursor",
68101
- outputFile: ".cursorrules",
68102
- generate: generateCursorRules
68103
- };
68104
-
68105
- // src/context/generators/gemini.ts
68106
- function generateGeminiConfig(context) {
68107
- const header = `# Gemini CLI Context
68108
-
68109
- This file is auto-generated from \`nax/context.md\`.
68110
- DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
68111
-
68112
- ---
68113
-
68114
- `;
68115
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68116
- return header + metaSection + context.markdown;
68117
- }
68118
- var geminiGenerator = {
68119
- name: "gemini",
68120
- outputFile: "GEMINI.md",
68121
- generate: generateGeminiConfig
68122
- };
68123
-
68124
- // src/context/generators/opencode.ts
68125
- function generateOpencodeConfig(context) {
68126
- const header = `# Agent Instructions
68127
-
68128
- This file is auto-generated from \`nax/context.md\`.
68129
- DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
68130
-
68131
- These instructions apply to all AI coding agents in this project.
68132
-
68133
- ---
68134
-
68135
- `;
68136
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68137
- return header + metaSection + context.markdown;
68138
- }
68139
- var opencodeGenerator = {
68140
- name: "opencode",
68141
- outputFile: "AGENTS.md",
68142
- generate: generateOpencodeConfig
68143
- };
68144
-
68145
- // src/context/generators/windsurf.ts
68146
- function generateWindsurfRules(context) {
68147
- const header = `# Windsurf Project Rules
68148
-
68149
- Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
68150
- DO NOT EDIT MANUALLY
68151
-
68152
- ---
68153
-
68154
- `;
68155
- const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
68156
- return header + metaSection + context.markdown;
68157
- }
68158
- var windsurfGenerator = {
68159
- name: "windsurf",
68160
- outputFile: ".windsurfrules",
68161
- generate: generateWindsurfRules
68162
- };
68163
-
68164
- // src/context/generator.ts
68165
- var GENERATORS = {
68166
- claude: claudeGenerator,
68167
- codex: codexGenerator,
68168
- opencode: opencodeGenerator,
68169
- cursor: cursorGenerator,
68170
- windsurf: windsurfGenerator,
68171
- aider: aiderGenerator,
68172
- gemini: geminiGenerator
68173
- };
68174
- async function loadContextContent(options, config2) {
68175
- if (!existsSync19(options.contextPath)) {
68176
- throw new Error(`Context file not found: ${options.contextPath}`);
68177
- }
68178
- const file2 = Bun.file(options.contextPath);
68179
- const markdown = await file2.text();
68180
- const autoInject = options.autoInject ?? true;
68181
- const metadata = autoInject ? await buildProjectMetadata(options.workdir, config2) : undefined;
68182
- return { markdown, metadata };
68183
- }
68184
- async function generateFor(agent, options, config2) {
68185
- const generator = GENERATORS[agent];
68186
- if (!generator) {
68187
- return { agent, outputFile: "", content: "", written: false, error: `Unknown agent: ${agent}` };
68188
- }
68189
- try {
68190
- const context = await loadContextContent(options, config2);
68191
- const content = generator.generate(context);
68192
- const outputPath = join27(options.outputDir, generator.outputFile);
68193
- validateFilePath(outputPath, options.outputDir);
68194
- if (!options.dryRun) {
68195
- await Bun.write(outputPath, content);
68651
+ if (errorCount > 0) {
68652
+ console.error(source_default.red(`
68653
+ \u2717 ${errorCount} generation(s) failed`));
68654
+ process.exit(1);
68196
68655
  }
68197
- return { agent, outputFile: generator.outputFile, content, written: !options.dryRun };
68198
- } catch (err) {
68199
- const error48 = err instanceof Error ? err.message : String(err);
68200
- return { agent, outputFile: generator.outputFile, content: "", written: false, error: error48 };
68656
+ return;
68201
68657
  }
68202
- }
68203
- async function generateAll(options, config2) {
68204
- const context = await loadContextContent(options, config2);
68205
- const results = [];
68206
- for (const [agentKey, generator] of Object.entries(GENERATORS)) {
68207
- try {
68208
- const content = generator.generate(context);
68209
- const outputPath = join27(options.outputDir, generator.outputFile);
68210
- validateFilePath(outputPath, options.outputDir);
68211
- if (!options.dryRun) {
68212
- await Bun.write(outputPath, content);
68213
- }
68214
- results.push({ agent: agentKey, outputFile: generator.outputFile, content, written: !options.dryRun });
68215
- } catch (err) {
68216
- const error48 = err instanceof Error ? err.message : String(err);
68217
- results.push({ agent: agentKey, outputFile: generator.outputFile, content: "", written: false, error: error48 });
68658
+ if (options.package) {
68659
+ const packageDir = join33(workdir, options.package);
68660
+ if (dryRun) {
68661
+ console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
68662
+ }
68663
+ console.log(source_default.blue(`\u2192 Generating CLAUDE.md for package: ${options.package}`));
68664
+ const result = await generateForPackage(packageDir, config2, dryRun);
68665
+ if (result.error) {
68666
+ console.error(source_default.red(`\u2717 ${result.error}`));
68667
+ process.exit(1);
68218
68668
  }
68669
+ const suffix = dryRun ? " (dry run)" : "";
68670
+ console.log(source_default.green(`\u2713 ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
68671
+ return;
68219
68672
  }
68220
- return results;
68221
- }
68222
-
68223
- // src/cli/generate.ts
68224
- var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68225
- async function generateCommand(options) {
68226
- const workdir = process.cwd();
68227
- const contextPath = options.context ? join28(workdir, options.context) : join28(workdir, "nax/context.md");
68228
- const outputDir = options.output ? join28(workdir, options.output) : workdir;
68673
+ const contextPath = options.context ? join33(workdir, options.context) : join33(workdir, "nax/context.md");
68674
+ const outputDir = options.output ? join33(workdir, options.output) : workdir;
68229
68675
  const autoInject = !options.noAutoInject;
68230
- const dryRun = options.dryRun ?? false;
68231
- if (!existsSync20(contextPath)) {
68676
+ if (!existsSync22(contextPath)) {
68232
68677
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
68233
68678
  console.error(source_default.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
68234
68679
  process.exit(1);
@@ -68245,12 +68690,6 @@ async function generateCommand(options) {
68245
68690
  if (autoInject) {
68246
68691
  console.log(source_default.dim(" Auto-injecting project metadata..."));
68247
68692
  }
68248
- let config2;
68249
- try {
68250
- config2 = await loadConfig(workdir);
68251
- } catch {
68252
- config2 = {};
68253
- }
68254
68693
  const genOptions = {
68255
68694
  contextPath,
68256
68695
  outputDir,
@@ -68300,8 +68739,8 @@ async function generateCommand(options) {
68300
68739
  }
68301
68740
  // src/cli/config-display.ts
68302
68741
  init_loader2();
68303
- import { existsSync as existsSync22 } from "fs";
68304
- import { join as join30 } from "path";
68742
+ import { existsSync as existsSync24 } from "fs";
68743
+ import { join as join35 } from "path";
68305
68744
 
68306
68745
  // src/cli/config-descriptions.ts
68307
68746
  var FIELD_DESCRIPTIONS = {
@@ -68509,10 +68948,10 @@ function deepEqual(a, b) {
68509
68948
  // src/cli/config-get.ts
68510
68949
  init_defaults();
68511
68950
  init_loader2();
68512
- import { existsSync as existsSync21 } from "fs";
68513
- import { join as join29 } from "path";
68951
+ import { existsSync as existsSync23 } from "fs";
68952
+ import { join as join34 } from "path";
68514
68953
  async function loadConfigFile(path14) {
68515
- if (!existsSync21(path14))
68954
+ if (!existsSync23(path14))
68516
68955
  return null;
68517
68956
  try {
68518
68957
  return await Bun.file(path14).json();
@@ -68532,7 +68971,7 @@ async function loadProjectConfig() {
68532
68971
  const projectDir = findProjectDir();
68533
68972
  if (!projectDir)
68534
68973
  return null;
68535
- const projectPath = join29(projectDir, "config.json");
68974
+ const projectPath = join34(projectDir, "config.json");
68536
68975
  return await loadConfigFile(projectPath);
68537
68976
  }
68538
68977
 
@@ -68592,14 +69031,14 @@ async function configCommand(config2, options = {}) {
68592
69031
  function determineConfigSources() {
68593
69032
  const globalPath = globalConfigPath();
68594
69033
  const projectDir = findProjectDir();
68595
- const projectPath = projectDir ? join30(projectDir, "config.json") : null;
69034
+ const projectPath = projectDir ? join35(projectDir, "config.json") : null;
68596
69035
  return {
68597
69036
  global: fileExists(globalPath) ? globalPath : null,
68598
69037
  project: projectPath && fileExists(projectPath) ? projectPath : null
68599
69038
  };
68600
69039
  }
68601
69040
  function fileExists(path14) {
68602
- return existsSync22(path14);
69041
+ return existsSync24(path14);
68603
69042
  }
68604
69043
  function displayConfigWithDescriptions(obj, path14, sources, indent = 0) {
68605
69044
  const indentStr = " ".repeat(indent);
@@ -68771,25 +69210,25 @@ async function diagnose(options) {
68771
69210
  }
68772
69211
 
68773
69212
  // src/commands/logs.ts
68774
- import { existsSync as existsSync24 } from "fs";
68775
- import { join as join33 } from "path";
69213
+ import { existsSync as existsSync26 } from "fs";
69214
+ import { join as join38 } from "path";
68776
69215
 
68777
69216
  // src/commands/logs-formatter.ts
68778
69217
  init_source();
68779
69218
  init_formatter();
68780
69219
  import { readdirSync as readdirSync7 } from "fs";
68781
- import { join as join32 } from "path";
69220
+ import { join as join37 } from "path";
68782
69221
 
68783
69222
  // src/commands/logs-reader.ts
68784
- import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
69223
+ import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
68785
69224
  import { readdir as readdir3 } from "fs/promises";
68786
69225
  import { homedir as homedir5 } from "os";
68787
- import { join as join31 } from "path";
68788
- var _deps6 = {
68789
- getRunsDir: () => process.env.NAX_RUNS_DIR ?? join31(homedir5(), ".nax", "runs")
69226
+ import { join as join36 } from "path";
69227
+ var _deps7 = {
69228
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join36(homedir5(), ".nax", "runs")
68790
69229
  };
68791
69230
  async function resolveRunFileFromRegistry(runId) {
68792
- const runsDir = _deps6.getRunsDir();
69231
+ const runsDir = _deps7.getRunsDir();
68793
69232
  let entries;
68794
69233
  try {
68795
69234
  entries = await readdir3(runsDir);
@@ -68798,7 +69237,7 @@ async function resolveRunFileFromRegistry(runId) {
68798
69237
  }
68799
69238
  let matched = null;
68800
69239
  for (const entry of entries) {
68801
- const metaPath = join31(runsDir, entry, "meta.json");
69240
+ const metaPath = join36(runsDir, entry, "meta.json");
68802
69241
  try {
68803
69242
  const meta3 = await Bun.file(metaPath).json();
68804
69243
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -68810,7 +69249,7 @@ async function resolveRunFileFromRegistry(runId) {
68810
69249
  if (!matched) {
68811
69250
  throw new Error(`Run not found in registry: ${runId}`);
68812
69251
  }
68813
- if (!existsSync23(matched.eventsDir)) {
69252
+ if (!existsSync25(matched.eventsDir)) {
68814
69253
  console.log(`Log directory unavailable for run: ${runId}`);
68815
69254
  return null;
68816
69255
  }
@@ -68820,14 +69259,14 @@ async function resolveRunFileFromRegistry(runId) {
68820
69259
  return null;
68821
69260
  }
68822
69261
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
68823
- return join31(matched.eventsDir, specificFile ?? files[0]);
69262
+ return join36(matched.eventsDir, specificFile ?? files[0]);
68824
69263
  }
68825
69264
  async function selectRunFile(runsDir) {
68826
69265
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
68827
69266
  if (files.length === 0) {
68828
69267
  return null;
68829
69268
  }
68830
- return join31(runsDir, files[0]);
69269
+ return join36(runsDir, files[0]);
68831
69270
  }
68832
69271
  async function extractRunSummary(filePath) {
68833
69272
  const file2 = Bun.file(filePath);
@@ -68912,7 +69351,7 @@ Runs:
68912
69351
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
68913
69352
  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"));
68914
69353
  for (const file2 of files) {
68915
- const filePath = join32(runsDir, file2);
69354
+ const filePath = join37(runsDir, file2);
68916
69355
  const summary = await extractRunSummary(filePath);
68917
69356
  const timestamp = file2.replace(".jsonl", "");
68918
69357
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -69037,7 +69476,7 @@ async function logsCommand(options) {
69037
69476
  return;
69038
69477
  }
69039
69478
  const resolved = resolveProject({ dir: options.dir });
69040
- const naxDir = join33(resolved.projectDir, "nax");
69479
+ const naxDir = join38(resolved.projectDir, "nax");
69041
69480
  const configPath = resolved.configPath;
69042
69481
  const configFile = Bun.file(configPath);
69043
69482
  const config2 = await configFile.json();
@@ -69045,9 +69484,9 @@ async function logsCommand(options) {
69045
69484
  if (!featureName) {
69046
69485
  throw new Error("No feature specified in config.json");
69047
69486
  }
69048
- const featureDir = join33(naxDir, "features", featureName);
69049
- const runsDir = join33(featureDir, "runs");
69050
- if (!existsSync24(runsDir)) {
69487
+ const featureDir = join38(naxDir, "features", featureName);
69488
+ const runsDir = join38(featureDir, "runs");
69489
+ if (!existsSync26(runsDir)) {
69051
69490
  throw new Error(`No runs directory found for feature: ${featureName}`);
69052
69491
  }
69053
69492
  if (options.list) {
@@ -69070,8 +69509,8 @@ init_source();
69070
69509
  init_config();
69071
69510
  init_prd();
69072
69511
  init_precheck();
69073
- import { existsSync as existsSync29 } from "fs";
69074
- import { join as join34 } from "path";
69512
+ import { existsSync as existsSync31 } from "fs";
69513
+ import { join as join39 } from "path";
69075
69514
  async function precheckCommand(options) {
69076
69515
  const resolved = resolveProject({
69077
69516
  dir: options.dir,
@@ -69087,14 +69526,14 @@ async function precheckCommand(options) {
69087
69526
  process.exit(1);
69088
69527
  }
69089
69528
  }
69090
- const naxDir = join34(resolved.projectDir, "nax");
69091
- const featureDir = join34(naxDir, "features", featureName);
69092
- const prdPath = join34(featureDir, "prd.json");
69093
- if (!existsSync29(featureDir)) {
69529
+ const naxDir = join39(resolved.projectDir, "nax");
69530
+ const featureDir = join39(naxDir, "features", featureName);
69531
+ const prdPath = join39(featureDir, "prd.json");
69532
+ if (!existsSync31(featureDir)) {
69094
69533
  console.error(source_default.red(`Feature not found: ${featureName}`));
69095
69534
  process.exit(1);
69096
69535
  }
69097
- if (!existsSync29(prdPath)) {
69536
+ if (!existsSync31(prdPath)) {
69098
69537
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
69099
69538
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
69100
69539
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -69113,10 +69552,10 @@ async function precheckCommand(options) {
69113
69552
  init_source();
69114
69553
  import { readdir as readdir4 } from "fs/promises";
69115
69554
  import { homedir as homedir6 } from "os";
69116
- import { join as join35 } from "path";
69555
+ import { join as join40 } from "path";
69117
69556
  var DEFAULT_LIMIT = 20;
69118
- var _deps8 = {
69119
- getRunsDir: () => join35(homedir6(), ".nax", "runs")
69557
+ var _deps9 = {
69558
+ getRunsDir: () => join40(homedir6(), ".nax", "runs")
69120
69559
  };
69121
69560
  function formatDuration3(ms) {
69122
69561
  if (ms <= 0)
@@ -69158,7 +69597,7 @@ function pad3(str, width) {
69158
69597
  return str + " ".repeat(padding);
69159
69598
  }
69160
69599
  async function runsCommand(options = {}) {
69161
- const runsDir = _deps8.getRunsDir();
69600
+ const runsDir = _deps9.getRunsDir();
69162
69601
  let entries;
69163
69602
  try {
69164
69603
  entries = await readdir4(runsDir);
@@ -69168,7 +69607,7 @@ async function runsCommand(options = {}) {
69168
69607
  }
69169
69608
  const rows = [];
69170
69609
  for (const entry of entries) {
69171
- const metaPath = join35(runsDir, entry, "meta.json");
69610
+ const metaPath = join40(runsDir, entry, "meta.json");
69172
69611
  let meta3;
69173
69612
  try {
69174
69613
  meta3 = await Bun.file(metaPath).json();
@@ -69245,7 +69684,7 @@ async function runsCommand(options = {}) {
69245
69684
 
69246
69685
  // src/commands/unlock.ts
69247
69686
  init_source();
69248
- import { join as join36 } from "path";
69687
+ import { join as join41 } from "path";
69249
69688
  function isProcessAlive3(pid) {
69250
69689
  try {
69251
69690
  process.kill(pid, 0);
@@ -69260,7 +69699,7 @@ function formatLockAge(ageMs) {
69260
69699
  }
69261
69700
  async function unlockCommand(options) {
69262
69701
  const workdir = options.dir ?? process.cwd();
69263
- const lockPath = join36(workdir, "nax.lock");
69702
+ const lockPath = join41(workdir, "nax.lock");
69264
69703
  const lockFile = Bun.file(lockPath);
69265
69704
  const exists = await lockFile.exists();
69266
69705
  if (!exists) {
@@ -77072,7 +77511,7 @@ async function promptForConfirmation(question) {
77072
77511
  process.stdin.on("data", handler);
77073
77512
  });
77074
77513
  }
77075
- program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).action(async (options) => {
77514
+ program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).option("--package <dir>", "Scaffold per-package nax/context.md (e.g. packages/api)").action(async (options) => {
77076
77515
  let workdir;
77077
77516
  try {
77078
77517
  workdir = validateDirectory(options.dir);
@@ -77080,15 +77519,30 @@ program2.command("init").description("Initialize nax in the current project").op
77080
77519
  console.error(source_default.red(`Invalid directory: ${err.message}`));
77081
77520
  process.exit(1);
77082
77521
  }
77083
- const naxDir = join43(workdir, "nax");
77084
- if (existsSync32(naxDir) && !options.force) {
77522
+ if (options.package) {
77523
+ const { initPackage: initPkg } = await Promise.resolve().then(() => (init_init_context(), exports_init_context));
77524
+ try {
77525
+ await initPkg(workdir, options.package, options.force);
77526
+ console.log(source_default.green(`
77527
+ [OK] Package scaffold created.`));
77528
+ console.log(source_default.dim(` Created: ${options.package}/nax/context.md`));
77529
+ console.log(source_default.dim(`
77530
+ Next: nax generate --package ${options.package}`));
77531
+ } catch (err) {
77532
+ console.error(source_default.red(`Error: ${err.message}`));
77533
+ process.exit(1);
77534
+ }
77535
+ return;
77536
+ }
77537
+ const naxDir = join48(workdir, "nax");
77538
+ if (existsSync34(naxDir) && !options.force) {
77085
77539
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
77086
77540
  return;
77087
77541
  }
77088
- mkdirSync6(join43(naxDir, "features"), { recursive: true });
77089
- mkdirSync6(join43(naxDir, "hooks"), { recursive: true });
77090
- await Bun.write(join43(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77091
- await Bun.write(join43(naxDir, "hooks.json"), JSON.stringify({
77542
+ mkdirSync6(join48(naxDir, "features"), { recursive: true });
77543
+ mkdirSync6(join48(naxDir, "hooks"), { recursive: true });
77544
+ await Bun.write(join48(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77545
+ await Bun.write(join48(naxDir, "hooks.json"), JSON.stringify({
77092
77546
  hooks: {
77093
77547
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
77094
77548
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -77096,12 +77550,12 @@ program2.command("init").description("Initialize nax in the current project").op
77096
77550
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
77097
77551
  }
77098
77552
  }, null, 2));
77099
- await Bun.write(join43(naxDir, ".gitignore"), `# nax temp files
77553
+ await Bun.write(join48(naxDir, ".gitignore"), `# nax temp files
77100
77554
  *.tmp
77101
77555
  .paused.json
77102
77556
  .nax-verifier-verdict.json
77103
77557
  `);
77104
- await Bun.write(join43(naxDir, "context.md"), `# Project Context
77558
+ await Bun.write(join48(naxDir, "context.md"), `# Project Context
77105
77559
 
77106
77560
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
77107
77561
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -77198,7 +77652,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77198
77652
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
77199
77653
  process.exit(1);
77200
77654
  }
77201
- if (options.from && !existsSync32(options.from)) {
77655
+ if (options.from && !existsSync34(options.from)) {
77202
77656
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
77203
77657
  process.exit(1);
77204
77658
  }
@@ -77227,10 +77681,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77227
77681
  console.error(source_default.red("nax not initialized. Run: nax init"));
77228
77682
  process.exit(1);
77229
77683
  }
77230
- const featureDir = join43(naxDir, "features", options.feature);
77231
- const prdPath = join43(featureDir, "prd.json");
77684
+ const featureDir = join48(naxDir, "features", options.feature);
77685
+ const prdPath = join48(featureDir, "prd.json");
77232
77686
  if (options.plan && options.from) {
77233
- if (existsSync32(prdPath) && !options.force) {
77687
+ if (existsSync34(prdPath) && !options.force) {
77234
77688
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
77235
77689
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
77236
77690
  process.exit(1);
@@ -77250,10 +77704,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77250
77704
  }
77251
77705
  }
77252
77706
  try {
77253
- const planLogDir = join43(featureDir, "plan");
77707
+ const planLogDir = join48(featureDir, "plan");
77254
77708
  mkdirSync6(planLogDir, { recursive: true });
77255
77709
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77256
- const planLogPath = join43(planLogDir, `${planLogId}.jsonl`);
77710
+ const planLogPath = join48(planLogDir, `${planLogId}.jsonl`);
77257
77711
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77258
77712
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77259
77713
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -77286,15 +77740,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
77286
77740
  process.exit(1);
77287
77741
  }
77288
77742
  }
77289
- if (!existsSync32(prdPath)) {
77743
+ if (!existsSync34(prdPath)) {
77290
77744
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
77291
77745
  process.exit(1);
77292
77746
  }
77293
77747
  resetLogger();
77294
- const runsDir = join43(featureDir, "runs");
77748
+ const runsDir = join48(featureDir, "runs");
77295
77749
  mkdirSync6(runsDir, { recursive: true });
77296
77750
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77297
- const logFilePath = join43(runsDir, `${runId}.jsonl`);
77751
+ const logFilePath = join48(runsDir, `${runId}.jsonl`);
77298
77752
  const isTTY = process.stdout.isTTY ?? false;
77299
77753
  const headlessFlag = options.headless ?? false;
77300
77754
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -77310,7 +77764,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77310
77764
  config2.autoMode.defaultAgent = options.agent;
77311
77765
  }
77312
77766
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
77313
- const globalNaxDir = join43(homedir10(), ".nax");
77767
+ const globalNaxDir = join48(homedir10(), ".nax");
77314
77768
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
77315
77769
  const eventEmitter = new PipelineEventEmitter;
77316
77770
  let tuiInstance;
@@ -77333,7 +77787,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77333
77787
  } else {
77334
77788
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
77335
77789
  }
77336
- const statusFilePath = join43(workdir, "nax", "status.json");
77790
+ const statusFilePath = join48(workdir, "nax", "status.json");
77337
77791
  let parallel;
77338
77792
  if (options.parallel !== undefined) {
77339
77793
  parallel = Number.parseInt(options.parallel, 10);
@@ -77359,9 +77813,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
77359
77813
  headless: useHeadless,
77360
77814
  skipPrecheck: options.skipPrecheck ?? false
77361
77815
  });
77362
- const latestSymlink = join43(runsDir, "latest.jsonl");
77816
+ const latestSymlink = join48(runsDir, "latest.jsonl");
77363
77817
  try {
77364
- if (existsSync32(latestSymlink)) {
77818
+ if (existsSync34(latestSymlink)) {
77365
77819
  Bun.spawnSync(["rm", latestSymlink]);
77366
77820
  }
77367
77821
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -77397,9 +77851,9 @@ features.command("create <name>").description("Create a new feature").option("-d
77397
77851
  console.error(source_default.red("nax not initialized. Run: nax init"));
77398
77852
  process.exit(1);
77399
77853
  }
77400
- const featureDir = join43(naxDir, "features", name);
77854
+ const featureDir = join48(naxDir, "features", name);
77401
77855
  mkdirSync6(featureDir, { recursive: true });
77402
- await Bun.write(join43(featureDir, "spec.md"), `# Feature: ${name}
77856
+ await Bun.write(join48(featureDir, "spec.md"), `# Feature: ${name}
77403
77857
 
77404
77858
  ## Overview
77405
77859
 
@@ -77407,7 +77861,7 @@ features.command("create <name>").description("Create a new feature").option("-d
77407
77861
 
77408
77862
  ## Acceptance Criteria
77409
77863
  `);
77410
- await Bun.write(join43(featureDir, "plan.md"), `# Plan: ${name}
77864
+ await Bun.write(join48(featureDir, "plan.md"), `# Plan: ${name}
77411
77865
 
77412
77866
  ## Architecture
77413
77867
 
@@ -77415,7 +77869,7 @@ features.command("create <name>").description("Create a new feature").option("-d
77415
77869
 
77416
77870
  ## Dependencies
77417
77871
  `);
77418
- await Bun.write(join43(featureDir, "tasks.md"), `# Tasks: ${name}
77872
+ await Bun.write(join48(featureDir, "tasks.md"), `# Tasks: ${name}
77419
77873
 
77420
77874
  ## US-001: [Title]
77421
77875
 
@@ -77424,7 +77878,7 @@ features.command("create <name>").description("Create a new feature").option("-d
77424
77878
  ### Acceptance Criteria
77425
77879
  - [ ] Criterion 1
77426
77880
  `);
77427
- await Bun.write(join43(featureDir, "progress.txt"), `# Progress: ${name}
77881
+ await Bun.write(join48(featureDir, "progress.txt"), `# Progress: ${name}
77428
77882
 
77429
77883
  Created: ${new Date().toISOString()}
77430
77884
 
@@ -77452,8 +77906,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
77452
77906
  console.error(source_default.red("nax not initialized."));
77453
77907
  process.exit(1);
77454
77908
  }
77455
- const featuresDir = join43(naxDir, "features");
77456
- if (!existsSync32(featuresDir)) {
77909
+ const featuresDir = join48(naxDir, "features");
77910
+ if (!existsSync34(featuresDir)) {
77457
77911
  console.log(source_default.dim("No features yet."));
77458
77912
  return;
77459
77913
  }
@@ -77467,8 +77921,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
77467
77921
  Features:
77468
77922
  `));
77469
77923
  for (const name of entries) {
77470
- const prdPath = join43(featuresDir, name, "prd.json");
77471
- if (existsSync32(prdPath)) {
77924
+ const prdPath = join48(featuresDir, name, "prd.json");
77925
+ if (existsSync34(prdPath)) {
77472
77926
  const prd = await loadPRD(prdPath);
77473
77927
  const c = countStories(prd);
77474
77928
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -77498,10 +77952,10 @@ Use: nax plan -f <feature> --from <spec>`));
77498
77952
  process.exit(1);
77499
77953
  }
77500
77954
  const config2 = await loadConfig(workdir);
77501
- const featureLogDir = join43(naxDir, "features", options.feature, "plan");
77955
+ const featureLogDir = join48(naxDir, "features", options.feature, "plan");
77502
77956
  mkdirSync6(featureLogDir, { recursive: true });
77503
77957
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77504
- const planLogPath = join43(featureLogDir, `${planLogId}.jsonl`);
77958
+ const planLogPath = join48(featureLogDir, `${planLogId}.jsonl`);
77505
77959
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77506
77960
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77507
77961
  try {
@@ -77538,8 +77992,8 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
77538
77992
  console.error(source_default.red("nax not initialized. Run: nax init"));
77539
77993
  process.exit(1);
77540
77994
  }
77541
- const featureDir = join43(naxDir, "features", options.feature);
77542
- if (!existsSync32(featureDir)) {
77995
+ const featureDir = join48(naxDir, "features", options.feature);
77996
+ if (!existsSync34(featureDir)) {
77543
77997
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
77544
77998
  process.exit(1);
77545
77999
  }
@@ -77554,7 +78008,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
77554
78008
  specPath: options.from,
77555
78009
  reclassify: options.reclassify
77556
78010
  });
77557
- const prdPath = join43(featureDir, "prd.json");
78011
+ const prdPath = join48(featureDir, "prd.json");
77558
78012
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
77559
78013
  const c = countStories(prd);
77560
78014
  console.log(source_default.green(`
@@ -77782,14 +78236,16 @@ program2.command("prompts").description("Assemble or initialize prompts").option
77782
78236
  process.exit(1);
77783
78237
  }
77784
78238
  });
77785
- program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").action(async (options) => {
78239
+ program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
77786
78240
  try {
77787
78241
  await generateCommand({
77788
78242
  context: options.context,
77789
78243
  output: options.output,
77790
78244
  agent: options.agent,
77791
78245
  dryRun: options.dryRun,
77792
- noAutoInject: !options.autoInject
78246
+ noAutoInject: !options.autoInject,
78247
+ package: options.package,
78248
+ allPackages: options.allPackages
77793
78249
  });
77794
78250
  } catch (err) {
77795
78251
  console.error(source_default.red(`Error: ${err.message}`));