@nathapp/nax 0.46.3 → 0.48.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
@@ -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.3",
22213
+ version: "0.48.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("37f8759"))
22255
- return "37f8759";
22286
+ if (/^[0-9a-f]{6,10}$/.test("3188738"))
22287
+ return "3188738";
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,528 @@ 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, readFileSync } 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 discoverWorkspacePackages(repoRoot) {
66856
+ const existing = await discoverPackages(repoRoot);
66857
+ if (existing.length > 0) {
66858
+ return existing.map((p) => p.replace(`${repoRoot}/`, ""));
66859
+ }
66860
+ const seen = new Set;
66861
+ const results = [];
66862
+ async function resolveGlobs(patterns) {
66863
+ for (const pattern of patterns) {
66864
+ if (pattern.startsWith("!"))
66865
+ continue;
66866
+ const base = pattern.replace(/\/+$/, "");
66867
+ const pkgPattern = base.endsWith("*") ? `${base}/package.json` : `${base}/*/package.json`;
66868
+ const g = new Bun.Glob(pkgPattern);
66869
+ for await (const match of g.scan(repoRoot)) {
66870
+ const rel = match.replace(/\/package\.json$/, "");
66871
+ if (!seen.has(rel)) {
66872
+ seen.add(rel);
66873
+ results.push(rel);
66874
+ }
66875
+ }
66876
+ }
66877
+ }
66878
+ const turboPath = join11(repoRoot, "turbo.json");
66879
+ if (existsSync10(turboPath)) {
66880
+ try {
66881
+ const turbo = JSON.parse(readFileSync(turboPath, "utf-8"));
66882
+ if (Array.isArray(turbo.packages)) {
66883
+ await resolveGlobs(turbo.packages);
66884
+ }
66885
+ } catch {}
66886
+ }
66887
+ const pkgPath = join11(repoRoot, "package.json");
66888
+ if (existsSync10(pkgPath)) {
66889
+ try {
66890
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
66891
+ const ws = pkg.workspaces;
66892
+ const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
66893
+ if (patterns.length > 0)
66894
+ await resolveGlobs(patterns);
66895
+ } catch {}
66896
+ }
66897
+ const pnpmPath = join11(repoRoot, "pnpm-workspace.yaml");
66898
+ if (existsSync10(pnpmPath)) {
66899
+ try {
66900
+ const raw = readFileSync(pnpmPath, "utf-8");
66901
+ const lines = raw.split(`
66902
+ `);
66903
+ let inPackages = false;
66904
+ const patterns = [];
66905
+ for (const line of lines) {
66906
+ if (/^packages\s*:/.test(line)) {
66907
+ inPackages = true;
66908
+ continue;
66909
+ }
66910
+ if (inPackages && /^\s+-\s+/.test(line)) {
66911
+ patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
66912
+ } else if (inPackages && !/^\s/.test(line)) {
66913
+ break;
66914
+ }
66915
+ }
66916
+ if (patterns.length > 0)
66917
+ await resolveGlobs(patterns);
66918
+ } catch {}
66919
+ }
66920
+ return results.sort();
66921
+ }
66922
+ async function generateForPackage(packageDir, config2, dryRun = false) {
66923
+ const contextPath = join11(packageDir, "nax", "context.md");
66924
+ if (!existsSync10(contextPath)) {
66925
+ return {
66926
+ packageDir,
66927
+ outputFile: "CLAUDE.md",
66928
+ content: "",
66929
+ written: false,
66930
+ error: `context.md not found: ${contextPath}`
66931
+ };
66932
+ }
66933
+ try {
66934
+ const options = {
66935
+ contextPath,
66936
+ outputDir: packageDir,
66937
+ workdir: packageDir,
66938
+ dryRun,
66939
+ autoInject: true
66940
+ };
66941
+ const result = await generateFor("claude", options, config2);
66942
+ return {
66943
+ packageDir,
66944
+ outputFile: result.outputFile,
66945
+ content: result.content,
66946
+ written: result.written,
66947
+ error: result.error
66948
+ };
66949
+ } catch (err) {
66950
+ const error48 = err instanceof Error ? err.message : String(err);
66951
+ return { packageDir, outputFile: "CLAUDE.md", content: "", written: false, error: error48 };
66952
+ }
66953
+ }
66954
+
66955
+ // src/cli/plan.ts
66120
66956
  init_pid_registry();
66121
66957
  init_logger2();
66122
66958
 
@@ -66202,6 +67038,20 @@ function validateStory(raw, index, allIds) {
66202
67038
  }
66203
67039
  const rawTags = s.tags;
66204
67040
  const tags = Array.isArray(rawTags) ? rawTags : [];
67041
+ const rawWorkdir = s.workdir;
67042
+ let workdir;
67043
+ if (rawWorkdir !== undefined && rawWorkdir !== null) {
67044
+ if (typeof rawWorkdir !== "string") {
67045
+ throw new Error(`[schema] story[${index}].workdir must be a string`);
67046
+ }
67047
+ if (rawWorkdir.startsWith("/")) {
67048
+ throw new Error(`[schema] story[${index}].workdir must be relative (no leading /): "${rawWorkdir}"`);
67049
+ }
67050
+ if (rawWorkdir.includes("..")) {
67051
+ throw new Error(`[schema] story[${index}].workdir must not contain '..': "${rawWorkdir}"`);
67052
+ }
67053
+ workdir = rawWorkdir;
67054
+ }
66205
67055
  return {
66206
67056
  id,
66207
67057
  title: title.trim(),
@@ -66217,7 +67067,8 @@ function validateStory(raw, index, allIds) {
66217
67067
  complexity,
66218
67068
  testStrategy,
66219
67069
  reasoning: "validated from LLM output"
66220
- }
67070
+ },
67071
+ ...workdir !== undefined ? { workdir } : {}
66221
67072
  };
66222
67073
  }
66223
67074
  function parseRawString(text) {
@@ -66268,36 +67119,47 @@ var _deps2 = {
66268
67119
  writeFile: (path, content) => Bun.write(path, content).then(() => {}),
66269
67120
  scanCodebase: (workdir) => scanCodebase(workdir),
66270
67121
  getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
66271
- readPackageJson: (workdir) => Bun.file(join10(workdir, "package.json")).json().catch(() => null),
67122
+ readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
66272
67123
  spawnSync: (cmd, opts) => {
66273
67124
  const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
66274
67125
  return { stdout: result.stdout, exitCode: result.exitCode };
66275
67126
  },
66276
67127
  mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {}),
66277
- existsSync: (path) => existsSync9(path)
67128
+ existsSync: (path) => existsSync11(path),
67129
+ discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages(repoRoot),
67130
+ readPackageJsonAt: (path) => Bun.file(path).json().catch(() => null),
67131
+ createInteractionBridge: () => createCliInteractionBridge()
66278
67132
  };
66279
67133
  async function planCommand(workdir, config2, options) {
66280
- const naxDir = join10(workdir, "nax");
66281
- if (!existsSync9(naxDir)) {
67134
+ const naxDir = join12(workdir, "nax");
67135
+ if (!existsSync11(naxDir)) {
66282
67136
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
66283
67137
  }
66284
67138
  const logger = getLogger();
66285
67139
  logger?.info("plan", "Reading spec", { from: options.from });
66286
67140
  const specContent = await _deps2.readFile(options.from);
66287
67141
  logger?.info("plan", "Scanning codebase...");
66288
- const scan = await _deps2.scanCodebase(workdir);
67142
+ const [scan, discoveredPackages, pkg] = await Promise.all([
67143
+ _deps2.scanCodebase(workdir),
67144
+ _deps2.discoverWorkspacePackages(workdir),
67145
+ _deps2.readPackageJson(workdir)
67146
+ ]);
66289
67147
  const codebaseContext = buildCodebaseContext2(scan);
66290
- const pkg = await _deps2.readPackageJson(workdir);
67148
+ const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
67149
+ const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
67150
+ const pkgJson = await _deps2.readPackageJsonAt(join12(workdir, rel, "package.json"));
67151
+ return buildPackageSummary(rel, pkgJson);
67152
+ })) : [];
66291
67153
  const projectName = detectProjectName(workdir, pkg);
66292
67154
  const branchName = options.branch ?? `feat/${options.feature}`;
66293
- const outputDir = join10(naxDir, "features", options.feature);
66294
- const outputPath = join10(outputDir, "prd.json");
67155
+ const outputDir = join12(naxDir, "features", options.feature);
67156
+ const outputPath = join12(outputDir, "prd.json");
66295
67157
  await _deps2.mkdirp(outputDir);
66296
67158
  const agentName = config2?.autoMode?.defaultAgent ?? "claude";
66297
67159
  const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
66298
67160
  let rawResponse;
66299
67161
  if (options.auto) {
66300
- const prompt = buildPlanningPrompt(specContent, codebaseContext);
67162
+ const prompt = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails);
66301
67163
  const cliAdapter = _deps2.getAgent(agentName);
66302
67164
  if (!cliAdapter)
66303
67165
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
@@ -66309,11 +67171,11 @@ async function planCommand(workdir, config2, options) {
66309
67171
  }
66310
67172
  } catch {}
66311
67173
  } else {
66312
- const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath);
67174
+ const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails);
66313
67175
  const adapter = _deps2.getAgent(agentName, config2);
66314
67176
  if (!adapter)
66315
67177
  throw new Error(`[plan] No agent adapter found for '${agentName}'`);
66316
- const interactionBridge = createCliInteractionBridge();
67178
+ const interactionBridge = _deps2.createInteractionBridge();
66317
67179
  const pidRegistry = new PidRegistry(workdir);
66318
67180
  const resolvedPerm = resolvePermissions(config2, "plan");
66319
67181
  const resolvedModel = config2?.plan?.model ?? "balanced";
@@ -66391,6 +67253,66 @@ function detectProjectName(workdir, pkg) {
66391
67253
  }
66392
67254
  return "unknown";
66393
67255
  }
67256
+ var FRAMEWORK_PATTERNS = [
67257
+ [/\bnext\b/, "Next.js"],
67258
+ [/\bnuxt\b/, "Nuxt"],
67259
+ [/\bremix\b/, "Remix"],
67260
+ [/\bexpress\b/, "Express"],
67261
+ [/\bfastify\b/, "Fastify"],
67262
+ [/\bhono\b/, "Hono"],
67263
+ [/\bnestjs|@nestjs\b/, "NestJS"],
67264
+ [/\breact\b/, "React"],
67265
+ [/\bvue\b/, "Vue"],
67266
+ [/\bsvelte\b/, "Svelte"],
67267
+ [/\bastro\b/, "Astro"],
67268
+ [/\belectron\b/, "Electron"]
67269
+ ];
67270
+ var TEST_RUNNER_PATTERNS = [
67271
+ [/\bvitest\b/, "vitest"],
67272
+ [/\bjest\b/, "jest"],
67273
+ [/\bmocha\b/, "mocha"],
67274
+ [/\bava\b/, "ava"]
67275
+ ];
67276
+ var KEY_DEP_PATTERNS = [
67277
+ [/\bprisma\b/, "prisma"],
67278
+ [/\bdrizzle-orm\b/, "drizzle"],
67279
+ [/\btypeorm\b/, "typeorm"],
67280
+ [/\bmongoose\b/, "mongoose"],
67281
+ [/\bsqlite\b|better-sqlite/, "sqlite"],
67282
+ [/\bstripe\b/, "stripe"],
67283
+ [/\bgraphql\b/, "graphql"],
67284
+ [/\btrpc\b/, "tRPC"],
67285
+ [/\bzod\b/, "zod"],
67286
+ [/\btailwind\b/, "tailwind"]
67287
+ ];
67288
+ function buildPackageSummary(rel, pkg) {
67289
+ const name = typeof pkg?.name === "string" ? pkg.name : rel;
67290
+ const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
67291
+ const depNames = Object.keys(allDeps).join(" ");
67292
+ const scripts = pkg?.scripts ?? {};
67293
+ const testScript = scripts.test ?? "";
67294
+ const runtime = testScript.includes("bun ") ? "bun" : testScript.includes("node ") ? "node" : "unknown";
67295
+ const framework = FRAMEWORK_PATTERNS.find(([re]) => re.test(depNames))?.[1] ?? "";
67296
+ const testRunner = TEST_RUNNER_PATTERNS.find(([re]) => re.test(depNames))?.[1] ?? (testScript.includes("bun test") ? "bun:test" : "");
67297
+ const keyDeps = KEY_DEP_PATTERNS.filter(([re]) => re.test(depNames)).map(([, label]) => label);
67298
+ return { path: rel, name, runtime, framework, testRunner, keyDeps };
67299
+ }
67300
+ function buildPackageDetailsSection(details) {
67301
+ if (details.length === 0)
67302
+ return "";
67303
+ const rows = details.map((d) => {
67304
+ const stack = [d.framework, d.testRunner, ...d.keyDeps].filter(Boolean).join(", ") || "\u2014";
67305
+ return `| \`${d.path}\` | ${d.name} | ${stack} |`;
67306
+ });
67307
+ return `
67308
+ ## Package Tech Stacks
67309
+
67310
+ | Path | Package | Stack |
67311
+ |:-----|:--------|:------|
67312
+ ${rows.join(`
67313
+ `)}
67314
+ `;
67315
+ }
66394
67316
  function buildCodebaseContext2(scan) {
66395
67317
  const sections = [];
66396
67318
  sections.push(`## Codebase Structure
@@ -66417,7 +67339,19 @@ function buildCodebaseContext2(scan) {
66417
67339
  return sections.join(`
66418
67340
  `);
66419
67341
  }
66420
- function buildPlanningPrompt(specContent, codebaseContext, outputFilePath) {
67342
+ function buildPlanningPrompt(specContent, codebaseContext, outputFilePath, packages, packageDetails) {
67343
+ const isMonorepo = packages && packages.length > 0;
67344
+ const packageDetailsSection = packageDetails && packageDetails.length > 0 ? buildPackageDetailsSection(packageDetails) : "";
67345
+ const monorepoHint = isMonorepo ? `
67346
+ ## Monorepo Context
67347
+
67348
+ This is a monorepo. Detected packages:
67349
+ ${packages.map((p) => `- ${p}`).join(`
67350
+ `)}
67351
+ ${packageDetailsSection}
67352
+ 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".` : "";
67353
+ const workdirField = isMonorepo ? `
67354
+ "workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
66421
67355
  return `You are a senior software architect generating a product requirements document (PRD) as JSON.
66422
67356
 
66423
67357
  ## Spec
@@ -66426,7 +67360,7 @@ ${specContent}
66426
67360
 
66427
67361
  ## Codebase Context
66428
67362
 
66429
- ${codebaseContext}
67363
+ ${codebaseContext}${monorepoHint}
66430
67364
 
66431
67365
  ## Output Schema
66432
67366
 
@@ -66445,7 +67379,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
66445
67379
  "description": "string \u2014 detailed description of the story",
66446
67380
  "acceptanceCriteria": ["string \u2014 each AC line"],
66447
67381
  "tags": ["string \u2014 routing tags, e.g. feature, security, api"],
66448
- "dependencies": ["string \u2014 story IDs this story depends on"],
67382
+ "dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
66449
67383
  "status": "pending",
66450
67384
  "passes": false,
66451
67385
  "routing": {
@@ -66615,14 +67549,14 @@ async function displayModelEfficiency(workdir) {
66615
67549
  }
66616
67550
  // src/cli/status-features.ts
66617
67551
  init_source();
66618
- import { existsSync as existsSync11, readdirSync as readdirSync3 } from "fs";
66619
- import { join as join13 } from "path";
67552
+ import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
67553
+ import { join as join15 } from "path";
66620
67554
 
66621
67555
  // src/commands/common.ts
66622
67556
  init_path_security2();
66623
67557
  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";
67558
+ import { existsSync as existsSync12, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
67559
+ import { join as join13, resolve as resolve6 } from "path";
66626
67560
  function resolveProject(options = {}) {
66627
67561
  const { dir, feature } = options;
66628
67562
  let projectRoot;
@@ -66630,37 +67564,37 @@ function resolveProject(options = {}) {
66630
67564
  let configPath;
66631
67565
  if (dir) {
66632
67566
  projectRoot = realpathSync3(resolve6(dir));
66633
- naxDir = join11(projectRoot, "nax");
66634
- if (!existsSync10(naxDir)) {
67567
+ naxDir = join13(projectRoot, "nax");
67568
+ if (!existsSync12(naxDir)) {
66635
67569
  throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
66636
67570
  Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
66637
67571
  }
66638
- configPath = join11(naxDir, "config.json");
66639
- if (!existsSync10(configPath)) {
67572
+ configPath = join13(naxDir, "config.json");
67573
+ if (!existsSync12(configPath)) {
66640
67574
  throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
66641
67575
  Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
66642
67576
  }
66643
67577
  } else {
66644
67578
  const found = findProjectRoot(process.cwd());
66645
67579
  if (!found) {
66646
- const cwdNaxDir = join11(process.cwd(), "nax");
66647
- if (existsSync10(cwdNaxDir)) {
66648
- const cwdConfigPath = join11(cwdNaxDir, "config.json");
67580
+ const cwdNaxDir = join13(process.cwd(), "nax");
67581
+ if (existsSync12(cwdNaxDir)) {
67582
+ const cwdConfigPath = join13(cwdNaxDir, "config.json");
66649
67583
  throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
66650
67584
  Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
66651
67585
  }
66652
67586
  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
67587
  }
66654
67588
  projectRoot = found;
66655
- naxDir = join11(projectRoot, "nax");
66656
- configPath = join11(naxDir, "config.json");
67589
+ naxDir = join13(projectRoot, "nax");
67590
+ configPath = join13(naxDir, "config.json");
66657
67591
  }
66658
67592
  let featureDir;
66659
67593
  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) : [];
67594
+ const featuresDir = join13(naxDir, "features");
67595
+ featureDir = join13(featuresDir, feature);
67596
+ if (!existsSync12(featureDir)) {
67597
+ const availableFeatures = existsSync12(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
66664
67598
  const availableMsg = availableFeatures.length > 0 ? `
66665
67599
 
66666
67600
  Available features:
@@ -66685,12 +67619,12 @@ function findProjectRoot(startDir) {
66685
67619
  let current = resolve6(startDir);
66686
67620
  let depth = 0;
66687
67621
  while (depth < MAX_DIRECTORY_DEPTH) {
66688
- const naxDir = join11(current, "nax");
66689
- const configPath = join11(naxDir, "config.json");
66690
- if (existsSync10(configPath)) {
67622
+ const naxDir = join13(current, "nax");
67623
+ const configPath = join13(naxDir, "config.json");
67624
+ if (existsSync12(configPath)) {
66691
67625
  return realpathSync3(current);
66692
67626
  }
66693
- const parent = join11(current, "..");
67627
+ const parent = join13(current, "..");
66694
67628
  if (parent === current) {
66695
67629
  break;
66696
67630
  }
@@ -66712,8 +67646,8 @@ function isPidAlive(pid) {
66712
67646
  }
66713
67647
  }
66714
67648
  async function loadStatusFile(featureDir) {
66715
- const statusPath = join13(featureDir, "status.json");
66716
- if (!existsSync11(statusPath)) {
67649
+ const statusPath = join15(featureDir, "status.json");
67650
+ if (!existsSync13(statusPath)) {
66717
67651
  return null;
66718
67652
  }
66719
67653
  try {
@@ -66724,8 +67658,8 @@ async function loadStatusFile(featureDir) {
66724
67658
  }
66725
67659
  }
66726
67660
  async function loadProjectStatusFile(projectDir) {
66727
- const statusPath = join13(projectDir, "nax", "status.json");
66728
- if (!existsSync11(statusPath)) {
67661
+ const statusPath = join15(projectDir, "nax", "status.json");
67662
+ if (!existsSync13(statusPath)) {
66729
67663
  return null;
66730
67664
  }
66731
67665
  try {
@@ -66736,8 +67670,8 @@ async function loadProjectStatusFile(projectDir) {
66736
67670
  }
66737
67671
  }
66738
67672
  async function getFeatureSummary(featureName, featureDir) {
66739
- const prdPath = join13(featureDir, "prd.json");
66740
- if (!existsSync11(prdPath)) {
67673
+ const prdPath = join15(featureDir, "prd.json");
67674
+ if (!existsSync13(prdPath)) {
66741
67675
  return {
66742
67676
  name: featureName,
66743
67677
  done: 0,
@@ -66779,8 +67713,8 @@ async function getFeatureSummary(featureName, featureDir) {
66779
67713
  };
66780
67714
  }
66781
67715
  }
66782
- const runsDir = join13(featureDir, "runs");
66783
- if (existsSync11(runsDir)) {
67716
+ const runsDir = join15(featureDir, "runs");
67717
+ if (existsSync13(runsDir)) {
66784
67718
  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
67719
  if (runs.length > 0) {
66786
67720
  const latestRun = runs[0].replace(".jsonl", "");
@@ -66790,8 +67724,8 @@ async function getFeatureSummary(featureName, featureDir) {
66790
67724
  return summary;
66791
67725
  }
66792
67726
  async function displayAllFeatures(projectDir) {
66793
- const featuresDir = join13(projectDir, "nax", "features");
66794
- if (!existsSync11(featuresDir)) {
67727
+ const featuresDir = join15(projectDir, "nax", "features");
67728
+ if (!existsSync13(featuresDir)) {
66795
67729
  console.log(source_default.dim("No features found."));
66796
67730
  return;
66797
67731
  }
@@ -66831,7 +67765,7 @@ async function displayAllFeatures(projectDir) {
66831
67765
  console.log();
66832
67766
  }
66833
67767
  }
66834
- const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join13(featuresDir, name))));
67768
+ const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join15(featuresDir, name))));
66835
67769
  console.log(source_default.bold(`\uD83D\uDCCA Features
66836
67770
  `));
66837
67771
  const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
@@ -66857,8 +67791,8 @@ async function displayAllFeatures(projectDir) {
66857
67791
  console.log();
66858
67792
  }
66859
67793
  async function displayFeatureDetails(featureName, featureDir) {
66860
- const prdPath = join13(featureDir, "prd.json");
66861
- if (!existsSync11(prdPath)) {
67794
+ const prdPath = join15(featureDir, "prd.json");
67795
+ if (!existsSync13(prdPath)) {
66862
67796
  console.log(source_default.bold(`
66863
67797
  \uD83D\uDCCA ${featureName}
66864
67798
  `));
@@ -66978,8 +67912,8 @@ async function displayFeatureStatus(options = {}) {
66978
67912
  // src/cli/runs.ts
66979
67913
  init_errors3();
66980
67914
  init_logger2();
66981
- import { existsSync as existsSync12, readdirSync as readdirSync4 } from "fs";
66982
- import { join as join14 } from "path";
67915
+ import { existsSync as existsSync14, readdirSync as readdirSync4 } from "fs";
67916
+ import { join as join16 } from "path";
66983
67917
  async function parseRunLog(logPath) {
66984
67918
  const logger = getLogger();
66985
67919
  try {
@@ -66995,8 +67929,8 @@ async function parseRunLog(logPath) {
66995
67929
  async function runsListCommand(options) {
66996
67930
  const logger = getLogger();
66997
67931
  const { feature, workdir } = options;
66998
- const runsDir = join14(workdir, "nax", "features", feature, "runs");
66999
- if (!existsSync12(runsDir)) {
67932
+ const runsDir = join16(workdir, "nax", "features", feature, "runs");
67933
+ if (!existsSync14(runsDir)) {
67000
67934
  logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
67001
67935
  return;
67002
67936
  }
@@ -67007,7 +67941,7 @@ async function runsListCommand(options) {
67007
67941
  }
67008
67942
  logger.info("cli", `Runs for ${feature}`, { count: files.length });
67009
67943
  for (const file2 of files.sort().reverse()) {
67010
- const logPath = join14(runsDir, file2);
67944
+ const logPath = join16(runsDir, file2);
67011
67945
  const entries = await parseRunLog(logPath);
67012
67946
  const startEvent = entries.find((e) => e.message === "run.start");
67013
67947
  const completeEvent = entries.find((e) => e.message === "run.complete");
@@ -67033,8 +67967,8 @@ async function runsListCommand(options) {
67033
67967
  async function runsShowCommand(options) {
67034
67968
  const logger = getLogger();
67035
67969
  const { runId, feature, workdir } = options;
67036
- const logPath = join14(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
67037
- if (!existsSync12(logPath)) {
67970
+ const logPath = join16(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
67971
+ if (!existsSync14(logPath)) {
67038
67972
  logger.error("cli", "Run not found", { runId, feature, logPath });
67039
67973
  throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
67040
67974
  }
@@ -67071,8 +68005,8 @@ async function runsShowCommand(options) {
67071
68005
  }
67072
68006
  // src/cli/prompts-main.ts
67073
68007
  init_logger2();
67074
- import { existsSync as existsSync15, mkdirSync as mkdirSync3 } from "fs";
67075
- import { join as join21 } from "path";
68008
+ import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
68009
+ import { join as join27 } from "path";
67076
68010
 
67077
68011
  // src/pipeline/index.ts
67078
68012
  init_runner();
@@ -67108,7 +68042,7 @@ init_prd();
67108
68042
 
67109
68043
  // src/cli/prompts-tdd.ts
67110
68044
  init_prompts2();
67111
- import { join as join20 } from "path";
68045
+ import { join as join26 } from "path";
67112
68046
  async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
67113
68047
  const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
67114
68048
  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 +68061,7 @@ ${frontmatter}---
67127
68061
 
67128
68062
  ${session.prompt}`;
67129
68063
  if (outputDir) {
67130
- const promptFile = join20(outputDir, `${story.id}.${session.role}.md`);
68064
+ const promptFile = join26(outputDir, `${story.id}.${session.role}.md`);
67131
68065
  await Bun.write(promptFile, fullOutput);
67132
68066
  logger.info("cli", "Written TDD prompt file", {
67133
68067
  storyId: story.id,
@@ -67143,7 +68077,7 @@ ${"=".repeat(80)}`);
67143
68077
  }
67144
68078
  }
67145
68079
  if (outputDir && ctx.contextMarkdown) {
67146
- const contextFile = join20(outputDir, `${story.id}.context.md`);
68080
+ const contextFile = join26(outputDir, `${story.id}.context.md`);
67147
68081
  const frontmatter = buildFrontmatter(story, ctx);
67148
68082
  const contextOutput = `---
67149
68083
  ${frontmatter}---
@@ -67157,13 +68091,13 @@ ${ctx.contextMarkdown}`;
67157
68091
  async function promptsCommand(options) {
67158
68092
  const logger = getLogger();
67159
68093
  const { feature, workdir, config: config2, storyId, outputDir } = options;
67160
- const naxDir = join21(workdir, "nax");
67161
- if (!existsSync15(naxDir)) {
68094
+ const naxDir = join27(workdir, "nax");
68095
+ if (!existsSync18(naxDir)) {
67162
68096
  throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
67163
68097
  }
67164
- const featureDir = join21(naxDir, "features", feature);
67165
- const prdPath = join21(featureDir, "prd.json");
67166
- if (!existsSync15(prdPath)) {
68098
+ const featureDir = join27(naxDir, "features", feature);
68099
+ const prdPath = join27(featureDir, "prd.json");
68100
+ if (!existsSync18(prdPath)) {
67167
68101
  throw new Error(`Feature "${feature}" not found or missing prd.json`);
67168
68102
  }
67169
68103
  const prd = await loadPRD(prdPath);
@@ -67222,10 +68156,10 @@ ${frontmatter}---
67222
68156
 
67223
68157
  ${ctx.prompt}`;
67224
68158
  if (outputDir) {
67225
- const promptFile = join21(outputDir, `${story.id}.prompt.md`);
68159
+ const promptFile = join27(outputDir, `${story.id}.prompt.md`);
67226
68160
  await Bun.write(promptFile, fullOutput);
67227
68161
  if (ctx.contextMarkdown) {
67228
- const contextFile = join21(outputDir, `${story.id}.context.md`);
68162
+ const contextFile = join27(outputDir, `${story.id}.context.md`);
67229
68163
  const contextOutput = `---
67230
68164
  ${frontmatter}---
67231
68165
 
@@ -67288,8 +68222,8 @@ function buildFrontmatter(story, ctx, role) {
67288
68222
  `;
67289
68223
  }
67290
68224
  // src/cli/prompts-init.ts
67291
- import { existsSync as existsSync16, mkdirSync as mkdirSync4 } from "fs";
67292
- import { join as join22 } from "path";
68225
+ import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
68226
+ import { join as join28 } from "path";
67293
68227
  var TEMPLATE_ROLES = [
67294
68228
  { file: "test-writer.md", role: "test-writer" },
67295
68229
  { file: "implementer.md", role: "implementer", variant: "standard" },
@@ -67313,9 +68247,9 @@ var TEMPLATE_HEADER = `<!--
67313
68247
  `;
67314
68248
  async function promptsInitCommand(options) {
67315
68249
  const { workdir, force = false, autoWireConfig = true } = options;
67316
- const templatesDir = join22(workdir, "nax", "templates");
68250
+ const templatesDir = join28(workdir, "nax", "templates");
67317
68251
  mkdirSync4(templatesDir, { recursive: true });
67318
- const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync16(join22(templatesDir, f)));
68252
+ const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join28(templatesDir, f)));
67319
68253
  if (existingFiles.length > 0 && !force) {
67320
68254
  console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
67321
68255
  Pass --force to overwrite existing templates.`);
@@ -67323,7 +68257,7 @@ async function promptsInitCommand(options) {
67323
68257
  }
67324
68258
  const written = [];
67325
68259
  for (const template of TEMPLATE_ROLES) {
67326
- const filePath = join22(templatesDir, template.file);
68260
+ const filePath = join28(templatesDir, template.file);
67327
68261
  const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
67328
68262
  const content = TEMPLATE_HEADER + roleBody;
67329
68263
  await Bun.write(filePath, content);
@@ -67339,8 +68273,8 @@ async function promptsInitCommand(options) {
67339
68273
  return written;
67340
68274
  }
67341
68275
  async function autoWirePromptsConfig(workdir) {
67342
- const configPath = join22(workdir, "nax.config.json");
67343
- if (!existsSync16(configPath)) {
68276
+ const configPath = join28(workdir, "nax.config.json");
68277
+ if (!existsSync19(configPath)) {
67344
68278
  const exampleConfig = JSON.stringify({
67345
68279
  prompts: {
67346
68280
  overrides: {
@@ -67435,9 +68369,7 @@ async function exportPromptCommand(options) {
67435
68369
  // src/cli/init.ts
67436
68370
  init_paths();
67437
68371
  init_logger2();
67438
-
67439
- // src/cli/init-context.ts
67440
- init_logger2();
68372
+ init_init_context();
67441
68373
  // src/cli/plugins.ts
67442
68374
  init_loader5();
67443
68375
  import * as os2 from "os";
@@ -67505,8 +68437,8 @@ function pad(str, width) {
67505
68437
  init_config();
67506
68438
  init_logger2();
67507
68439
  init_prd();
67508
- import { existsSync as existsSync17, readdirSync as readdirSync5 } from "fs";
67509
- import { join as join25 } from "path";
68440
+ import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
68441
+ import { join as join32 } from "path";
67510
68442
 
67511
68443
  // src/cli/diagnose-analysis.ts
67512
68444
  function detectFailurePattern(story, prd, status) {
@@ -67705,8 +68637,8 @@ function isProcessAlive2(pid) {
67705
68637
  }
67706
68638
  }
67707
68639
  async function loadStatusFile2(workdir) {
67708
- const statusPath = join25(workdir, "nax", "status.json");
67709
- if (!existsSync17(statusPath))
68640
+ const statusPath = join32(workdir, "nax", "status.json");
68641
+ if (!existsSync21(statusPath))
67710
68642
  return null;
67711
68643
  try {
67712
68644
  return await Bun.file(statusPath).json();
@@ -67733,7 +68665,7 @@ async function countCommitsSince(workdir, since) {
67733
68665
  }
67734
68666
  }
67735
68667
  async function checkLock(workdir) {
67736
- const lockFile = Bun.file(join25(workdir, "nax.lock"));
68668
+ const lockFile = Bun.file(join32(workdir, "nax.lock"));
67737
68669
  if (!await lockFile.exists())
67738
68670
  return { lockPresent: false };
67739
68671
  try {
@@ -67751,8 +68683,8 @@ async function diagnoseCommand(options = {}) {
67751
68683
  const logger = getLogger();
67752
68684
  const workdir = options.workdir ?? process.cwd();
67753
68685
  const naxSubdir = findProjectDir(workdir);
67754
- let projectDir = naxSubdir ? join25(naxSubdir, "..") : null;
67755
- if (!projectDir && existsSync17(join25(workdir, "nax"))) {
68686
+ let projectDir = naxSubdir ? join32(naxSubdir, "..") : null;
68687
+ if (!projectDir && existsSync21(join32(workdir, "nax"))) {
67756
68688
  projectDir = workdir;
67757
68689
  }
67758
68690
  if (!projectDir)
@@ -67763,8 +68695,8 @@ async function diagnoseCommand(options = {}) {
67763
68695
  if (status2) {
67764
68696
  feature = status2.run.feature;
67765
68697
  } else {
67766
- const featuresDir = join25(projectDir, "nax", "features");
67767
- if (!existsSync17(featuresDir))
68698
+ const featuresDir = join32(projectDir, "nax", "features");
68699
+ if (!existsSync21(featuresDir))
67768
68700
  throw new Error("No features found in project");
67769
68701
  const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
67770
68702
  if (features.length === 0)
@@ -67773,9 +68705,9 @@ async function diagnoseCommand(options = {}) {
67773
68705
  logger.info("diagnose", "No feature specified, using first found", { feature });
67774
68706
  }
67775
68707
  }
67776
- const featureDir = join25(projectDir, "nax", "features", feature);
67777
- const prdPath = join25(featureDir, "prd.json");
67778
- if (!existsSync17(prdPath))
68708
+ const featureDir = join32(projectDir, "nax", "features", feature);
68709
+ const prdPath = join32(featureDir, "prd.json");
68710
+ if (!existsSync21(prdPath))
67779
68711
  throw new Error(`Feature not found: ${feature}`);
67780
68712
  const prd = await loadPRD(prdPath);
67781
68713
  const status = await loadStatusFile2(projectDir);
@@ -67816,419 +68748,66 @@ init_interaction();
67816
68748
  // src/cli/generate.ts
67817
68749
  init_source();
67818
68750
  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;
68751
+ import { existsSync as existsSync22 } from "fs";
68752
+ import { join as join33 } from "path";
68753
+ var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
68754
+ async function generateCommand(options) {
68755
+ const workdir = process.cwd();
68756
+ const dryRun = options.dryRun ?? false;
68757
+ let config2;
67862
68758
  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 };
68759
+ config2 = await loadConfig(workdir);
67871
68760
  } catch {
67872
- return null;
68761
+ config2 = {};
67873
68762
  }
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
- }
68763
+ if (options.allPackages) {
68764
+ if (dryRun) {
68765
+ console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
67893
68766
  }
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 };
68767
+ console.log(source_default.blue("\u2192 Discovering packages with nax/context.md..."));
68768
+ const packages = await discoverPackages(workdir);
68769
+ if (packages.length === 0) {
68770
+ console.log(source_default.yellow(" No packages found (no */nax/context.md or */*/nax/context.md)"));
68771
+ return;
67927
68772
  }
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 };
68773
+ console.log(source_default.blue(`\u2192 Generating CLAUDE.md for ${packages.length} package(s)...`));
68774
+ let errorCount = 0;
68775
+ for (const pkgDir of packages) {
68776
+ const result = await generateForPackage(pkgDir, config2, dryRun);
68777
+ if (result.error) {
68778
+ console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
68779
+ errorCount++;
68780
+ } else {
68781
+ const suffix = dryRun ? " (dry run)" : "";
68782
+ console.log(source_default.green(`\u2713 ${pkgDir}/${result.outputFile} (${result.content.length} bytes${suffix})`));
68783
+ }
67973
68784
  }
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);
68785
+ if (errorCount > 0) {
68786
+ console.error(source_default.red(`
68787
+ \u2717 ${errorCount} generation(s) failed`));
68788
+ process.exit(1);
68196
68789
  }
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 };
68790
+ return;
68201
68791
  }
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 });
68792
+ if (options.package) {
68793
+ const packageDir = join33(workdir, options.package);
68794
+ if (dryRun) {
68795
+ console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
68218
68796
  }
68797
+ console.log(source_default.blue(`\u2192 Generating CLAUDE.md for package: ${options.package}`));
68798
+ const result = await generateForPackage(packageDir, config2, dryRun);
68799
+ if (result.error) {
68800
+ console.error(source_default.red(`\u2717 ${result.error}`));
68801
+ process.exit(1);
68802
+ }
68803
+ const suffix = dryRun ? " (dry run)" : "";
68804
+ console.log(source_default.green(`\u2713 ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
68805
+ return;
68219
68806
  }
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;
68807
+ const contextPath = options.context ? join33(workdir, options.context) : join33(workdir, "nax/context.md");
68808
+ const outputDir = options.output ? join33(workdir, options.output) : workdir;
68229
68809
  const autoInject = !options.noAutoInject;
68230
- const dryRun = options.dryRun ?? false;
68231
- if (!existsSync20(contextPath)) {
68810
+ if (!existsSync22(contextPath)) {
68232
68811
  console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
68233
68812
  console.error(source_default.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
68234
68813
  process.exit(1);
@@ -68245,12 +68824,6 @@ async function generateCommand(options) {
68245
68824
  if (autoInject) {
68246
68825
  console.log(source_default.dim(" Auto-injecting project metadata..."));
68247
68826
  }
68248
- let config2;
68249
- try {
68250
- config2 = await loadConfig(workdir);
68251
- } catch {
68252
- config2 = {};
68253
- }
68254
68827
  const genOptions = {
68255
68828
  contextPath,
68256
68829
  outputDir,
@@ -68270,8 +68843,15 @@ async function generateCommand(options) {
68270
68843
  const suffix = dryRun ? " (dry run)" : "";
68271
68844
  console.log(source_default.green(`\u2713 ${agent} \u2192 ${result.outputFile} (${result.content.length} bytes${suffix})`));
68272
68845
  } else {
68273
- console.log(source_default.blue("\u2192 Generating configs for all agents..."));
68274
- const results = await generateAll(genOptions, config2);
68846
+ const configAgents = config2?.generate?.agents;
68847
+ const agentFilter = configAgents && configAgents.length > 0 ? configAgents : null;
68848
+ if (agentFilter) {
68849
+ console.log(source_default.blue(`\u2192 Generating configs for: ${agentFilter.join(", ")} (from config)...`));
68850
+ } else {
68851
+ console.log(source_default.blue("\u2192 Generating configs for all agents..."));
68852
+ }
68853
+ const allResults = await generateAll(genOptions, config2);
68854
+ const results = agentFilter ? allResults.filter((r) => agentFilter.includes(r.agent)) : allResults;
68275
68855
  let errorCount = 0;
68276
68856
  for (const result of results) {
68277
68857
  if (result.error) {
@@ -68300,8 +68880,8 @@ async function generateCommand(options) {
68300
68880
  }
68301
68881
  // src/cli/config-display.ts
68302
68882
  init_loader2();
68303
- import { existsSync as existsSync22 } from "fs";
68304
- import { join as join30 } from "path";
68883
+ import { existsSync as existsSync24 } from "fs";
68884
+ import { join as join35 } from "path";
68305
68885
 
68306
68886
  // src/cli/config-descriptions.ts
68307
68887
  var FIELD_DESCRIPTIONS = {
@@ -68509,10 +69089,10 @@ function deepEqual(a, b) {
68509
69089
  // src/cli/config-get.ts
68510
69090
  init_defaults();
68511
69091
  init_loader2();
68512
- import { existsSync as existsSync21 } from "fs";
68513
- import { join as join29 } from "path";
69092
+ import { existsSync as existsSync23 } from "fs";
69093
+ import { join as join34 } from "path";
68514
69094
  async function loadConfigFile(path14) {
68515
- if (!existsSync21(path14))
69095
+ if (!existsSync23(path14))
68516
69096
  return null;
68517
69097
  try {
68518
69098
  return await Bun.file(path14).json();
@@ -68532,7 +69112,7 @@ async function loadProjectConfig() {
68532
69112
  const projectDir = findProjectDir();
68533
69113
  if (!projectDir)
68534
69114
  return null;
68535
- const projectPath = join29(projectDir, "config.json");
69115
+ const projectPath = join34(projectDir, "config.json");
68536
69116
  return await loadConfigFile(projectPath);
68537
69117
  }
68538
69118
 
@@ -68592,14 +69172,14 @@ async function configCommand(config2, options = {}) {
68592
69172
  function determineConfigSources() {
68593
69173
  const globalPath = globalConfigPath();
68594
69174
  const projectDir = findProjectDir();
68595
- const projectPath = projectDir ? join30(projectDir, "config.json") : null;
69175
+ const projectPath = projectDir ? join35(projectDir, "config.json") : null;
68596
69176
  return {
68597
69177
  global: fileExists(globalPath) ? globalPath : null,
68598
69178
  project: projectPath && fileExists(projectPath) ? projectPath : null
68599
69179
  };
68600
69180
  }
68601
69181
  function fileExists(path14) {
68602
- return existsSync22(path14);
69182
+ return existsSync24(path14);
68603
69183
  }
68604
69184
  function displayConfigWithDescriptions(obj, path14, sources, indent = 0) {
68605
69185
  const indentStr = " ".repeat(indent);
@@ -68771,25 +69351,25 @@ async function diagnose(options) {
68771
69351
  }
68772
69352
 
68773
69353
  // src/commands/logs.ts
68774
- import { existsSync as existsSync24 } from "fs";
68775
- import { join as join33 } from "path";
69354
+ import { existsSync as existsSync26 } from "fs";
69355
+ import { join as join38 } from "path";
68776
69356
 
68777
69357
  // src/commands/logs-formatter.ts
68778
69358
  init_source();
68779
69359
  init_formatter();
68780
69360
  import { readdirSync as readdirSync7 } from "fs";
68781
- import { join as join32 } from "path";
69361
+ import { join as join37 } from "path";
68782
69362
 
68783
69363
  // src/commands/logs-reader.ts
68784
- import { existsSync as existsSync23, readdirSync as readdirSync6 } from "fs";
69364
+ import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
68785
69365
  import { readdir as readdir3 } from "fs/promises";
68786
69366
  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")
69367
+ import { join as join36 } from "path";
69368
+ var _deps7 = {
69369
+ getRunsDir: () => process.env.NAX_RUNS_DIR ?? join36(homedir5(), ".nax", "runs")
68790
69370
  };
68791
69371
  async function resolveRunFileFromRegistry(runId) {
68792
- const runsDir = _deps6.getRunsDir();
69372
+ const runsDir = _deps7.getRunsDir();
68793
69373
  let entries;
68794
69374
  try {
68795
69375
  entries = await readdir3(runsDir);
@@ -68798,7 +69378,7 @@ async function resolveRunFileFromRegistry(runId) {
68798
69378
  }
68799
69379
  let matched = null;
68800
69380
  for (const entry of entries) {
68801
- const metaPath = join31(runsDir, entry, "meta.json");
69381
+ const metaPath = join36(runsDir, entry, "meta.json");
68802
69382
  try {
68803
69383
  const meta3 = await Bun.file(metaPath).json();
68804
69384
  if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
@@ -68810,7 +69390,7 @@ async function resolveRunFileFromRegistry(runId) {
68810
69390
  if (!matched) {
68811
69391
  throw new Error(`Run not found in registry: ${runId}`);
68812
69392
  }
68813
- if (!existsSync23(matched.eventsDir)) {
69393
+ if (!existsSync25(matched.eventsDir)) {
68814
69394
  console.log(`Log directory unavailable for run: ${runId}`);
68815
69395
  return null;
68816
69396
  }
@@ -68820,14 +69400,14 @@ async function resolveRunFileFromRegistry(runId) {
68820
69400
  return null;
68821
69401
  }
68822
69402
  const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
68823
- return join31(matched.eventsDir, specificFile ?? files[0]);
69403
+ return join36(matched.eventsDir, specificFile ?? files[0]);
68824
69404
  }
68825
69405
  async function selectRunFile(runsDir) {
68826
69406
  const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
68827
69407
  if (files.length === 0) {
68828
69408
  return null;
68829
69409
  }
68830
- return join31(runsDir, files[0]);
69410
+ return join36(runsDir, files[0]);
68831
69411
  }
68832
69412
  async function extractRunSummary(filePath) {
68833
69413
  const file2 = Bun.file(filePath);
@@ -68912,7 +69492,7 @@ Runs:
68912
69492
  console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
68913
69493
  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
69494
  for (const file2 of files) {
68915
- const filePath = join32(runsDir, file2);
69495
+ const filePath = join37(runsDir, file2);
68916
69496
  const summary = await extractRunSummary(filePath);
68917
69497
  const timestamp = file2.replace(".jsonl", "");
68918
69498
  const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
@@ -69037,7 +69617,7 @@ async function logsCommand(options) {
69037
69617
  return;
69038
69618
  }
69039
69619
  const resolved = resolveProject({ dir: options.dir });
69040
- const naxDir = join33(resolved.projectDir, "nax");
69620
+ const naxDir = join38(resolved.projectDir, "nax");
69041
69621
  const configPath = resolved.configPath;
69042
69622
  const configFile = Bun.file(configPath);
69043
69623
  const config2 = await configFile.json();
@@ -69045,9 +69625,9 @@ async function logsCommand(options) {
69045
69625
  if (!featureName) {
69046
69626
  throw new Error("No feature specified in config.json");
69047
69627
  }
69048
- const featureDir = join33(naxDir, "features", featureName);
69049
- const runsDir = join33(featureDir, "runs");
69050
- if (!existsSync24(runsDir)) {
69628
+ const featureDir = join38(naxDir, "features", featureName);
69629
+ const runsDir = join38(featureDir, "runs");
69630
+ if (!existsSync26(runsDir)) {
69051
69631
  throw new Error(`No runs directory found for feature: ${featureName}`);
69052
69632
  }
69053
69633
  if (options.list) {
@@ -69070,8 +69650,8 @@ init_source();
69070
69650
  init_config();
69071
69651
  init_prd();
69072
69652
  init_precheck();
69073
- import { existsSync as existsSync29 } from "fs";
69074
- import { join as join34 } from "path";
69653
+ import { existsSync as existsSync31 } from "fs";
69654
+ import { join as join39 } from "path";
69075
69655
  async function precheckCommand(options) {
69076
69656
  const resolved = resolveProject({
69077
69657
  dir: options.dir,
@@ -69087,14 +69667,14 @@ async function precheckCommand(options) {
69087
69667
  process.exit(1);
69088
69668
  }
69089
69669
  }
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)) {
69670
+ const naxDir = join39(resolved.projectDir, "nax");
69671
+ const featureDir = join39(naxDir, "features", featureName);
69672
+ const prdPath = join39(featureDir, "prd.json");
69673
+ if (!existsSync31(featureDir)) {
69094
69674
  console.error(source_default.red(`Feature not found: ${featureName}`));
69095
69675
  process.exit(1);
69096
69676
  }
69097
- if (!existsSync29(prdPath)) {
69677
+ if (!existsSync31(prdPath)) {
69098
69678
  console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
69099
69679
  console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
69100
69680
  process.exit(EXIT_CODES.INVALID_PRD);
@@ -69113,10 +69693,10 @@ async function precheckCommand(options) {
69113
69693
  init_source();
69114
69694
  import { readdir as readdir4 } from "fs/promises";
69115
69695
  import { homedir as homedir6 } from "os";
69116
- import { join as join35 } from "path";
69696
+ import { join as join40 } from "path";
69117
69697
  var DEFAULT_LIMIT = 20;
69118
- var _deps8 = {
69119
- getRunsDir: () => join35(homedir6(), ".nax", "runs")
69698
+ var _deps9 = {
69699
+ getRunsDir: () => join40(homedir6(), ".nax", "runs")
69120
69700
  };
69121
69701
  function formatDuration3(ms) {
69122
69702
  if (ms <= 0)
@@ -69158,7 +69738,7 @@ function pad3(str, width) {
69158
69738
  return str + " ".repeat(padding);
69159
69739
  }
69160
69740
  async function runsCommand(options = {}) {
69161
- const runsDir = _deps8.getRunsDir();
69741
+ const runsDir = _deps9.getRunsDir();
69162
69742
  let entries;
69163
69743
  try {
69164
69744
  entries = await readdir4(runsDir);
@@ -69168,7 +69748,7 @@ async function runsCommand(options = {}) {
69168
69748
  }
69169
69749
  const rows = [];
69170
69750
  for (const entry of entries) {
69171
- const metaPath = join35(runsDir, entry, "meta.json");
69751
+ const metaPath = join40(runsDir, entry, "meta.json");
69172
69752
  let meta3;
69173
69753
  try {
69174
69754
  meta3 = await Bun.file(metaPath).json();
@@ -69245,7 +69825,7 @@ async function runsCommand(options = {}) {
69245
69825
 
69246
69826
  // src/commands/unlock.ts
69247
69827
  init_source();
69248
- import { join as join36 } from "path";
69828
+ import { join as join41 } from "path";
69249
69829
  function isProcessAlive3(pid) {
69250
69830
  try {
69251
69831
  process.kill(pid, 0);
@@ -69260,7 +69840,7 @@ function formatLockAge(ageMs) {
69260
69840
  }
69261
69841
  async function unlockCommand(options) {
69262
69842
  const workdir = options.dir ?? process.cwd();
69263
- const lockPath = join36(workdir, "nax.lock");
69843
+ const lockPath = join41(workdir, "nax.lock");
69264
69844
  const lockFile = Bun.file(lockPath);
69265
69845
  const exists = await lockFile.exists();
69266
69846
  if (!exists) {
@@ -77072,7 +77652,7 @@ async function promptForConfirmation(question) {
77072
77652
  process.stdin.on("data", handler);
77073
77653
  });
77074
77654
  }
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) => {
77655
+ 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
77656
  let workdir;
77077
77657
  try {
77078
77658
  workdir = validateDirectory(options.dir);
@@ -77080,15 +77660,30 @@ program2.command("init").description("Initialize nax in the current project").op
77080
77660
  console.error(source_default.red(`Invalid directory: ${err.message}`));
77081
77661
  process.exit(1);
77082
77662
  }
77083
- const naxDir = join43(workdir, "nax");
77084
- if (existsSync32(naxDir) && !options.force) {
77663
+ if (options.package) {
77664
+ const { initPackage: initPkg } = await Promise.resolve().then(() => (init_init_context(), exports_init_context));
77665
+ try {
77666
+ await initPkg(workdir, options.package, options.force);
77667
+ console.log(source_default.green(`
77668
+ [OK] Package scaffold created.`));
77669
+ console.log(source_default.dim(` Created: ${options.package}/nax/context.md`));
77670
+ console.log(source_default.dim(`
77671
+ Next: nax generate --package ${options.package}`));
77672
+ } catch (err) {
77673
+ console.error(source_default.red(`Error: ${err.message}`));
77674
+ process.exit(1);
77675
+ }
77676
+ return;
77677
+ }
77678
+ const naxDir = join48(workdir, "nax");
77679
+ if (existsSync34(naxDir) && !options.force) {
77085
77680
  console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
77086
77681
  return;
77087
77682
  }
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({
77683
+ mkdirSync6(join48(naxDir, "features"), { recursive: true });
77684
+ mkdirSync6(join48(naxDir, "hooks"), { recursive: true });
77685
+ await Bun.write(join48(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
77686
+ await Bun.write(join48(naxDir, "hooks.json"), JSON.stringify({
77092
77687
  hooks: {
77093
77688
  "on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
77094
77689
  "on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
@@ -77096,12 +77691,12 @@ program2.command("init").description("Initialize nax in the current project").op
77096
77691
  "on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
77097
77692
  }
77098
77693
  }, null, 2));
77099
- await Bun.write(join43(naxDir, ".gitignore"), `# nax temp files
77694
+ await Bun.write(join48(naxDir, ".gitignore"), `# nax temp files
77100
77695
  *.tmp
77101
77696
  .paused.json
77102
77697
  .nax-verifier-verdict.json
77103
77698
  `);
77104
- await Bun.write(join43(naxDir, "context.md"), `# Project Context
77699
+ await Bun.write(join48(naxDir, "context.md"), `# Project Context
77105
77700
 
77106
77701
  This document defines coding standards, architectural decisions, and forbidden patterns for this project.
77107
77702
  Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
@@ -77198,7 +77793,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77198
77793
  console.error(source_default.red("Error: --plan requires --from <spec-path>"));
77199
77794
  process.exit(1);
77200
77795
  }
77201
- if (options.from && !existsSync32(options.from)) {
77796
+ if (options.from && !existsSync34(options.from)) {
77202
77797
  console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
77203
77798
  process.exit(1);
77204
77799
  }
@@ -77227,10 +77822,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77227
77822
  console.error(source_default.red("nax not initialized. Run: nax init"));
77228
77823
  process.exit(1);
77229
77824
  }
77230
- const featureDir = join43(naxDir, "features", options.feature);
77231
- const prdPath = join43(featureDir, "prd.json");
77825
+ const featureDir = join48(naxDir, "features", options.feature);
77826
+ const prdPath = join48(featureDir, "prd.json");
77232
77827
  if (options.plan && options.from) {
77233
- if (existsSync32(prdPath) && !options.force) {
77828
+ if (existsSync34(prdPath) && !options.force) {
77234
77829
  console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
77235
77830
  console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
77236
77831
  process.exit(1);
@@ -77250,10 +77845,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
77250
77845
  }
77251
77846
  }
77252
77847
  try {
77253
- const planLogDir = join43(featureDir, "plan");
77848
+ const planLogDir = join48(featureDir, "plan");
77254
77849
  mkdirSync6(planLogDir, { recursive: true });
77255
77850
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77256
- const planLogPath = join43(planLogDir, `${planLogId}.jsonl`);
77851
+ const planLogPath = join48(planLogDir, `${planLogId}.jsonl`);
77257
77852
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77258
77853
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77259
77854
  console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
@@ -77286,15 +77881,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
77286
77881
  process.exit(1);
77287
77882
  }
77288
77883
  }
77289
- if (!existsSync32(prdPath)) {
77884
+ if (!existsSync34(prdPath)) {
77290
77885
  console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
77291
77886
  process.exit(1);
77292
77887
  }
77293
77888
  resetLogger();
77294
- const runsDir = join43(featureDir, "runs");
77889
+ const runsDir = join48(featureDir, "runs");
77295
77890
  mkdirSync6(runsDir, { recursive: true });
77296
77891
  const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77297
- const logFilePath = join43(runsDir, `${runId}.jsonl`);
77892
+ const logFilePath = join48(runsDir, `${runId}.jsonl`);
77298
77893
  const isTTY = process.stdout.isTTY ?? false;
77299
77894
  const headlessFlag = options.headless ?? false;
77300
77895
  const headlessEnv = process.env.NAX_HEADLESS === "1";
@@ -77310,7 +77905,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77310
77905
  config2.autoMode.defaultAgent = options.agent;
77311
77906
  }
77312
77907
  config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
77313
- const globalNaxDir = join43(homedir10(), ".nax");
77908
+ const globalNaxDir = join48(homedir10(), ".nax");
77314
77909
  const hooks = await loadHooksConfig(naxDir, globalNaxDir);
77315
77910
  const eventEmitter = new PipelineEventEmitter;
77316
77911
  let tuiInstance;
@@ -77333,7 +77928,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
77333
77928
  } else {
77334
77929
  console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
77335
77930
  }
77336
- const statusFilePath = join43(workdir, "nax", "status.json");
77931
+ const statusFilePath = join48(workdir, "nax", "status.json");
77337
77932
  let parallel;
77338
77933
  if (options.parallel !== undefined) {
77339
77934
  parallel = Number.parseInt(options.parallel, 10);
@@ -77359,9 +77954,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
77359
77954
  headless: useHeadless,
77360
77955
  skipPrecheck: options.skipPrecheck ?? false
77361
77956
  });
77362
- const latestSymlink = join43(runsDir, "latest.jsonl");
77957
+ const latestSymlink = join48(runsDir, "latest.jsonl");
77363
77958
  try {
77364
- if (existsSync32(latestSymlink)) {
77959
+ if (existsSync34(latestSymlink)) {
77365
77960
  Bun.spawnSync(["rm", latestSymlink]);
77366
77961
  }
77367
77962
  Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
@@ -77397,9 +77992,9 @@ features.command("create <name>").description("Create a new feature").option("-d
77397
77992
  console.error(source_default.red("nax not initialized. Run: nax init"));
77398
77993
  process.exit(1);
77399
77994
  }
77400
- const featureDir = join43(naxDir, "features", name);
77995
+ const featureDir = join48(naxDir, "features", name);
77401
77996
  mkdirSync6(featureDir, { recursive: true });
77402
- await Bun.write(join43(featureDir, "spec.md"), `# Feature: ${name}
77997
+ await Bun.write(join48(featureDir, "spec.md"), `# Feature: ${name}
77403
77998
 
77404
77999
  ## Overview
77405
78000
 
@@ -77407,7 +78002,7 @@ features.command("create <name>").description("Create a new feature").option("-d
77407
78002
 
77408
78003
  ## Acceptance Criteria
77409
78004
  `);
77410
- await Bun.write(join43(featureDir, "plan.md"), `# Plan: ${name}
78005
+ await Bun.write(join48(featureDir, "plan.md"), `# Plan: ${name}
77411
78006
 
77412
78007
  ## Architecture
77413
78008
 
@@ -77415,7 +78010,7 @@ features.command("create <name>").description("Create a new feature").option("-d
77415
78010
 
77416
78011
  ## Dependencies
77417
78012
  `);
77418
- await Bun.write(join43(featureDir, "tasks.md"), `# Tasks: ${name}
78013
+ await Bun.write(join48(featureDir, "tasks.md"), `# Tasks: ${name}
77419
78014
 
77420
78015
  ## US-001: [Title]
77421
78016
 
@@ -77424,7 +78019,7 @@ features.command("create <name>").description("Create a new feature").option("-d
77424
78019
  ### Acceptance Criteria
77425
78020
  - [ ] Criterion 1
77426
78021
  `);
77427
- await Bun.write(join43(featureDir, "progress.txt"), `# Progress: ${name}
78022
+ await Bun.write(join48(featureDir, "progress.txt"), `# Progress: ${name}
77428
78023
 
77429
78024
  Created: ${new Date().toISOString()}
77430
78025
 
@@ -77452,8 +78047,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
77452
78047
  console.error(source_default.red("nax not initialized."));
77453
78048
  process.exit(1);
77454
78049
  }
77455
- const featuresDir = join43(naxDir, "features");
77456
- if (!existsSync32(featuresDir)) {
78050
+ const featuresDir = join48(naxDir, "features");
78051
+ if (!existsSync34(featuresDir)) {
77457
78052
  console.log(source_default.dim("No features yet."));
77458
78053
  return;
77459
78054
  }
@@ -77467,8 +78062,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
77467
78062
  Features:
77468
78063
  `));
77469
78064
  for (const name of entries) {
77470
- const prdPath = join43(featuresDir, name, "prd.json");
77471
- if (existsSync32(prdPath)) {
78065
+ const prdPath = join48(featuresDir, name, "prd.json");
78066
+ if (existsSync34(prdPath)) {
77472
78067
  const prd = await loadPRD(prdPath);
77473
78068
  const c = countStories(prd);
77474
78069
  console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
@@ -77498,10 +78093,10 @@ Use: nax plan -f <feature> --from <spec>`));
77498
78093
  process.exit(1);
77499
78094
  }
77500
78095
  const config2 = await loadConfig(workdir);
77501
- const featureLogDir = join43(naxDir, "features", options.feature, "plan");
78096
+ const featureLogDir = join48(naxDir, "features", options.feature, "plan");
77502
78097
  mkdirSync6(featureLogDir, { recursive: true });
77503
78098
  const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
77504
- const planLogPath = join43(featureLogDir, `${planLogId}.jsonl`);
78099
+ const planLogPath = join48(featureLogDir, `${planLogId}.jsonl`);
77505
78100
  initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
77506
78101
  console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
77507
78102
  try {
@@ -77538,8 +78133,8 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
77538
78133
  console.error(source_default.red("nax not initialized. Run: nax init"));
77539
78134
  process.exit(1);
77540
78135
  }
77541
- const featureDir = join43(naxDir, "features", options.feature);
77542
- if (!existsSync32(featureDir)) {
78136
+ const featureDir = join48(naxDir, "features", options.feature);
78137
+ if (!existsSync34(featureDir)) {
77543
78138
  console.error(source_default.red(`Feature "${options.feature}" not found.`));
77544
78139
  process.exit(1);
77545
78140
  }
@@ -77554,7 +78149,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
77554
78149
  specPath: options.from,
77555
78150
  reclassify: options.reclassify
77556
78151
  });
77557
- const prdPath = join43(featureDir, "prd.json");
78152
+ const prdPath = join48(featureDir, "prd.json");
77558
78153
  await Bun.write(prdPath, JSON.stringify(prd, null, 2));
77559
78154
  const c = countStories(prd);
77560
78155
  console.log(source_default.green(`
@@ -77782,14 +78377,16 @@ program2.command("prompts").description("Assemble or initialize prompts").option
77782
78377
  process.exit(1);
77783
78378
  }
77784
78379
  });
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) => {
78380
+ 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
78381
  try {
77787
78382
  await generateCommand({
77788
78383
  context: options.context,
77789
78384
  output: options.output,
77790
78385
  agent: options.agent,
77791
78386
  dryRun: options.dryRun,
77792
- noAutoInject: !options.autoInject
78387
+ noAutoInject: !options.autoInject,
78388
+ package: options.package,
78389
+ allPackages: options.allPackages
77793
78390
  });
77794
78391
  } catch (err) {
77795
78392
  console.error(source_default.red(`Error: ${err.message}`));