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