@nathapp/nax 0.46.2 → 0.47.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nax.ts +20 -0
- package/dist/nax.js +1201 -745
- package/package.json +1 -1
- package/src/acceptance/generator.ts +1 -1
- package/src/cli/generate.ts +73 -11
- package/src/cli/init-context.ts +57 -0
- package/src/cli/init.ts +14 -1
- package/src/cli/plan.ts +30 -7
- package/src/config/loader.ts +34 -1
- package/src/config/merge.ts +37 -0
- package/src/context/generator.ts +85 -0
- package/src/execution/story-context.ts +33 -2
- package/src/pipeline/stages/context.ts +5 -1
- package/src/pipeline/stages/execution.ts +26 -3
- package/src/pipeline/stages/review.ts +6 -1
- package/src/pipeline/stages/verify.ts +23 -7
- package/src/prd/schema.ts +17 -0
- package/src/prd/types.ts +6 -0
- package/src/precheck/checks-system.ts +25 -87
- package/src/review/orchestrator.ts +6 -2
- package/src/verification/smart-runner.ts +24 -2
package/dist/nax.js
CHANGED
|
@@ -18812,7 +18812,7 @@ Generate a complete acceptance.test.ts file using bun:test framework. Follow the
|
|
|
18812
18812
|
1. **One test per AC**: Each acceptance criterion maps to exactly one test
|
|
18813
18813
|
2. **Test observable behavior only**: No implementation details, only user-facing behavior
|
|
18814
18814
|
3. **Independent tests**: No shared state between tests
|
|
18815
|
-
4. **
|
|
18815
|
+
4. **Real-implementation**: Tests should use real implementations without mocking (test observable behavior, not internal units)
|
|
18816
18816
|
5. **Clear test names**: Use format "AC-N: <description>" for test names
|
|
18817
18817
|
6. **Async where needed**: Use async/await for operations that may be asynchronous
|
|
18818
18818
|
|
|
@@ -20695,6 +20695,24 @@ var init_json_file = __esm(() => {
|
|
|
20695
20695
|
init_logger2();
|
|
20696
20696
|
});
|
|
20697
20697
|
|
|
20698
|
+
// src/config/merge.ts
|
|
20699
|
+
function mergePackageConfig(root, packageOverride) {
|
|
20700
|
+
const packageCommands = packageOverride.quality?.commands;
|
|
20701
|
+
if (!packageCommands) {
|
|
20702
|
+
return root;
|
|
20703
|
+
}
|
|
20704
|
+
return {
|
|
20705
|
+
...root,
|
|
20706
|
+
quality: {
|
|
20707
|
+
...root.quality,
|
|
20708
|
+
commands: {
|
|
20709
|
+
...root.quality.commands,
|
|
20710
|
+
...packageCommands
|
|
20711
|
+
}
|
|
20712
|
+
}
|
|
20713
|
+
};
|
|
20714
|
+
}
|
|
20715
|
+
|
|
20698
20716
|
// src/config/merger.ts
|
|
20699
20717
|
function deepMergeConfig(base, override) {
|
|
20700
20718
|
const result = { ...base };
|
|
@@ -20853,7 +20871,7 @@ var init_paths = () => {};
|
|
|
20853
20871
|
|
|
20854
20872
|
// src/config/loader.ts
|
|
20855
20873
|
import { existsSync as existsSync5 } from "fs";
|
|
20856
|
-
import { join as join7, resolve as resolve5 } from "path";
|
|
20874
|
+
import { dirname as dirname2, join as join7, resolve as resolve5 } from "path";
|
|
20857
20875
|
function globalConfigPath() {
|
|
20858
20876
|
return join7(globalConfigDir(), "config.json");
|
|
20859
20877
|
}
|
|
@@ -20924,6 +20942,20 @@ ${errors3.join(`
|
|
|
20924
20942
|
}
|
|
20925
20943
|
return result.data;
|
|
20926
20944
|
}
|
|
20945
|
+
async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
20946
|
+
const rootNaxDir = dirname2(rootConfigPath);
|
|
20947
|
+
const rootConfig = await loadConfig(rootNaxDir);
|
|
20948
|
+
if (!packageDir) {
|
|
20949
|
+
return rootConfig;
|
|
20950
|
+
}
|
|
20951
|
+
const repoRoot = dirname2(rootNaxDir);
|
|
20952
|
+
const packageConfigPath = join7(repoRoot, packageDir, "nax", "config.json");
|
|
20953
|
+
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
20954
|
+
if (!packageOverride) {
|
|
20955
|
+
return rootConfig;
|
|
20956
|
+
}
|
|
20957
|
+
return mergePackageConfig(rootConfig, packageOverride);
|
|
20958
|
+
}
|
|
20927
20959
|
var init_loader2 = __esm(() => {
|
|
20928
20960
|
init_logger2();
|
|
20929
20961
|
init_json_file();
|
|
@@ -22178,7 +22210,7 @@ var package_default;
|
|
|
22178
22210
|
var init_package = __esm(() => {
|
|
22179
22211
|
package_default = {
|
|
22180
22212
|
name: "@nathapp/nax",
|
|
22181
|
-
version: "0.
|
|
22213
|
+
version: "0.47.0",
|
|
22182
22214
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22183
22215
|
type: "module",
|
|
22184
22216
|
bin: {
|
|
@@ -22251,8 +22283,8 @@ var init_version = __esm(() => {
|
|
|
22251
22283
|
NAX_VERSION = package_default.version;
|
|
22252
22284
|
NAX_COMMIT = (() => {
|
|
22253
22285
|
try {
|
|
22254
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22255
|
-
return "
|
|
22286
|
+
if (/^[0-9a-f]{6,10}$/.test("ed0a660"))
|
|
22287
|
+
return "ed0a660";
|
|
22256
22288
|
} catch {}
|
|
22257
22289
|
try {
|
|
22258
22290
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -24444,7 +24476,7 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
24444
24476
|
}
|
|
24445
24477
|
|
|
24446
24478
|
class ReviewOrchestrator {
|
|
24447
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef) {
|
|
24479
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix) {
|
|
24448
24480
|
const logger = getSafeLogger();
|
|
24449
24481
|
const builtIn = await runReview(reviewConfig, workdir, executionConfig);
|
|
24450
24482
|
if (!builtIn.success) {
|
|
@@ -24459,13 +24491,14 @@ class ReviewOrchestrator {
|
|
|
24459
24491
|
if (reviewers.length > 0) {
|
|
24460
24492
|
const baseRef = storyGitRef ?? executionConfig?.storyGitRef;
|
|
24461
24493
|
const changedFiles = await getChangedFiles(workdir, baseRef);
|
|
24494
|
+
const scopedFiles = scopePrefix ? changedFiles.filter((f) => f === scopePrefix || f.startsWith(`${scopePrefix}/`)) : changedFiles;
|
|
24462
24495
|
const pluginResults = [];
|
|
24463
24496
|
for (const reviewer of reviewers) {
|
|
24464
24497
|
logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
|
|
24465
|
-
changedFiles:
|
|
24498
|
+
changedFiles: scopedFiles.length
|
|
24466
24499
|
});
|
|
24467
24500
|
try {
|
|
24468
|
-
const result = await reviewer.check(workdir,
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
24833
|
-
import { join as
|
|
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 =
|
|
24856
|
-
if (
|
|
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 =
|
|
24866
|
-
if (
|
|
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
|
|
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 ?
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
26377
|
-
import { join as
|
|
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 (!
|
|
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
|
|
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 =
|
|
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:
|
|
27723
|
-
if (!
|
|
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:
|
|
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:
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
29501
|
-
const testScopedTemplate =
|
|
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.
|
|
29513
|
-
const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles,
|
|
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,
|
|
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:
|
|
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
|
|
29984
|
-
if (
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
30719
|
+
var _deps8;
|
|
30351
30720
|
var init_checks_cli = __esm(() => {
|
|
30352
|
-
|
|
30721
|
+
_deps8 = {
|
|
30353
30722
|
spawn: Bun.spawn
|
|
30354
30723
|
};
|
|
30355
30724
|
});
|
|
30356
30725
|
|
|
30357
30726
|
// src/precheck/checks-system.ts
|
|
30358
|
-
import { existsSync as
|
|
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 (
|
|
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: "
|
|
30759
|
+
tier: "warning",
|
|
30391
30760
|
passed: true,
|
|
30392
|
-
message: "Test command not configured (
|
|
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: "
|
|
30776
|
+
tier: "warning",
|
|
30425
30777
|
passed: true,
|
|
30426
30778
|
message: "Lint command not configured (skipped)"
|
|
30427
30779
|
};
|
|
30428
30780
|
}
|
|
30429
|
-
|
|
30430
|
-
|
|
30431
|
-
|
|
30432
|
-
|
|
30433
|
-
|
|
30434
|
-
|
|
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: "
|
|
30793
|
+
tier: "warning",
|
|
30459
30794
|
passed: true,
|
|
30460
30795
|
message: "Typecheck command not configured (skipped)"
|
|
30461
30796
|
};
|
|
30462
30797
|
}
|
|
30463
|
-
|
|
30464
|
-
|
|
30465
|
-
|
|
30466
|
-
|
|
30467
|
-
|
|
30468
|
-
|
|
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
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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 =
|
|
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
|
|
32032
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
32063
|
-
if (
|
|
32064
|
-
const nodeModulesTarget =
|
|
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 =
|
|
32073
|
-
if (
|
|
32074
|
-
const envTarget =
|
|
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 =
|
|
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
|
|
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 =
|
|
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:
|
|
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
|
|
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 =
|
|
33065
|
-
const eventsDir =
|
|
33066
|
-
const eventsFile =
|
|
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
|
|
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
|
|
33542
|
+
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
33225
33543
|
import { homedir as homedir8 } from "os";
|
|
33226
|
-
import { basename as
|
|
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 =
|
|
33230
|
-
const runDir =
|
|
33231
|
-
const metaFile =
|
|
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
|
|
33553
|
+
await mkdir3(runDir, { recursive: true });
|
|
33236
33554
|
const meta3 = {
|
|
33237
33555
|
runId,
|
|
33238
33556
|
project,
|
|
33239
33557
|
feature,
|
|
33240
33558
|
workdir,
|
|
33241
|
-
statusPath:
|
|
33242
|
-
eventsDir:
|
|
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
|
|
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 =
|
|
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
|
|
65947
|
+
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
65630
65948
|
import { homedir as homedir10 } from "os";
|
|
65631
|
-
import { join as
|
|
65949
|
+
import { join as join48 } from "path";
|
|
65632
65950
|
|
|
65633
65951
|
// node_modules/commander/esm.mjs
|
|
65634
65952
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -66113,10 +66431,461 @@ async function generateAcceptanceTestsForFeature(specContent, featureName, featu
|
|
|
66113
66431
|
}
|
|
66114
66432
|
// src/cli/plan.ts
|
|
66115
66433
|
init_registry();
|
|
66116
|
-
import { existsSync as
|
|
66117
|
-
import { join as
|
|
66434
|
+
import { existsSync as existsSync11 } from "fs";
|
|
66435
|
+
import { join as join12 } from "path";
|
|
66118
66436
|
import { createInterface } from "readline";
|
|
66119
66437
|
init_test_strategy();
|
|
66438
|
+
|
|
66439
|
+
// src/context/generator.ts
|
|
66440
|
+
init_path_security2();
|
|
66441
|
+
import { existsSync as existsSync10 } from "fs";
|
|
66442
|
+
import { join as join11 } from "path";
|
|
66443
|
+
|
|
66444
|
+
// src/context/injector.ts
|
|
66445
|
+
import { existsSync as existsSync9 } from "fs";
|
|
66446
|
+
import { join as join10 } from "path";
|
|
66447
|
+
var NOTABLE_NODE_DEPS = [
|
|
66448
|
+
"@nestjs",
|
|
66449
|
+
"express",
|
|
66450
|
+
"fastify",
|
|
66451
|
+
"koa",
|
|
66452
|
+
"hono",
|
|
66453
|
+
"next",
|
|
66454
|
+
"nuxt",
|
|
66455
|
+
"react",
|
|
66456
|
+
"vue",
|
|
66457
|
+
"svelte",
|
|
66458
|
+
"solid",
|
|
66459
|
+
"prisma",
|
|
66460
|
+
"typeorm",
|
|
66461
|
+
"mongoose",
|
|
66462
|
+
"drizzle",
|
|
66463
|
+
"sequelize",
|
|
66464
|
+
"jest",
|
|
66465
|
+
"vitest",
|
|
66466
|
+
"mocha",
|
|
66467
|
+
"bun",
|
|
66468
|
+
"zod",
|
|
66469
|
+
"typescript",
|
|
66470
|
+
"graphql",
|
|
66471
|
+
"trpc",
|
|
66472
|
+
"bull",
|
|
66473
|
+
"ioredis"
|
|
66474
|
+
];
|
|
66475
|
+
async function detectNode(workdir) {
|
|
66476
|
+
const pkgPath = join10(workdir, "package.json");
|
|
66477
|
+
if (!existsSync9(pkgPath))
|
|
66478
|
+
return null;
|
|
66479
|
+
try {
|
|
66480
|
+
const file2 = Bun.file(pkgPath);
|
|
66481
|
+
const pkg = await file2.json();
|
|
66482
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
66483
|
+
const notable = [
|
|
66484
|
+
...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
|
|
66485
|
+
].slice(0, 10);
|
|
66486
|
+
const lang = pkg.devDependencies?.typescript || pkg.dependencies?.typescript ? "TypeScript" : "JavaScript";
|
|
66487
|
+
return { name: pkg.name, lang, dependencies: notable };
|
|
66488
|
+
} catch {
|
|
66489
|
+
return null;
|
|
66490
|
+
}
|
|
66491
|
+
}
|
|
66492
|
+
async function detectGo(workdir) {
|
|
66493
|
+
const goMod = join10(workdir, "go.mod");
|
|
66494
|
+
if (!existsSync9(goMod))
|
|
66495
|
+
return null;
|
|
66496
|
+
try {
|
|
66497
|
+
const content = await Bun.file(goMod).text();
|
|
66498
|
+
const moduleMatch = content.match(/^module\s+(\S+)/m);
|
|
66499
|
+
const name = moduleMatch?.[1];
|
|
66500
|
+
const requires = [];
|
|
66501
|
+
const requireBlock = content.match(/require\s*\(([^)]+)\)/s)?.[1] ?? "";
|
|
66502
|
+
for (const line of requireBlock.split(`
|
|
66503
|
+
`)) {
|
|
66504
|
+
const trimmed = line.trim();
|
|
66505
|
+
if (trimmed && !trimmed.startsWith("//") && !trimmed.includes("// indirect")) {
|
|
66506
|
+
const dep = trimmed.split(/\s+/)[0];
|
|
66507
|
+
if (dep)
|
|
66508
|
+
requires.push(dep.split("/").slice(-1)[0]);
|
|
66509
|
+
}
|
|
66510
|
+
}
|
|
66511
|
+
return { name, lang: "Go", dependencies: requires.slice(0, 10) };
|
|
66512
|
+
} catch {
|
|
66513
|
+
return null;
|
|
66514
|
+
}
|
|
66515
|
+
}
|
|
66516
|
+
async function detectRust(workdir) {
|
|
66517
|
+
const cargoPath = join10(workdir, "Cargo.toml");
|
|
66518
|
+
if (!existsSync9(cargoPath))
|
|
66519
|
+
return null;
|
|
66520
|
+
try {
|
|
66521
|
+
const content = await Bun.file(cargoPath).text();
|
|
66522
|
+
const nameMatch = content.match(/^\[package\][^[]*name\s*=\s*"([^"]+)"/ms);
|
|
66523
|
+
const name = nameMatch?.[1];
|
|
66524
|
+
const depsSection = content.match(/^\[dependencies\]([^[]*)/ms)?.[1] ?? "";
|
|
66525
|
+
const deps = depsSection.split(`
|
|
66526
|
+
`).map((l) => l.split("=")[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
|
|
66527
|
+
return { name, lang: "Rust", dependencies: deps };
|
|
66528
|
+
} catch {
|
|
66529
|
+
return null;
|
|
66530
|
+
}
|
|
66531
|
+
}
|
|
66532
|
+
async function detectPython(workdir) {
|
|
66533
|
+
const pyproject = join10(workdir, "pyproject.toml");
|
|
66534
|
+
const requirements = join10(workdir, "requirements.txt");
|
|
66535
|
+
if (!existsSync9(pyproject) && !existsSync9(requirements))
|
|
66536
|
+
return null;
|
|
66537
|
+
try {
|
|
66538
|
+
if (existsSync9(pyproject)) {
|
|
66539
|
+
const content = await Bun.file(pyproject).text();
|
|
66540
|
+
const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
66541
|
+
const depsSection = content.match(/^\[project\][^[]*dependencies\s*=\s*\[([^\]]*)\]/ms)?.[1] ?? "";
|
|
66542
|
+
const deps = depsSection.split(",").map((d) => d.trim().replace(/["'\s>=<!^~].*/g, "")).filter(Boolean).slice(0, 10);
|
|
66543
|
+
return { name: nameMatch?.[1], lang: "Python", dependencies: deps };
|
|
66544
|
+
}
|
|
66545
|
+
const lines = (await Bun.file(requirements).text()).split(`
|
|
66546
|
+
`).map((l) => l.split(/[>=<!]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
|
|
66547
|
+
return { lang: "Python", dependencies: lines };
|
|
66548
|
+
} catch {
|
|
66549
|
+
return null;
|
|
66550
|
+
}
|
|
66551
|
+
}
|
|
66552
|
+
async function detectPhp(workdir) {
|
|
66553
|
+
const composerPath = join10(workdir, "composer.json");
|
|
66554
|
+
if (!existsSync9(composerPath))
|
|
66555
|
+
return null;
|
|
66556
|
+
try {
|
|
66557
|
+
const file2 = Bun.file(composerPath);
|
|
66558
|
+
const composer = await file2.json();
|
|
66559
|
+
const deps = Object.keys({ ...composer.require ?? {}, ...composer["require-dev"] ?? {} }).filter((d) => d !== "php").map((d) => d.split("/").pop() ?? d).slice(0, 10);
|
|
66560
|
+
return { name: composer.name, lang: "PHP", dependencies: deps };
|
|
66561
|
+
} catch {
|
|
66562
|
+
return null;
|
|
66563
|
+
}
|
|
66564
|
+
}
|
|
66565
|
+
async function detectRuby(workdir) {
|
|
66566
|
+
const gemfile = join10(workdir, "Gemfile");
|
|
66567
|
+
if (!existsSync9(gemfile))
|
|
66568
|
+
return null;
|
|
66569
|
+
try {
|
|
66570
|
+
const content = await Bun.file(gemfile).text();
|
|
66571
|
+
const gems = [...content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)].map((m) => m[1]).slice(0, 10);
|
|
66572
|
+
return { lang: "Ruby", dependencies: gems };
|
|
66573
|
+
} catch {
|
|
66574
|
+
return null;
|
|
66575
|
+
}
|
|
66576
|
+
}
|
|
66577
|
+
async function detectJvm(workdir) {
|
|
66578
|
+
const pom = join10(workdir, "pom.xml");
|
|
66579
|
+
const gradle = join10(workdir, "build.gradle");
|
|
66580
|
+
const gradleKts = join10(workdir, "build.gradle.kts");
|
|
66581
|
+
if (!existsSync9(pom) && !existsSync9(gradle) && !existsSync9(gradleKts))
|
|
66582
|
+
return null;
|
|
66583
|
+
try {
|
|
66584
|
+
if (existsSync9(pom)) {
|
|
66585
|
+
const content2 = await Bun.file(pom).text();
|
|
66586
|
+
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
66587
|
+
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
66588
|
+
const lang2 = existsSync9(join10(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
66589
|
+
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
66590
|
+
}
|
|
66591
|
+
const gradleFile = existsSync9(gradleKts) ? gradleKts : gradle;
|
|
66592
|
+
const content = await Bun.file(gradleFile).text();
|
|
66593
|
+
const lang = gradleFile.endsWith(".kts") ? "Kotlin" : "Java";
|
|
66594
|
+
const deps = [...content.matchAll(/implementation[^'"]*['"]([^:'"]+:[^:'"]+)[^'"]*['"]/g)].map((m) => m[1].split(":").pop() ?? m[1]).slice(0, 10);
|
|
66595
|
+
return { lang, dependencies: deps };
|
|
66596
|
+
} catch {
|
|
66597
|
+
return null;
|
|
66598
|
+
}
|
|
66599
|
+
}
|
|
66600
|
+
async function buildProjectMetadata(workdir, config2) {
|
|
66601
|
+
const detected = await detectGo(workdir) ?? await detectRust(workdir) ?? await detectPython(workdir) ?? await detectPhp(workdir) ?? await detectRuby(workdir) ?? await detectJvm(workdir) ?? await detectNode(workdir);
|
|
66602
|
+
return {
|
|
66603
|
+
name: detected?.name,
|
|
66604
|
+
language: detected?.lang,
|
|
66605
|
+
dependencies: detected?.dependencies ?? [],
|
|
66606
|
+
testCommand: config2.execution?.testCommand ?? undefined,
|
|
66607
|
+
lintCommand: config2.execution?.lintCommand ?? undefined,
|
|
66608
|
+
typecheckCommand: config2.execution?.typecheckCommand ?? undefined
|
|
66609
|
+
};
|
|
66610
|
+
}
|
|
66611
|
+
function formatMetadataSection(metadata) {
|
|
66612
|
+
const lines = ["## Project Metadata", "", "> Auto-injected by `nax generate`", ""];
|
|
66613
|
+
if (metadata.name) {
|
|
66614
|
+
lines.push(`**Project:** \`${metadata.name}\``);
|
|
66615
|
+
lines.push("");
|
|
66616
|
+
}
|
|
66617
|
+
if (metadata.language) {
|
|
66618
|
+
lines.push(`**Language:** ${metadata.language}`);
|
|
66619
|
+
lines.push("");
|
|
66620
|
+
}
|
|
66621
|
+
if (metadata.dependencies.length > 0) {
|
|
66622
|
+
lines.push(`**Key dependencies:** ${metadata.dependencies.join(", ")}`);
|
|
66623
|
+
lines.push("");
|
|
66624
|
+
}
|
|
66625
|
+
const commands = [];
|
|
66626
|
+
if (metadata.testCommand)
|
|
66627
|
+
commands.push(`test: \`${metadata.testCommand}\``);
|
|
66628
|
+
if (metadata.lintCommand)
|
|
66629
|
+
commands.push(`lint: \`${metadata.lintCommand}\``);
|
|
66630
|
+
if (metadata.typecheckCommand)
|
|
66631
|
+
commands.push(`typecheck: \`${metadata.typecheckCommand}\``);
|
|
66632
|
+
if (commands.length > 0) {
|
|
66633
|
+
lines.push(`**Commands:** ${commands.join(" | ")}`);
|
|
66634
|
+
lines.push("");
|
|
66635
|
+
}
|
|
66636
|
+
lines.push("---");
|
|
66637
|
+
lines.push("");
|
|
66638
|
+
return lines.join(`
|
|
66639
|
+
`);
|
|
66640
|
+
}
|
|
66641
|
+
|
|
66642
|
+
// src/context/generators/aider.ts
|
|
66643
|
+
function generateAiderConfig(context) {
|
|
66644
|
+
const header = `# Aider Configuration
|
|
66645
|
+
# Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
66646
|
+
# DO NOT EDIT MANUALLY
|
|
66647
|
+
|
|
66648
|
+
# Project instructions
|
|
66649
|
+
instructions: |
|
|
66650
|
+
`;
|
|
66651
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66652
|
+
const combined = metaSection + context.markdown;
|
|
66653
|
+
const indented = combined.split(`
|
|
66654
|
+
`).map((line) => ` ${line}`).join(`
|
|
66655
|
+
`);
|
|
66656
|
+
return `${header}${indented}
|
|
66657
|
+
`;
|
|
66658
|
+
}
|
|
66659
|
+
var aiderGenerator = {
|
|
66660
|
+
name: "aider",
|
|
66661
|
+
outputFile: ".aider.conf.yml",
|
|
66662
|
+
generate: generateAiderConfig
|
|
66663
|
+
};
|
|
66664
|
+
|
|
66665
|
+
// src/context/generators/claude.ts
|
|
66666
|
+
function generateClaudeConfig(context) {
|
|
66667
|
+
const header = `# Project Context
|
|
66668
|
+
|
|
66669
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66670
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66671
|
+
|
|
66672
|
+
---
|
|
66673
|
+
|
|
66674
|
+
`;
|
|
66675
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66676
|
+
return header + metaSection + context.markdown;
|
|
66677
|
+
}
|
|
66678
|
+
var claudeGenerator = {
|
|
66679
|
+
name: "claude",
|
|
66680
|
+
outputFile: "CLAUDE.md",
|
|
66681
|
+
generate: generateClaudeConfig
|
|
66682
|
+
};
|
|
66683
|
+
|
|
66684
|
+
// src/context/generators/codex.ts
|
|
66685
|
+
function generateCodexConfig(context) {
|
|
66686
|
+
const header = `# Codex Instructions
|
|
66687
|
+
|
|
66688
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66689
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66690
|
+
|
|
66691
|
+
---
|
|
66692
|
+
|
|
66693
|
+
`;
|
|
66694
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66695
|
+
return header + metaSection + context.markdown;
|
|
66696
|
+
}
|
|
66697
|
+
var codexGenerator = {
|
|
66698
|
+
name: "codex",
|
|
66699
|
+
outputFile: "codex.md",
|
|
66700
|
+
generate: generateCodexConfig
|
|
66701
|
+
};
|
|
66702
|
+
|
|
66703
|
+
// src/context/generators/cursor.ts
|
|
66704
|
+
function generateCursorRules(context) {
|
|
66705
|
+
const header = `# Project Rules
|
|
66706
|
+
|
|
66707
|
+
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
66708
|
+
DO NOT EDIT MANUALLY
|
|
66709
|
+
|
|
66710
|
+
---
|
|
66711
|
+
|
|
66712
|
+
`;
|
|
66713
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66714
|
+
return header + metaSection + context.markdown;
|
|
66715
|
+
}
|
|
66716
|
+
var cursorGenerator = {
|
|
66717
|
+
name: "cursor",
|
|
66718
|
+
outputFile: ".cursorrules",
|
|
66719
|
+
generate: generateCursorRules
|
|
66720
|
+
};
|
|
66721
|
+
|
|
66722
|
+
// src/context/generators/gemini.ts
|
|
66723
|
+
function generateGeminiConfig(context) {
|
|
66724
|
+
const header = `# Gemini CLI Context
|
|
66725
|
+
|
|
66726
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66727
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66728
|
+
|
|
66729
|
+
---
|
|
66730
|
+
|
|
66731
|
+
`;
|
|
66732
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66733
|
+
return header + metaSection + context.markdown;
|
|
66734
|
+
}
|
|
66735
|
+
var geminiGenerator = {
|
|
66736
|
+
name: "gemini",
|
|
66737
|
+
outputFile: "GEMINI.md",
|
|
66738
|
+
generate: generateGeminiConfig
|
|
66739
|
+
};
|
|
66740
|
+
|
|
66741
|
+
// src/context/generators/opencode.ts
|
|
66742
|
+
function generateOpencodeConfig(context) {
|
|
66743
|
+
const header = `# Agent Instructions
|
|
66744
|
+
|
|
66745
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66746
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66747
|
+
|
|
66748
|
+
These instructions apply to all AI coding agents in this project.
|
|
66749
|
+
|
|
66750
|
+
---
|
|
66751
|
+
|
|
66752
|
+
`;
|
|
66753
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66754
|
+
return header + metaSection + context.markdown;
|
|
66755
|
+
}
|
|
66756
|
+
var opencodeGenerator = {
|
|
66757
|
+
name: "opencode",
|
|
66758
|
+
outputFile: "AGENTS.md",
|
|
66759
|
+
generate: generateOpencodeConfig
|
|
66760
|
+
};
|
|
66761
|
+
|
|
66762
|
+
// src/context/generators/windsurf.ts
|
|
66763
|
+
function generateWindsurfRules(context) {
|
|
66764
|
+
const header = `# Windsurf Project Rules
|
|
66765
|
+
|
|
66766
|
+
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
66767
|
+
DO NOT EDIT MANUALLY
|
|
66768
|
+
|
|
66769
|
+
---
|
|
66770
|
+
|
|
66771
|
+
`;
|
|
66772
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66773
|
+
return header + metaSection + context.markdown;
|
|
66774
|
+
}
|
|
66775
|
+
var windsurfGenerator = {
|
|
66776
|
+
name: "windsurf",
|
|
66777
|
+
outputFile: ".windsurfrules",
|
|
66778
|
+
generate: generateWindsurfRules
|
|
66779
|
+
};
|
|
66780
|
+
|
|
66781
|
+
// src/context/generator.ts
|
|
66782
|
+
var GENERATORS = {
|
|
66783
|
+
claude: claudeGenerator,
|
|
66784
|
+
codex: codexGenerator,
|
|
66785
|
+
opencode: opencodeGenerator,
|
|
66786
|
+
cursor: cursorGenerator,
|
|
66787
|
+
windsurf: windsurfGenerator,
|
|
66788
|
+
aider: aiderGenerator,
|
|
66789
|
+
gemini: geminiGenerator
|
|
66790
|
+
};
|
|
66791
|
+
async function loadContextContent(options, config2) {
|
|
66792
|
+
if (!existsSync10(options.contextPath)) {
|
|
66793
|
+
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
66794
|
+
}
|
|
66795
|
+
const file2 = Bun.file(options.contextPath);
|
|
66796
|
+
const markdown = await file2.text();
|
|
66797
|
+
const autoInject = options.autoInject ?? true;
|
|
66798
|
+
const metadata = autoInject ? await buildProjectMetadata(options.workdir, config2) : undefined;
|
|
66799
|
+
return { markdown, metadata };
|
|
66800
|
+
}
|
|
66801
|
+
async function generateFor(agent, options, config2) {
|
|
66802
|
+
const generator = GENERATORS[agent];
|
|
66803
|
+
if (!generator) {
|
|
66804
|
+
return { agent, outputFile: "", content: "", written: false, error: `Unknown agent: ${agent}` };
|
|
66805
|
+
}
|
|
66806
|
+
try {
|
|
66807
|
+
const context = await loadContextContent(options, config2);
|
|
66808
|
+
const content = generator.generate(context);
|
|
66809
|
+
const outputPath = join11(options.outputDir, generator.outputFile);
|
|
66810
|
+
validateFilePath(outputPath, options.outputDir);
|
|
66811
|
+
if (!options.dryRun) {
|
|
66812
|
+
await Bun.write(outputPath, content);
|
|
66813
|
+
}
|
|
66814
|
+
return { agent, outputFile: generator.outputFile, content, written: !options.dryRun };
|
|
66815
|
+
} catch (err) {
|
|
66816
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66817
|
+
return { agent, outputFile: generator.outputFile, content: "", written: false, error: error48 };
|
|
66818
|
+
}
|
|
66819
|
+
}
|
|
66820
|
+
async function generateAll(options, config2) {
|
|
66821
|
+
const context = await loadContextContent(options, config2);
|
|
66822
|
+
const results = [];
|
|
66823
|
+
for (const [agentKey, generator] of Object.entries(GENERATORS)) {
|
|
66824
|
+
try {
|
|
66825
|
+
const content = generator.generate(context);
|
|
66826
|
+
const outputPath = join11(options.outputDir, generator.outputFile);
|
|
66827
|
+
validateFilePath(outputPath, options.outputDir);
|
|
66828
|
+
if (!options.dryRun) {
|
|
66829
|
+
await Bun.write(outputPath, content);
|
|
66830
|
+
}
|
|
66831
|
+
results.push({ agent: agentKey, outputFile: generator.outputFile, content, written: !options.dryRun });
|
|
66832
|
+
} catch (err) {
|
|
66833
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66834
|
+
results.push({ agent: agentKey, outputFile: generator.outputFile, content: "", written: false, error: error48 });
|
|
66835
|
+
}
|
|
66836
|
+
}
|
|
66837
|
+
return results;
|
|
66838
|
+
}
|
|
66839
|
+
async function discoverPackages(repoRoot) {
|
|
66840
|
+
const packages = [];
|
|
66841
|
+
const seen = new Set;
|
|
66842
|
+
for (const pattern of ["*/nax/context.md", "*/*/nax/context.md"]) {
|
|
66843
|
+
const glob = new Bun.Glob(pattern);
|
|
66844
|
+
for await (const match of glob.scan(repoRoot)) {
|
|
66845
|
+
const pkgRelative = match.replace(/\/nax\/context\.md$/, "");
|
|
66846
|
+
const pkgAbsolute = join11(repoRoot, pkgRelative);
|
|
66847
|
+
if (!seen.has(pkgAbsolute)) {
|
|
66848
|
+
seen.add(pkgAbsolute);
|
|
66849
|
+
packages.push(pkgAbsolute);
|
|
66850
|
+
}
|
|
66851
|
+
}
|
|
66852
|
+
}
|
|
66853
|
+
return packages;
|
|
66854
|
+
}
|
|
66855
|
+
async function generateForPackage(packageDir, config2, dryRun = false) {
|
|
66856
|
+
const contextPath = join11(packageDir, "nax", "context.md");
|
|
66857
|
+
if (!existsSync10(contextPath)) {
|
|
66858
|
+
return {
|
|
66859
|
+
packageDir,
|
|
66860
|
+
outputFile: "CLAUDE.md",
|
|
66861
|
+
content: "",
|
|
66862
|
+
written: false,
|
|
66863
|
+
error: `context.md not found: ${contextPath}`
|
|
66864
|
+
};
|
|
66865
|
+
}
|
|
66866
|
+
try {
|
|
66867
|
+
const options = {
|
|
66868
|
+
contextPath,
|
|
66869
|
+
outputDir: packageDir,
|
|
66870
|
+
workdir: packageDir,
|
|
66871
|
+
dryRun,
|
|
66872
|
+
autoInject: true
|
|
66873
|
+
};
|
|
66874
|
+
const result = await generateFor("claude", options, config2);
|
|
66875
|
+
return {
|
|
66876
|
+
packageDir,
|
|
66877
|
+
outputFile: result.outputFile,
|
|
66878
|
+
content: result.content,
|
|
66879
|
+
written: result.written,
|
|
66880
|
+
error: result.error
|
|
66881
|
+
};
|
|
66882
|
+
} catch (err) {
|
|
66883
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66884
|
+
return { packageDir, outputFile: "CLAUDE.md", content: "", written: false, error: error48 };
|
|
66885
|
+
}
|
|
66886
|
+
}
|
|
66887
|
+
|
|
66888
|
+
// src/cli/plan.ts
|
|
66120
66889
|
init_pid_registry();
|
|
66121
66890
|
init_logger2();
|
|
66122
66891
|
|
|
@@ -66202,6 +66971,20 @@ function validateStory(raw, index, allIds) {
|
|
|
66202
66971
|
}
|
|
66203
66972
|
const rawTags = s.tags;
|
|
66204
66973
|
const tags = Array.isArray(rawTags) ? rawTags : [];
|
|
66974
|
+
const rawWorkdir = s.workdir;
|
|
66975
|
+
let workdir;
|
|
66976
|
+
if (rawWorkdir !== undefined && rawWorkdir !== null) {
|
|
66977
|
+
if (typeof rawWorkdir !== "string") {
|
|
66978
|
+
throw new Error(`[schema] story[${index}].workdir must be a string`);
|
|
66979
|
+
}
|
|
66980
|
+
if (rawWorkdir.startsWith("/")) {
|
|
66981
|
+
throw new Error(`[schema] story[${index}].workdir must be relative (no leading /): "${rawWorkdir}"`);
|
|
66982
|
+
}
|
|
66983
|
+
if (rawWorkdir.includes("..")) {
|
|
66984
|
+
throw new Error(`[schema] story[${index}].workdir must not contain '..': "${rawWorkdir}"`);
|
|
66985
|
+
}
|
|
66986
|
+
workdir = rawWorkdir;
|
|
66987
|
+
}
|
|
66205
66988
|
return {
|
|
66206
66989
|
id,
|
|
66207
66990
|
title: title.trim(),
|
|
@@ -66217,7 +67000,8 @@ function validateStory(raw, index, allIds) {
|
|
|
66217
67000
|
complexity,
|
|
66218
67001
|
testStrategy,
|
|
66219
67002
|
reasoning: "validated from LLM output"
|
|
66220
|
-
}
|
|
67003
|
+
},
|
|
67004
|
+
...workdir !== undefined ? { workdir } : {}
|
|
66221
67005
|
};
|
|
66222
67006
|
}
|
|
66223
67007
|
function parseRawString(text) {
|
|
@@ -66268,36 +67052,41 @@ var _deps2 = {
|
|
|
66268
67052
|
writeFile: (path, content) => Bun.write(path, content).then(() => {}),
|
|
66269
67053
|
scanCodebase: (workdir) => scanCodebase(workdir),
|
|
66270
67054
|
getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
|
|
66271
|
-
readPackageJson: (workdir) => Bun.file(
|
|
67055
|
+
readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
|
|
66272
67056
|
spawnSync: (cmd, opts) => {
|
|
66273
67057
|
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
|
|
66274
67058
|
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
66275
67059
|
},
|
|
66276
67060
|
mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {}),
|
|
66277
|
-
existsSync: (path) =>
|
|
67061
|
+
existsSync: (path) => existsSync11(path),
|
|
67062
|
+
discoverPackages: (repoRoot) => discoverPackages(repoRoot)
|
|
66278
67063
|
};
|
|
66279
67064
|
async function planCommand(workdir, config2, options) {
|
|
66280
|
-
const naxDir =
|
|
66281
|
-
if (!
|
|
67065
|
+
const naxDir = join12(workdir, "nax");
|
|
67066
|
+
if (!existsSync11(naxDir)) {
|
|
66282
67067
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
66283
67068
|
}
|
|
66284
67069
|
const logger = getLogger();
|
|
66285
67070
|
logger?.info("plan", "Reading spec", { from: options.from });
|
|
66286
67071
|
const specContent = await _deps2.readFile(options.from);
|
|
66287
67072
|
logger?.info("plan", "Scanning codebase...");
|
|
66288
|
-
const scan = await
|
|
67073
|
+
const [scan, discoveredPackages, pkg] = await Promise.all([
|
|
67074
|
+
_deps2.scanCodebase(workdir),
|
|
67075
|
+
_deps2.discoverPackages(workdir),
|
|
67076
|
+
_deps2.readPackageJson(workdir)
|
|
67077
|
+
]);
|
|
66289
67078
|
const codebaseContext = buildCodebaseContext2(scan);
|
|
66290
|
-
const
|
|
67079
|
+
const relativePackages = discoveredPackages.map((p) => p.replace(`${workdir}/`, ""));
|
|
66291
67080
|
const projectName = detectProjectName(workdir, pkg);
|
|
66292
67081
|
const branchName = options.branch ?? `feat/${options.feature}`;
|
|
66293
|
-
const outputDir =
|
|
66294
|
-
const outputPath =
|
|
67082
|
+
const outputDir = join12(naxDir, "features", options.feature);
|
|
67083
|
+
const outputPath = join12(outputDir, "prd.json");
|
|
66295
67084
|
await _deps2.mkdirp(outputDir);
|
|
66296
67085
|
const agentName = config2?.autoMode?.defaultAgent ?? "claude";
|
|
66297
67086
|
const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
|
|
66298
67087
|
let rawResponse;
|
|
66299
67088
|
if (options.auto) {
|
|
66300
|
-
const prompt = buildPlanningPrompt(specContent, codebaseContext);
|
|
67089
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages);
|
|
66301
67090
|
const cliAdapter = _deps2.getAgent(agentName);
|
|
66302
67091
|
if (!cliAdapter)
|
|
66303
67092
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -66309,7 +67098,7 @@ async function planCommand(workdir, config2, options) {
|
|
|
66309
67098
|
}
|
|
66310
67099
|
} catch {}
|
|
66311
67100
|
} else {
|
|
66312
|
-
const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath);
|
|
67101
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages);
|
|
66313
67102
|
const adapter = _deps2.getAgent(agentName, config2);
|
|
66314
67103
|
if (!adapter)
|
|
66315
67104
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -66417,7 +67206,18 @@ function buildCodebaseContext2(scan) {
|
|
|
66417
67206
|
return sections.join(`
|
|
66418
67207
|
`);
|
|
66419
67208
|
}
|
|
66420
|
-
function buildPlanningPrompt(specContent, codebaseContext, outputFilePath) {
|
|
67209
|
+
function buildPlanningPrompt(specContent, codebaseContext, outputFilePath, packages) {
|
|
67210
|
+
const isMonorepo = packages && packages.length > 0;
|
|
67211
|
+
const monorepoHint = isMonorepo ? `
|
|
67212
|
+
## Monorepo Context
|
|
67213
|
+
|
|
67214
|
+
This is a monorepo. Detected packages:
|
|
67215
|
+
${packages.map((p) => `- ${p}`).join(`
|
|
67216
|
+
`)}
|
|
67217
|
+
|
|
67218
|
+
For each user story, set the "workdir" field to the relevant package path (e.g. "packages/api"). Stories that span the root should omit "workdir".` : "";
|
|
67219
|
+
const workdirField = isMonorepo ? `
|
|
67220
|
+
"workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
|
|
66421
67221
|
return `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
66422
67222
|
|
|
66423
67223
|
## Spec
|
|
@@ -66426,7 +67226,7 @@ ${specContent}
|
|
|
66426
67226
|
|
|
66427
67227
|
## Codebase Context
|
|
66428
67228
|
|
|
66429
|
-
${codebaseContext}
|
|
67229
|
+
${codebaseContext}${monorepoHint}
|
|
66430
67230
|
|
|
66431
67231
|
## Output Schema
|
|
66432
67232
|
|
|
@@ -66445,7 +67245,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
66445
67245
|
"description": "string \u2014 detailed description of the story",
|
|
66446
67246
|
"acceptanceCriteria": ["string \u2014 each AC line"],
|
|
66447
67247
|
"tags": ["string \u2014 routing tags, e.g. feature, security, api"],
|
|
66448
|
-
"dependencies": ["string \u2014 story IDs this story depends on"]
|
|
67248
|
+
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
66449
67249
|
"status": "pending",
|
|
66450
67250
|
"passes": false,
|
|
66451
67251
|
"routing": {
|
|
@@ -66615,14 +67415,14 @@ async function displayModelEfficiency(workdir) {
|
|
|
66615
67415
|
}
|
|
66616
67416
|
// src/cli/status-features.ts
|
|
66617
67417
|
init_source();
|
|
66618
|
-
import { existsSync as
|
|
66619
|
-
import { join as
|
|
67418
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
|
|
67419
|
+
import { join as join15 } from "path";
|
|
66620
67420
|
|
|
66621
67421
|
// src/commands/common.ts
|
|
66622
67422
|
init_path_security2();
|
|
66623
67423
|
init_errors3();
|
|
66624
|
-
import { existsSync as
|
|
66625
|
-
import { join as
|
|
67424
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
|
|
67425
|
+
import { join as join13, resolve as resolve6 } from "path";
|
|
66626
67426
|
function resolveProject(options = {}) {
|
|
66627
67427
|
const { dir, feature } = options;
|
|
66628
67428
|
let projectRoot;
|
|
@@ -66630,37 +67430,37 @@ function resolveProject(options = {}) {
|
|
|
66630
67430
|
let configPath;
|
|
66631
67431
|
if (dir) {
|
|
66632
67432
|
projectRoot = realpathSync3(resolve6(dir));
|
|
66633
|
-
naxDir =
|
|
66634
|
-
if (!
|
|
67433
|
+
naxDir = join13(projectRoot, "nax");
|
|
67434
|
+
if (!existsSync12(naxDir)) {
|
|
66635
67435
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
66636
67436
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
66637
67437
|
}
|
|
66638
|
-
configPath =
|
|
66639
|
-
if (!
|
|
67438
|
+
configPath = join13(naxDir, "config.json");
|
|
67439
|
+
if (!existsSync12(configPath)) {
|
|
66640
67440
|
throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
|
|
66641
67441
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
66642
67442
|
}
|
|
66643
67443
|
} else {
|
|
66644
67444
|
const found = findProjectRoot(process.cwd());
|
|
66645
67445
|
if (!found) {
|
|
66646
|
-
const cwdNaxDir =
|
|
66647
|
-
if (
|
|
66648
|
-
const cwdConfigPath =
|
|
67446
|
+
const cwdNaxDir = join13(process.cwd(), "nax");
|
|
67447
|
+
if (existsSync12(cwdNaxDir)) {
|
|
67448
|
+
const cwdConfigPath = join13(cwdNaxDir, "config.json");
|
|
66649
67449
|
throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
|
|
66650
67450
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
66651
67451
|
}
|
|
66652
67452
|
throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
|
|
66653
67453
|
}
|
|
66654
67454
|
projectRoot = found;
|
|
66655
|
-
naxDir =
|
|
66656
|
-
configPath =
|
|
67455
|
+
naxDir = join13(projectRoot, "nax");
|
|
67456
|
+
configPath = join13(naxDir, "config.json");
|
|
66657
67457
|
}
|
|
66658
67458
|
let featureDir;
|
|
66659
67459
|
if (feature) {
|
|
66660
|
-
const featuresDir =
|
|
66661
|
-
featureDir =
|
|
66662
|
-
if (!
|
|
66663
|
-
const availableFeatures =
|
|
67460
|
+
const featuresDir = join13(naxDir, "features");
|
|
67461
|
+
featureDir = join13(featuresDir, feature);
|
|
67462
|
+
if (!existsSync12(featureDir)) {
|
|
67463
|
+
const availableFeatures = existsSync12(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
66664
67464
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
66665
67465
|
|
|
66666
67466
|
Available features:
|
|
@@ -66685,12 +67485,12 @@ function findProjectRoot(startDir) {
|
|
|
66685
67485
|
let current = resolve6(startDir);
|
|
66686
67486
|
let depth = 0;
|
|
66687
67487
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
66688
|
-
const naxDir =
|
|
66689
|
-
const configPath =
|
|
66690
|
-
if (
|
|
67488
|
+
const naxDir = join13(current, "nax");
|
|
67489
|
+
const configPath = join13(naxDir, "config.json");
|
|
67490
|
+
if (existsSync12(configPath)) {
|
|
66691
67491
|
return realpathSync3(current);
|
|
66692
67492
|
}
|
|
66693
|
-
const parent =
|
|
67493
|
+
const parent = join13(current, "..");
|
|
66694
67494
|
if (parent === current) {
|
|
66695
67495
|
break;
|
|
66696
67496
|
}
|
|
@@ -66712,8 +67512,8 @@ function isPidAlive(pid) {
|
|
|
66712
67512
|
}
|
|
66713
67513
|
}
|
|
66714
67514
|
async function loadStatusFile(featureDir) {
|
|
66715
|
-
const statusPath =
|
|
66716
|
-
if (!
|
|
67515
|
+
const statusPath = join15(featureDir, "status.json");
|
|
67516
|
+
if (!existsSync13(statusPath)) {
|
|
66717
67517
|
return null;
|
|
66718
67518
|
}
|
|
66719
67519
|
try {
|
|
@@ -66724,8 +67524,8 @@ async function loadStatusFile(featureDir) {
|
|
|
66724
67524
|
}
|
|
66725
67525
|
}
|
|
66726
67526
|
async function loadProjectStatusFile(projectDir) {
|
|
66727
|
-
const statusPath =
|
|
66728
|
-
if (!
|
|
67527
|
+
const statusPath = join15(projectDir, "nax", "status.json");
|
|
67528
|
+
if (!existsSync13(statusPath)) {
|
|
66729
67529
|
return null;
|
|
66730
67530
|
}
|
|
66731
67531
|
try {
|
|
@@ -66736,8 +67536,8 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
66736
67536
|
}
|
|
66737
67537
|
}
|
|
66738
67538
|
async function getFeatureSummary(featureName, featureDir) {
|
|
66739
|
-
const prdPath =
|
|
66740
|
-
if (!
|
|
67539
|
+
const prdPath = join15(featureDir, "prd.json");
|
|
67540
|
+
if (!existsSync13(prdPath)) {
|
|
66741
67541
|
return {
|
|
66742
67542
|
name: featureName,
|
|
66743
67543
|
done: 0,
|
|
@@ -66779,8 +67579,8 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
66779
67579
|
};
|
|
66780
67580
|
}
|
|
66781
67581
|
}
|
|
66782
|
-
const runsDir =
|
|
66783
|
-
if (
|
|
67582
|
+
const runsDir = join15(featureDir, "runs");
|
|
67583
|
+
if (existsSync13(runsDir)) {
|
|
66784
67584
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
66785
67585
|
if (runs.length > 0) {
|
|
66786
67586
|
const latestRun = runs[0].replace(".jsonl", "");
|
|
@@ -66790,8 +67590,8 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
66790
67590
|
return summary;
|
|
66791
67591
|
}
|
|
66792
67592
|
async function displayAllFeatures(projectDir) {
|
|
66793
|
-
const featuresDir =
|
|
66794
|
-
if (!
|
|
67593
|
+
const featuresDir = join15(projectDir, "nax", "features");
|
|
67594
|
+
if (!existsSync13(featuresDir)) {
|
|
66795
67595
|
console.log(source_default.dim("No features found."));
|
|
66796
67596
|
return;
|
|
66797
67597
|
}
|
|
@@ -66831,7 +67631,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
66831
67631
|
console.log();
|
|
66832
67632
|
}
|
|
66833
67633
|
}
|
|
66834
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
67634
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join15(featuresDir, name))));
|
|
66835
67635
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
66836
67636
|
`));
|
|
66837
67637
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -66857,8 +67657,8 @@ async function displayAllFeatures(projectDir) {
|
|
|
66857
67657
|
console.log();
|
|
66858
67658
|
}
|
|
66859
67659
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
66860
|
-
const prdPath =
|
|
66861
|
-
if (!
|
|
67660
|
+
const prdPath = join15(featureDir, "prd.json");
|
|
67661
|
+
if (!existsSync13(prdPath)) {
|
|
66862
67662
|
console.log(source_default.bold(`
|
|
66863
67663
|
\uD83D\uDCCA ${featureName}
|
|
66864
67664
|
`));
|
|
@@ -66978,8 +67778,8 @@ async function displayFeatureStatus(options = {}) {
|
|
|
66978
67778
|
// src/cli/runs.ts
|
|
66979
67779
|
init_errors3();
|
|
66980
67780
|
init_logger2();
|
|
66981
|
-
import { existsSync as
|
|
66982
|
-
import { join as
|
|
67781
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4 } from "fs";
|
|
67782
|
+
import { join as join16 } from "path";
|
|
66983
67783
|
async function parseRunLog(logPath) {
|
|
66984
67784
|
const logger = getLogger();
|
|
66985
67785
|
try {
|
|
@@ -66995,8 +67795,8 @@ async function parseRunLog(logPath) {
|
|
|
66995
67795
|
async function runsListCommand(options) {
|
|
66996
67796
|
const logger = getLogger();
|
|
66997
67797
|
const { feature, workdir } = options;
|
|
66998
|
-
const runsDir =
|
|
66999
|
-
if (!
|
|
67798
|
+
const runsDir = join16(workdir, "nax", "features", feature, "runs");
|
|
67799
|
+
if (!existsSync14(runsDir)) {
|
|
67000
67800
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
67001
67801
|
return;
|
|
67002
67802
|
}
|
|
@@ -67007,7 +67807,7 @@ async function runsListCommand(options) {
|
|
|
67007
67807
|
}
|
|
67008
67808
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
67009
67809
|
for (const file2 of files.sort().reverse()) {
|
|
67010
|
-
const logPath =
|
|
67810
|
+
const logPath = join16(runsDir, file2);
|
|
67011
67811
|
const entries = await parseRunLog(logPath);
|
|
67012
67812
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
67013
67813
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -67033,8 +67833,8 @@ async function runsListCommand(options) {
|
|
|
67033
67833
|
async function runsShowCommand(options) {
|
|
67034
67834
|
const logger = getLogger();
|
|
67035
67835
|
const { runId, feature, workdir } = options;
|
|
67036
|
-
const logPath =
|
|
67037
|
-
if (!
|
|
67836
|
+
const logPath = join16(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
67837
|
+
if (!existsSync14(logPath)) {
|
|
67038
67838
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
67039
67839
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
67040
67840
|
}
|
|
@@ -67071,8 +67871,8 @@ async function runsShowCommand(options) {
|
|
|
67071
67871
|
}
|
|
67072
67872
|
// src/cli/prompts-main.ts
|
|
67073
67873
|
init_logger2();
|
|
67074
|
-
import { existsSync as
|
|
67075
|
-
import { join as
|
|
67874
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
|
|
67875
|
+
import { join as join27 } from "path";
|
|
67076
67876
|
|
|
67077
67877
|
// src/pipeline/index.ts
|
|
67078
67878
|
init_runner();
|
|
@@ -67108,7 +67908,7 @@ init_prd();
|
|
|
67108
67908
|
|
|
67109
67909
|
// src/cli/prompts-tdd.ts
|
|
67110
67910
|
init_prompts2();
|
|
67111
|
-
import { join as
|
|
67911
|
+
import { join as join26 } from "path";
|
|
67112
67912
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
67113
67913
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
67114
67914
|
PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
@@ -67127,7 +67927,7 @@ ${frontmatter}---
|
|
|
67127
67927
|
|
|
67128
67928
|
${session.prompt}`;
|
|
67129
67929
|
if (outputDir) {
|
|
67130
|
-
const promptFile =
|
|
67930
|
+
const promptFile = join26(outputDir, `${story.id}.${session.role}.md`);
|
|
67131
67931
|
await Bun.write(promptFile, fullOutput);
|
|
67132
67932
|
logger.info("cli", "Written TDD prompt file", {
|
|
67133
67933
|
storyId: story.id,
|
|
@@ -67143,7 +67943,7 @@ ${"=".repeat(80)}`);
|
|
|
67143
67943
|
}
|
|
67144
67944
|
}
|
|
67145
67945
|
if (outputDir && ctx.contextMarkdown) {
|
|
67146
|
-
const contextFile =
|
|
67946
|
+
const contextFile = join26(outputDir, `${story.id}.context.md`);
|
|
67147
67947
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
67148
67948
|
const contextOutput = `---
|
|
67149
67949
|
${frontmatter}---
|
|
@@ -67157,13 +67957,13 @@ ${ctx.contextMarkdown}`;
|
|
|
67157
67957
|
async function promptsCommand(options) {
|
|
67158
67958
|
const logger = getLogger();
|
|
67159
67959
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
67160
|
-
const naxDir =
|
|
67161
|
-
if (!
|
|
67960
|
+
const naxDir = join27(workdir, "nax");
|
|
67961
|
+
if (!existsSync18(naxDir)) {
|
|
67162
67962
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
67163
67963
|
}
|
|
67164
|
-
const featureDir =
|
|
67165
|
-
const prdPath =
|
|
67166
|
-
if (!
|
|
67964
|
+
const featureDir = join27(naxDir, "features", feature);
|
|
67965
|
+
const prdPath = join27(featureDir, "prd.json");
|
|
67966
|
+
if (!existsSync18(prdPath)) {
|
|
67167
67967
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
67168
67968
|
}
|
|
67169
67969
|
const prd = await loadPRD(prdPath);
|
|
@@ -67222,10 +68022,10 @@ ${frontmatter}---
|
|
|
67222
68022
|
|
|
67223
68023
|
${ctx.prompt}`;
|
|
67224
68024
|
if (outputDir) {
|
|
67225
|
-
const promptFile =
|
|
68025
|
+
const promptFile = join27(outputDir, `${story.id}.prompt.md`);
|
|
67226
68026
|
await Bun.write(promptFile, fullOutput);
|
|
67227
68027
|
if (ctx.contextMarkdown) {
|
|
67228
|
-
const contextFile =
|
|
68028
|
+
const contextFile = join27(outputDir, `${story.id}.context.md`);
|
|
67229
68029
|
const contextOutput = `---
|
|
67230
68030
|
${frontmatter}---
|
|
67231
68031
|
|
|
@@ -67288,8 +68088,8 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
67288
68088
|
`;
|
|
67289
68089
|
}
|
|
67290
68090
|
// src/cli/prompts-init.ts
|
|
67291
|
-
import { existsSync as
|
|
67292
|
-
import { join as
|
|
68091
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
|
|
68092
|
+
import { join as join28 } from "path";
|
|
67293
68093
|
var TEMPLATE_ROLES = [
|
|
67294
68094
|
{ file: "test-writer.md", role: "test-writer" },
|
|
67295
68095
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -67313,9 +68113,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
67313
68113
|
`;
|
|
67314
68114
|
async function promptsInitCommand(options) {
|
|
67315
68115
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
67316
|
-
const templatesDir =
|
|
68116
|
+
const templatesDir = join28(workdir, "nax", "templates");
|
|
67317
68117
|
mkdirSync4(templatesDir, { recursive: true });
|
|
67318
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) =>
|
|
68118
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join28(templatesDir, f)));
|
|
67319
68119
|
if (existingFiles.length > 0 && !force) {
|
|
67320
68120
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
67321
68121
|
Pass --force to overwrite existing templates.`);
|
|
@@ -67323,7 +68123,7 @@ async function promptsInitCommand(options) {
|
|
|
67323
68123
|
}
|
|
67324
68124
|
const written = [];
|
|
67325
68125
|
for (const template of TEMPLATE_ROLES) {
|
|
67326
|
-
const filePath =
|
|
68126
|
+
const filePath = join28(templatesDir, template.file);
|
|
67327
68127
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
67328
68128
|
const content = TEMPLATE_HEADER + roleBody;
|
|
67329
68129
|
await Bun.write(filePath, content);
|
|
@@ -67339,8 +68139,8 @@ async function promptsInitCommand(options) {
|
|
|
67339
68139
|
return written;
|
|
67340
68140
|
}
|
|
67341
68141
|
async function autoWirePromptsConfig(workdir) {
|
|
67342
|
-
const configPath =
|
|
67343
|
-
if (!
|
|
68142
|
+
const configPath = join28(workdir, "nax.config.json");
|
|
68143
|
+
if (!existsSync19(configPath)) {
|
|
67344
68144
|
const exampleConfig = JSON.stringify({
|
|
67345
68145
|
prompts: {
|
|
67346
68146
|
overrides: {
|
|
@@ -67435,9 +68235,7 @@ async function exportPromptCommand(options) {
|
|
|
67435
68235
|
// src/cli/init.ts
|
|
67436
68236
|
init_paths();
|
|
67437
68237
|
init_logger2();
|
|
67438
|
-
|
|
67439
|
-
// src/cli/init-context.ts
|
|
67440
|
-
init_logger2();
|
|
68238
|
+
init_init_context();
|
|
67441
68239
|
// src/cli/plugins.ts
|
|
67442
68240
|
init_loader5();
|
|
67443
68241
|
import * as os2 from "os";
|
|
@@ -67505,8 +68303,8 @@ function pad(str, width) {
|
|
|
67505
68303
|
init_config();
|
|
67506
68304
|
init_logger2();
|
|
67507
68305
|
init_prd();
|
|
67508
|
-
import { existsSync as
|
|
67509
|
-
import { join as
|
|
68306
|
+
import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
|
|
68307
|
+
import { join as join32 } from "path";
|
|
67510
68308
|
|
|
67511
68309
|
// src/cli/diagnose-analysis.ts
|
|
67512
68310
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -67705,8 +68503,8 @@ function isProcessAlive2(pid) {
|
|
|
67705
68503
|
}
|
|
67706
68504
|
}
|
|
67707
68505
|
async function loadStatusFile2(workdir) {
|
|
67708
|
-
const statusPath =
|
|
67709
|
-
if (!
|
|
68506
|
+
const statusPath = join32(workdir, "nax", "status.json");
|
|
68507
|
+
if (!existsSync21(statusPath))
|
|
67710
68508
|
return null;
|
|
67711
68509
|
try {
|
|
67712
68510
|
return await Bun.file(statusPath).json();
|
|
@@ -67733,7 +68531,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
67733
68531
|
}
|
|
67734
68532
|
}
|
|
67735
68533
|
async function checkLock(workdir) {
|
|
67736
|
-
const lockFile = Bun.file(
|
|
68534
|
+
const lockFile = Bun.file(join32(workdir, "nax.lock"));
|
|
67737
68535
|
if (!await lockFile.exists())
|
|
67738
68536
|
return { lockPresent: false };
|
|
67739
68537
|
try {
|
|
@@ -67751,8 +68549,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
67751
68549
|
const logger = getLogger();
|
|
67752
68550
|
const workdir = options.workdir ?? process.cwd();
|
|
67753
68551
|
const naxSubdir = findProjectDir(workdir);
|
|
67754
|
-
let projectDir = naxSubdir ?
|
|
67755
|
-
if (!projectDir &&
|
|
68552
|
+
let projectDir = naxSubdir ? join32(naxSubdir, "..") : null;
|
|
68553
|
+
if (!projectDir && existsSync21(join32(workdir, "nax"))) {
|
|
67756
68554
|
projectDir = workdir;
|
|
67757
68555
|
}
|
|
67758
68556
|
if (!projectDir)
|
|
@@ -67763,8 +68561,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
67763
68561
|
if (status2) {
|
|
67764
68562
|
feature = status2.run.feature;
|
|
67765
68563
|
} else {
|
|
67766
|
-
const featuresDir =
|
|
67767
|
-
if (!
|
|
68564
|
+
const featuresDir = join32(projectDir, "nax", "features");
|
|
68565
|
+
if (!existsSync21(featuresDir))
|
|
67768
68566
|
throw new Error("No features found in project");
|
|
67769
68567
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
67770
68568
|
if (features.length === 0)
|
|
@@ -67773,9 +68571,9 @@ async function diagnoseCommand(options = {}) {
|
|
|
67773
68571
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
67774
68572
|
}
|
|
67775
68573
|
}
|
|
67776
|
-
const featureDir =
|
|
67777
|
-
const prdPath =
|
|
67778
|
-
if (!
|
|
68574
|
+
const featureDir = join32(projectDir, "nax", "features", feature);
|
|
68575
|
+
const prdPath = join32(featureDir, "prd.json");
|
|
68576
|
+
if (!existsSync21(prdPath))
|
|
67779
68577
|
throw new Error(`Feature not found: ${feature}`);
|
|
67780
68578
|
const prd = await loadPRD(prdPath);
|
|
67781
68579
|
const status = await loadStatusFile2(projectDir);
|
|
@@ -67816,419 +68614,66 @@ init_interaction();
|
|
|
67816
68614
|
// src/cli/generate.ts
|
|
67817
68615
|
init_source();
|
|
67818
68616
|
init_loader2();
|
|
67819
|
-
import { existsSync as
|
|
67820
|
-
import { join as
|
|
67821
|
-
|
|
67822
|
-
|
|
67823
|
-
|
|
67824
|
-
|
|
67825
|
-
|
|
67826
|
-
|
|
67827
|
-
// src/context/injector.ts
|
|
67828
|
-
import { existsSync as existsSync18 } from "fs";
|
|
67829
|
-
import { join as join26 } from "path";
|
|
67830
|
-
var NOTABLE_NODE_DEPS = [
|
|
67831
|
-
"@nestjs",
|
|
67832
|
-
"express",
|
|
67833
|
-
"fastify",
|
|
67834
|
-
"koa",
|
|
67835
|
-
"hono",
|
|
67836
|
-
"next",
|
|
67837
|
-
"nuxt",
|
|
67838
|
-
"react",
|
|
67839
|
-
"vue",
|
|
67840
|
-
"svelte",
|
|
67841
|
-
"solid",
|
|
67842
|
-
"prisma",
|
|
67843
|
-
"typeorm",
|
|
67844
|
-
"mongoose",
|
|
67845
|
-
"drizzle",
|
|
67846
|
-
"sequelize",
|
|
67847
|
-
"jest",
|
|
67848
|
-
"vitest",
|
|
67849
|
-
"mocha",
|
|
67850
|
-
"bun",
|
|
67851
|
-
"zod",
|
|
67852
|
-
"typescript",
|
|
67853
|
-
"graphql",
|
|
67854
|
-
"trpc",
|
|
67855
|
-
"bull",
|
|
67856
|
-
"ioredis"
|
|
67857
|
-
];
|
|
67858
|
-
async function detectNode(workdir) {
|
|
67859
|
-
const pkgPath = join26(workdir, "package.json");
|
|
67860
|
-
if (!existsSync18(pkgPath))
|
|
67861
|
-
return null;
|
|
68617
|
+
import { existsSync as existsSync22 } from "fs";
|
|
68618
|
+
import { join as join33 } from "path";
|
|
68619
|
+
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68620
|
+
async function generateCommand(options) {
|
|
68621
|
+
const workdir = process.cwd();
|
|
68622
|
+
const dryRun = options.dryRun ?? false;
|
|
68623
|
+
let config2;
|
|
67862
68624
|
try {
|
|
67863
|
-
|
|
67864
|
-
const pkg = await file2.json();
|
|
67865
|
-
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
67866
|
-
const notable = [
|
|
67867
|
-
...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
|
|
67868
|
-
].slice(0, 10);
|
|
67869
|
-
const lang = pkg.devDependencies?.typescript || pkg.dependencies?.typescript ? "TypeScript" : "JavaScript";
|
|
67870
|
-
return { name: pkg.name, lang, dependencies: notable };
|
|
68625
|
+
config2 = await loadConfig(workdir);
|
|
67871
68626
|
} catch {
|
|
67872
|
-
|
|
68627
|
+
config2 = {};
|
|
67873
68628
|
}
|
|
67874
|
-
|
|
67875
|
-
|
|
67876
|
-
|
|
67877
|
-
if (!existsSync18(goMod))
|
|
67878
|
-
return null;
|
|
67879
|
-
try {
|
|
67880
|
-
const content = await Bun.file(goMod).text();
|
|
67881
|
-
const moduleMatch = content.match(/^module\s+(\S+)/m);
|
|
67882
|
-
const name = moduleMatch?.[1];
|
|
67883
|
-
const requires = [];
|
|
67884
|
-
const requireBlock = content.match(/require\s*\(([^)]+)\)/s)?.[1] ?? "";
|
|
67885
|
-
for (const line of requireBlock.split(`
|
|
67886
|
-
`)) {
|
|
67887
|
-
const trimmed = line.trim();
|
|
67888
|
-
if (trimmed && !trimmed.startsWith("//") && !trimmed.includes("// indirect")) {
|
|
67889
|
-
const dep = trimmed.split(/\s+/)[0];
|
|
67890
|
-
if (dep)
|
|
67891
|
-
requires.push(dep.split("/").slice(-1)[0]);
|
|
67892
|
-
}
|
|
68629
|
+
if (options.allPackages) {
|
|
68630
|
+
if (dryRun) {
|
|
68631
|
+
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
67893
68632
|
}
|
|
67894
|
-
|
|
67895
|
-
|
|
67896
|
-
|
|
67897
|
-
|
|
67898
|
-
|
|
67899
|
-
async function detectRust(workdir) {
|
|
67900
|
-
const cargoPath = join26(workdir, "Cargo.toml");
|
|
67901
|
-
if (!existsSync18(cargoPath))
|
|
67902
|
-
return null;
|
|
67903
|
-
try {
|
|
67904
|
-
const content = await Bun.file(cargoPath).text();
|
|
67905
|
-
const nameMatch = content.match(/^\[package\][^[]*name\s*=\s*"([^"]+)"/ms);
|
|
67906
|
-
const name = nameMatch?.[1];
|
|
67907
|
-
const depsSection = content.match(/^\[dependencies\]([^[]*)/ms)?.[1] ?? "";
|
|
67908
|
-
const deps = depsSection.split(`
|
|
67909
|
-
`).map((l) => l.split("=")[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
|
|
67910
|
-
return { name, lang: "Rust", dependencies: deps };
|
|
67911
|
-
} catch {
|
|
67912
|
-
return null;
|
|
67913
|
-
}
|
|
67914
|
-
}
|
|
67915
|
-
async function detectPython(workdir) {
|
|
67916
|
-
const pyproject = join26(workdir, "pyproject.toml");
|
|
67917
|
-
const requirements = join26(workdir, "requirements.txt");
|
|
67918
|
-
if (!existsSync18(pyproject) && !existsSync18(requirements))
|
|
67919
|
-
return null;
|
|
67920
|
-
try {
|
|
67921
|
-
if (existsSync18(pyproject)) {
|
|
67922
|
-
const content = await Bun.file(pyproject).text();
|
|
67923
|
-
const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
67924
|
-
const depsSection = content.match(/^\[project\][^[]*dependencies\s*=\s*\[([^\]]*)\]/ms)?.[1] ?? "";
|
|
67925
|
-
const deps = depsSection.split(",").map((d) => d.trim().replace(/["'\s>=<!^~].*/g, "")).filter(Boolean).slice(0, 10);
|
|
67926
|
-
return { name: nameMatch?.[1], lang: "Python", dependencies: deps };
|
|
68633
|
+
console.log(source_default.blue("\u2192 Discovering packages with nax/context.md..."));
|
|
68634
|
+
const packages = await discoverPackages(workdir);
|
|
68635
|
+
if (packages.length === 0) {
|
|
68636
|
+
console.log(source_default.yellow(" No packages found (no */nax/context.md or */*/nax/context.md)"));
|
|
68637
|
+
return;
|
|
67927
68638
|
}
|
|
67928
|
-
|
|
67929
|
-
|
|
67930
|
-
|
|
67931
|
-
|
|
67932
|
-
|
|
67933
|
-
|
|
67934
|
-
|
|
67935
|
-
|
|
67936
|
-
|
|
67937
|
-
|
|
67938
|
-
|
|
67939
|
-
try {
|
|
67940
|
-
const file2 = Bun.file(composerPath);
|
|
67941
|
-
const composer = await file2.json();
|
|
67942
|
-
const deps = Object.keys({ ...composer.require ?? {}, ...composer["require-dev"] ?? {} }).filter((d) => d !== "php").map((d) => d.split("/").pop() ?? d).slice(0, 10);
|
|
67943
|
-
return { name: composer.name, lang: "PHP", dependencies: deps };
|
|
67944
|
-
} catch {
|
|
67945
|
-
return null;
|
|
67946
|
-
}
|
|
67947
|
-
}
|
|
67948
|
-
async function detectRuby(workdir) {
|
|
67949
|
-
const gemfile = join26(workdir, "Gemfile");
|
|
67950
|
-
if (!existsSync18(gemfile))
|
|
67951
|
-
return null;
|
|
67952
|
-
try {
|
|
67953
|
-
const content = await Bun.file(gemfile).text();
|
|
67954
|
-
const gems = [...content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)].map((m) => m[1]).slice(0, 10);
|
|
67955
|
-
return { lang: "Ruby", dependencies: gems };
|
|
67956
|
-
} catch {
|
|
67957
|
-
return null;
|
|
67958
|
-
}
|
|
67959
|
-
}
|
|
67960
|
-
async function detectJvm(workdir) {
|
|
67961
|
-
const pom = join26(workdir, "pom.xml");
|
|
67962
|
-
const gradle = join26(workdir, "build.gradle");
|
|
67963
|
-
const gradleKts = join26(workdir, "build.gradle.kts");
|
|
67964
|
-
if (!existsSync18(pom) && !existsSync18(gradle) && !existsSync18(gradleKts))
|
|
67965
|
-
return null;
|
|
67966
|
-
try {
|
|
67967
|
-
if (existsSync18(pom)) {
|
|
67968
|
-
const content2 = await Bun.file(pom).text();
|
|
67969
|
-
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
67970
|
-
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
67971
|
-
const lang2 = existsSync18(join26(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
67972
|
-
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
68639
|
+
console.log(source_default.blue(`\u2192 Generating CLAUDE.md for ${packages.length} package(s)...`));
|
|
68640
|
+
let errorCount = 0;
|
|
68641
|
+
for (const pkgDir of packages) {
|
|
68642
|
+
const result = await generateForPackage(pkgDir, config2, dryRun);
|
|
68643
|
+
if (result.error) {
|
|
68644
|
+
console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
|
|
68645
|
+
errorCount++;
|
|
68646
|
+
} else {
|
|
68647
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68648
|
+
console.log(source_default.green(`\u2713 ${pkgDir}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68649
|
+
}
|
|
67973
68650
|
}
|
|
67974
|
-
|
|
67975
|
-
|
|
67976
|
-
|
|
67977
|
-
|
|
67978
|
-
return { lang, dependencies: deps };
|
|
67979
|
-
} catch {
|
|
67980
|
-
return null;
|
|
67981
|
-
}
|
|
67982
|
-
}
|
|
67983
|
-
async function buildProjectMetadata(workdir, config2) {
|
|
67984
|
-
const detected = await detectGo(workdir) ?? await detectRust(workdir) ?? await detectPython(workdir) ?? await detectPhp(workdir) ?? await detectRuby(workdir) ?? await detectJvm(workdir) ?? await detectNode(workdir);
|
|
67985
|
-
return {
|
|
67986
|
-
name: detected?.name,
|
|
67987
|
-
language: detected?.lang,
|
|
67988
|
-
dependencies: detected?.dependencies ?? [],
|
|
67989
|
-
testCommand: config2.execution?.testCommand ?? undefined,
|
|
67990
|
-
lintCommand: config2.execution?.lintCommand ?? undefined,
|
|
67991
|
-
typecheckCommand: config2.execution?.typecheckCommand ?? undefined
|
|
67992
|
-
};
|
|
67993
|
-
}
|
|
67994
|
-
function formatMetadataSection(metadata) {
|
|
67995
|
-
const lines = ["## Project Metadata", "", "> Auto-injected by `nax generate`", ""];
|
|
67996
|
-
if (metadata.name) {
|
|
67997
|
-
lines.push(`**Project:** \`${metadata.name}\``);
|
|
67998
|
-
lines.push("");
|
|
67999
|
-
}
|
|
68000
|
-
if (metadata.language) {
|
|
68001
|
-
lines.push(`**Language:** ${metadata.language}`);
|
|
68002
|
-
lines.push("");
|
|
68003
|
-
}
|
|
68004
|
-
if (metadata.dependencies.length > 0) {
|
|
68005
|
-
lines.push(`**Key dependencies:** ${metadata.dependencies.join(", ")}`);
|
|
68006
|
-
lines.push("");
|
|
68007
|
-
}
|
|
68008
|
-
const commands = [];
|
|
68009
|
-
if (metadata.testCommand)
|
|
68010
|
-
commands.push(`test: \`${metadata.testCommand}\``);
|
|
68011
|
-
if (metadata.lintCommand)
|
|
68012
|
-
commands.push(`lint: \`${metadata.lintCommand}\``);
|
|
68013
|
-
if (metadata.typecheckCommand)
|
|
68014
|
-
commands.push(`typecheck: \`${metadata.typecheckCommand}\``);
|
|
68015
|
-
if (commands.length > 0) {
|
|
68016
|
-
lines.push(`**Commands:** ${commands.join(" | ")}`);
|
|
68017
|
-
lines.push("");
|
|
68018
|
-
}
|
|
68019
|
-
lines.push("---");
|
|
68020
|
-
lines.push("");
|
|
68021
|
-
return lines.join(`
|
|
68022
|
-
`);
|
|
68023
|
-
}
|
|
68024
|
-
|
|
68025
|
-
// src/context/generators/aider.ts
|
|
68026
|
-
function generateAiderConfig(context) {
|
|
68027
|
-
const header = `# Aider Configuration
|
|
68028
|
-
# Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
68029
|
-
# DO NOT EDIT MANUALLY
|
|
68030
|
-
|
|
68031
|
-
# Project instructions
|
|
68032
|
-
instructions: |
|
|
68033
|
-
`;
|
|
68034
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68035
|
-
const combined = metaSection + context.markdown;
|
|
68036
|
-
const indented = combined.split(`
|
|
68037
|
-
`).map((line) => ` ${line}`).join(`
|
|
68038
|
-
`);
|
|
68039
|
-
return `${header}${indented}
|
|
68040
|
-
`;
|
|
68041
|
-
}
|
|
68042
|
-
var aiderGenerator = {
|
|
68043
|
-
name: "aider",
|
|
68044
|
-
outputFile: ".aider.conf.yml",
|
|
68045
|
-
generate: generateAiderConfig
|
|
68046
|
-
};
|
|
68047
|
-
|
|
68048
|
-
// src/context/generators/claude.ts
|
|
68049
|
-
function generateClaudeConfig(context) {
|
|
68050
|
-
const header = `# Project Context
|
|
68051
|
-
|
|
68052
|
-
This file is auto-generated from \`nax/context.md\`.
|
|
68053
|
-
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
68054
|
-
|
|
68055
|
-
---
|
|
68056
|
-
|
|
68057
|
-
`;
|
|
68058
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68059
|
-
return header + metaSection + context.markdown;
|
|
68060
|
-
}
|
|
68061
|
-
var claudeGenerator = {
|
|
68062
|
-
name: "claude",
|
|
68063
|
-
outputFile: "CLAUDE.md",
|
|
68064
|
-
generate: generateClaudeConfig
|
|
68065
|
-
};
|
|
68066
|
-
|
|
68067
|
-
// src/context/generators/codex.ts
|
|
68068
|
-
function generateCodexConfig(context) {
|
|
68069
|
-
const header = `# Codex Instructions
|
|
68070
|
-
|
|
68071
|
-
This file is auto-generated from \`nax/context.md\`.
|
|
68072
|
-
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
68073
|
-
|
|
68074
|
-
---
|
|
68075
|
-
|
|
68076
|
-
`;
|
|
68077
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68078
|
-
return header + metaSection + context.markdown;
|
|
68079
|
-
}
|
|
68080
|
-
var codexGenerator = {
|
|
68081
|
-
name: "codex",
|
|
68082
|
-
outputFile: "codex.md",
|
|
68083
|
-
generate: generateCodexConfig
|
|
68084
|
-
};
|
|
68085
|
-
|
|
68086
|
-
// src/context/generators/cursor.ts
|
|
68087
|
-
function generateCursorRules(context) {
|
|
68088
|
-
const header = `# Project Rules
|
|
68089
|
-
|
|
68090
|
-
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
68091
|
-
DO NOT EDIT MANUALLY
|
|
68092
|
-
|
|
68093
|
-
---
|
|
68094
|
-
|
|
68095
|
-
`;
|
|
68096
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68097
|
-
return header + metaSection + context.markdown;
|
|
68098
|
-
}
|
|
68099
|
-
var cursorGenerator = {
|
|
68100
|
-
name: "cursor",
|
|
68101
|
-
outputFile: ".cursorrules",
|
|
68102
|
-
generate: generateCursorRules
|
|
68103
|
-
};
|
|
68104
|
-
|
|
68105
|
-
// src/context/generators/gemini.ts
|
|
68106
|
-
function generateGeminiConfig(context) {
|
|
68107
|
-
const header = `# Gemini CLI Context
|
|
68108
|
-
|
|
68109
|
-
This file is auto-generated from \`nax/context.md\`.
|
|
68110
|
-
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
68111
|
-
|
|
68112
|
-
---
|
|
68113
|
-
|
|
68114
|
-
`;
|
|
68115
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68116
|
-
return header + metaSection + context.markdown;
|
|
68117
|
-
}
|
|
68118
|
-
var geminiGenerator = {
|
|
68119
|
-
name: "gemini",
|
|
68120
|
-
outputFile: "GEMINI.md",
|
|
68121
|
-
generate: generateGeminiConfig
|
|
68122
|
-
};
|
|
68123
|
-
|
|
68124
|
-
// src/context/generators/opencode.ts
|
|
68125
|
-
function generateOpencodeConfig(context) {
|
|
68126
|
-
const header = `# Agent Instructions
|
|
68127
|
-
|
|
68128
|
-
This file is auto-generated from \`nax/context.md\`.
|
|
68129
|
-
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
68130
|
-
|
|
68131
|
-
These instructions apply to all AI coding agents in this project.
|
|
68132
|
-
|
|
68133
|
-
---
|
|
68134
|
-
|
|
68135
|
-
`;
|
|
68136
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68137
|
-
return header + metaSection + context.markdown;
|
|
68138
|
-
}
|
|
68139
|
-
var opencodeGenerator = {
|
|
68140
|
-
name: "opencode",
|
|
68141
|
-
outputFile: "AGENTS.md",
|
|
68142
|
-
generate: generateOpencodeConfig
|
|
68143
|
-
};
|
|
68144
|
-
|
|
68145
|
-
// src/context/generators/windsurf.ts
|
|
68146
|
-
function generateWindsurfRules(context) {
|
|
68147
|
-
const header = `# Windsurf Project Rules
|
|
68148
|
-
|
|
68149
|
-
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
68150
|
-
DO NOT EDIT MANUALLY
|
|
68151
|
-
|
|
68152
|
-
---
|
|
68153
|
-
|
|
68154
|
-
`;
|
|
68155
|
-
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
68156
|
-
return header + metaSection + context.markdown;
|
|
68157
|
-
}
|
|
68158
|
-
var windsurfGenerator = {
|
|
68159
|
-
name: "windsurf",
|
|
68160
|
-
outputFile: ".windsurfrules",
|
|
68161
|
-
generate: generateWindsurfRules
|
|
68162
|
-
};
|
|
68163
|
-
|
|
68164
|
-
// src/context/generator.ts
|
|
68165
|
-
var GENERATORS = {
|
|
68166
|
-
claude: claudeGenerator,
|
|
68167
|
-
codex: codexGenerator,
|
|
68168
|
-
opencode: opencodeGenerator,
|
|
68169
|
-
cursor: cursorGenerator,
|
|
68170
|
-
windsurf: windsurfGenerator,
|
|
68171
|
-
aider: aiderGenerator,
|
|
68172
|
-
gemini: geminiGenerator
|
|
68173
|
-
};
|
|
68174
|
-
async function loadContextContent(options, config2) {
|
|
68175
|
-
if (!existsSync19(options.contextPath)) {
|
|
68176
|
-
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
68177
|
-
}
|
|
68178
|
-
const file2 = Bun.file(options.contextPath);
|
|
68179
|
-
const markdown = await file2.text();
|
|
68180
|
-
const autoInject = options.autoInject ?? true;
|
|
68181
|
-
const metadata = autoInject ? await buildProjectMetadata(options.workdir, config2) : undefined;
|
|
68182
|
-
return { markdown, metadata };
|
|
68183
|
-
}
|
|
68184
|
-
async function generateFor(agent, options, config2) {
|
|
68185
|
-
const generator = GENERATORS[agent];
|
|
68186
|
-
if (!generator) {
|
|
68187
|
-
return { agent, outputFile: "", content: "", written: false, error: `Unknown agent: ${agent}` };
|
|
68188
|
-
}
|
|
68189
|
-
try {
|
|
68190
|
-
const context = await loadContextContent(options, config2);
|
|
68191
|
-
const content = generator.generate(context);
|
|
68192
|
-
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
68193
|
-
validateFilePath(outputPath, options.outputDir);
|
|
68194
|
-
if (!options.dryRun) {
|
|
68195
|
-
await Bun.write(outputPath, content);
|
|
68651
|
+
if (errorCount > 0) {
|
|
68652
|
+
console.error(source_default.red(`
|
|
68653
|
+
\u2717 ${errorCount} generation(s) failed`));
|
|
68654
|
+
process.exit(1);
|
|
68196
68655
|
}
|
|
68197
|
-
return
|
|
68198
|
-
} catch (err) {
|
|
68199
|
-
const error48 = err instanceof Error ? err.message : String(err);
|
|
68200
|
-
return { agent, outputFile: generator.outputFile, content: "", written: false, error: error48 };
|
|
68656
|
+
return;
|
|
68201
68657
|
}
|
|
68202
|
-
|
|
68203
|
-
|
|
68204
|
-
|
|
68205
|
-
|
|
68206
|
-
|
|
68207
|
-
|
|
68208
|
-
|
|
68209
|
-
|
|
68210
|
-
|
|
68211
|
-
|
|
68212
|
-
await Bun.write(outputPath, content);
|
|
68213
|
-
}
|
|
68214
|
-
results.push({ agent: agentKey, outputFile: generator.outputFile, content, written: !options.dryRun });
|
|
68215
|
-
} catch (err) {
|
|
68216
|
-
const error48 = err instanceof Error ? err.message : String(err);
|
|
68217
|
-
results.push({ agent: agentKey, outputFile: generator.outputFile, content: "", written: false, error: error48 });
|
|
68658
|
+
if (options.package) {
|
|
68659
|
+
const packageDir = join33(workdir, options.package);
|
|
68660
|
+
if (dryRun) {
|
|
68661
|
+
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
68662
|
+
}
|
|
68663
|
+
console.log(source_default.blue(`\u2192 Generating CLAUDE.md for package: ${options.package}`));
|
|
68664
|
+
const result = await generateForPackage(packageDir, config2, dryRun);
|
|
68665
|
+
if (result.error) {
|
|
68666
|
+
console.error(source_default.red(`\u2717 ${result.error}`));
|
|
68667
|
+
process.exit(1);
|
|
68218
68668
|
}
|
|
68669
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68670
|
+
console.log(source_default.green(`\u2713 ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68671
|
+
return;
|
|
68219
68672
|
}
|
|
68220
|
-
|
|
68221
|
-
|
|
68222
|
-
|
|
68223
|
-
// src/cli/generate.ts
|
|
68224
|
-
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68225
|
-
async function generateCommand(options) {
|
|
68226
|
-
const workdir = process.cwd();
|
|
68227
|
-
const contextPath = options.context ? join28(workdir, options.context) : join28(workdir, "nax/context.md");
|
|
68228
|
-
const outputDir = options.output ? join28(workdir, options.output) : workdir;
|
|
68673
|
+
const contextPath = options.context ? join33(workdir, options.context) : join33(workdir, "nax/context.md");
|
|
68674
|
+
const outputDir = options.output ? join33(workdir, options.output) : workdir;
|
|
68229
68675
|
const autoInject = !options.noAutoInject;
|
|
68230
|
-
|
|
68231
|
-
if (!existsSync20(contextPath)) {
|
|
68676
|
+
if (!existsSync22(contextPath)) {
|
|
68232
68677
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
68233
68678
|
console.error(source_default.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
|
|
68234
68679
|
process.exit(1);
|
|
@@ -68245,12 +68690,6 @@ async function generateCommand(options) {
|
|
|
68245
68690
|
if (autoInject) {
|
|
68246
68691
|
console.log(source_default.dim(" Auto-injecting project metadata..."));
|
|
68247
68692
|
}
|
|
68248
|
-
let config2;
|
|
68249
|
-
try {
|
|
68250
|
-
config2 = await loadConfig(workdir);
|
|
68251
|
-
} catch {
|
|
68252
|
-
config2 = {};
|
|
68253
|
-
}
|
|
68254
68693
|
const genOptions = {
|
|
68255
68694
|
contextPath,
|
|
68256
68695
|
outputDir,
|
|
@@ -68300,8 +68739,8 @@ async function generateCommand(options) {
|
|
|
68300
68739
|
}
|
|
68301
68740
|
// src/cli/config-display.ts
|
|
68302
68741
|
init_loader2();
|
|
68303
|
-
import { existsSync as
|
|
68304
|
-
import { join as
|
|
68742
|
+
import { existsSync as existsSync24 } from "fs";
|
|
68743
|
+
import { join as join35 } from "path";
|
|
68305
68744
|
|
|
68306
68745
|
// src/cli/config-descriptions.ts
|
|
68307
68746
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -68509,10 +68948,10 @@ function deepEqual(a, b) {
|
|
|
68509
68948
|
// src/cli/config-get.ts
|
|
68510
68949
|
init_defaults();
|
|
68511
68950
|
init_loader2();
|
|
68512
|
-
import { existsSync as
|
|
68513
|
-
import { join as
|
|
68951
|
+
import { existsSync as existsSync23 } from "fs";
|
|
68952
|
+
import { join as join34 } from "path";
|
|
68514
68953
|
async function loadConfigFile(path14) {
|
|
68515
|
-
if (!
|
|
68954
|
+
if (!existsSync23(path14))
|
|
68516
68955
|
return null;
|
|
68517
68956
|
try {
|
|
68518
68957
|
return await Bun.file(path14).json();
|
|
@@ -68532,7 +68971,7 @@ async function loadProjectConfig() {
|
|
|
68532
68971
|
const projectDir = findProjectDir();
|
|
68533
68972
|
if (!projectDir)
|
|
68534
68973
|
return null;
|
|
68535
|
-
const projectPath =
|
|
68974
|
+
const projectPath = join34(projectDir, "config.json");
|
|
68536
68975
|
return await loadConfigFile(projectPath);
|
|
68537
68976
|
}
|
|
68538
68977
|
|
|
@@ -68592,14 +69031,14 @@ async function configCommand(config2, options = {}) {
|
|
|
68592
69031
|
function determineConfigSources() {
|
|
68593
69032
|
const globalPath = globalConfigPath();
|
|
68594
69033
|
const projectDir = findProjectDir();
|
|
68595
|
-
const projectPath = projectDir ?
|
|
69034
|
+
const projectPath = projectDir ? join35(projectDir, "config.json") : null;
|
|
68596
69035
|
return {
|
|
68597
69036
|
global: fileExists(globalPath) ? globalPath : null,
|
|
68598
69037
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
68599
69038
|
};
|
|
68600
69039
|
}
|
|
68601
69040
|
function fileExists(path14) {
|
|
68602
|
-
return
|
|
69041
|
+
return existsSync24(path14);
|
|
68603
69042
|
}
|
|
68604
69043
|
function displayConfigWithDescriptions(obj, path14, sources, indent = 0) {
|
|
68605
69044
|
const indentStr = " ".repeat(indent);
|
|
@@ -68771,25 +69210,25 @@ async function diagnose(options) {
|
|
|
68771
69210
|
}
|
|
68772
69211
|
|
|
68773
69212
|
// src/commands/logs.ts
|
|
68774
|
-
import { existsSync as
|
|
68775
|
-
import { join as
|
|
69213
|
+
import { existsSync as existsSync26 } from "fs";
|
|
69214
|
+
import { join as join38 } from "path";
|
|
68776
69215
|
|
|
68777
69216
|
// src/commands/logs-formatter.ts
|
|
68778
69217
|
init_source();
|
|
68779
69218
|
init_formatter();
|
|
68780
69219
|
import { readdirSync as readdirSync7 } from "fs";
|
|
68781
|
-
import { join as
|
|
69220
|
+
import { join as join37 } from "path";
|
|
68782
69221
|
|
|
68783
69222
|
// src/commands/logs-reader.ts
|
|
68784
|
-
import { existsSync as
|
|
69223
|
+
import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
|
|
68785
69224
|
import { readdir as readdir3 } from "fs/promises";
|
|
68786
69225
|
import { homedir as homedir5 } from "os";
|
|
68787
|
-
import { join as
|
|
68788
|
-
var
|
|
68789
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
69226
|
+
import { join as join36 } from "path";
|
|
69227
|
+
var _deps7 = {
|
|
69228
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join36(homedir5(), ".nax", "runs")
|
|
68790
69229
|
};
|
|
68791
69230
|
async function resolveRunFileFromRegistry(runId) {
|
|
68792
|
-
const runsDir =
|
|
69231
|
+
const runsDir = _deps7.getRunsDir();
|
|
68793
69232
|
let entries;
|
|
68794
69233
|
try {
|
|
68795
69234
|
entries = await readdir3(runsDir);
|
|
@@ -68798,7 +69237,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
68798
69237
|
}
|
|
68799
69238
|
let matched = null;
|
|
68800
69239
|
for (const entry of entries) {
|
|
68801
|
-
const metaPath =
|
|
69240
|
+
const metaPath = join36(runsDir, entry, "meta.json");
|
|
68802
69241
|
try {
|
|
68803
69242
|
const meta3 = await Bun.file(metaPath).json();
|
|
68804
69243
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -68810,7 +69249,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
68810
69249
|
if (!matched) {
|
|
68811
69250
|
throw new Error(`Run not found in registry: ${runId}`);
|
|
68812
69251
|
}
|
|
68813
|
-
if (!
|
|
69252
|
+
if (!existsSync25(matched.eventsDir)) {
|
|
68814
69253
|
console.log(`Log directory unavailable for run: ${runId}`);
|
|
68815
69254
|
return null;
|
|
68816
69255
|
}
|
|
@@ -68820,14 +69259,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
68820
69259
|
return null;
|
|
68821
69260
|
}
|
|
68822
69261
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
68823
|
-
return
|
|
69262
|
+
return join36(matched.eventsDir, specificFile ?? files[0]);
|
|
68824
69263
|
}
|
|
68825
69264
|
async function selectRunFile(runsDir) {
|
|
68826
69265
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
68827
69266
|
if (files.length === 0) {
|
|
68828
69267
|
return null;
|
|
68829
69268
|
}
|
|
68830
|
-
return
|
|
69269
|
+
return join36(runsDir, files[0]);
|
|
68831
69270
|
}
|
|
68832
69271
|
async function extractRunSummary(filePath) {
|
|
68833
69272
|
const file2 = Bun.file(filePath);
|
|
@@ -68912,7 +69351,7 @@ Runs:
|
|
|
68912
69351
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
68913
69352
|
console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
68914
69353
|
for (const file2 of files) {
|
|
68915
|
-
const filePath =
|
|
69354
|
+
const filePath = join37(runsDir, file2);
|
|
68916
69355
|
const summary = await extractRunSummary(filePath);
|
|
68917
69356
|
const timestamp = file2.replace(".jsonl", "");
|
|
68918
69357
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -69037,7 +69476,7 @@ async function logsCommand(options) {
|
|
|
69037
69476
|
return;
|
|
69038
69477
|
}
|
|
69039
69478
|
const resolved = resolveProject({ dir: options.dir });
|
|
69040
|
-
const naxDir =
|
|
69479
|
+
const naxDir = join38(resolved.projectDir, "nax");
|
|
69041
69480
|
const configPath = resolved.configPath;
|
|
69042
69481
|
const configFile = Bun.file(configPath);
|
|
69043
69482
|
const config2 = await configFile.json();
|
|
@@ -69045,9 +69484,9 @@ async function logsCommand(options) {
|
|
|
69045
69484
|
if (!featureName) {
|
|
69046
69485
|
throw new Error("No feature specified in config.json");
|
|
69047
69486
|
}
|
|
69048
|
-
const featureDir =
|
|
69049
|
-
const runsDir =
|
|
69050
|
-
if (!
|
|
69487
|
+
const featureDir = join38(naxDir, "features", featureName);
|
|
69488
|
+
const runsDir = join38(featureDir, "runs");
|
|
69489
|
+
if (!existsSync26(runsDir)) {
|
|
69051
69490
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
69052
69491
|
}
|
|
69053
69492
|
if (options.list) {
|
|
@@ -69070,8 +69509,8 @@ init_source();
|
|
|
69070
69509
|
init_config();
|
|
69071
69510
|
init_prd();
|
|
69072
69511
|
init_precheck();
|
|
69073
|
-
import { existsSync as
|
|
69074
|
-
import { join as
|
|
69512
|
+
import { existsSync as existsSync31 } from "fs";
|
|
69513
|
+
import { join as join39 } from "path";
|
|
69075
69514
|
async function precheckCommand(options) {
|
|
69076
69515
|
const resolved = resolveProject({
|
|
69077
69516
|
dir: options.dir,
|
|
@@ -69087,14 +69526,14 @@ async function precheckCommand(options) {
|
|
|
69087
69526
|
process.exit(1);
|
|
69088
69527
|
}
|
|
69089
69528
|
}
|
|
69090
|
-
const naxDir =
|
|
69091
|
-
const featureDir =
|
|
69092
|
-
const prdPath =
|
|
69093
|
-
if (!
|
|
69529
|
+
const naxDir = join39(resolved.projectDir, "nax");
|
|
69530
|
+
const featureDir = join39(naxDir, "features", featureName);
|
|
69531
|
+
const prdPath = join39(featureDir, "prd.json");
|
|
69532
|
+
if (!existsSync31(featureDir)) {
|
|
69094
69533
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
69095
69534
|
process.exit(1);
|
|
69096
69535
|
}
|
|
69097
|
-
if (!
|
|
69536
|
+
if (!existsSync31(prdPath)) {
|
|
69098
69537
|
console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
|
|
69099
69538
|
console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
|
|
69100
69539
|
process.exit(EXIT_CODES.INVALID_PRD);
|
|
@@ -69113,10 +69552,10 @@ async function precheckCommand(options) {
|
|
|
69113
69552
|
init_source();
|
|
69114
69553
|
import { readdir as readdir4 } from "fs/promises";
|
|
69115
69554
|
import { homedir as homedir6 } from "os";
|
|
69116
|
-
import { join as
|
|
69555
|
+
import { join as join40 } from "path";
|
|
69117
69556
|
var DEFAULT_LIMIT = 20;
|
|
69118
|
-
var
|
|
69119
|
-
getRunsDir: () =>
|
|
69557
|
+
var _deps9 = {
|
|
69558
|
+
getRunsDir: () => join40(homedir6(), ".nax", "runs")
|
|
69120
69559
|
};
|
|
69121
69560
|
function formatDuration3(ms) {
|
|
69122
69561
|
if (ms <= 0)
|
|
@@ -69158,7 +69597,7 @@ function pad3(str, width) {
|
|
|
69158
69597
|
return str + " ".repeat(padding);
|
|
69159
69598
|
}
|
|
69160
69599
|
async function runsCommand(options = {}) {
|
|
69161
|
-
const runsDir =
|
|
69600
|
+
const runsDir = _deps9.getRunsDir();
|
|
69162
69601
|
let entries;
|
|
69163
69602
|
try {
|
|
69164
69603
|
entries = await readdir4(runsDir);
|
|
@@ -69168,7 +69607,7 @@ async function runsCommand(options = {}) {
|
|
|
69168
69607
|
}
|
|
69169
69608
|
const rows = [];
|
|
69170
69609
|
for (const entry of entries) {
|
|
69171
|
-
const metaPath =
|
|
69610
|
+
const metaPath = join40(runsDir, entry, "meta.json");
|
|
69172
69611
|
let meta3;
|
|
69173
69612
|
try {
|
|
69174
69613
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -69245,7 +69684,7 @@ async function runsCommand(options = {}) {
|
|
|
69245
69684
|
|
|
69246
69685
|
// src/commands/unlock.ts
|
|
69247
69686
|
init_source();
|
|
69248
|
-
import { join as
|
|
69687
|
+
import { join as join41 } from "path";
|
|
69249
69688
|
function isProcessAlive3(pid) {
|
|
69250
69689
|
try {
|
|
69251
69690
|
process.kill(pid, 0);
|
|
@@ -69260,7 +69699,7 @@ function formatLockAge(ageMs) {
|
|
|
69260
69699
|
}
|
|
69261
69700
|
async function unlockCommand(options) {
|
|
69262
69701
|
const workdir = options.dir ?? process.cwd();
|
|
69263
|
-
const lockPath =
|
|
69702
|
+
const lockPath = join41(workdir, "nax.lock");
|
|
69264
69703
|
const lockFile = Bun.file(lockPath);
|
|
69265
69704
|
const exists = await lockFile.exists();
|
|
69266
69705
|
if (!exists) {
|
|
@@ -77072,7 +77511,7 @@ async function promptForConfirmation(question) {
|
|
|
77072
77511
|
process.stdin.on("data", handler);
|
|
77073
77512
|
});
|
|
77074
77513
|
}
|
|
77075
|
-
program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).action(async (options) => {
|
|
77514
|
+
program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).option("--package <dir>", "Scaffold per-package nax/context.md (e.g. packages/api)").action(async (options) => {
|
|
77076
77515
|
let workdir;
|
|
77077
77516
|
try {
|
|
77078
77517
|
workdir = validateDirectory(options.dir);
|
|
@@ -77080,15 +77519,30 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
77080
77519
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
77081
77520
|
process.exit(1);
|
|
77082
77521
|
}
|
|
77083
|
-
|
|
77084
|
-
|
|
77522
|
+
if (options.package) {
|
|
77523
|
+
const { initPackage: initPkg } = await Promise.resolve().then(() => (init_init_context(), exports_init_context));
|
|
77524
|
+
try {
|
|
77525
|
+
await initPkg(workdir, options.package, options.force);
|
|
77526
|
+
console.log(source_default.green(`
|
|
77527
|
+
[OK] Package scaffold created.`));
|
|
77528
|
+
console.log(source_default.dim(` Created: ${options.package}/nax/context.md`));
|
|
77529
|
+
console.log(source_default.dim(`
|
|
77530
|
+
Next: nax generate --package ${options.package}`));
|
|
77531
|
+
} catch (err) {
|
|
77532
|
+
console.error(source_default.red(`Error: ${err.message}`));
|
|
77533
|
+
process.exit(1);
|
|
77534
|
+
}
|
|
77535
|
+
return;
|
|
77536
|
+
}
|
|
77537
|
+
const naxDir = join48(workdir, "nax");
|
|
77538
|
+
if (existsSync34(naxDir) && !options.force) {
|
|
77085
77539
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
77086
77540
|
return;
|
|
77087
77541
|
}
|
|
77088
|
-
mkdirSync6(
|
|
77089
|
-
mkdirSync6(
|
|
77090
|
-
await Bun.write(
|
|
77091
|
-
await Bun.write(
|
|
77542
|
+
mkdirSync6(join48(naxDir, "features"), { recursive: true });
|
|
77543
|
+
mkdirSync6(join48(naxDir, "hooks"), { recursive: true });
|
|
77544
|
+
await Bun.write(join48(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
77545
|
+
await Bun.write(join48(naxDir, "hooks.json"), JSON.stringify({
|
|
77092
77546
|
hooks: {
|
|
77093
77547
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
77094
77548
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -77096,12 +77550,12 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
77096
77550
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
77097
77551
|
}
|
|
77098
77552
|
}, null, 2));
|
|
77099
|
-
await Bun.write(
|
|
77553
|
+
await Bun.write(join48(naxDir, ".gitignore"), `# nax temp files
|
|
77100
77554
|
*.tmp
|
|
77101
77555
|
.paused.json
|
|
77102
77556
|
.nax-verifier-verdict.json
|
|
77103
77557
|
`);
|
|
77104
|
-
await Bun.write(
|
|
77558
|
+
await Bun.write(join48(naxDir, "context.md"), `# Project Context
|
|
77105
77559
|
|
|
77106
77560
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
77107
77561
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -77198,7 +77652,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77198
77652
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
77199
77653
|
process.exit(1);
|
|
77200
77654
|
}
|
|
77201
|
-
if (options.from && !
|
|
77655
|
+
if (options.from && !existsSync34(options.from)) {
|
|
77202
77656
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
77203
77657
|
process.exit(1);
|
|
77204
77658
|
}
|
|
@@ -77227,10 +77681,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77227
77681
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77228
77682
|
process.exit(1);
|
|
77229
77683
|
}
|
|
77230
|
-
const featureDir =
|
|
77231
|
-
const prdPath =
|
|
77684
|
+
const featureDir = join48(naxDir, "features", options.feature);
|
|
77685
|
+
const prdPath = join48(featureDir, "prd.json");
|
|
77232
77686
|
if (options.plan && options.from) {
|
|
77233
|
-
if (
|
|
77687
|
+
if (existsSync34(prdPath) && !options.force) {
|
|
77234
77688
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
77235
77689
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
77236
77690
|
process.exit(1);
|
|
@@ -77250,10 +77704,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77250
77704
|
}
|
|
77251
77705
|
}
|
|
77252
77706
|
try {
|
|
77253
|
-
const planLogDir =
|
|
77707
|
+
const planLogDir = join48(featureDir, "plan");
|
|
77254
77708
|
mkdirSync6(planLogDir, { recursive: true });
|
|
77255
77709
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77256
|
-
const planLogPath =
|
|
77710
|
+
const planLogPath = join48(planLogDir, `${planLogId}.jsonl`);
|
|
77257
77711
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77258
77712
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77259
77713
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -77286,15 +77740,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77286
77740
|
process.exit(1);
|
|
77287
77741
|
}
|
|
77288
77742
|
}
|
|
77289
|
-
if (!
|
|
77743
|
+
if (!existsSync34(prdPath)) {
|
|
77290
77744
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
77291
77745
|
process.exit(1);
|
|
77292
77746
|
}
|
|
77293
77747
|
resetLogger();
|
|
77294
|
-
const runsDir =
|
|
77748
|
+
const runsDir = join48(featureDir, "runs");
|
|
77295
77749
|
mkdirSync6(runsDir, { recursive: true });
|
|
77296
77750
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77297
|
-
const logFilePath =
|
|
77751
|
+
const logFilePath = join48(runsDir, `${runId}.jsonl`);
|
|
77298
77752
|
const isTTY = process.stdout.isTTY ?? false;
|
|
77299
77753
|
const headlessFlag = options.headless ?? false;
|
|
77300
77754
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -77310,7 +77764,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77310
77764
|
config2.autoMode.defaultAgent = options.agent;
|
|
77311
77765
|
}
|
|
77312
77766
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
77313
|
-
const globalNaxDir =
|
|
77767
|
+
const globalNaxDir = join48(homedir10(), ".nax");
|
|
77314
77768
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
77315
77769
|
const eventEmitter = new PipelineEventEmitter;
|
|
77316
77770
|
let tuiInstance;
|
|
@@ -77333,7 +77787,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77333
77787
|
} else {
|
|
77334
77788
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
77335
77789
|
}
|
|
77336
|
-
const statusFilePath =
|
|
77790
|
+
const statusFilePath = join48(workdir, "nax", "status.json");
|
|
77337
77791
|
let parallel;
|
|
77338
77792
|
if (options.parallel !== undefined) {
|
|
77339
77793
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -77359,9 +77813,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77359
77813
|
headless: useHeadless,
|
|
77360
77814
|
skipPrecheck: options.skipPrecheck ?? false
|
|
77361
77815
|
});
|
|
77362
|
-
const latestSymlink =
|
|
77816
|
+
const latestSymlink = join48(runsDir, "latest.jsonl");
|
|
77363
77817
|
try {
|
|
77364
|
-
if (
|
|
77818
|
+
if (existsSync34(latestSymlink)) {
|
|
77365
77819
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
77366
77820
|
}
|
|
77367
77821
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -77397,9 +77851,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77397
77851
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77398
77852
|
process.exit(1);
|
|
77399
77853
|
}
|
|
77400
|
-
const featureDir =
|
|
77854
|
+
const featureDir = join48(naxDir, "features", name);
|
|
77401
77855
|
mkdirSync6(featureDir, { recursive: true });
|
|
77402
|
-
await Bun.write(
|
|
77856
|
+
await Bun.write(join48(featureDir, "spec.md"), `# Feature: ${name}
|
|
77403
77857
|
|
|
77404
77858
|
## Overview
|
|
77405
77859
|
|
|
@@ -77407,7 +77861,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77407
77861
|
|
|
77408
77862
|
## Acceptance Criteria
|
|
77409
77863
|
`);
|
|
77410
|
-
await Bun.write(
|
|
77864
|
+
await Bun.write(join48(featureDir, "plan.md"), `# Plan: ${name}
|
|
77411
77865
|
|
|
77412
77866
|
## Architecture
|
|
77413
77867
|
|
|
@@ -77415,7 +77869,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77415
77869
|
|
|
77416
77870
|
## Dependencies
|
|
77417
77871
|
`);
|
|
77418
|
-
await Bun.write(
|
|
77872
|
+
await Bun.write(join48(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
77419
77873
|
|
|
77420
77874
|
## US-001: [Title]
|
|
77421
77875
|
|
|
@@ -77424,7 +77878,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77424
77878
|
### Acceptance Criteria
|
|
77425
77879
|
- [ ] Criterion 1
|
|
77426
77880
|
`);
|
|
77427
|
-
await Bun.write(
|
|
77881
|
+
await Bun.write(join48(featureDir, "progress.txt"), `# Progress: ${name}
|
|
77428
77882
|
|
|
77429
77883
|
Created: ${new Date().toISOString()}
|
|
77430
77884
|
|
|
@@ -77452,8 +77906,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
77452
77906
|
console.error(source_default.red("nax not initialized."));
|
|
77453
77907
|
process.exit(1);
|
|
77454
77908
|
}
|
|
77455
|
-
const featuresDir =
|
|
77456
|
-
if (!
|
|
77909
|
+
const featuresDir = join48(naxDir, "features");
|
|
77910
|
+
if (!existsSync34(featuresDir)) {
|
|
77457
77911
|
console.log(source_default.dim("No features yet."));
|
|
77458
77912
|
return;
|
|
77459
77913
|
}
|
|
@@ -77467,8 +77921,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
77467
77921
|
Features:
|
|
77468
77922
|
`));
|
|
77469
77923
|
for (const name of entries) {
|
|
77470
|
-
const prdPath =
|
|
77471
|
-
if (
|
|
77924
|
+
const prdPath = join48(featuresDir, name, "prd.json");
|
|
77925
|
+
if (existsSync34(prdPath)) {
|
|
77472
77926
|
const prd = await loadPRD(prdPath);
|
|
77473
77927
|
const c = countStories(prd);
|
|
77474
77928
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -77498,10 +77952,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
77498
77952
|
process.exit(1);
|
|
77499
77953
|
}
|
|
77500
77954
|
const config2 = await loadConfig(workdir);
|
|
77501
|
-
const featureLogDir =
|
|
77955
|
+
const featureLogDir = join48(naxDir, "features", options.feature, "plan");
|
|
77502
77956
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
77503
77957
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77504
|
-
const planLogPath =
|
|
77958
|
+
const planLogPath = join48(featureLogDir, `${planLogId}.jsonl`);
|
|
77505
77959
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77506
77960
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77507
77961
|
try {
|
|
@@ -77538,8 +77992,8 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
77538
77992
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77539
77993
|
process.exit(1);
|
|
77540
77994
|
}
|
|
77541
|
-
const featureDir =
|
|
77542
|
-
if (!
|
|
77995
|
+
const featureDir = join48(naxDir, "features", options.feature);
|
|
77996
|
+
if (!existsSync34(featureDir)) {
|
|
77543
77997
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
77544
77998
|
process.exit(1);
|
|
77545
77999
|
}
|
|
@@ -77554,7 +78008,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
77554
78008
|
specPath: options.from,
|
|
77555
78009
|
reclassify: options.reclassify
|
|
77556
78010
|
});
|
|
77557
|
-
const prdPath =
|
|
78011
|
+
const prdPath = join48(featureDir, "prd.json");
|
|
77558
78012
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
77559
78013
|
const c = countStories(prd);
|
|
77560
78014
|
console.log(source_default.green(`
|
|
@@ -77782,14 +78236,16 @@ program2.command("prompts").description("Assemble or initialize prompts").option
|
|
|
77782
78236
|
process.exit(1);
|
|
77783
78237
|
}
|
|
77784
78238
|
});
|
|
77785
|
-
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").action(async (options) => {
|
|
78239
|
+
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
|
|
77786
78240
|
try {
|
|
77787
78241
|
await generateCommand({
|
|
77788
78242
|
context: options.context,
|
|
77789
78243
|
output: options.output,
|
|
77790
78244
|
agent: options.agent,
|
|
77791
78245
|
dryRun: options.dryRun,
|
|
77792
|
-
noAutoInject: !options.autoInject
|
|
78246
|
+
noAutoInject: !options.autoInject,
|
|
78247
|
+
package: options.package,
|
|
78248
|
+
allPackages: options.allPackages
|
|
77793
78249
|
});
|
|
77794
78250
|
} catch (err) {
|
|
77795
78251
|
console.error(source_default.red(`Error: ${err.message}`));
|