@nathapp/nax 0.46.3 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/nax.ts +20 -0
- package/dist/nax.js +1344 -747
- package/package.json +1 -1
- package/src/cli/generate.ts +86 -13
- package/src/cli/init-context.ts +57 -0
- package/src/cli/init.ts +14 -1
- package/src/cli/plan.ts +139 -8
- package/src/config/loader.ts +34 -1
- package/src/config/merge.ts +37 -0
- package/src/config/runtime-types.ts +12 -0
- package/src/context/generator.ts +181 -1
- 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
|
@@ -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.48.0",
|
|
22182
22214
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22183
22215
|
type: "module",
|
|
22184
22216
|
bin: {
|
|
@@ -22251,8 +22283,8 @@ var init_version = __esm(() => {
|
|
|
22251
22283
|
NAX_VERSION = package_default.version;
|
|
22252
22284
|
NAX_COMMIT = (() => {
|
|
22253
22285
|
try {
|
|
22254
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22255
|
-
return "
|
|
22286
|
+
if (/^[0-9a-f]{6,10}$/.test("3188738"))
|
|
22287
|
+
return "3188738";
|
|
22256
22288
|
} catch {}
|
|
22257
22289
|
try {
|
|
22258
22290
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -24444,7 +24476,7 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
24444
24476
|
}
|
|
24445
24477
|
|
|
24446
24478
|
class ReviewOrchestrator {
|
|
24447
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef) {
|
|
24479
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix) {
|
|
24448
24480
|
const logger = getSafeLogger();
|
|
24449
24481
|
const builtIn = await runReview(reviewConfig, workdir, executionConfig);
|
|
24450
24482
|
if (!builtIn.success) {
|
|
@@ -24459,13 +24491,14 @@ class ReviewOrchestrator {
|
|
|
24459
24491
|
if (reviewers.length > 0) {
|
|
24460
24492
|
const baseRef = storyGitRef ?? executionConfig?.storyGitRef;
|
|
24461
24493
|
const changedFiles = await getChangedFiles(workdir, baseRef);
|
|
24494
|
+
const scopedFiles = scopePrefix ? changedFiles.filter((f) => f === scopePrefix || f.startsWith(`${scopePrefix}/`)) : changedFiles;
|
|
24462
24495
|
const pluginResults = [];
|
|
24463
24496
|
for (const reviewer of reviewers) {
|
|
24464
24497
|
logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
|
|
24465
|
-
changedFiles:
|
|
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,528 @@ 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, readFileSync } from "fs";
|
|
66442
|
+
import { join as join11 } from "path";
|
|
66443
|
+
|
|
66444
|
+
// src/context/injector.ts
|
|
66445
|
+
import { existsSync as existsSync9 } from "fs";
|
|
66446
|
+
import { join as join10 } from "path";
|
|
66447
|
+
var NOTABLE_NODE_DEPS = [
|
|
66448
|
+
"@nestjs",
|
|
66449
|
+
"express",
|
|
66450
|
+
"fastify",
|
|
66451
|
+
"koa",
|
|
66452
|
+
"hono",
|
|
66453
|
+
"next",
|
|
66454
|
+
"nuxt",
|
|
66455
|
+
"react",
|
|
66456
|
+
"vue",
|
|
66457
|
+
"svelte",
|
|
66458
|
+
"solid",
|
|
66459
|
+
"prisma",
|
|
66460
|
+
"typeorm",
|
|
66461
|
+
"mongoose",
|
|
66462
|
+
"drizzle",
|
|
66463
|
+
"sequelize",
|
|
66464
|
+
"jest",
|
|
66465
|
+
"vitest",
|
|
66466
|
+
"mocha",
|
|
66467
|
+
"bun",
|
|
66468
|
+
"zod",
|
|
66469
|
+
"typescript",
|
|
66470
|
+
"graphql",
|
|
66471
|
+
"trpc",
|
|
66472
|
+
"bull",
|
|
66473
|
+
"ioredis"
|
|
66474
|
+
];
|
|
66475
|
+
async function detectNode(workdir) {
|
|
66476
|
+
const pkgPath = join10(workdir, "package.json");
|
|
66477
|
+
if (!existsSync9(pkgPath))
|
|
66478
|
+
return null;
|
|
66479
|
+
try {
|
|
66480
|
+
const file2 = Bun.file(pkgPath);
|
|
66481
|
+
const pkg = await file2.json();
|
|
66482
|
+
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
66483
|
+
const notable = [
|
|
66484
|
+
...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
|
|
66485
|
+
].slice(0, 10);
|
|
66486
|
+
const lang = pkg.devDependencies?.typescript || pkg.dependencies?.typescript ? "TypeScript" : "JavaScript";
|
|
66487
|
+
return { name: pkg.name, lang, dependencies: notable };
|
|
66488
|
+
} catch {
|
|
66489
|
+
return null;
|
|
66490
|
+
}
|
|
66491
|
+
}
|
|
66492
|
+
async function detectGo(workdir) {
|
|
66493
|
+
const goMod = join10(workdir, "go.mod");
|
|
66494
|
+
if (!existsSync9(goMod))
|
|
66495
|
+
return null;
|
|
66496
|
+
try {
|
|
66497
|
+
const content = await Bun.file(goMod).text();
|
|
66498
|
+
const moduleMatch = content.match(/^module\s+(\S+)/m);
|
|
66499
|
+
const name = moduleMatch?.[1];
|
|
66500
|
+
const requires = [];
|
|
66501
|
+
const requireBlock = content.match(/require\s*\(([^)]+)\)/s)?.[1] ?? "";
|
|
66502
|
+
for (const line of requireBlock.split(`
|
|
66503
|
+
`)) {
|
|
66504
|
+
const trimmed = line.trim();
|
|
66505
|
+
if (trimmed && !trimmed.startsWith("//") && !trimmed.includes("// indirect")) {
|
|
66506
|
+
const dep = trimmed.split(/\s+/)[0];
|
|
66507
|
+
if (dep)
|
|
66508
|
+
requires.push(dep.split("/").slice(-1)[0]);
|
|
66509
|
+
}
|
|
66510
|
+
}
|
|
66511
|
+
return { name, lang: "Go", dependencies: requires.slice(0, 10) };
|
|
66512
|
+
} catch {
|
|
66513
|
+
return null;
|
|
66514
|
+
}
|
|
66515
|
+
}
|
|
66516
|
+
async function detectRust(workdir) {
|
|
66517
|
+
const cargoPath = join10(workdir, "Cargo.toml");
|
|
66518
|
+
if (!existsSync9(cargoPath))
|
|
66519
|
+
return null;
|
|
66520
|
+
try {
|
|
66521
|
+
const content = await Bun.file(cargoPath).text();
|
|
66522
|
+
const nameMatch = content.match(/^\[package\][^[]*name\s*=\s*"([^"]+)"/ms);
|
|
66523
|
+
const name = nameMatch?.[1];
|
|
66524
|
+
const depsSection = content.match(/^\[dependencies\]([^[]*)/ms)?.[1] ?? "";
|
|
66525
|
+
const deps = depsSection.split(`
|
|
66526
|
+
`).map((l) => l.split("=")[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
|
|
66527
|
+
return { name, lang: "Rust", dependencies: deps };
|
|
66528
|
+
} catch {
|
|
66529
|
+
return null;
|
|
66530
|
+
}
|
|
66531
|
+
}
|
|
66532
|
+
async function detectPython(workdir) {
|
|
66533
|
+
const pyproject = join10(workdir, "pyproject.toml");
|
|
66534
|
+
const requirements = join10(workdir, "requirements.txt");
|
|
66535
|
+
if (!existsSync9(pyproject) && !existsSync9(requirements))
|
|
66536
|
+
return null;
|
|
66537
|
+
try {
|
|
66538
|
+
if (existsSync9(pyproject)) {
|
|
66539
|
+
const content = await Bun.file(pyproject).text();
|
|
66540
|
+
const nameMatch = content.match(/^\s*name\s*=\s*"([^"]+)"/m);
|
|
66541
|
+
const depsSection = content.match(/^\[project\][^[]*dependencies\s*=\s*\[([^\]]*)\]/ms)?.[1] ?? "";
|
|
66542
|
+
const deps = depsSection.split(",").map((d) => d.trim().replace(/["'\s>=<!^~].*/g, "")).filter(Boolean).slice(0, 10);
|
|
66543
|
+
return { name: nameMatch?.[1], lang: "Python", dependencies: deps };
|
|
66544
|
+
}
|
|
66545
|
+
const lines = (await Bun.file(requirements).text()).split(`
|
|
66546
|
+
`).map((l) => l.split(/[>=<!]/)[0].trim()).filter((l) => l && !l.startsWith("#")).slice(0, 10);
|
|
66547
|
+
return { lang: "Python", dependencies: lines };
|
|
66548
|
+
} catch {
|
|
66549
|
+
return null;
|
|
66550
|
+
}
|
|
66551
|
+
}
|
|
66552
|
+
async function detectPhp(workdir) {
|
|
66553
|
+
const composerPath = join10(workdir, "composer.json");
|
|
66554
|
+
if (!existsSync9(composerPath))
|
|
66555
|
+
return null;
|
|
66556
|
+
try {
|
|
66557
|
+
const file2 = Bun.file(composerPath);
|
|
66558
|
+
const composer = await file2.json();
|
|
66559
|
+
const deps = Object.keys({ ...composer.require ?? {}, ...composer["require-dev"] ?? {} }).filter((d) => d !== "php").map((d) => d.split("/").pop() ?? d).slice(0, 10);
|
|
66560
|
+
return { name: composer.name, lang: "PHP", dependencies: deps };
|
|
66561
|
+
} catch {
|
|
66562
|
+
return null;
|
|
66563
|
+
}
|
|
66564
|
+
}
|
|
66565
|
+
async function detectRuby(workdir) {
|
|
66566
|
+
const gemfile = join10(workdir, "Gemfile");
|
|
66567
|
+
if (!existsSync9(gemfile))
|
|
66568
|
+
return null;
|
|
66569
|
+
try {
|
|
66570
|
+
const content = await Bun.file(gemfile).text();
|
|
66571
|
+
const gems = [...content.matchAll(/^\s*gem\s+['"]([^'"]+)['"]/gm)].map((m) => m[1]).slice(0, 10);
|
|
66572
|
+
return { lang: "Ruby", dependencies: gems };
|
|
66573
|
+
} catch {
|
|
66574
|
+
return null;
|
|
66575
|
+
}
|
|
66576
|
+
}
|
|
66577
|
+
async function detectJvm(workdir) {
|
|
66578
|
+
const pom = join10(workdir, "pom.xml");
|
|
66579
|
+
const gradle = join10(workdir, "build.gradle");
|
|
66580
|
+
const gradleKts = join10(workdir, "build.gradle.kts");
|
|
66581
|
+
if (!existsSync9(pom) && !existsSync9(gradle) && !existsSync9(gradleKts))
|
|
66582
|
+
return null;
|
|
66583
|
+
try {
|
|
66584
|
+
if (existsSync9(pom)) {
|
|
66585
|
+
const content2 = await Bun.file(pom).text();
|
|
66586
|
+
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
66587
|
+
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
66588
|
+
const lang2 = existsSync9(join10(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
66589
|
+
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
66590
|
+
}
|
|
66591
|
+
const gradleFile = existsSync9(gradleKts) ? gradleKts : gradle;
|
|
66592
|
+
const content = await Bun.file(gradleFile).text();
|
|
66593
|
+
const lang = gradleFile.endsWith(".kts") ? "Kotlin" : "Java";
|
|
66594
|
+
const deps = [...content.matchAll(/implementation[^'"]*['"]([^:'"]+:[^:'"]+)[^'"]*['"]/g)].map((m) => m[1].split(":").pop() ?? m[1]).slice(0, 10);
|
|
66595
|
+
return { lang, dependencies: deps };
|
|
66596
|
+
} catch {
|
|
66597
|
+
return null;
|
|
66598
|
+
}
|
|
66599
|
+
}
|
|
66600
|
+
async function buildProjectMetadata(workdir, config2) {
|
|
66601
|
+
const detected = await detectGo(workdir) ?? await detectRust(workdir) ?? await detectPython(workdir) ?? await detectPhp(workdir) ?? await detectRuby(workdir) ?? await detectJvm(workdir) ?? await detectNode(workdir);
|
|
66602
|
+
return {
|
|
66603
|
+
name: detected?.name,
|
|
66604
|
+
language: detected?.lang,
|
|
66605
|
+
dependencies: detected?.dependencies ?? [],
|
|
66606
|
+
testCommand: config2.execution?.testCommand ?? undefined,
|
|
66607
|
+
lintCommand: config2.execution?.lintCommand ?? undefined,
|
|
66608
|
+
typecheckCommand: config2.execution?.typecheckCommand ?? undefined
|
|
66609
|
+
};
|
|
66610
|
+
}
|
|
66611
|
+
function formatMetadataSection(metadata) {
|
|
66612
|
+
const lines = ["## Project Metadata", "", "> Auto-injected by `nax generate`", ""];
|
|
66613
|
+
if (metadata.name) {
|
|
66614
|
+
lines.push(`**Project:** \`${metadata.name}\``);
|
|
66615
|
+
lines.push("");
|
|
66616
|
+
}
|
|
66617
|
+
if (metadata.language) {
|
|
66618
|
+
lines.push(`**Language:** ${metadata.language}`);
|
|
66619
|
+
lines.push("");
|
|
66620
|
+
}
|
|
66621
|
+
if (metadata.dependencies.length > 0) {
|
|
66622
|
+
lines.push(`**Key dependencies:** ${metadata.dependencies.join(", ")}`);
|
|
66623
|
+
lines.push("");
|
|
66624
|
+
}
|
|
66625
|
+
const commands = [];
|
|
66626
|
+
if (metadata.testCommand)
|
|
66627
|
+
commands.push(`test: \`${metadata.testCommand}\``);
|
|
66628
|
+
if (metadata.lintCommand)
|
|
66629
|
+
commands.push(`lint: \`${metadata.lintCommand}\``);
|
|
66630
|
+
if (metadata.typecheckCommand)
|
|
66631
|
+
commands.push(`typecheck: \`${metadata.typecheckCommand}\``);
|
|
66632
|
+
if (commands.length > 0) {
|
|
66633
|
+
lines.push(`**Commands:** ${commands.join(" | ")}`);
|
|
66634
|
+
lines.push("");
|
|
66635
|
+
}
|
|
66636
|
+
lines.push("---");
|
|
66637
|
+
lines.push("");
|
|
66638
|
+
return lines.join(`
|
|
66639
|
+
`);
|
|
66640
|
+
}
|
|
66641
|
+
|
|
66642
|
+
// src/context/generators/aider.ts
|
|
66643
|
+
function generateAiderConfig(context) {
|
|
66644
|
+
const header = `# Aider Configuration
|
|
66645
|
+
# Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
66646
|
+
# DO NOT EDIT MANUALLY
|
|
66647
|
+
|
|
66648
|
+
# Project instructions
|
|
66649
|
+
instructions: |
|
|
66650
|
+
`;
|
|
66651
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66652
|
+
const combined = metaSection + context.markdown;
|
|
66653
|
+
const indented = combined.split(`
|
|
66654
|
+
`).map((line) => ` ${line}`).join(`
|
|
66655
|
+
`);
|
|
66656
|
+
return `${header}${indented}
|
|
66657
|
+
`;
|
|
66658
|
+
}
|
|
66659
|
+
var aiderGenerator = {
|
|
66660
|
+
name: "aider",
|
|
66661
|
+
outputFile: ".aider.conf.yml",
|
|
66662
|
+
generate: generateAiderConfig
|
|
66663
|
+
};
|
|
66664
|
+
|
|
66665
|
+
// src/context/generators/claude.ts
|
|
66666
|
+
function generateClaudeConfig(context) {
|
|
66667
|
+
const header = `# Project Context
|
|
66668
|
+
|
|
66669
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66670
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66671
|
+
|
|
66672
|
+
---
|
|
66673
|
+
|
|
66674
|
+
`;
|
|
66675
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66676
|
+
return header + metaSection + context.markdown;
|
|
66677
|
+
}
|
|
66678
|
+
var claudeGenerator = {
|
|
66679
|
+
name: "claude",
|
|
66680
|
+
outputFile: "CLAUDE.md",
|
|
66681
|
+
generate: generateClaudeConfig
|
|
66682
|
+
};
|
|
66683
|
+
|
|
66684
|
+
// src/context/generators/codex.ts
|
|
66685
|
+
function generateCodexConfig(context) {
|
|
66686
|
+
const header = `# Codex Instructions
|
|
66687
|
+
|
|
66688
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66689
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66690
|
+
|
|
66691
|
+
---
|
|
66692
|
+
|
|
66693
|
+
`;
|
|
66694
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66695
|
+
return header + metaSection + context.markdown;
|
|
66696
|
+
}
|
|
66697
|
+
var codexGenerator = {
|
|
66698
|
+
name: "codex",
|
|
66699
|
+
outputFile: "codex.md",
|
|
66700
|
+
generate: generateCodexConfig
|
|
66701
|
+
};
|
|
66702
|
+
|
|
66703
|
+
// src/context/generators/cursor.ts
|
|
66704
|
+
function generateCursorRules(context) {
|
|
66705
|
+
const header = `# Project Rules
|
|
66706
|
+
|
|
66707
|
+
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
66708
|
+
DO NOT EDIT MANUALLY
|
|
66709
|
+
|
|
66710
|
+
---
|
|
66711
|
+
|
|
66712
|
+
`;
|
|
66713
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66714
|
+
return header + metaSection + context.markdown;
|
|
66715
|
+
}
|
|
66716
|
+
var cursorGenerator = {
|
|
66717
|
+
name: "cursor",
|
|
66718
|
+
outputFile: ".cursorrules",
|
|
66719
|
+
generate: generateCursorRules
|
|
66720
|
+
};
|
|
66721
|
+
|
|
66722
|
+
// src/context/generators/gemini.ts
|
|
66723
|
+
function generateGeminiConfig(context) {
|
|
66724
|
+
const header = `# Gemini CLI Context
|
|
66725
|
+
|
|
66726
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66727
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66728
|
+
|
|
66729
|
+
---
|
|
66730
|
+
|
|
66731
|
+
`;
|
|
66732
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66733
|
+
return header + metaSection + context.markdown;
|
|
66734
|
+
}
|
|
66735
|
+
var geminiGenerator = {
|
|
66736
|
+
name: "gemini",
|
|
66737
|
+
outputFile: "GEMINI.md",
|
|
66738
|
+
generate: generateGeminiConfig
|
|
66739
|
+
};
|
|
66740
|
+
|
|
66741
|
+
// src/context/generators/opencode.ts
|
|
66742
|
+
function generateOpencodeConfig(context) {
|
|
66743
|
+
const header = `# Agent Instructions
|
|
66744
|
+
|
|
66745
|
+
This file is auto-generated from \`nax/context.md\`.
|
|
66746
|
+
DO NOT EDIT MANUALLY \u2014 run \`nax generate\` to regenerate.
|
|
66747
|
+
|
|
66748
|
+
These instructions apply to all AI coding agents in this project.
|
|
66749
|
+
|
|
66750
|
+
---
|
|
66751
|
+
|
|
66752
|
+
`;
|
|
66753
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66754
|
+
return header + metaSection + context.markdown;
|
|
66755
|
+
}
|
|
66756
|
+
var opencodeGenerator = {
|
|
66757
|
+
name: "opencode",
|
|
66758
|
+
outputFile: "AGENTS.md",
|
|
66759
|
+
generate: generateOpencodeConfig
|
|
66760
|
+
};
|
|
66761
|
+
|
|
66762
|
+
// src/context/generators/windsurf.ts
|
|
66763
|
+
function generateWindsurfRules(context) {
|
|
66764
|
+
const header = `# Windsurf Project Rules
|
|
66765
|
+
|
|
66766
|
+
Auto-generated from nax/context.md \u2014 run \`nax generate\` to regenerate.
|
|
66767
|
+
DO NOT EDIT MANUALLY
|
|
66768
|
+
|
|
66769
|
+
---
|
|
66770
|
+
|
|
66771
|
+
`;
|
|
66772
|
+
const metaSection = context.metadata ? formatMetadataSection(context.metadata) : "";
|
|
66773
|
+
return header + metaSection + context.markdown;
|
|
66774
|
+
}
|
|
66775
|
+
var windsurfGenerator = {
|
|
66776
|
+
name: "windsurf",
|
|
66777
|
+
outputFile: ".windsurfrules",
|
|
66778
|
+
generate: generateWindsurfRules
|
|
66779
|
+
};
|
|
66780
|
+
|
|
66781
|
+
// src/context/generator.ts
|
|
66782
|
+
var GENERATORS = {
|
|
66783
|
+
claude: claudeGenerator,
|
|
66784
|
+
codex: codexGenerator,
|
|
66785
|
+
opencode: opencodeGenerator,
|
|
66786
|
+
cursor: cursorGenerator,
|
|
66787
|
+
windsurf: windsurfGenerator,
|
|
66788
|
+
aider: aiderGenerator,
|
|
66789
|
+
gemini: geminiGenerator
|
|
66790
|
+
};
|
|
66791
|
+
async function loadContextContent(options, config2) {
|
|
66792
|
+
if (!existsSync10(options.contextPath)) {
|
|
66793
|
+
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
66794
|
+
}
|
|
66795
|
+
const file2 = Bun.file(options.contextPath);
|
|
66796
|
+
const markdown = await file2.text();
|
|
66797
|
+
const autoInject = options.autoInject ?? true;
|
|
66798
|
+
const metadata = autoInject ? await buildProjectMetadata(options.workdir, config2) : undefined;
|
|
66799
|
+
return { markdown, metadata };
|
|
66800
|
+
}
|
|
66801
|
+
async function generateFor(agent, options, config2) {
|
|
66802
|
+
const generator = GENERATORS[agent];
|
|
66803
|
+
if (!generator) {
|
|
66804
|
+
return { agent, outputFile: "", content: "", written: false, error: `Unknown agent: ${agent}` };
|
|
66805
|
+
}
|
|
66806
|
+
try {
|
|
66807
|
+
const context = await loadContextContent(options, config2);
|
|
66808
|
+
const content = generator.generate(context);
|
|
66809
|
+
const outputPath = join11(options.outputDir, generator.outputFile);
|
|
66810
|
+
validateFilePath(outputPath, options.outputDir);
|
|
66811
|
+
if (!options.dryRun) {
|
|
66812
|
+
await Bun.write(outputPath, content);
|
|
66813
|
+
}
|
|
66814
|
+
return { agent, outputFile: generator.outputFile, content, written: !options.dryRun };
|
|
66815
|
+
} catch (err) {
|
|
66816
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66817
|
+
return { agent, outputFile: generator.outputFile, content: "", written: false, error: error48 };
|
|
66818
|
+
}
|
|
66819
|
+
}
|
|
66820
|
+
async function generateAll(options, config2) {
|
|
66821
|
+
const context = await loadContextContent(options, config2);
|
|
66822
|
+
const results = [];
|
|
66823
|
+
for (const [agentKey, generator] of Object.entries(GENERATORS)) {
|
|
66824
|
+
try {
|
|
66825
|
+
const content = generator.generate(context);
|
|
66826
|
+
const outputPath = join11(options.outputDir, generator.outputFile);
|
|
66827
|
+
validateFilePath(outputPath, options.outputDir);
|
|
66828
|
+
if (!options.dryRun) {
|
|
66829
|
+
await Bun.write(outputPath, content);
|
|
66830
|
+
}
|
|
66831
|
+
results.push({ agent: agentKey, outputFile: generator.outputFile, content, written: !options.dryRun });
|
|
66832
|
+
} catch (err) {
|
|
66833
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66834
|
+
results.push({ agent: agentKey, outputFile: generator.outputFile, content: "", written: false, error: error48 });
|
|
66835
|
+
}
|
|
66836
|
+
}
|
|
66837
|
+
return results;
|
|
66838
|
+
}
|
|
66839
|
+
async function discoverPackages(repoRoot) {
|
|
66840
|
+
const packages = [];
|
|
66841
|
+
const seen = new Set;
|
|
66842
|
+
for (const pattern of ["*/nax/context.md", "*/*/nax/context.md"]) {
|
|
66843
|
+
const glob = new Bun.Glob(pattern);
|
|
66844
|
+
for await (const match of glob.scan(repoRoot)) {
|
|
66845
|
+
const pkgRelative = match.replace(/\/nax\/context\.md$/, "");
|
|
66846
|
+
const pkgAbsolute = join11(repoRoot, pkgRelative);
|
|
66847
|
+
if (!seen.has(pkgAbsolute)) {
|
|
66848
|
+
seen.add(pkgAbsolute);
|
|
66849
|
+
packages.push(pkgAbsolute);
|
|
66850
|
+
}
|
|
66851
|
+
}
|
|
66852
|
+
}
|
|
66853
|
+
return packages;
|
|
66854
|
+
}
|
|
66855
|
+
async function discoverWorkspacePackages(repoRoot) {
|
|
66856
|
+
const existing = await discoverPackages(repoRoot);
|
|
66857
|
+
if (existing.length > 0) {
|
|
66858
|
+
return existing.map((p) => p.replace(`${repoRoot}/`, ""));
|
|
66859
|
+
}
|
|
66860
|
+
const seen = new Set;
|
|
66861
|
+
const results = [];
|
|
66862
|
+
async function resolveGlobs(patterns) {
|
|
66863
|
+
for (const pattern of patterns) {
|
|
66864
|
+
if (pattern.startsWith("!"))
|
|
66865
|
+
continue;
|
|
66866
|
+
const base = pattern.replace(/\/+$/, "");
|
|
66867
|
+
const pkgPattern = base.endsWith("*") ? `${base}/package.json` : `${base}/*/package.json`;
|
|
66868
|
+
const g = new Bun.Glob(pkgPattern);
|
|
66869
|
+
for await (const match of g.scan(repoRoot)) {
|
|
66870
|
+
const rel = match.replace(/\/package\.json$/, "");
|
|
66871
|
+
if (!seen.has(rel)) {
|
|
66872
|
+
seen.add(rel);
|
|
66873
|
+
results.push(rel);
|
|
66874
|
+
}
|
|
66875
|
+
}
|
|
66876
|
+
}
|
|
66877
|
+
}
|
|
66878
|
+
const turboPath = join11(repoRoot, "turbo.json");
|
|
66879
|
+
if (existsSync10(turboPath)) {
|
|
66880
|
+
try {
|
|
66881
|
+
const turbo = JSON.parse(readFileSync(turboPath, "utf-8"));
|
|
66882
|
+
if (Array.isArray(turbo.packages)) {
|
|
66883
|
+
await resolveGlobs(turbo.packages);
|
|
66884
|
+
}
|
|
66885
|
+
} catch {}
|
|
66886
|
+
}
|
|
66887
|
+
const pkgPath = join11(repoRoot, "package.json");
|
|
66888
|
+
if (existsSync10(pkgPath)) {
|
|
66889
|
+
try {
|
|
66890
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
66891
|
+
const ws = pkg.workspaces;
|
|
66892
|
+
const patterns = Array.isArray(ws) ? ws : Array.isArray(ws?.packages) ? ws.packages : [];
|
|
66893
|
+
if (patterns.length > 0)
|
|
66894
|
+
await resolveGlobs(patterns);
|
|
66895
|
+
} catch {}
|
|
66896
|
+
}
|
|
66897
|
+
const pnpmPath = join11(repoRoot, "pnpm-workspace.yaml");
|
|
66898
|
+
if (existsSync10(pnpmPath)) {
|
|
66899
|
+
try {
|
|
66900
|
+
const raw = readFileSync(pnpmPath, "utf-8");
|
|
66901
|
+
const lines = raw.split(`
|
|
66902
|
+
`);
|
|
66903
|
+
let inPackages = false;
|
|
66904
|
+
const patterns = [];
|
|
66905
|
+
for (const line of lines) {
|
|
66906
|
+
if (/^packages\s*:/.test(line)) {
|
|
66907
|
+
inPackages = true;
|
|
66908
|
+
continue;
|
|
66909
|
+
}
|
|
66910
|
+
if (inPackages && /^\s+-\s+/.test(line)) {
|
|
66911
|
+
patterns.push(line.replace(/^\s+-\s+['"]?/, "").replace(/['"]?\s*$/, ""));
|
|
66912
|
+
} else if (inPackages && !/^\s/.test(line)) {
|
|
66913
|
+
break;
|
|
66914
|
+
}
|
|
66915
|
+
}
|
|
66916
|
+
if (patterns.length > 0)
|
|
66917
|
+
await resolveGlobs(patterns);
|
|
66918
|
+
} catch {}
|
|
66919
|
+
}
|
|
66920
|
+
return results.sort();
|
|
66921
|
+
}
|
|
66922
|
+
async function generateForPackage(packageDir, config2, dryRun = false) {
|
|
66923
|
+
const contextPath = join11(packageDir, "nax", "context.md");
|
|
66924
|
+
if (!existsSync10(contextPath)) {
|
|
66925
|
+
return {
|
|
66926
|
+
packageDir,
|
|
66927
|
+
outputFile: "CLAUDE.md",
|
|
66928
|
+
content: "",
|
|
66929
|
+
written: false,
|
|
66930
|
+
error: `context.md not found: ${contextPath}`
|
|
66931
|
+
};
|
|
66932
|
+
}
|
|
66933
|
+
try {
|
|
66934
|
+
const options = {
|
|
66935
|
+
contextPath,
|
|
66936
|
+
outputDir: packageDir,
|
|
66937
|
+
workdir: packageDir,
|
|
66938
|
+
dryRun,
|
|
66939
|
+
autoInject: true
|
|
66940
|
+
};
|
|
66941
|
+
const result = await generateFor("claude", options, config2);
|
|
66942
|
+
return {
|
|
66943
|
+
packageDir,
|
|
66944
|
+
outputFile: result.outputFile,
|
|
66945
|
+
content: result.content,
|
|
66946
|
+
written: result.written,
|
|
66947
|
+
error: result.error
|
|
66948
|
+
};
|
|
66949
|
+
} catch (err) {
|
|
66950
|
+
const error48 = err instanceof Error ? err.message : String(err);
|
|
66951
|
+
return { packageDir, outputFile: "CLAUDE.md", content: "", written: false, error: error48 };
|
|
66952
|
+
}
|
|
66953
|
+
}
|
|
66954
|
+
|
|
66955
|
+
// src/cli/plan.ts
|
|
66120
66956
|
init_pid_registry();
|
|
66121
66957
|
init_logger2();
|
|
66122
66958
|
|
|
@@ -66202,6 +67038,20 @@ function validateStory(raw, index, allIds) {
|
|
|
66202
67038
|
}
|
|
66203
67039
|
const rawTags = s.tags;
|
|
66204
67040
|
const tags = Array.isArray(rawTags) ? rawTags : [];
|
|
67041
|
+
const rawWorkdir = s.workdir;
|
|
67042
|
+
let workdir;
|
|
67043
|
+
if (rawWorkdir !== undefined && rawWorkdir !== null) {
|
|
67044
|
+
if (typeof rawWorkdir !== "string") {
|
|
67045
|
+
throw new Error(`[schema] story[${index}].workdir must be a string`);
|
|
67046
|
+
}
|
|
67047
|
+
if (rawWorkdir.startsWith("/")) {
|
|
67048
|
+
throw new Error(`[schema] story[${index}].workdir must be relative (no leading /): "${rawWorkdir}"`);
|
|
67049
|
+
}
|
|
67050
|
+
if (rawWorkdir.includes("..")) {
|
|
67051
|
+
throw new Error(`[schema] story[${index}].workdir must not contain '..': "${rawWorkdir}"`);
|
|
67052
|
+
}
|
|
67053
|
+
workdir = rawWorkdir;
|
|
67054
|
+
}
|
|
66205
67055
|
return {
|
|
66206
67056
|
id,
|
|
66207
67057
|
title: title.trim(),
|
|
@@ -66217,7 +67067,8 @@ function validateStory(raw, index, allIds) {
|
|
|
66217
67067
|
complexity,
|
|
66218
67068
|
testStrategy,
|
|
66219
67069
|
reasoning: "validated from LLM output"
|
|
66220
|
-
}
|
|
67070
|
+
},
|
|
67071
|
+
...workdir !== undefined ? { workdir } : {}
|
|
66221
67072
|
};
|
|
66222
67073
|
}
|
|
66223
67074
|
function parseRawString(text) {
|
|
@@ -66268,36 +67119,47 @@ var _deps2 = {
|
|
|
66268
67119
|
writeFile: (path, content) => Bun.write(path, content).then(() => {}),
|
|
66269
67120
|
scanCodebase: (workdir) => scanCodebase(workdir),
|
|
66270
67121
|
getAgent: (name, cfg) => cfg ? createAgentRegistry(cfg).getAgent(name) : getAgent(name),
|
|
66271
|
-
readPackageJson: (workdir) => Bun.file(
|
|
67122
|
+
readPackageJson: (workdir) => Bun.file(join12(workdir, "package.json")).json().catch(() => null),
|
|
66272
67123
|
spawnSync: (cmd, opts) => {
|
|
66273
67124
|
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
|
|
66274
67125
|
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
66275
67126
|
},
|
|
66276
67127
|
mkdirp: (path) => Bun.spawn(["mkdir", "-p", path]).exited.then(() => {}),
|
|
66277
|
-
existsSync: (path) =>
|
|
67128
|
+
existsSync: (path) => existsSync11(path),
|
|
67129
|
+
discoverWorkspacePackages: (repoRoot) => discoverWorkspacePackages(repoRoot),
|
|
67130
|
+
readPackageJsonAt: (path) => Bun.file(path).json().catch(() => null),
|
|
67131
|
+
createInteractionBridge: () => createCliInteractionBridge()
|
|
66278
67132
|
};
|
|
66279
67133
|
async function planCommand(workdir, config2, options) {
|
|
66280
|
-
const naxDir =
|
|
66281
|
-
if (!
|
|
67134
|
+
const naxDir = join12(workdir, "nax");
|
|
67135
|
+
if (!existsSync11(naxDir)) {
|
|
66282
67136
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
66283
67137
|
}
|
|
66284
67138
|
const logger = getLogger();
|
|
66285
67139
|
logger?.info("plan", "Reading spec", { from: options.from });
|
|
66286
67140
|
const specContent = await _deps2.readFile(options.from);
|
|
66287
67141
|
logger?.info("plan", "Scanning codebase...");
|
|
66288
|
-
const scan = await
|
|
67142
|
+
const [scan, discoveredPackages, pkg] = await Promise.all([
|
|
67143
|
+
_deps2.scanCodebase(workdir),
|
|
67144
|
+
_deps2.discoverWorkspacePackages(workdir),
|
|
67145
|
+
_deps2.readPackageJson(workdir)
|
|
67146
|
+
]);
|
|
66289
67147
|
const codebaseContext = buildCodebaseContext2(scan);
|
|
66290
|
-
const
|
|
67148
|
+
const relativePackages = discoveredPackages.map((p) => p.startsWith("/") ? p.replace(`${workdir}/`, "") : p);
|
|
67149
|
+
const packageDetails = relativePackages.length > 0 ? await Promise.all(relativePackages.map(async (rel) => {
|
|
67150
|
+
const pkgJson = await _deps2.readPackageJsonAt(join12(workdir, rel, "package.json"));
|
|
67151
|
+
return buildPackageSummary(rel, pkgJson);
|
|
67152
|
+
})) : [];
|
|
66291
67153
|
const projectName = detectProjectName(workdir, pkg);
|
|
66292
67154
|
const branchName = options.branch ?? `feat/${options.feature}`;
|
|
66293
|
-
const outputDir =
|
|
66294
|
-
const outputPath =
|
|
67155
|
+
const outputDir = join12(naxDir, "features", options.feature);
|
|
67156
|
+
const outputPath = join12(outputDir, "prd.json");
|
|
66295
67157
|
await _deps2.mkdirp(outputDir);
|
|
66296
67158
|
const agentName = config2?.autoMode?.defaultAgent ?? "claude";
|
|
66297
67159
|
const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
|
|
66298
67160
|
let rawResponse;
|
|
66299
67161
|
if (options.auto) {
|
|
66300
|
-
const prompt = buildPlanningPrompt(specContent, codebaseContext);
|
|
67162
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext, undefined, relativePackages, packageDetails);
|
|
66301
67163
|
const cliAdapter = _deps2.getAgent(agentName);
|
|
66302
67164
|
if (!cliAdapter)
|
|
66303
67165
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
@@ -66309,11 +67171,11 @@ async function planCommand(workdir, config2, options) {
|
|
|
66309
67171
|
}
|
|
66310
67172
|
} catch {}
|
|
66311
67173
|
} else {
|
|
66312
|
-
const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath);
|
|
67174
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails);
|
|
66313
67175
|
const adapter = _deps2.getAgent(agentName, config2);
|
|
66314
67176
|
if (!adapter)
|
|
66315
67177
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
66316
|
-
const interactionBridge =
|
|
67178
|
+
const interactionBridge = _deps2.createInteractionBridge();
|
|
66317
67179
|
const pidRegistry = new PidRegistry(workdir);
|
|
66318
67180
|
const resolvedPerm = resolvePermissions(config2, "plan");
|
|
66319
67181
|
const resolvedModel = config2?.plan?.model ?? "balanced";
|
|
@@ -66391,6 +67253,66 @@ function detectProjectName(workdir, pkg) {
|
|
|
66391
67253
|
}
|
|
66392
67254
|
return "unknown";
|
|
66393
67255
|
}
|
|
67256
|
+
var FRAMEWORK_PATTERNS = [
|
|
67257
|
+
[/\bnext\b/, "Next.js"],
|
|
67258
|
+
[/\bnuxt\b/, "Nuxt"],
|
|
67259
|
+
[/\bremix\b/, "Remix"],
|
|
67260
|
+
[/\bexpress\b/, "Express"],
|
|
67261
|
+
[/\bfastify\b/, "Fastify"],
|
|
67262
|
+
[/\bhono\b/, "Hono"],
|
|
67263
|
+
[/\bnestjs|@nestjs\b/, "NestJS"],
|
|
67264
|
+
[/\breact\b/, "React"],
|
|
67265
|
+
[/\bvue\b/, "Vue"],
|
|
67266
|
+
[/\bsvelte\b/, "Svelte"],
|
|
67267
|
+
[/\bastro\b/, "Astro"],
|
|
67268
|
+
[/\belectron\b/, "Electron"]
|
|
67269
|
+
];
|
|
67270
|
+
var TEST_RUNNER_PATTERNS = [
|
|
67271
|
+
[/\bvitest\b/, "vitest"],
|
|
67272
|
+
[/\bjest\b/, "jest"],
|
|
67273
|
+
[/\bmocha\b/, "mocha"],
|
|
67274
|
+
[/\bava\b/, "ava"]
|
|
67275
|
+
];
|
|
67276
|
+
var KEY_DEP_PATTERNS = [
|
|
67277
|
+
[/\bprisma\b/, "prisma"],
|
|
67278
|
+
[/\bdrizzle-orm\b/, "drizzle"],
|
|
67279
|
+
[/\btypeorm\b/, "typeorm"],
|
|
67280
|
+
[/\bmongoose\b/, "mongoose"],
|
|
67281
|
+
[/\bsqlite\b|better-sqlite/, "sqlite"],
|
|
67282
|
+
[/\bstripe\b/, "stripe"],
|
|
67283
|
+
[/\bgraphql\b/, "graphql"],
|
|
67284
|
+
[/\btrpc\b/, "tRPC"],
|
|
67285
|
+
[/\bzod\b/, "zod"],
|
|
67286
|
+
[/\btailwind\b/, "tailwind"]
|
|
67287
|
+
];
|
|
67288
|
+
function buildPackageSummary(rel, pkg) {
|
|
67289
|
+
const name = typeof pkg?.name === "string" ? pkg.name : rel;
|
|
67290
|
+
const allDeps = { ...pkg?.dependencies, ...pkg?.devDependencies };
|
|
67291
|
+
const depNames = Object.keys(allDeps).join(" ");
|
|
67292
|
+
const scripts = pkg?.scripts ?? {};
|
|
67293
|
+
const testScript = scripts.test ?? "";
|
|
67294
|
+
const runtime = testScript.includes("bun ") ? "bun" : testScript.includes("node ") ? "node" : "unknown";
|
|
67295
|
+
const framework = FRAMEWORK_PATTERNS.find(([re]) => re.test(depNames))?.[1] ?? "";
|
|
67296
|
+
const testRunner = TEST_RUNNER_PATTERNS.find(([re]) => re.test(depNames))?.[1] ?? (testScript.includes("bun test") ? "bun:test" : "");
|
|
67297
|
+
const keyDeps = KEY_DEP_PATTERNS.filter(([re]) => re.test(depNames)).map(([, label]) => label);
|
|
67298
|
+
return { path: rel, name, runtime, framework, testRunner, keyDeps };
|
|
67299
|
+
}
|
|
67300
|
+
function buildPackageDetailsSection(details) {
|
|
67301
|
+
if (details.length === 0)
|
|
67302
|
+
return "";
|
|
67303
|
+
const rows = details.map((d) => {
|
|
67304
|
+
const stack = [d.framework, d.testRunner, ...d.keyDeps].filter(Boolean).join(", ") || "\u2014";
|
|
67305
|
+
return `| \`${d.path}\` | ${d.name} | ${stack} |`;
|
|
67306
|
+
});
|
|
67307
|
+
return `
|
|
67308
|
+
## Package Tech Stacks
|
|
67309
|
+
|
|
67310
|
+
| Path | Package | Stack |
|
|
67311
|
+
|:-----|:--------|:------|
|
|
67312
|
+
${rows.join(`
|
|
67313
|
+
`)}
|
|
67314
|
+
`;
|
|
67315
|
+
}
|
|
66394
67316
|
function buildCodebaseContext2(scan) {
|
|
66395
67317
|
const sections = [];
|
|
66396
67318
|
sections.push(`## Codebase Structure
|
|
@@ -66417,7 +67339,19 @@ function buildCodebaseContext2(scan) {
|
|
|
66417
67339
|
return sections.join(`
|
|
66418
67340
|
`);
|
|
66419
67341
|
}
|
|
66420
|
-
function buildPlanningPrompt(specContent, codebaseContext, outputFilePath) {
|
|
67342
|
+
function buildPlanningPrompt(specContent, codebaseContext, outputFilePath, packages, packageDetails) {
|
|
67343
|
+
const isMonorepo = packages && packages.length > 0;
|
|
67344
|
+
const packageDetailsSection = packageDetails && packageDetails.length > 0 ? buildPackageDetailsSection(packageDetails) : "";
|
|
67345
|
+
const monorepoHint = isMonorepo ? `
|
|
67346
|
+
## Monorepo Context
|
|
67347
|
+
|
|
67348
|
+
This is a monorepo. Detected packages:
|
|
67349
|
+
${packages.map((p) => `- ${p}`).join(`
|
|
67350
|
+
`)}
|
|
67351
|
+
${packageDetailsSection}
|
|
67352
|
+
For each user story, set the "workdir" field to the relevant package path (e.g. "packages/api"). Stories that span the root should omit "workdir".` : "";
|
|
67353
|
+
const workdirField = isMonorepo ? `
|
|
67354
|
+
"workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
|
|
66421
67355
|
return `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
66422
67356
|
|
|
66423
67357
|
## Spec
|
|
@@ -66426,7 +67360,7 @@ ${specContent}
|
|
|
66426
67360
|
|
|
66427
67361
|
## Codebase Context
|
|
66428
67362
|
|
|
66429
|
-
${codebaseContext}
|
|
67363
|
+
${codebaseContext}${monorepoHint}
|
|
66430
67364
|
|
|
66431
67365
|
## Output Schema
|
|
66432
67366
|
|
|
@@ -66445,7 +67379,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
66445
67379
|
"description": "string \u2014 detailed description of the story",
|
|
66446
67380
|
"acceptanceCriteria": ["string \u2014 each AC line"],
|
|
66447
67381
|
"tags": ["string \u2014 routing tags, e.g. feature, security, api"],
|
|
66448
|
-
"dependencies": ["string \u2014 story IDs this story depends on"]
|
|
67382
|
+
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
66449
67383
|
"status": "pending",
|
|
66450
67384
|
"passes": false,
|
|
66451
67385
|
"routing": {
|
|
@@ -66615,14 +67549,14 @@ async function displayModelEfficiency(workdir) {
|
|
|
66615
67549
|
}
|
|
66616
67550
|
// src/cli/status-features.ts
|
|
66617
67551
|
init_source();
|
|
66618
|
-
import { existsSync as
|
|
66619
|
-
import { join as
|
|
67552
|
+
import { existsSync as existsSync13, readdirSync as readdirSync3 } from "fs";
|
|
67553
|
+
import { join as join15 } from "path";
|
|
66620
67554
|
|
|
66621
67555
|
// src/commands/common.ts
|
|
66622
67556
|
init_path_security2();
|
|
66623
67557
|
init_errors3();
|
|
66624
|
-
import { existsSync as
|
|
66625
|
-
import { join as
|
|
67558
|
+
import { existsSync as existsSync12, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
|
|
67559
|
+
import { join as join13, resolve as resolve6 } from "path";
|
|
66626
67560
|
function resolveProject(options = {}) {
|
|
66627
67561
|
const { dir, feature } = options;
|
|
66628
67562
|
let projectRoot;
|
|
@@ -66630,37 +67564,37 @@ function resolveProject(options = {}) {
|
|
|
66630
67564
|
let configPath;
|
|
66631
67565
|
if (dir) {
|
|
66632
67566
|
projectRoot = realpathSync3(resolve6(dir));
|
|
66633
|
-
naxDir =
|
|
66634
|
-
if (!
|
|
67567
|
+
naxDir = join13(projectRoot, "nax");
|
|
67568
|
+
if (!existsSync12(naxDir)) {
|
|
66635
67569
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
66636
67570
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
66637
67571
|
}
|
|
66638
|
-
configPath =
|
|
66639
|
-
if (!
|
|
67572
|
+
configPath = join13(naxDir, "config.json");
|
|
67573
|
+
if (!existsSync12(configPath)) {
|
|
66640
67574
|
throw new NaxError(`nax directory found but config.json is missing: ${naxDir}
|
|
66641
67575
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
66642
67576
|
}
|
|
66643
67577
|
} else {
|
|
66644
67578
|
const found = findProjectRoot(process.cwd());
|
|
66645
67579
|
if (!found) {
|
|
66646
|
-
const cwdNaxDir =
|
|
66647
|
-
if (
|
|
66648
|
-
const cwdConfigPath =
|
|
67580
|
+
const cwdNaxDir = join13(process.cwd(), "nax");
|
|
67581
|
+
if (existsSync12(cwdNaxDir)) {
|
|
67582
|
+
const cwdConfigPath = join13(cwdNaxDir, "config.json");
|
|
66649
67583
|
throw new NaxError(`nax directory found but config.json is missing: ${cwdNaxDir}
|
|
66650
67584
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
66651
67585
|
}
|
|
66652
67586
|
throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
|
|
66653
67587
|
}
|
|
66654
67588
|
projectRoot = found;
|
|
66655
|
-
naxDir =
|
|
66656
|
-
configPath =
|
|
67589
|
+
naxDir = join13(projectRoot, "nax");
|
|
67590
|
+
configPath = join13(naxDir, "config.json");
|
|
66657
67591
|
}
|
|
66658
67592
|
let featureDir;
|
|
66659
67593
|
if (feature) {
|
|
66660
|
-
const featuresDir =
|
|
66661
|
-
featureDir =
|
|
66662
|
-
if (!
|
|
66663
|
-
const availableFeatures =
|
|
67594
|
+
const featuresDir = join13(naxDir, "features");
|
|
67595
|
+
featureDir = join13(featuresDir, feature);
|
|
67596
|
+
if (!existsSync12(featureDir)) {
|
|
67597
|
+
const availableFeatures = existsSync12(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
66664
67598
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
66665
67599
|
|
|
66666
67600
|
Available features:
|
|
@@ -66685,12 +67619,12 @@ function findProjectRoot(startDir) {
|
|
|
66685
67619
|
let current = resolve6(startDir);
|
|
66686
67620
|
let depth = 0;
|
|
66687
67621
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
66688
|
-
const naxDir =
|
|
66689
|
-
const configPath =
|
|
66690
|
-
if (
|
|
67622
|
+
const naxDir = join13(current, "nax");
|
|
67623
|
+
const configPath = join13(naxDir, "config.json");
|
|
67624
|
+
if (existsSync12(configPath)) {
|
|
66691
67625
|
return realpathSync3(current);
|
|
66692
67626
|
}
|
|
66693
|
-
const parent =
|
|
67627
|
+
const parent = join13(current, "..");
|
|
66694
67628
|
if (parent === current) {
|
|
66695
67629
|
break;
|
|
66696
67630
|
}
|
|
@@ -66712,8 +67646,8 @@ function isPidAlive(pid) {
|
|
|
66712
67646
|
}
|
|
66713
67647
|
}
|
|
66714
67648
|
async function loadStatusFile(featureDir) {
|
|
66715
|
-
const statusPath =
|
|
66716
|
-
if (!
|
|
67649
|
+
const statusPath = join15(featureDir, "status.json");
|
|
67650
|
+
if (!existsSync13(statusPath)) {
|
|
66717
67651
|
return null;
|
|
66718
67652
|
}
|
|
66719
67653
|
try {
|
|
@@ -66724,8 +67658,8 @@ async function loadStatusFile(featureDir) {
|
|
|
66724
67658
|
}
|
|
66725
67659
|
}
|
|
66726
67660
|
async function loadProjectStatusFile(projectDir) {
|
|
66727
|
-
const statusPath =
|
|
66728
|
-
if (!
|
|
67661
|
+
const statusPath = join15(projectDir, "nax", "status.json");
|
|
67662
|
+
if (!existsSync13(statusPath)) {
|
|
66729
67663
|
return null;
|
|
66730
67664
|
}
|
|
66731
67665
|
try {
|
|
@@ -66736,8 +67670,8 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
66736
67670
|
}
|
|
66737
67671
|
}
|
|
66738
67672
|
async function getFeatureSummary(featureName, featureDir) {
|
|
66739
|
-
const prdPath =
|
|
66740
|
-
if (!
|
|
67673
|
+
const prdPath = join15(featureDir, "prd.json");
|
|
67674
|
+
if (!existsSync13(prdPath)) {
|
|
66741
67675
|
return {
|
|
66742
67676
|
name: featureName,
|
|
66743
67677
|
done: 0,
|
|
@@ -66779,8 +67713,8 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
66779
67713
|
};
|
|
66780
67714
|
}
|
|
66781
67715
|
}
|
|
66782
|
-
const runsDir =
|
|
66783
|
-
if (
|
|
67716
|
+
const runsDir = join15(featureDir, "runs");
|
|
67717
|
+
if (existsSync13(runsDir)) {
|
|
66784
67718
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
66785
67719
|
if (runs.length > 0) {
|
|
66786
67720
|
const latestRun = runs[0].replace(".jsonl", "");
|
|
@@ -66790,8 +67724,8 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
66790
67724
|
return summary;
|
|
66791
67725
|
}
|
|
66792
67726
|
async function displayAllFeatures(projectDir) {
|
|
66793
|
-
const featuresDir =
|
|
66794
|
-
if (!
|
|
67727
|
+
const featuresDir = join15(projectDir, "nax", "features");
|
|
67728
|
+
if (!existsSync13(featuresDir)) {
|
|
66795
67729
|
console.log(source_default.dim("No features found."));
|
|
66796
67730
|
return;
|
|
66797
67731
|
}
|
|
@@ -66831,7 +67765,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
66831
67765
|
console.log();
|
|
66832
67766
|
}
|
|
66833
67767
|
}
|
|
66834
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
67768
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join15(featuresDir, name))));
|
|
66835
67769
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
66836
67770
|
`));
|
|
66837
67771
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -66857,8 +67791,8 @@ async function displayAllFeatures(projectDir) {
|
|
|
66857
67791
|
console.log();
|
|
66858
67792
|
}
|
|
66859
67793
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
66860
|
-
const prdPath =
|
|
66861
|
-
if (!
|
|
67794
|
+
const prdPath = join15(featureDir, "prd.json");
|
|
67795
|
+
if (!existsSync13(prdPath)) {
|
|
66862
67796
|
console.log(source_default.bold(`
|
|
66863
67797
|
\uD83D\uDCCA ${featureName}
|
|
66864
67798
|
`));
|
|
@@ -66978,8 +67912,8 @@ async function displayFeatureStatus(options = {}) {
|
|
|
66978
67912
|
// src/cli/runs.ts
|
|
66979
67913
|
init_errors3();
|
|
66980
67914
|
init_logger2();
|
|
66981
|
-
import { existsSync as
|
|
66982
|
-
import { join as
|
|
67915
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4 } from "fs";
|
|
67916
|
+
import { join as join16 } from "path";
|
|
66983
67917
|
async function parseRunLog(logPath) {
|
|
66984
67918
|
const logger = getLogger();
|
|
66985
67919
|
try {
|
|
@@ -66995,8 +67929,8 @@ async function parseRunLog(logPath) {
|
|
|
66995
67929
|
async function runsListCommand(options) {
|
|
66996
67930
|
const logger = getLogger();
|
|
66997
67931
|
const { feature, workdir } = options;
|
|
66998
|
-
const runsDir =
|
|
66999
|
-
if (!
|
|
67932
|
+
const runsDir = join16(workdir, "nax", "features", feature, "runs");
|
|
67933
|
+
if (!existsSync14(runsDir)) {
|
|
67000
67934
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
67001
67935
|
return;
|
|
67002
67936
|
}
|
|
@@ -67007,7 +67941,7 @@ async function runsListCommand(options) {
|
|
|
67007
67941
|
}
|
|
67008
67942
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
67009
67943
|
for (const file2 of files.sort().reverse()) {
|
|
67010
|
-
const logPath =
|
|
67944
|
+
const logPath = join16(runsDir, file2);
|
|
67011
67945
|
const entries = await parseRunLog(logPath);
|
|
67012
67946
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
67013
67947
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -67033,8 +67967,8 @@ async function runsListCommand(options) {
|
|
|
67033
67967
|
async function runsShowCommand(options) {
|
|
67034
67968
|
const logger = getLogger();
|
|
67035
67969
|
const { runId, feature, workdir } = options;
|
|
67036
|
-
const logPath =
|
|
67037
|
-
if (!
|
|
67970
|
+
const logPath = join16(workdir, "nax", "features", feature, "runs", `${runId}.jsonl`);
|
|
67971
|
+
if (!existsSync14(logPath)) {
|
|
67038
67972
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
67039
67973
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
67040
67974
|
}
|
|
@@ -67071,8 +68005,8 @@ async function runsShowCommand(options) {
|
|
|
67071
68005
|
}
|
|
67072
68006
|
// src/cli/prompts-main.ts
|
|
67073
68007
|
init_logger2();
|
|
67074
|
-
import { existsSync as
|
|
67075
|
-
import { join as
|
|
68008
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
|
|
68009
|
+
import { join as join27 } from "path";
|
|
67076
68010
|
|
|
67077
68011
|
// src/pipeline/index.ts
|
|
67078
68012
|
init_runner();
|
|
@@ -67108,7 +68042,7 @@ init_prd();
|
|
|
67108
68042
|
|
|
67109
68043
|
// src/cli/prompts-tdd.ts
|
|
67110
68044
|
init_prompts2();
|
|
67111
|
-
import { join as
|
|
68045
|
+
import { join as join26 } from "path";
|
|
67112
68046
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
67113
68047
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
67114
68048
|
PromptBuilder.for("test-writer", { isolation: "strict" }).withLoader(ctx.workdir, ctx.config).story(story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(ctx.config.quality?.commands?.test).build(),
|
|
@@ -67127,7 +68061,7 @@ ${frontmatter}---
|
|
|
67127
68061
|
|
|
67128
68062
|
${session.prompt}`;
|
|
67129
68063
|
if (outputDir) {
|
|
67130
|
-
const promptFile =
|
|
68064
|
+
const promptFile = join26(outputDir, `${story.id}.${session.role}.md`);
|
|
67131
68065
|
await Bun.write(promptFile, fullOutput);
|
|
67132
68066
|
logger.info("cli", "Written TDD prompt file", {
|
|
67133
68067
|
storyId: story.id,
|
|
@@ -67143,7 +68077,7 @@ ${"=".repeat(80)}`);
|
|
|
67143
68077
|
}
|
|
67144
68078
|
}
|
|
67145
68079
|
if (outputDir && ctx.contextMarkdown) {
|
|
67146
|
-
const contextFile =
|
|
68080
|
+
const contextFile = join26(outputDir, `${story.id}.context.md`);
|
|
67147
68081
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
67148
68082
|
const contextOutput = `---
|
|
67149
68083
|
${frontmatter}---
|
|
@@ -67157,13 +68091,13 @@ ${ctx.contextMarkdown}`;
|
|
|
67157
68091
|
async function promptsCommand(options) {
|
|
67158
68092
|
const logger = getLogger();
|
|
67159
68093
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
67160
|
-
const naxDir =
|
|
67161
|
-
if (!
|
|
68094
|
+
const naxDir = join27(workdir, "nax");
|
|
68095
|
+
if (!existsSync18(naxDir)) {
|
|
67162
68096
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
67163
68097
|
}
|
|
67164
|
-
const featureDir =
|
|
67165
|
-
const prdPath =
|
|
67166
|
-
if (!
|
|
68098
|
+
const featureDir = join27(naxDir, "features", feature);
|
|
68099
|
+
const prdPath = join27(featureDir, "prd.json");
|
|
68100
|
+
if (!existsSync18(prdPath)) {
|
|
67167
68101
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
67168
68102
|
}
|
|
67169
68103
|
const prd = await loadPRD(prdPath);
|
|
@@ -67222,10 +68156,10 @@ ${frontmatter}---
|
|
|
67222
68156
|
|
|
67223
68157
|
${ctx.prompt}`;
|
|
67224
68158
|
if (outputDir) {
|
|
67225
|
-
const promptFile =
|
|
68159
|
+
const promptFile = join27(outputDir, `${story.id}.prompt.md`);
|
|
67226
68160
|
await Bun.write(promptFile, fullOutput);
|
|
67227
68161
|
if (ctx.contextMarkdown) {
|
|
67228
|
-
const contextFile =
|
|
68162
|
+
const contextFile = join27(outputDir, `${story.id}.context.md`);
|
|
67229
68163
|
const contextOutput = `---
|
|
67230
68164
|
${frontmatter}---
|
|
67231
68165
|
|
|
@@ -67288,8 +68222,8 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
67288
68222
|
`;
|
|
67289
68223
|
}
|
|
67290
68224
|
// src/cli/prompts-init.ts
|
|
67291
|
-
import { existsSync as
|
|
67292
|
-
import { join as
|
|
68225
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
|
|
68226
|
+
import { join as join28 } from "path";
|
|
67293
68227
|
var TEMPLATE_ROLES = [
|
|
67294
68228
|
{ file: "test-writer.md", role: "test-writer" },
|
|
67295
68229
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -67313,9 +68247,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
67313
68247
|
`;
|
|
67314
68248
|
async function promptsInitCommand(options) {
|
|
67315
68249
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
67316
|
-
const templatesDir =
|
|
68250
|
+
const templatesDir = join28(workdir, "nax", "templates");
|
|
67317
68251
|
mkdirSync4(templatesDir, { recursive: true });
|
|
67318
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) =>
|
|
68252
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join28(templatesDir, f)));
|
|
67319
68253
|
if (existingFiles.length > 0 && !force) {
|
|
67320
68254
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
67321
68255
|
Pass --force to overwrite existing templates.`);
|
|
@@ -67323,7 +68257,7 @@ async function promptsInitCommand(options) {
|
|
|
67323
68257
|
}
|
|
67324
68258
|
const written = [];
|
|
67325
68259
|
for (const template of TEMPLATE_ROLES) {
|
|
67326
|
-
const filePath =
|
|
68260
|
+
const filePath = join28(templatesDir, template.file);
|
|
67327
68261
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
67328
68262
|
const content = TEMPLATE_HEADER + roleBody;
|
|
67329
68263
|
await Bun.write(filePath, content);
|
|
@@ -67339,8 +68273,8 @@ async function promptsInitCommand(options) {
|
|
|
67339
68273
|
return written;
|
|
67340
68274
|
}
|
|
67341
68275
|
async function autoWirePromptsConfig(workdir) {
|
|
67342
|
-
const configPath =
|
|
67343
|
-
if (!
|
|
68276
|
+
const configPath = join28(workdir, "nax.config.json");
|
|
68277
|
+
if (!existsSync19(configPath)) {
|
|
67344
68278
|
const exampleConfig = JSON.stringify({
|
|
67345
68279
|
prompts: {
|
|
67346
68280
|
overrides: {
|
|
@@ -67435,9 +68369,7 @@ async function exportPromptCommand(options) {
|
|
|
67435
68369
|
// src/cli/init.ts
|
|
67436
68370
|
init_paths();
|
|
67437
68371
|
init_logger2();
|
|
67438
|
-
|
|
67439
|
-
// src/cli/init-context.ts
|
|
67440
|
-
init_logger2();
|
|
68372
|
+
init_init_context();
|
|
67441
68373
|
// src/cli/plugins.ts
|
|
67442
68374
|
init_loader5();
|
|
67443
68375
|
import * as os2 from "os";
|
|
@@ -67505,8 +68437,8 @@ function pad(str, width) {
|
|
|
67505
68437
|
init_config();
|
|
67506
68438
|
init_logger2();
|
|
67507
68439
|
init_prd();
|
|
67508
|
-
import { existsSync as
|
|
67509
|
-
import { join as
|
|
68440
|
+
import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
|
|
68441
|
+
import { join as join32 } from "path";
|
|
67510
68442
|
|
|
67511
68443
|
// src/cli/diagnose-analysis.ts
|
|
67512
68444
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -67705,8 +68637,8 @@ function isProcessAlive2(pid) {
|
|
|
67705
68637
|
}
|
|
67706
68638
|
}
|
|
67707
68639
|
async function loadStatusFile2(workdir) {
|
|
67708
|
-
const statusPath =
|
|
67709
|
-
if (!
|
|
68640
|
+
const statusPath = join32(workdir, "nax", "status.json");
|
|
68641
|
+
if (!existsSync21(statusPath))
|
|
67710
68642
|
return null;
|
|
67711
68643
|
try {
|
|
67712
68644
|
return await Bun.file(statusPath).json();
|
|
@@ -67733,7 +68665,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
67733
68665
|
}
|
|
67734
68666
|
}
|
|
67735
68667
|
async function checkLock(workdir) {
|
|
67736
|
-
const lockFile = Bun.file(
|
|
68668
|
+
const lockFile = Bun.file(join32(workdir, "nax.lock"));
|
|
67737
68669
|
if (!await lockFile.exists())
|
|
67738
68670
|
return { lockPresent: false };
|
|
67739
68671
|
try {
|
|
@@ -67751,8 +68683,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
67751
68683
|
const logger = getLogger();
|
|
67752
68684
|
const workdir = options.workdir ?? process.cwd();
|
|
67753
68685
|
const naxSubdir = findProjectDir(workdir);
|
|
67754
|
-
let projectDir = naxSubdir ?
|
|
67755
|
-
if (!projectDir &&
|
|
68686
|
+
let projectDir = naxSubdir ? join32(naxSubdir, "..") : null;
|
|
68687
|
+
if (!projectDir && existsSync21(join32(workdir, "nax"))) {
|
|
67756
68688
|
projectDir = workdir;
|
|
67757
68689
|
}
|
|
67758
68690
|
if (!projectDir)
|
|
@@ -67763,8 +68695,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
67763
68695
|
if (status2) {
|
|
67764
68696
|
feature = status2.run.feature;
|
|
67765
68697
|
} else {
|
|
67766
|
-
const featuresDir =
|
|
67767
|
-
if (!
|
|
68698
|
+
const featuresDir = join32(projectDir, "nax", "features");
|
|
68699
|
+
if (!existsSync21(featuresDir))
|
|
67768
68700
|
throw new Error("No features found in project");
|
|
67769
68701
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
67770
68702
|
if (features.length === 0)
|
|
@@ -67773,9 +68705,9 @@ async function diagnoseCommand(options = {}) {
|
|
|
67773
68705
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
67774
68706
|
}
|
|
67775
68707
|
}
|
|
67776
|
-
const featureDir =
|
|
67777
|
-
const prdPath =
|
|
67778
|
-
if (!
|
|
68708
|
+
const featureDir = join32(projectDir, "nax", "features", feature);
|
|
68709
|
+
const prdPath = join32(featureDir, "prd.json");
|
|
68710
|
+
if (!existsSync21(prdPath))
|
|
67779
68711
|
throw new Error(`Feature not found: ${feature}`);
|
|
67780
68712
|
const prd = await loadPRD(prdPath);
|
|
67781
68713
|
const status = await loadStatusFile2(projectDir);
|
|
@@ -67816,419 +68748,66 @@ init_interaction();
|
|
|
67816
68748
|
// src/cli/generate.ts
|
|
67817
68749
|
init_source();
|
|
67818
68750
|
init_loader2();
|
|
67819
|
-
import { existsSync as
|
|
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;
|
|
68751
|
+
import { existsSync as existsSync22 } from "fs";
|
|
68752
|
+
import { join as join33 } from "path";
|
|
68753
|
+
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68754
|
+
async function generateCommand(options) {
|
|
68755
|
+
const workdir = process.cwd();
|
|
68756
|
+
const dryRun = options.dryRun ?? false;
|
|
68757
|
+
let config2;
|
|
67862
68758
|
try {
|
|
67863
|
-
|
|
67864
|
-
const pkg = await file2.json();
|
|
67865
|
-
const allDeps = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
67866
|
-
const notable = [
|
|
67867
|
-
...new Set(Object.keys(allDeps).filter((dep) => NOTABLE_NODE_DEPS.some((kw) => dep === kw || dep.startsWith(`${kw}/`) || dep.includes(kw))))
|
|
67868
|
-
].slice(0, 10);
|
|
67869
|
-
const lang = pkg.devDependencies?.typescript || pkg.dependencies?.typescript ? "TypeScript" : "JavaScript";
|
|
67870
|
-
return { name: pkg.name, lang, dependencies: notable };
|
|
68759
|
+
config2 = await loadConfig(workdir);
|
|
67871
68760
|
} catch {
|
|
67872
|
-
|
|
68761
|
+
config2 = {};
|
|
67873
68762
|
}
|
|
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
|
-
}
|
|
68763
|
+
if (options.allPackages) {
|
|
68764
|
+
if (dryRun) {
|
|
68765
|
+
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
67893
68766
|
}
|
|
67894
|
-
|
|
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 };
|
|
68767
|
+
console.log(source_default.blue("\u2192 Discovering packages with nax/context.md..."));
|
|
68768
|
+
const packages = await discoverPackages(workdir);
|
|
68769
|
+
if (packages.length === 0) {
|
|
68770
|
+
console.log(source_default.yellow(" No packages found (no */nax/context.md or */*/nax/context.md)"));
|
|
68771
|
+
return;
|
|
67927
68772
|
}
|
|
67928
|
-
|
|
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 };
|
|
68773
|
+
console.log(source_default.blue(`\u2192 Generating CLAUDE.md for ${packages.length} package(s)...`));
|
|
68774
|
+
let errorCount = 0;
|
|
68775
|
+
for (const pkgDir of packages) {
|
|
68776
|
+
const result = await generateForPackage(pkgDir, config2, dryRun);
|
|
68777
|
+
if (result.error) {
|
|
68778
|
+
console.error(source_default.red(`\u2717 ${pkgDir}: ${result.error}`));
|
|
68779
|
+
errorCount++;
|
|
68780
|
+
} else {
|
|
68781
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68782
|
+
console.log(source_default.green(`\u2713 ${pkgDir}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68783
|
+
}
|
|
67973
68784
|
}
|
|
67974
|
-
|
|
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);
|
|
68785
|
+
if (errorCount > 0) {
|
|
68786
|
+
console.error(source_default.red(`
|
|
68787
|
+
\u2717 ${errorCount} generation(s) failed`));
|
|
68788
|
+
process.exit(1);
|
|
68196
68789
|
}
|
|
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 };
|
|
68790
|
+
return;
|
|
68201
68791
|
}
|
|
68202
|
-
|
|
68203
|
-
|
|
68204
|
-
|
|
68205
|
-
|
|
68206
|
-
for (const [agentKey, generator] of Object.entries(GENERATORS)) {
|
|
68207
|
-
try {
|
|
68208
|
-
const content = generator.generate(context);
|
|
68209
|
-
const outputPath = join27(options.outputDir, generator.outputFile);
|
|
68210
|
-
validateFilePath(outputPath, options.outputDir);
|
|
68211
|
-
if (!options.dryRun) {
|
|
68212
|
-
await Bun.write(outputPath, content);
|
|
68213
|
-
}
|
|
68214
|
-
results.push({ agent: agentKey, outputFile: generator.outputFile, content, written: !options.dryRun });
|
|
68215
|
-
} catch (err) {
|
|
68216
|
-
const error48 = err instanceof Error ? err.message : String(err);
|
|
68217
|
-
results.push({ agent: agentKey, outputFile: generator.outputFile, content: "", written: false, error: error48 });
|
|
68792
|
+
if (options.package) {
|
|
68793
|
+
const packageDir = join33(workdir, options.package);
|
|
68794
|
+
if (dryRun) {
|
|
68795
|
+
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
68218
68796
|
}
|
|
68797
|
+
console.log(source_default.blue(`\u2192 Generating CLAUDE.md for package: ${options.package}`));
|
|
68798
|
+
const result = await generateForPackage(packageDir, config2, dryRun);
|
|
68799
|
+
if (result.error) {
|
|
68800
|
+
console.error(source_default.red(`\u2717 ${result.error}`));
|
|
68801
|
+
process.exit(1);
|
|
68802
|
+
}
|
|
68803
|
+
const suffix = dryRun ? " (dry run)" : "";
|
|
68804
|
+
console.log(source_default.green(`\u2713 ${options.package}/${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68805
|
+
return;
|
|
68219
68806
|
}
|
|
68220
|
-
|
|
68221
|
-
|
|
68222
|
-
|
|
68223
|
-
// src/cli/generate.ts
|
|
68224
|
-
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68225
|
-
async function generateCommand(options) {
|
|
68226
|
-
const workdir = process.cwd();
|
|
68227
|
-
const contextPath = options.context ? join28(workdir, options.context) : join28(workdir, "nax/context.md");
|
|
68228
|
-
const outputDir = options.output ? join28(workdir, options.output) : workdir;
|
|
68807
|
+
const contextPath = options.context ? join33(workdir, options.context) : join33(workdir, "nax/context.md");
|
|
68808
|
+
const outputDir = options.output ? join33(workdir, options.output) : workdir;
|
|
68229
68809
|
const autoInject = !options.noAutoInject;
|
|
68230
|
-
|
|
68231
|
-
if (!existsSync20(contextPath)) {
|
|
68810
|
+
if (!existsSync22(contextPath)) {
|
|
68232
68811
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
68233
68812
|
console.error(source_default.yellow(" Create nax/context.md first, or run `nax init` to scaffold it."));
|
|
68234
68813
|
process.exit(1);
|
|
@@ -68245,12 +68824,6 @@ async function generateCommand(options) {
|
|
|
68245
68824
|
if (autoInject) {
|
|
68246
68825
|
console.log(source_default.dim(" Auto-injecting project metadata..."));
|
|
68247
68826
|
}
|
|
68248
|
-
let config2;
|
|
68249
|
-
try {
|
|
68250
|
-
config2 = await loadConfig(workdir);
|
|
68251
|
-
} catch {
|
|
68252
|
-
config2 = {};
|
|
68253
|
-
}
|
|
68254
68827
|
const genOptions = {
|
|
68255
68828
|
contextPath,
|
|
68256
68829
|
outputDir,
|
|
@@ -68270,8 +68843,15 @@ async function generateCommand(options) {
|
|
|
68270
68843
|
const suffix = dryRun ? " (dry run)" : "";
|
|
68271
68844
|
console.log(source_default.green(`\u2713 ${agent} \u2192 ${result.outputFile} (${result.content.length} bytes${suffix})`));
|
|
68272
68845
|
} else {
|
|
68273
|
-
|
|
68274
|
-
const
|
|
68846
|
+
const configAgents = config2?.generate?.agents;
|
|
68847
|
+
const agentFilter = configAgents && configAgents.length > 0 ? configAgents : null;
|
|
68848
|
+
if (agentFilter) {
|
|
68849
|
+
console.log(source_default.blue(`\u2192 Generating configs for: ${agentFilter.join(", ")} (from config)...`));
|
|
68850
|
+
} else {
|
|
68851
|
+
console.log(source_default.blue("\u2192 Generating configs for all agents..."));
|
|
68852
|
+
}
|
|
68853
|
+
const allResults = await generateAll(genOptions, config2);
|
|
68854
|
+
const results = agentFilter ? allResults.filter((r) => agentFilter.includes(r.agent)) : allResults;
|
|
68275
68855
|
let errorCount = 0;
|
|
68276
68856
|
for (const result of results) {
|
|
68277
68857
|
if (result.error) {
|
|
@@ -68300,8 +68880,8 @@ async function generateCommand(options) {
|
|
|
68300
68880
|
}
|
|
68301
68881
|
// src/cli/config-display.ts
|
|
68302
68882
|
init_loader2();
|
|
68303
|
-
import { existsSync as
|
|
68304
|
-
import { join as
|
|
68883
|
+
import { existsSync as existsSync24 } from "fs";
|
|
68884
|
+
import { join as join35 } from "path";
|
|
68305
68885
|
|
|
68306
68886
|
// src/cli/config-descriptions.ts
|
|
68307
68887
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -68509,10 +69089,10 @@ function deepEqual(a, b) {
|
|
|
68509
69089
|
// src/cli/config-get.ts
|
|
68510
69090
|
init_defaults();
|
|
68511
69091
|
init_loader2();
|
|
68512
|
-
import { existsSync as
|
|
68513
|
-
import { join as
|
|
69092
|
+
import { existsSync as existsSync23 } from "fs";
|
|
69093
|
+
import { join as join34 } from "path";
|
|
68514
69094
|
async function loadConfigFile(path14) {
|
|
68515
|
-
if (!
|
|
69095
|
+
if (!existsSync23(path14))
|
|
68516
69096
|
return null;
|
|
68517
69097
|
try {
|
|
68518
69098
|
return await Bun.file(path14).json();
|
|
@@ -68532,7 +69112,7 @@ async function loadProjectConfig() {
|
|
|
68532
69112
|
const projectDir = findProjectDir();
|
|
68533
69113
|
if (!projectDir)
|
|
68534
69114
|
return null;
|
|
68535
|
-
const projectPath =
|
|
69115
|
+
const projectPath = join34(projectDir, "config.json");
|
|
68536
69116
|
return await loadConfigFile(projectPath);
|
|
68537
69117
|
}
|
|
68538
69118
|
|
|
@@ -68592,14 +69172,14 @@ async function configCommand(config2, options = {}) {
|
|
|
68592
69172
|
function determineConfigSources() {
|
|
68593
69173
|
const globalPath = globalConfigPath();
|
|
68594
69174
|
const projectDir = findProjectDir();
|
|
68595
|
-
const projectPath = projectDir ?
|
|
69175
|
+
const projectPath = projectDir ? join35(projectDir, "config.json") : null;
|
|
68596
69176
|
return {
|
|
68597
69177
|
global: fileExists(globalPath) ? globalPath : null,
|
|
68598
69178
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
68599
69179
|
};
|
|
68600
69180
|
}
|
|
68601
69181
|
function fileExists(path14) {
|
|
68602
|
-
return
|
|
69182
|
+
return existsSync24(path14);
|
|
68603
69183
|
}
|
|
68604
69184
|
function displayConfigWithDescriptions(obj, path14, sources, indent = 0) {
|
|
68605
69185
|
const indentStr = " ".repeat(indent);
|
|
@@ -68771,25 +69351,25 @@ async function diagnose(options) {
|
|
|
68771
69351
|
}
|
|
68772
69352
|
|
|
68773
69353
|
// src/commands/logs.ts
|
|
68774
|
-
import { existsSync as
|
|
68775
|
-
import { join as
|
|
69354
|
+
import { existsSync as existsSync26 } from "fs";
|
|
69355
|
+
import { join as join38 } from "path";
|
|
68776
69356
|
|
|
68777
69357
|
// src/commands/logs-formatter.ts
|
|
68778
69358
|
init_source();
|
|
68779
69359
|
init_formatter();
|
|
68780
69360
|
import { readdirSync as readdirSync7 } from "fs";
|
|
68781
|
-
import { join as
|
|
69361
|
+
import { join as join37 } from "path";
|
|
68782
69362
|
|
|
68783
69363
|
// src/commands/logs-reader.ts
|
|
68784
|
-
import { existsSync as
|
|
69364
|
+
import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
|
|
68785
69365
|
import { readdir as readdir3 } from "fs/promises";
|
|
68786
69366
|
import { homedir as homedir5 } from "os";
|
|
68787
|
-
import { join as
|
|
68788
|
-
var
|
|
68789
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
69367
|
+
import { join as join36 } from "path";
|
|
69368
|
+
var _deps7 = {
|
|
69369
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join36(homedir5(), ".nax", "runs")
|
|
68790
69370
|
};
|
|
68791
69371
|
async function resolveRunFileFromRegistry(runId) {
|
|
68792
|
-
const runsDir =
|
|
69372
|
+
const runsDir = _deps7.getRunsDir();
|
|
68793
69373
|
let entries;
|
|
68794
69374
|
try {
|
|
68795
69375
|
entries = await readdir3(runsDir);
|
|
@@ -68798,7 +69378,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
68798
69378
|
}
|
|
68799
69379
|
let matched = null;
|
|
68800
69380
|
for (const entry of entries) {
|
|
68801
|
-
const metaPath =
|
|
69381
|
+
const metaPath = join36(runsDir, entry, "meta.json");
|
|
68802
69382
|
try {
|
|
68803
69383
|
const meta3 = await Bun.file(metaPath).json();
|
|
68804
69384
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -68810,7 +69390,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
68810
69390
|
if (!matched) {
|
|
68811
69391
|
throw new Error(`Run not found in registry: ${runId}`);
|
|
68812
69392
|
}
|
|
68813
|
-
if (!
|
|
69393
|
+
if (!existsSync25(matched.eventsDir)) {
|
|
68814
69394
|
console.log(`Log directory unavailable for run: ${runId}`);
|
|
68815
69395
|
return null;
|
|
68816
69396
|
}
|
|
@@ -68820,14 +69400,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
68820
69400
|
return null;
|
|
68821
69401
|
}
|
|
68822
69402
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
68823
|
-
return
|
|
69403
|
+
return join36(matched.eventsDir, specificFile ?? files[0]);
|
|
68824
69404
|
}
|
|
68825
69405
|
async function selectRunFile(runsDir) {
|
|
68826
69406
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
68827
69407
|
if (files.length === 0) {
|
|
68828
69408
|
return null;
|
|
68829
69409
|
}
|
|
68830
|
-
return
|
|
69410
|
+
return join36(runsDir, files[0]);
|
|
68831
69411
|
}
|
|
68832
69412
|
async function extractRunSummary(filePath) {
|
|
68833
69413
|
const file2 = Bun.file(filePath);
|
|
@@ -68912,7 +69492,7 @@ Runs:
|
|
|
68912
69492
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
68913
69493
|
console.log(source_default.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
68914
69494
|
for (const file2 of files) {
|
|
68915
|
-
const filePath =
|
|
69495
|
+
const filePath = join37(runsDir, file2);
|
|
68916
69496
|
const summary = await extractRunSummary(filePath);
|
|
68917
69497
|
const timestamp = file2.replace(".jsonl", "");
|
|
68918
69498
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -69037,7 +69617,7 @@ async function logsCommand(options) {
|
|
|
69037
69617
|
return;
|
|
69038
69618
|
}
|
|
69039
69619
|
const resolved = resolveProject({ dir: options.dir });
|
|
69040
|
-
const naxDir =
|
|
69620
|
+
const naxDir = join38(resolved.projectDir, "nax");
|
|
69041
69621
|
const configPath = resolved.configPath;
|
|
69042
69622
|
const configFile = Bun.file(configPath);
|
|
69043
69623
|
const config2 = await configFile.json();
|
|
@@ -69045,9 +69625,9 @@ async function logsCommand(options) {
|
|
|
69045
69625
|
if (!featureName) {
|
|
69046
69626
|
throw new Error("No feature specified in config.json");
|
|
69047
69627
|
}
|
|
69048
|
-
const featureDir =
|
|
69049
|
-
const runsDir =
|
|
69050
|
-
if (!
|
|
69628
|
+
const featureDir = join38(naxDir, "features", featureName);
|
|
69629
|
+
const runsDir = join38(featureDir, "runs");
|
|
69630
|
+
if (!existsSync26(runsDir)) {
|
|
69051
69631
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
69052
69632
|
}
|
|
69053
69633
|
if (options.list) {
|
|
@@ -69070,8 +69650,8 @@ init_source();
|
|
|
69070
69650
|
init_config();
|
|
69071
69651
|
init_prd();
|
|
69072
69652
|
init_precheck();
|
|
69073
|
-
import { existsSync as
|
|
69074
|
-
import { join as
|
|
69653
|
+
import { existsSync as existsSync31 } from "fs";
|
|
69654
|
+
import { join as join39 } from "path";
|
|
69075
69655
|
async function precheckCommand(options) {
|
|
69076
69656
|
const resolved = resolveProject({
|
|
69077
69657
|
dir: options.dir,
|
|
@@ -69087,14 +69667,14 @@ async function precheckCommand(options) {
|
|
|
69087
69667
|
process.exit(1);
|
|
69088
69668
|
}
|
|
69089
69669
|
}
|
|
69090
|
-
const naxDir =
|
|
69091
|
-
const featureDir =
|
|
69092
|
-
const prdPath =
|
|
69093
|
-
if (!
|
|
69670
|
+
const naxDir = join39(resolved.projectDir, "nax");
|
|
69671
|
+
const featureDir = join39(naxDir, "features", featureName);
|
|
69672
|
+
const prdPath = join39(featureDir, "prd.json");
|
|
69673
|
+
if (!existsSync31(featureDir)) {
|
|
69094
69674
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
69095
69675
|
process.exit(1);
|
|
69096
69676
|
}
|
|
69097
|
-
if (!
|
|
69677
|
+
if (!existsSync31(prdPath)) {
|
|
69098
69678
|
console.error(source_default.red(`Missing prd.json for feature: ${featureName}`));
|
|
69099
69679
|
console.error(source_default.dim(`Run: nax plan -f ${featureName} --from spec.md --auto`));
|
|
69100
69680
|
process.exit(EXIT_CODES.INVALID_PRD);
|
|
@@ -69113,10 +69693,10 @@ async function precheckCommand(options) {
|
|
|
69113
69693
|
init_source();
|
|
69114
69694
|
import { readdir as readdir4 } from "fs/promises";
|
|
69115
69695
|
import { homedir as homedir6 } from "os";
|
|
69116
|
-
import { join as
|
|
69696
|
+
import { join as join40 } from "path";
|
|
69117
69697
|
var DEFAULT_LIMIT = 20;
|
|
69118
|
-
var
|
|
69119
|
-
getRunsDir: () =>
|
|
69698
|
+
var _deps9 = {
|
|
69699
|
+
getRunsDir: () => join40(homedir6(), ".nax", "runs")
|
|
69120
69700
|
};
|
|
69121
69701
|
function formatDuration3(ms) {
|
|
69122
69702
|
if (ms <= 0)
|
|
@@ -69158,7 +69738,7 @@ function pad3(str, width) {
|
|
|
69158
69738
|
return str + " ".repeat(padding);
|
|
69159
69739
|
}
|
|
69160
69740
|
async function runsCommand(options = {}) {
|
|
69161
|
-
const runsDir =
|
|
69741
|
+
const runsDir = _deps9.getRunsDir();
|
|
69162
69742
|
let entries;
|
|
69163
69743
|
try {
|
|
69164
69744
|
entries = await readdir4(runsDir);
|
|
@@ -69168,7 +69748,7 @@ async function runsCommand(options = {}) {
|
|
|
69168
69748
|
}
|
|
69169
69749
|
const rows = [];
|
|
69170
69750
|
for (const entry of entries) {
|
|
69171
|
-
const metaPath =
|
|
69751
|
+
const metaPath = join40(runsDir, entry, "meta.json");
|
|
69172
69752
|
let meta3;
|
|
69173
69753
|
try {
|
|
69174
69754
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -69245,7 +69825,7 @@ async function runsCommand(options = {}) {
|
|
|
69245
69825
|
|
|
69246
69826
|
// src/commands/unlock.ts
|
|
69247
69827
|
init_source();
|
|
69248
|
-
import { join as
|
|
69828
|
+
import { join as join41 } from "path";
|
|
69249
69829
|
function isProcessAlive3(pid) {
|
|
69250
69830
|
try {
|
|
69251
69831
|
process.kill(pid, 0);
|
|
@@ -69260,7 +69840,7 @@ function formatLockAge(ageMs) {
|
|
|
69260
69840
|
}
|
|
69261
69841
|
async function unlockCommand(options) {
|
|
69262
69842
|
const workdir = options.dir ?? process.cwd();
|
|
69263
|
-
const lockPath =
|
|
69843
|
+
const lockPath = join41(workdir, "nax.lock");
|
|
69264
69844
|
const lockFile = Bun.file(lockPath);
|
|
69265
69845
|
const exists = await lockFile.exists();
|
|
69266
69846
|
if (!exists) {
|
|
@@ -77072,7 +77652,7 @@ async function promptForConfirmation(question) {
|
|
|
77072
77652
|
process.stdin.on("data", handler);
|
|
77073
77653
|
});
|
|
77074
77654
|
}
|
|
77075
|
-
program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).action(async (options) => {
|
|
77655
|
+
program2.command("init").description("Initialize nax in the current project").option("-d, --dir <path>", "Project directory", process.cwd()).option("-f, --force", "Force overwrite existing files", false).option("--package <dir>", "Scaffold per-package nax/context.md (e.g. packages/api)").action(async (options) => {
|
|
77076
77656
|
let workdir;
|
|
77077
77657
|
try {
|
|
77078
77658
|
workdir = validateDirectory(options.dir);
|
|
@@ -77080,15 +77660,30 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
77080
77660
|
console.error(source_default.red(`Invalid directory: ${err.message}`));
|
|
77081
77661
|
process.exit(1);
|
|
77082
77662
|
}
|
|
77083
|
-
|
|
77084
|
-
|
|
77663
|
+
if (options.package) {
|
|
77664
|
+
const { initPackage: initPkg } = await Promise.resolve().then(() => (init_init_context(), exports_init_context));
|
|
77665
|
+
try {
|
|
77666
|
+
await initPkg(workdir, options.package, options.force);
|
|
77667
|
+
console.log(source_default.green(`
|
|
77668
|
+
[OK] Package scaffold created.`));
|
|
77669
|
+
console.log(source_default.dim(` Created: ${options.package}/nax/context.md`));
|
|
77670
|
+
console.log(source_default.dim(`
|
|
77671
|
+
Next: nax generate --package ${options.package}`));
|
|
77672
|
+
} catch (err) {
|
|
77673
|
+
console.error(source_default.red(`Error: ${err.message}`));
|
|
77674
|
+
process.exit(1);
|
|
77675
|
+
}
|
|
77676
|
+
return;
|
|
77677
|
+
}
|
|
77678
|
+
const naxDir = join48(workdir, "nax");
|
|
77679
|
+
if (existsSync34(naxDir) && !options.force) {
|
|
77085
77680
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
77086
77681
|
return;
|
|
77087
77682
|
}
|
|
77088
|
-
mkdirSync6(
|
|
77089
|
-
mkdirSync6(
|
|
77090
|
-
await Bun.write(
|
|
77091
|
-
await Bun.write(
|
|
77683
|
+
mkdirSync6(join48(naxDir, "features"), { recursive: true });
|
|
77684
|
+
mkdirSync6(join48(naxDir, "hooks"), { recursive: true });
|
|
77685
|
+
await Bun.write(join48(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
77686
|
+
await Bun.write(join48(naxDir, "hooks.json"), JSON.stringify({
|
|
77092
77687
|
hooks: {
|
|
77093
77688
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
77094
77689
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -77096,12 +77691,12 @@ program2.command("init").description("Initialize nax in the current project").op
|
|
|
77096
77691
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
77097
77692
|
}
|
|
77098
77693
|
}, null, 2));
|
|
77099
|
-
await Bun.write(
|
|
77694
|
+
await Bun.write(join48(naxDir, ".gitignore"), `# nax temp files
|
|
77100
77695
|
*.tmp
|
|
77101
77696
|
.paused.json
|
|
77102
77697
|
.nax-verifier-verdict.json
|
|
77103
77698
|
`);
|
|
77104
|
-
await Bun.write(
|
|
77699
|
+
await Bun.write(join48(naxDir, "context.md"), `# Project Context
|
|
77105
77700
|
|
|
77106
77701
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
77107
77702
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -77198,7 +77793,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77198
77793
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
77199
77794
|
process.exit(1);
|
|
77200
77795
|
}
|
|
77201
|
-
if (options.from && !
|
|
77796
|
+
if (options.from && !existsSync34(options.from)) {
|
|
77202
77797
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
77203
77798
|
process.exit(1);
|
|
77204
77799
|
}
|
|
@@ -77227,10 +77822,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77227
77822
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77228
77823
|
process.exit(1);
|
|
77229
77824
|
}
|
|
77230
|
-
const featureDir =
|
|
77231
|
-
const prdPath =
|
|
77825
|
+
const featureDir = join48(naxDir, "features", options.feature);
|
|
77826
|
+
const prdPath = join48(featureDir, "prd.json");
|
|
77232
77827
|
if (options.plan && options.from) {
|
|
77233
|
-
if (
|
|
77828
|
+
if (existsSync34(prdPath) && !options.force) {
|
|
77234
77829
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
77235
77830
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
77236
77831
|
process.exit(1);
|
|
@@ -77250,10 +77845,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77250
77845
|
}
|
|
77251
77846
|
}
|
|
77252
77847
|
try {
|
|
77253
|
-
const planLogDir =
|
|
77848
|
+
const planLogDir = join48(featureDir, "plan");
|
|
77254
77849
|
mkdirSync6(planLogDir, { recursive: true });
|
|
77255
77850
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77256
|
-
const planLogPath =
|
|
77851
|
+
const planLogPath = join48(planLogDir, `${planLogId}.jsonl`);
|
|
77257
77852
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77258
77853
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77259
77854
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -77286,15 +77881,15 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77286
77881
|
process.exit(1);
|
|
77287
77882
|
}
|
|
77288
77883
|
}
|
|
77289
|
-
if (!
|
|
77884
|
+
if (!existsSync34(prdPath)) {
|
|
77290
77885
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
77291
77886
|
process.exit(1);
|
|
77292
77887
|
}
|
|
77293
77888
|
resetLogger();
|
|
77294
|
-
const runsDir =
|
|
77889
|
+
const runsDir = join48(featureDir, "runs");
|
|
77295
77890
|
mkdirSync6(runsDir, { recursive: true });
|
|
77296
77891
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77297
|
-
const logFilePath =
|
|
77892
|
+
const logFilePath = join48(runsDir, `${runId}.jsonl`);
|
|
77298
77893
|
const isTTY = process.stdout.isTTY ?? false;
|
|
77299
77894
|
const headlessFlag = options.headless ?? false;
|
|
77300
77895
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -77310,7 +77905,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77310
77905
|
config2.autoMode.defaultAgent = options.agent;
|
|
77311
77906
|
}
|
|
77312
77907
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
77313
|
-
const globalNaxDir =
|
|
77908
|
+
const globalNaxDir = join48(homedir10(), ".nax");
|
|
77314
77909
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
77315
77910
|
const eventEmitter = new PipelineEventEmitter;
|
|
77316
77911
|
let tuiInstance;
|
|
@@ -77333,7 +77928,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77333
77928
|
} else {
|
|
77334
77929
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
77335
77930
|
}
|
|
77336
|
-
const statusFilePath =
|
|
77931
|
+
const statusFilePath = join48(workdir, "nax", "status.json");
|
|
77337
77932
|
let parallel;
|
|
77338
77933
|
if (options.parallel !== undefined) {
|
|
77339
77934
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -77359,9 +77954,9 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77359
77954
|
headless: useHeadless,
|
|
77360
77955
|
skipPrecheck: options.skipPrecheck ?? false
|
|
77361
77956
|
});
|
|
77362
|
-
const latestSymlink =
|
|
77957
|
+
const latestSymlink = join48(runsDir, "latest.jsonl");
|
|
77363
77958
|
try {
|
|
77364
|
-
if (
|
|
77959
|
+
if (existsSync34(latestSymlink)) {
|
|
77365
77960
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
77366
77961
|
}
|
|
77367
77962
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -77397,9 +77992,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77397
77992
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77398
77993
|
process.exit(1);
|
|
77399
77994
|
}
|
|
77400
|
-
const featureDir =
|
|
77995
|
+
const featureDir = join48(naxDir, "features", name);
|
|
77401
77996
|
mkdirSync6(featureDir, { recursive: true });
|
|
77402
|
-
await Bun.write(
|
|
77997
|
+
await Bun.write(join48(featureDir, "spec.md"), `# Feature: ${name}
|
|
77403
77998
|
|
|
77404
77999
|
## Overview
|
|
77405
78000
|
|
|
@@ -77407,7 +78002,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77407
78002
|
|
|
77408
78003
|
## Acceptance Criteria
|
|
77409
78004
|
`);
|
|
77410
|
-
await Bun.write(
|
|
78005
|
+
await Bun.write(join48(featureDir, "plan.md"), `# Plan: ${name}
|
|
77411
78006
|
|
|
77412
78007
|
## Architecture
|
|
77413
78008
|
|
|
@@ -77415,7 +78010,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77415
78010
|
|
|
77416
78011
|
## Dependencies
|
|
77417
78012
|
`);
|
|
77418
|
-
await Bun.write(
|
|
78013
|
+
await Bun.write(join48(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
77419
78014
|
|
|
77420
78015
|
## US-001: [Title]
|
|
77421
78016
|
|
|
@@ -77424,7 +78019,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
77424
78019
|
### Acceptance Criteria
|
|
77425
78020
|
- [ ] Criterion 1
|
|
77426
78021
|
`);
|
|
77427
|
-
await Bun.write(
|
|
78022
|
+
await Bun.write(join48(featureDir, "progress.txt"), `# Progress: ${name}
|
|
77428
78023
|
|
|
77429
78024
|
Created: ${new Date().toISOString()}
|
|
77430
78025
|
|
|
@@ -77452,8 +78047,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
77452
78047
|
console.error(source_default.red("nax not initialized."));
|
|
77453
78048
|
process.exit(1);
|
|
77454
78049
|
}
|
|
77455
|
-
const featuresDir =
|
|
77456
|
-
if (!
|
|
78050
|
+
const featuresDir = join48(naxDir, "features");
|
|
78051
|
+
if (!existsSync34(featuresDir)) {
|
|
77457
78052
|
console.log(source_default.dim("No features yet."));
|
|
77458
78053
|
return;
|
|
77459
78054
|
}
|
|
@@ -77467,8 +78062,8 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
77467
78062
|
Features:
|
|
77468
78063
|
`));
|
|
77469
78064
|
for (const name of entries) {
|
|
77470
|
-
const prdPath =
|
|
77471
|
-
if (
|
|
78065
|
+
const prdPath = join48(featuresDir, name, "prd.json");
|
|
78066
|
+
if (existsSync34(prdPath)) {
|
|
77472
78067
|
const prd = await loadPRD(prdPath);
|
|
77473
78068
|
const c = countStories(prd);
|
|
77474
78069
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -77498,10 +78093,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
77498
78093
|
process.exit(1);
|
|
77499
78094
|
}
|
|
77500
78095
|
const config2 = await loadConfig(workdir);
|
|
77501
|
-
const featureLogDir =
|
|
78096
|
+
const featureLogDir = join48(naxDir, "features", options.feature, "plan");
|
|
77502
78097
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
77503
78098
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77504
|
-
const planLogPath =
|
|
78099
|
+
const planLogPath = join48(featureLogDir, `${planLogId}.jsonl`);
|
|
77505
78100
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77506
78101
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77507
78102
|
try {
|
|
@@ -77538,8 +78133,8 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
77538
78133
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77539
78134
|
process.exit(1);
|
|
77540
78135
|
}
|
|
77541
|
-
const featureDir =
|
|
77542
|
-
if (!
|
|
78136
|
+
const featureDir = join48(naxDir, "features", options.feature);
|
|
78137
|
+
if (!existsSync34(featureDir)) {
|
|
77543
78138
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
77544
78139
|
process.exit(1);
|
|
77545
78140
|
}
|
|
@@ -77554,7 +78149,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
77554
78149
|
specPath: options.from,
|
|
77555
78150
|
reclassify: options.reclassify
|
|
77556
78151
|
});
|
|
77557
|
-
const prdPath =
|
|
78152
|
+
const prdPath = join48(featureDir, "prd.json");
|
|
77558
78153
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
77559
78154
|
const c = countStories(prd);
|
|
77560
78155
|
console.log(source_default.green(`
|
|
@@ -77782,14 +78377,16 @@ program2.command("prompts").description("Assemble or initialize prompts").option
|
|
|
77782
78377
|
process.exit(1);
|
|
77783
78378
|
}
|
|
77784
78379
|
});
|
|
77785
|
-
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").action(async (options) => {
|
|
78380
|
+
program2.command("generate").description("Generate agent config files (CLAUDE.md, AGENTS.md, etc.) from nax/context.md").option("-c, --context <path>", "Context file path (default: nax/context.md)").option("-o, --output <dir>", "Output directory (default: project root)").option("-a, --agent <name>", "Specific agent (claude|opencode|cursor|windsurf|aider)").option("--dry-run", "Preview without writing files", false).option("--no-auto-inject", "Disable auto-injection of project metadata").option("--package <dir>", "Generate CLAUDE.md for a specific package (e.g. packages/api)").option("--all-packages", "Generate CLAUDE.md for all discovered packages", false).action(async (options) => {
|
|
77786
78381
|
try {
|
|
77787
78382
|
await generateCommand({
|
|
77788
78383
|
context: options.context,
|
|
77789
78384
|
output: options.output,
|
|
77790
78385
|
agent: options.agent,
|
|
77791
78386
|
dryRun: options.dryRun,
|
|
77792
|
-
noAutoInject: !options.autoInject
|
|
78387
|
+
noAutoInject: !options.autoInject,
|
|
78388
|
+
package: options.package,
|
|
78389
|
+
allPackages: options.allPackages
|
|
77793
78390
|
});
|
|
77794
78391
|
} catch (err) {
|
|
77795
78392
|
console.error(source_default.red(`Error: ${err.message}`));
|