@nathapp/nax 0.48.4 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +242 -179
- package/package.json +1 -1
- package/src/acceptance/generator.ts +4 -5
- package/src/cli/prompts-main.ts +1 -0
- package/src/config/merge.ts +55 -9
- package/src/execution/iteration-runner.ts +15 -0
- package/src/execution/lifecycle/acceptance-loop.ts +2 -0
- package/src/execution/parallel-coordinator.ts +1 -0
- package/src/execution/parallel-executor-rectify.ts +1 -0
- package/src/execution/parallel-worker.ts +1 -0
- package/src/execution/sequential-executor.ts +1 -0
- package/src/pipeline/stages/acceptance.ts +6 -2
- package/src/pipeline/stages/autofix.ts +15 -7
- package/src/pipeline/stages/execution.ts +6 -0
- package/src/pipeline/stages/prompt.ts +5 -2
- package/src/pipeline/stages/rectify.ts +4 -2
- package/src/pipeline/stages/regression.ts +10 -6
- package/src/pipeline/stages/review.ts +11 -7
- package/src/pipeline/stages/verify.ts +23 -20
- package/src/pipeline/types.ts +7 -0
package/dist/nax.js
CHANGED
|
@@ -18717,9 +18717,8 @@ ${criteriaList}
|
|
|
18717
18717
|
|
|
18718
18718
|
${strategyInstructions}Generate a complete acceptance.test.ts file using bun:test framework. Each AC maps to exactly one test named "AC-N: <description>".
|
|
18719
18719
|
|
|
18720
|
-
|
|
18720
|
+
Structure example (do NOT wrap in markdown fences \u2014 output raw TypeScript only):
|
|
18721
18721
|
|
|
18722
|
-
\`\`\`typescript
|
|
18723
18722
|
import { describe, test, expect } from "bun:test";
|
|
18724
18723
|
|
|
18725
18724
|
describe("${options.featureName} - Acceptance Tests", () => {
|
|
@@ -18727,11 +18726,11 @@ describe("${options.featureName} - Acceptance Tests", () => {
|
|
|
18727
18726
|
// Test implementation
|
|
18728
18727
|
});
|
|
18729
18728
|
});
|
|
18730
|
-
\`\`\`
|
|
18731
18729
|
|
|
18732
|
-
|
|
18730
|
+
IMPORTANT: Output raw TypeScript code only. Do NOT use markdown code fences (\`\`\`typescript or \`\`\`). Start directly with the import statement.`;
|
|
18733
18731
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
18734
|
-
const
|
|
18732
|
+
const rawOutput = await _generatorPRDDeps.adapter.complete(prompt, { config: options.config });
|
|
18733
|
+
const testCode = extractTestCode(rawOutput);
|
|
18735
18734
|
const refinedJsonContent = JSON.stringify(refinedCriteria.map((c, i) => ({
|
|
18736
18735
|
acId: `AC-${i + 1}`,
|
|
18737
18736
|
original: c.original,
|
|
@@ -20697,17 +20696,49 @@ var init_json_file = __esm(() => {
|
|
|
20697
20696
|
|
|
20698
20697
|
// src/config/merge.ts
|
|
20699
20698
|
function mergePackageConfig(root, packageOverride) {
|
|
20700
|
-
const
|
|
20701
|
-
if (!
|
|
20699
|
+
const hasAnyMergeableField = packageOverride.execution !== undefined || packageOverride.review !== undefined || packageOverride.acceptance !== undefined || packageOverride.quality !== undefined || packageOverride.context !== undefined;
|
|
20700
|
+
if (!hasAnyMergeableField) {
|
|
20702
20701
|
return root;
|
|
20703
20702
|
}
|
|
20704
20703
|
return {
|
|
20705
20704
|
...root,
|
|
20705
|
+
execution: {
|
|
20706
|
+
...root.execution,
|
|
20707
|
+
...packageOverride.execution,
|
|
20708
|
+
smartTestRunner: packageOverride.execution?.smartTestRunner ?? root.execution.smartTestRunner,
|
|
20709
|
+
regressionGate: {
|
|
20710
|
+
...root.execution.regressionGate,
|
|
20711
|
+
...packageOverride.execution?.regressionGate
|
|
20712
|
+
},
|
|
20713
|
+
verificationTimeoutSeconds: packageOverride.execution?.verificationTimeoutSeconds ?? root.execution.verificationTimeoutSeconds
|
|
20714
|
+
},
|
|
20715
|
+
review: {
|
|
20716
|
+
...root.review,
|
|
20717
|
+
...packageOverride.review,
|
|
20718
|
+
commands: {
|
|
20719
|
+
...root.review.commands,
|
|
20720
|
+
...packageOverride.review?.commands
|
|
20721
|
+
}
|
|
20722
|
+
},
|
|
20723
|
+
acceptance: {
|
|
20724
|
+
...root.acceptance,
|
|
20725
|
+
...packageOverride.acceptance
|
|
20726
|
+
},
|
|
20706
20727
|
quality: {
|
|
20707
20728
|
...root.quality,
|
|
20729
|
+
requireTests: packageOverride.quality?.requireTests ?? root.quality.requireTests,
|
|
20730
|
+
requireTypecheck: packageOverride.quality?.requireTypecheck ?? root.quality.requireTypecheck,
|
|
20731
|
+
requireLint: packageOverride.quality?.requireLint ?? root.quality.requireLint,
|
|
20708
20732
|
commands: {
|
|
20709
20733
|
...root.quality.commands,
|
|
20710
|
-
...
|
|
20734
|
+
...packageOverride.quality?.commands
|
|
20735
|
+
}
|
|
20736
|
+
},
|
|
20737
|
+
context: {
|
|
20738
|
+
...root.context,
|
|
20739
|
+
testCoverage: {
|
|
20740
|
+
...root.context.testCoverage,
|
|
20741
|
+
...packageOverride.context?.testCoverage
|
|
20711
20742
|
}
|
|
20712
20743
|
}
|
|
20713
20744
|
};
|
|
@@ -22210,7 +22241,7 @@ var package_default;
|
|
|
22210
22241
|
var init_package = __esm(() => {
|
|
22211
22242
|
package_default = {
|
|
22212
22243
|
name: "@nathapp/nax",
|
|
22213
|
-
version: "0.
|
|
22244
|
+
version: "0.49.0",
|
|
22214
22245
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22215
22246
|
type: "module",
|
|
22216
22247
|
bin: {
|
|
@@ -22283,8 +22314,8 @@ var init_version = __esm(() => {
|
|
|
22283
22314
|
NAX_VERSION = package_default.version;
|
|
22284
22315
|
NAX_COMMIT = (() => {
|
|
22285
22316
|
try {
|
|
22286
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22287
|
-
return "
|
|
22317
|
+
if (/^[0-9a-f]{6,10}$/.test("6a5bc7a"))
|
|
22318
|
+
return "6a5bc7a";
|
|
22288
22319
|
} catch {}
|
|
22289
22320
|
try {
|
|
22290
22321
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -23933,7 +23964,8 @@ var init_acceptance2 = __esm(() => {
|
|
|
23933
23964
|
acceptanceStage = {
|
|
23934
23965
|
name: "acceptance",
|
|
23935
23966
|
enabled(ctx) {
|
|
23936
|
-
|
|
23967
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
23968
|
+
if (!effectiveConfig.acceptance.enabled) {
|
|
23937
23969
|
return false;
|
|
23938
23970
|
}
|
|
23939
23971
|
if (!areAllStoriesComplete(ctx)) {
|
|
@@ -23943,12 +23975,13 @@ var init_acceptance2 = __esm(() => {
|
|
|
23943
23975
|
},
|
|
23944
23976
|
async execute(ctx) {
|
|
23945
23977
|
const logger = getLogger();
|
|
23978
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
23946
23979
|
logger.info("acceptance", "Running acceptance tests");
|
|
23947
23980
|
if (!ctx.featureDir) {
|
|
23948
23981
|
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
|
|
23949
23982
|
return { action: "continue" };
|
|
23950
23983
|
}
|
|
23951
|
-
const testPath = path4.join(ctx.featureDir,
|
|
23984
|
+
const testPath = path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath);
|
|
23952
23985
|
const testFile = Bun.file(testPath);
|
|
23953
23986
|
const exists = await testFile.exists();
|
|
23954
23987
|
if (!exists) {
|
|
@@ -24577,12 +24610,13 @@ var init_review = __esm(() => {
|
|
|
24577
24610
|
init_orchestrator();
|
|
24578
24611
|
reviewStage = {
|
|
24579
24612
|
name: "review",
|
|
24580
|
-
enabled: (ctx) => ctx.config.review.enabled,
|
|
24613
|
+
enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
|
|
24581
24614
|
async execute(ctx) {
|
|
24582
24615
|
const logger = getLogger();
|
|
24616
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24583
24617
|
logger.info("review", "Running review phase", { storyId: ctx.story.id });
|
|
24584
24618
|
const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24585
|
-
const result = await reviewOrchestrator.review(
|
|
24619
|
+
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
|
|
24586
24620
|
ctx.reviewResult = result.builtIn;
|
|
24587
24621
|
if (!result.success) {
|
|
24588
24622
|
const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -24590,8 +24624,8 @@ var init_review = __esm(() => {
|
|
|
24590
24624
|
ctx.reviewFindings = allFindings;
|
|
24591
24625
|
}
|
|
24592
24626
|
if (result.pluginFailed) {
|
|
24593
|
-
if (ctx.interaction && isTriggerEnabled("security-review",
|
|
24594
|
-
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id },
|
|
24627
|
+
if (ctx.interaction && isTriggerEnabled("security-review", effectiveConfig)) {
|
|
24628
|
+
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, effectiveConfig, ctx.interaction);
|
|
24595
24629
|
if (!shouldContinue) {
|
|
24596
24630
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
24597
24631
|
return { action: "fail", reason: `Review failed: ${result.failureReason}` };
|
|
@@ -24602,11 +24636,11 @@ var init_review = __esm(() => {
|
|
|
24602
24636
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
24603
24637
|
return { action: "fail", reason: `Review failed: ${result.failureReason}` };
|
|
24604
24638
|
}
|
|
24605
|
-
logger.warn("review", "Review failed (built-in checks) \u2014
|
|
24639
|
+
logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
|
|
24606
24640
|
reason: result.failureReason,
|
|
24607
24641
|
storyId: ctx.story.id
|
|
24608
24642
|
});
|
|
24609
|
-
return { action: "
|
|
24643
|
+
return { action: "continue" };
|
|
24610
24644
|
}
|
|
24611
24645
|
logger.info("review", "Review passed", {
|
|
24612
24646
|
durationMs: result.builtIn.totalDurationMs,
|
|
@@ -24621,6 +24655,7 @@ var init_review = __esm(() => {
|
|
|
24621
24655
|
});
|
|
24622
24656
|
|
|
24623
24657
|
// src/pipeline/stages/autofix.ts
|
|
24658
|
+
import { join as join18 } from "path";
|
|
24624
24659
|
async function runCommand(cmd, cwd) {
|
|
24625
24660
|
const parts = cmd.split(/\s+/);
|
|
24626
24661
|
const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
@@ -24663,7 +24698,8 @@ Commit your fixes when done.`;
|
|
|
24663
24698
|
}
|
|
24664
24699
|
async function runAgentRectification(ctx) {
|
|
24665
24700
|
const logger = getLogger();
|
|
24666
|
-
const
|
|
24701
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24702
|
+
const maxAttempts = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
|
|
24667
24703
|
const failedChecks = collectFailedChecks(ctx);
|
|
24668
24704
|
if (failedChecks.length === 0) {
|
|
24669
24705
|
logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
|
|
@@ -24720,6 +24756,7 @@ var autofixStage, _autofixDeps;
|
|
|
24720
24756
|
var init_autofix = __esm(() => {
|
|
24721
24757
|
init_agents();
|
|
24722
24758
|
init_config();
|
|
24759
|
+
init_loader2();
|
|
24723
24760
|
init_logger2();
|
|
24724
24761
|
init_event_bus();
|
|
24725
24762
|
autofixStage = {
|
|
@@ -24729,7 +24766,7 @@ var init_autofix = __esm(() => {
|
|
|
24729
24766
|
return false;
|
|
24730
24767
|
if (ctx.reviewResult.success)
|
|
24731
24768
|
return false;
|
|
24732
|
-
const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
|
|
24769
|
+
const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
|
|
24733
24770
|
return autofixEnabled;
|
|
24734
24771
|
},
|
|
24735
24772
|
skipReason(ctx) {
|
|
@@ -24743,12 +24780,14 @@ var init_autofix = __esm(() => {
|
|
|
24743
24780
|
if (!reviewResult || reviewResult.success) {
|
|
24744
24781
|
return { action: "continue" };
|
|
24745
24782
|
}
|
|
24746
|
-
const
|
|
24747
|
-
const
|
|
24783
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24784
|
+
const lintFixCmd = effectiveConfig.quality.commands.lintFix;
|
|
24785
|
+
const formatFixCmd = effectiveConfig.quality.commands.formatFix;
|
|
24786
|
+
const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24748
24787
|
if (lintFixCmd || formatFixCmd) {
|
|
24749
24788
|
if (lintFixCmd) {
|
|
24750
24789
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
|
|
24751
|
-
const lintResult = await _autofixDeps.runCommand(lintFixCmd,
|
|
24790
|
+
const lintResult = await _autofixDeps.runCommand(lintFixCmd, effectiveWorkdir);
|
|
24752
24791
|
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id });
|
|
24753
24792
|
if (lintResult.exitCode !== 0) {
|
|
24754
24793
|
logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -24759,7 +24798,7 @@ var init_autofix = __esm(() => {
|
|
|
24759
24798
|
}
|
|
24760
24799
|
if (formatFixCmd) {
|
|
24761
24800
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
|
|
24762
|
-
const fmtResult = await _autofixDeps.runCommand(formatFixCmd,
|
|
24801
|
+
const fmtResult = await _autofixDeps.runCommand(formatFixCmd, effectiveWorkdir);
|
|
24763
24802
|
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, { storyId: ctx.story.id });
|
|
24764
24803
|
if (fmtResult.exitCode !== 0) {
|
|
24765
24804
|
logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -24788,15 +24827,15 @@ var init_autofix = __esm(() => {
|
|
|
24788
24827
|
return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
|
|
24789
24828
|
}
|
|
24790
24829
|
};
|
|
24791
|
-
_autofixDeps = { runCommand, recheckReview, runAgentRectification };
|
|
24830
|
+
_autofixDeps = { runCommand, recheckReview, runAgentRectification, loadConfigForWorkdir };
|
|
24792
24831
|
});
|
|
24793
24832
|
|
|
24794
24833
|
// src/execution/progress.ts
|
|
24795
24834
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
24796
|
-
import { join as
|
|
24835
|
+
import { join as join19 } from "path";
|
|
24797
24836
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
24798
24837
|
mkdirSync2(featureDir, { recursive: true });
|
|
24799
|
-
const progressPath =
|
|
24838
|
+
const progressPath = join19(featureDir, "progress.txt");
|
|
24800
24839
|
const timestamp = new Date().toISOString();
|
|
24801
24840
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
24802
24841
|
`;
|
|
@@ -24880,7 +24919,7 @@ function estimateTokens(text) {
|
|
|
24880
24919
|
|
|
24881
24920
|
// src/constitution/loader.ts
|
|
24882
24921
|
import { existsSync as existsSync15 } from "fs";
|
|
24883
|
-
import { join as
|
|
24922
|
+
import { join as join20 } from "path";
|
|
24884
24923
|
function truncateToTokens(text, maxTokens) {
|
|
24885
24924
|
const maxChars = maxTokens * 3;
|
|
24886
24925
|
if (text.length <= maxChars) {
|
|
@@ -24902,7 +24941,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
24902
24941
|
}
|
|
24903
24942
|
let combinedContent = "";
|
|
24904
24943
|
if (!config2.skipGlobal) {
|
|
24905
|
-
const globalPath =
|
|
24944
|
+
const globalPath = join20(globalConfigDir(), config2.path);
|
|
24906
24945
|
if (existsSync15(globalPath)) {
|
|
24907
24946
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
24908
24947
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -24912,7 +24951,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
24912
24951
|
}
|
|
24913
24952
|
}
|
|
24914
24953
|
}
|
|
24915
|
-
const projectPath =
|
|
24954
|
+
const projectPath = join20(projectDir, config2.path);
|
|
24916
24955
|
if (existsSync15(projectPath)) {
|
|
24917
24956
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
24918
24957
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -25884,7 +25923,7 @@ var init_helpers = __esm(() => {
|
|
|
25884
25923
|
});
|
|
25885
25924
|
|
|
25886
25925
|
// src/pipeline/stages/context.ts
|
|
25887
|
-
import { join as
|
|
25926
|
+
import { join as join21 } from "path";
|
|
25888
25927
|
var contextStage;
|
|
25889
25928
|
var init_context2 = __esm(() => {
|
|
25890
25929
|
init_helpers();
|
|
@@ -25894,7 +25933,7 @@ var init_context2 = __esm(() => {
|
|
|
25894
25933
|
enabled: () => true,
|
|
25895
25934
|
async execute(ctx) {
|
|
25896
25935
|
const logger = getLogger();
|
|
25897
|
-
const packageWorkdir = ctx.story.workdir ?
|
|
25936
|
+
const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
|
|
25898
25937
|
const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
25899
25938
|
if (result) {
|
|
25900
25939
|
ctx.contextMarkdown = result.markdown;
|
|
@@ -26026,14 +26065,14 @@ var init_isolation = __esm(() => {
|
|
|
26026
26065
|
|
|
26027
26066
|
// src/context/greenfield.ts
|
|
26028
26067
|
import { readdir } from "fs/promises";
|
|
26029
|
-
import { join as
|
|
26068
|
+
import { join as join22 } from "path";
|
|
26030
26069
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
26031
26070
|
const results = [];
|
|
26032
26071
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
26033
26072
|
try {
|
|
26034
26073
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
26035
26074
|
for (const entry of entries) {
|
|
26036
|
-
const fullPath =
|
|
26075
|
+
const fullPath = join22(dir, entry.name);
|
|
26037
26076
|
if (entry.isDirectory()) {
|
|
26038
26077
|
if (ignoreDirs.has(entry.name))
|
|
26039
26078
|
continue;
|
|
@@ -26445,13 +26484,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
26445
26484
|
|
|
26446
26485
|
// src/verification/runners.ts
|
|
26447
26486
|
import { existsSync as existsSync16 } from "fs";
|
|
26448
|
-
import { join as
|
|
26487
|
+
import { join as join23 } from "path";
|
|
26449
26488
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
26450
26489
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
26451
26490
|
return { success: true, missingFiles: [] };
|
|
26452
26491
|
const missingFiles = [];
|
|
26453
26492
|
for (const file2 of expectedFiles) {
|
|
26454
|
-
if (!existsSync16(
|
|
26493
|
+
if (!existsSync16(join23(workingDirectory, file2)))
|
|
26455
26494
|
missingFiles.push(file2);
|
|
26456
26495
|
}
|
|
26457
26496
|
if (missingFiles.length > 0) {
|
|
@@ -27136,13 +27175,13 @@ var exports_loader = {};
|
|
|
27136
27175
|
__export(exports_loader, {
|
|
27137
27176
|
loadOverride: () => loadOverride
|
|
27138
27177
|
});
|
|
27139
|
-
import { join as
|
|
27178
|
+
import { join as join24 } from "path";
|
|
27140
27179
|
async function loadOverride(role, workdir, config2) {
|
|
27141
27180
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
27142
27181
|
if (!overridePath) {
|
|
27143
27182
|
return null;
|
|
27144
27183
|
}
|
|
27145
|
-
const absolutePath =
|
|
27184
|
+
const absolutePath = join24(workdir, overridePath);
|
|
27146
27185
|
const file2 = Bun.file(absolutePath);
|
|
27147
27186
|
if (!await file2.exists()) {
|
|
27148
27187
|
return null;
|
|
@@ -27964,11 +28003,11 @@ var init_tdd = __esm(() => {
|
|
|
27964
28003
|
|
|
27965
28004
|
// src/pipeline/stages/execution.ts
|
|
27966
28005
|
import { existsSync as existsSync17 } from "fs";
|
|
27967
|
-
import { join as
|
|
28006
|
+
import { join as join25 } from "path";
|
|
27968
28007
|
function resolveStoryWorkdir(repoRoot, storyWorkdir) {
|
|
27969
28008
|
if (!storyWorkdir)
|
|
27970
28009
|
return repoRoot;
|
|
27971
|
-
const resolved =
|
|
28010
|
+
const resolved = join25(repoRoot, storyWorkdir);
|
|
27972
28011
|
if (!existsSync17(resolved)) {
|
|
27973
28012
|
throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
|
|
27974
28013
|
}
|
|
@@ -27998,6 +28037,9 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
|
|
|
27998
28037
|
if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
|
|
27999
28038
|
return { action: "escalate" };
|
|
28000
28039
|
}
|
|
28040
|
+
if (failureCategory === "greenfield-no-tests") {
|
|
28041
|
+
return { action: "escalate" };
|
|
28042
|
+
}
|
|
28001
28043
|
return {
|
|
28002
28044
|
action: "pause",
|
|
28003
28045
|
reason: reviewReason || "Three-session TDD requires review"
|
|
@@ -28438,13 +28480,14 @@ var init_prompt = __esm(() => {
|
|
|
28438
28480
|
async execute(ctx) {
|
|
28439
28481
|
const logger = getLogger();
|
|
28440
28482
|
const isBatch = ctx.stories.length > 1;
|
|
28483
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
28441
28484
|
let prompt;
|
|
28442
28485
|
if (isBatch) {
|
|
28443
|
-
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(
|
|
28486
|
+
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test);
|
|
28444
28487
|
prompt = await builder.build();
|
|
28445
28488
|
} else {
|
|
28446
28489
|
const role = "tdd-simple";
|
|
28447
|
-
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(
|
|
28490
|
+
const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(effectiveConfig.quality?.commands?.test);
|
|
28448
28491
|
prompt = await builder.build();
|
|
28449
28492
|
}
|
|
28450
28493
|
ctx.prompt = prompt;
|
|
@@ -28788,13 +28831,14 @@ var init_rectify = __esm(() => {
|
|
|
28788
28831
|
attempt: rectifyAttempt,
|
|
28789
28832
|
testOutput
|
|
28790
28833
|
});
|
|
28791
|
-
const
|
|
28834
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
28835
|
+
const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
|
|
28792
28836
|
const fixed = await _rectifyDeps.runRectificationLoop({
|
|
28793
28837
|
config: ctx.config,
|
|
28794
28838
|
workdir: ctx.workdir,
|
|
28795
28839
|
story: ctx.story,
|
|
28796
28840
|
testCommand,
|
|
28797
|
-
timeoutSeconds:
|
|
28841
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
28798
28842
|
testOutput
|
|
28799
28843
|
});
|
|
28800
28844
|
pipelineEventBus.emit({
|
|
@@ -29322,31 +29366,34 @@ var init_regression2 = __esm(() => {
|
|
|
29322
29366
|
regressionStage = {
|
|
29323
29367
|
name: "regression",
|
|
29324
29368
|
enabled(ctx) {
|
|
29325
|
-
const
|
|
29369
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29370
|
+
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29326
29371
|
if (mode !== "per-story")
|
|
29327
29372
|
return false;
|
|
29328
29373
|
if (ctx.verifyResult && !ctx.verifyResult.success)
|
|
29329
29374
|
return false;
|
|
29330
|
-
const gateEnabled =
|
|
29375
|
+
const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
|
|
29331
29376
|
return gateEnabled;
|
|
29332
29377
|
},
|
|
29333
29378
|
skipReason(ctx) {
|
|
29334
|
-
const
|
|
29379
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29380
|
+
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29335
29381
|
if (mode !== "per-story")
|
|
29336
29382
|
return `not needed (regression mode is '${mode}', not 'per-story')`;
|
|
29337
29383
|
return "disabled (regression gate not enabled in config)";
|
|
29338
29384
|
},
|
|
29339
29385
|
async execute(ctx) {
|
|
29340
29386
|
const logger = getLogger();
|
|
29341
|
-
const
|
|
29342
|
-
const
|
|
29387
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29388
|
+
const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
|
|
29389
|
+
const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
29343
29390
|
logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
|
|
29344
29391
|
const verifyCtx = {
|
|
29345
29392
|
workdir: ctx.workdir,
|
|
29346
29393
|
testCommand,
|
|
29347
29394
|
timeoutSeconds,
|
|
29348
29395
|
storyId: ctx.story.id,
|
|
29349
|
-
acceptOnTimeout:
|
|
29396
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
|
|
29350
29397
|
config: ctx.config
|
|
29351
29398
|
};
|
|
29352
29399
|
const result = await _regressionStageDeps.verifyRegression(verifyCtx);
|
|
@@ -29381,7 +29428,7 @@ var init_regression2 = __esm(() => {
|
|
|
29381
29428
|
});
|
|
29382
29429
|
|
|
29383
29430
|
// src/pipeline/stages/routing.ts
|
|
29384
|
-
import { join as
|
|
29431
|
+
import { join as join26 } from "path";
|
|
29385
29432
|
async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
29386
29433
|
const naxDecompose = config2.decompose;
|
|
29387
29434
|
const builderConfig = {
|
|
@@ -29455,7 +29502,7 @@ var init_routing2 = __esm(() => {
|
|
|
29455
29502
|
}
|
|
29456
29503
|
const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
|
|
29457
29504
|
if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
|
|
29458
|
-
const greenfieldScanDir = ctx.story.workdir ?
|
|
29505
|
+
const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29459
29506
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
|
|
29460
29507
|
if (isGreenfield) {
|
|
29461
29508
|
logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
|
|
@@ -29552,7 +29599,7 @@ var init_crash_detector = __esm(() => {
|
|
|
29552
29599
|
});
|
|
29553
29600
|
|
|
29554
29601
|
// src/pipeline/stages/verify.ts
|
|
29555
|
-
import { join as
|
|
29602
|
+
import { join as join27 } from "path";
|
|
29556
29603
|
function coerceSmartTestRunner(val) {
|
|
29557
29604
|
if (val === undefined || val === true)
|
|
29558
29605
|
return DEFAULT_SMART_RUNNER_CONFIG2;
|
|
@@ -29568,7 +29615,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
29568
29615
|
}
|
|
29569
29616
|
async function readPackageName(dir) {
|
|
29570
29617
|
try {
|
|
29571
|
-
const content = await Bun.file(
|
|
29618
|
+
const content = await Bun.file(join27(dir, "package.json")).json();
|
|
29572
29619
|
return typeof content.name === "string" ? content.name : null;
|
|
29573
29620
|
} catch {
|
|
29574
29621
|
return null;
|
|
@@ -29585,7 +29632,6 @@ async function resolvePackageTemplate(template, packageDir) {
|
|
|
29585
29632
|
}
|
|
29586
29633
|
var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
|
|
29587
29634
|
var init_verify = __esm(() => {
|
|
29588
|
-
init_loader2();
|
|
29589
29635
|
init_logger2();
|
|
29590
29636
|
init_crash_detector();
|
|
29591
29637
|
init_runners();
|
|
@@ -29602,7 +29648,7 @@ var init_verify = __esm(() => {
|
|
|
29602
29648
|
skipReason: () => "not needed (full-suite gate already passed)",
|
|
29603
29649
|
async execute(ctx) {
|
|
29604
29650
|
const logger = getLogger();
|
|
29605
|
-
const effectiveConfig = ctx.
|
|
29651
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29606
29652
|
if (!effectiveConfig.quality.requireTests) {
|
|
29607
29653
|
logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
|
|
29608
29654
|
return { action: "continue" };
|
|
@@ -29614,11 +29660,11 @@ var init_verify = __esm(() => {
|
|
|
29614
29660
|
return { action: "continue" };
|
|
29615
29661
|
}
|
|
29616
29662
|
logger.info("verify", "Running verification", { storyId: ctx.story.id });
|
|
29617
|
-
const effectiveWorkdir = ctx.story.workdir ?
|
|
29663
|
+
const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29618
29664
|
let effectiveCommand = testCommand;
|
|
29619
29665
|
let isFullSuite = true;
|
|
29620
|
-
const smartRunnerConfig = coerceSmartTestRunner(
|
|
29621
|
-
const regressionMode =
|
|
29666
|
+
const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
|
|
29667
|
+
const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29622
29668
|
let resolvedTestScopedTemplate = testScopedTemplate;
|
|
29623
29669
|
if (testScopedTemplate && ctx.story.workdir) {
|
|
29624
29670
|
const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
|
|
@@ -29677,8 +29723,8 @@ var init_verify = __esm(() => {
|
|
|
29677
29723
|
const result = await _verifyDeps.regression({
|
|
29678
29724
|
workdir: effectiveWorkdir,
|
|
29679
29725
|
command: effectiveCommand,
|
|
29680
|
-
timeoutSeconds:
|
|
29681
|
-
acceptOnTimeout:
|
|
29726
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
29727
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
|
|
29682
29728
|
});
|
|
29683
29729
|
ctx.verifyResult = {
|
|
29684
29730
|
success: result.success,
|
|
@@ -29695,7 +29741,7 @@ var init_verify = __esm(() => {
|
|
|
29695
29741
|
};
|
|
29696
29742
|
if (!result.success) {
|
|
29697
29743
|
if (result.status === "TIMEOUT") {
|
|
29698
|
-
const timeout =
|
|
29744
|
+
const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
|
|
29699
29745
|
logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
|
|
29700
29746
|
exitCode: result.status,
|
|
29701
29747
|
storyId: ctx.story.id,
|
|
@@ -29710,10 +29756,13 @@ var init_verify = __esm(() => {
|
|
|
29710
29756
|
if (result.status !== "TIMEOUT") {
|
|
29711
29757
|
logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
|
|
29712
29758
|
}
|
|
29713
|
-
|
|
29714
|
-
|
|
29715
|
-
|
|
29716
|
-
|
|
29759
|
+
if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
|
|
29760
|
+
return {
|
|
29761
|
+
action: "escalate",
|
|
29762
|
+
reason: result.status === "TIMEOUT" ? `Test suite TIMEOUT after ${effectiveConfig.execution.verificationTimeoutSeconds}s (not a code failure)` : `Tests failed with runtime crash (exit code ${result.status ?? "non-zero"})`
|
|
29763
|
+
};
|
|
29764
|
+
}
|
|
29765
|
+
return { action: "continue" };
|
|
29717
29766
|
}
|
|
29718
29767
|
logger.info("verify", "Tests passed", { storyId: ctx.story.id });
|
|
29719
29768
|
return { action: "continue" };
|
|
@@ -29721,7 +29770,6 @@ var init_verify = __esm(() => {
|
|
|
29721
29770
|
};
|
|
29722
29771
|
_verifyDeps = {
|
|
29723
29772
|
regression,
|
|
29724
|
-
loadConfigForWorkdir,
|
|
29725
29773
|
readPackageName
|
|
29726
29774
|
};
|
|
29727
29775
|
});
|
|
@@ -29809,7 +29857,7 @@ __export(exports_init_context, {
|
|
|
29809
29857
|
});
|
|
29810
29858
|
import { existsSync as existsSync20 } from "fs";
|
|
29811
29859
|
import { mkdir } from "fs/promises";
|
|
29812
|
-
import { basename as basename2, join as
|
|
29860
|
+
import { basename as basename2, join as join31 } from "path";
|
|
29813
29861
|
async function findFiles(dir, maxFiles = 200) {
|
|
29814
29862
|
try {
|
|
29815
29863
|
const proc = Bun.spawnSync([
|
|
@@ -29837,7 +29885,7 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
29837
29885
|
return [];
|
|
29838
29886
|
}
|
|
29839
29887
|
async function readPackageManifest(projectRoot) {
|
|
29840
|
-
const packageJsonPath =
|
|
29888
|
+
const packageJsonPath = join31(projectRoot, "package.json");
|
|
29841
29889
|
if (!existsSync20(packageJsonPath)) {
|
|
29842
29890
|
return null;
|
|
29843
29891
|
}
|
|
@@ -29855,7 +29903,7 @@ async function readPackageManifest(projectRoot) {
|
|
|
29855
29903
|
}
|
|
29856
29904
|
}
|
|
29857
29905
|
async function readReadmeSnippet(projectRoot) {
|
|
29858
|
-
const readmePath =
|
|
29906
|
+
const readmePath = join31(projectRoot, "README.md");
|
|
29859
29907
|
if (!existsSync20(readmePath)) {
|
|
29860
29908
|
return null;
|
|
29861
29909
|
}
|
|
@@ -29873,7 +29921,7 @@ async function detectEntryPoints(projectRoot) {
|
|
|
29873
29921
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
29874
29922
|
const found = [];
|
|
29875
29923
|
for (const candidate of candidates) {
|
|
29876
|
-
const path12 =
|
|
29924
|
+
const path12 = join31(projectRoot, candidate);
|
|
29877
29925
|
if (existsSync20(path12)) {
|
|
29878
29926
|
found.push(candidate);
|
|
29879
29927
|
}
|
|
@@ -29884,7 +29932,7 @@ async function detectConfigFiles(projectRoot) {
|
|
|
29884
29932
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
29885
29933
|
const found = [];
|
|
29886
29934
|
for (const candidate of candidates) {
|
|
29887
|
-
const path12 =
|
|
29935
|
+
const path12 = join31(projectRoot, candidate);
|
|
29888
29936
|
if (existsSync20(path12)) {
|
|
29889
29937
|
found.push(candidate);
|
|
29890
29938
|
}
|
|
@@ -30045,9 +30093,9 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
30045
30093
|
}
|
|
30046
30094
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
30047
30095
|
const logger = getLogger();
|
|
30048
|
-
const packageDir =
|
|
30049
|
-
const naxDir =
|
|
30050
|
-
const contextPath =
|
|
30096
|
+
const packageDir = join31(repoRoot, packagePath);
|
|
30097
|
+
const naxDir = join31(packageDir, "nax");
|
|
30098
|
+
const contextPath = join31(naxDir, "context.md");
|
|
30051
30099
|
if (existsSync20(contextPath) && !force) {
|
|
30052
30100
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
30053
30101
|
return;
|
|
@@ -30061,8 +30109,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
30061
30109
|
}
|
|
30062
30110
|
async function initContext(projectRoot, options = {}) {
|
|
30063
30111
|
const logger = getLogger();
|
|
30064
|
-
const naxDir =
|
|
30065
|
-
const contextPath =
|
|
30112
|
+
const naxDir = join31(projectRoot, "nax");
|
|
30113
|
+
const contextPath = join31(naxDir, "context.md");
|
|
30066
30114
|
if (existsSync20(contextPath) && !options.force) {
|
|
30067
30115
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
30068
30116
|
return;
|
|
@@ -31370,19 +31418,19 @@ var init_precheck = __esm(() => {
|
|
|
31370
31418
|
});
|
|
31371
31419
|
|
|
31372
31420
|
// src/hooks/runner.ts
|
|
31373
|
-
import { join as
|
|
31421
|
+
import { join as join44 } from "path";
|
|
31374
31422
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
31375
31423
|
let globalHooks = { hooks: {} };
|
|
31376
31424
|
let projectHooks = { hooks: {} };
|
|
31377
31425
|
let skipGlobal = false;
|
|
31378
|
-
const projectPath =
|
|
31426
|
+
const projectPath = join44(projectDir, "hooks.json");
|
|
31379
31427
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
31380
31428
|
if (projectData) {
|
|
31381
31429
|
projectHooks = projectData;
|
|
31382
31430
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
31383
31431
|
}
|
|
31384
31432
|
if (!skipGlobal && globalDir) {
|
|
31385
|
-
const globalPath =
|
|
31433
|
+
const globalPath = join44(globalDir, "hooks.json");
|
|
31386
31434
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
31387
31435
|
if (globalData) {
|
|
31388
31436
|
globalHooks = globalData;
|
|
@@ -31873,6 +31921,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
31873
31921
|
}), ctx.workdir);
|
|
31874
31922
|
const fixContext = {
|
|
31875
31923
|
config: ctx.config,
|
|
31924
|
+
effectiveConfig: ctx.config,
|
|
31876
31925
|
prd,
|
|
31877
31926
|
story,
|
|
31878
31927
|
stories: [story],
|
|
@@ -31906,6 +31955,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
31906
31955
|
const firstStory = prd.userStories[0];
|
|
31907
31956
|
const acceptanceContext = {
|
|
31908
31957
|
config: ctx.config,
|
|
31958
|
+
effectiveConfig: ctx.config,
|
|
31909
31959
|
prd,
|
|
31910
31960
|
story: firstStory,
|
|
31911
31961
|
stories: [firstStory],
|
|
@@ -32414,12 +32464,12 @@ __export(exports_manager, {
|
|
|
32414
32464
|
WorktreeManager: () => WorktreeManager
|
|
32415
32465
|
});
|
|
32416
32466
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
32417
|
-
import { join as
|
|
32467
|
+
import { join as join45 } from "path";
|
|
32418
32468
|
|
|
32419
32469
|
class WorktreeManager {
|
|
32420
32470
|
async create(projectRoot, storyId) {
|
|
32421
32471
|
validateStoryId(storyId);
|
|
32422
|
-
const worktreePath =
|
|
32472
|
+
const worktreePath = join45(projectRoot, ".nax-wt", storyId);
|
|
32423
32473
|
const branchName = `nax/${storyId}`;
|
|
32424
32474
|
try {
|
|
32425
32475
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -32444,9 +32494,9 @@ class WorktreeManager {
|
|
|
32444
32494
|
}
|
|
32445
32495
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
32446
32496
|
}
|
|
32447
|
-
const nodeModulesSource =
|
|
32497
|
+
const nodeModulesSource = join45(projectRoot, "node_modules");
|
|
32448
32498
|
if (existsSync32(nodeModulesSource)) {
|
|
32449
|
-
const nodeModulesTarget =
|
|
32499
|
+
const nodeModulesTarget = join45(worktreePath, "node_modules");
|
|
32450
32500
|
try {
|
|
32451
32501
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
32452
32502
|
} catch (error48) {
|
|
@@ -32454,9 +32504,9 @@ class WorktreeManager {
|
|
|
32454
32504
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
32455
32505
|
}
|
|
32456
32506
|
}
|
|
32457
|
-
const envSource =
|
|
32507
|
+
const envSource = join45(projectRoot, ".env");
|
|
32458
32508
|
if (existsSync32(envSource)) {
|
|
32459
|
-
const envTarget =
|
|
32509
|
+
const envTarget = join45(worktreePath, ".env");
|
|
32460
32510
|
try {
|
|
32461
32511
|
symlinkSync(envSource, envTarget, "file");
|
|
32462
32512
|
} catch (error48) {
|
|
@@ -32467,7 +32517,7 @@ class WorktreeManager {
|
|
|
32467
32517
|
}
|
|
32468
32518
|
async remove(projectRoot, storyId) {
|
|
32469
32519
|
validateStoryId(storyId);
|
|
32470
|
-
const worktreePath =
|
|
32520
|
+
const worktreePath = join45(projectRoot, ".nax-wt", storyId);
|
|
32471
32521
|
const branchName = `nax/${storyId}`;
|
|
32472
32522
|
try {
|
|
32473
32523
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -32777,6 +32827,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
32777
32827
|
try {
|
|
32778
32828
|
const pipelineContext = {
|
|
32779
32829
|
...context,
|
|
32830
|
+
effectiveConfig: context.effectiveConfig ?? context.config,
|
|
32780
32831
|
story,
|
|
32781
32832
|
stories: [story],
|
|
32782
32833
|
workdir: worktreePath,
|
|
@@ -32857,7 +32908,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
32857
32908
|
|
|
32858
32909
|
// src/execution/parallel-coordinator.ts
|
|
32859
32910
|
import os3 from "os";
|
|
32860
|
-
import { join as
|
|
32911
|
+
import { join as join46 } from "path";
|
|
32861
32912
|
function groupStoriesByDependencies(stories) {
|
|
32862
32913
|
const batches = [];
|
|
32863
32914
|
const processed = new Set;
|
|
@@ -32926,6 +32977,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32926
32977
|
});
|
|
32927
32978
|
const baseContext = {
|
|
32928
32979
|
config: config2,
|
|
32980
|
+
effectiveConfig: config2,
|
|
32929
32981
|
prd: currentPrd,
|
|
32930
32982
|
featureDir,
|
|
32931
32983
|
hooks,
|
|
@@ -32935,7 +32987,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32935
32987
|
};
|
|
32936
32988
|
const worktreePaths = new Map;
|
|
32937
32989
|
for (const story of batch) {
|
|
32938
|
-
const worktreePath =
|
|
32990
|
+
const worktreePath = join46(projectRoot, ".nax-wt", story.id);
|
|
32939
32991
|
try {
|
|
32940
32992
|
await worktreeManager.create(projectRoot, story.id);
|
|
32941
32993
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -32984,7 +33036,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32984
33036
|
});
|
|
32985
33037
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
32986
33038
|
storyId: mergeResult.storyId,
|
|
32987
|
-
worktreePath:
|
|
33039
|
+
worktreePath: join46(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
32988
33040
|
});
|
|
32989
33041
|
}
|
|
32990
33042
|
}
|
|
@@ -33063,6 +33115,7 @@ async function rectifyConflictedStory(options) {
|
|
|
33063
33115
|
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
33064
33116
|
const pipelineContext = {
|
|
33065
33117
|
config: config2,
|
|
33118
|
+
effectiveConfig: config2,
|
|
33066
33119
|
prd,
|
|
33067
33120
|
story,
|
|
33068
33121
|
stories: [story],
|
|
@@ -33443,12 +33496,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
33443
33496
|
// src/pipeline/subscribers/events-writer.ts
|
|
33444
33497
|
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
33445
33498
|
import { homedir as homedir7 } from "os";
|
|
33446
|
-
import { basename as basename5, join as
|
|
33499
|
+
import { basename as basename5, join as join47 } from "path";
|
|
33447
33500
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
33448
33501
|
const logger = getSafeLogger();
|
|
33449
33502
|
const project = basename5(workdir);
|
|
33450
|
-
const eventsDir =
|
|
33451
|
-
const eventsFile =
|
|
33503
|
+
const eventsDir = join47(homedir7(), ".nax", "events", project);
|
|
33504
|
+
const eventsFile = join47(eventsDir, "events.jsonl");
|
|
33452
33505
|
let dirReady = false;
|
|
33453
33506
|
const write = (line) => {
|
|
33454
33507
|
(async () => {
|
|
@@ -33608,12 +33661,12 @@ var init_interaction2 = __esm(() => {
|
|
|
33608
33661
|
// src/pipeline/subscribers/registry.ts
|
|
33609
33662
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
33610
33663
|
import { homedir as homedir8 } from "os";
|
|
33611
|
-
import { basename as basename6, join as
|
|
33664
|
+
import { basename as basename6, join as join48 } from "path";
|
|
33612
33665
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
33613
33666
|
const logger = getSafeLogger();
|
|
33614
33667
|
const project = basename6(workdir);
|
|
33615
|
-
const runDir =
|
|
33616
|
-
const metaFile =
|
|
33668
|
+
const runDir = join48(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
33669
|
+
const metaFile = join48(runDir, "meta.json");
|
|
33617
33670
|
const unsub = bus.on("run:started", (_ev) => {
|
|
33618
33671
|
(async () => {
|
|
33619
33672
|
try {
|
|
@@ -33623,8 +33676,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
33623
33676
|
project,
|
|
33624
33677
|
feature,
|
|
33625
33678
|
workdir,
|
|
33626
|
-
statusPath:
|
|
33627
|
-
eventsDir:
|
|
33679
|
+
statusPath: join48(workdir, "nax", "features", feature, "status.json"),
|
|
33680
|
+
eventsDir: join48(workdir, "nax", "features", feature, "runs"),
|
|
33628
33681
|
registeredAt: new Date().toISOString()
|
|
33629
33682
|
};
|
|
33630
33683
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -34253,6 +34306,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
34253
34306
|
});
|
|
34254
34307
|
|
|
34255
34308
|
// src/execution/iteration-runner.ts
|
|
34309
|
+
import { join as join49 } from "path";
|
|
34256
34310
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34257
34311
|
const logger = getSafeLogger();
|
|
34258
34312
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -34278,8 +34332,10 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34278
34332
|
const storyStartTime = Date.now();
|
|
34279
34333
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
34280
34334
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
34335
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, "nax", "config.json"), story.workdir) : ctx.config;
|
|
34281
34336
|
const pipelineContext = {
|
|
34282
34337
|
config: ctx.config,
|
|
34338
|
+
effectiveConfig,
|
|
34283
34339
|
prd,
|
|
34284
34340
|
story,
|
|
34285
34341
|
stories: storiesToExecute,
|
|
@@ -34350,13 +34406,18 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34350
34406
|
reason: pipelineResult.reason
|
|
34351
34407
|
};
|
|
34352
34408
|
}
|
|
34409
|
+
var _iterationRunnerDeps;
|
|
34353
34410
|
var init_iteration_runner = __esm(() => {
|
|
34411
|
+
init_loader2();
|
|
34354
34412
|
init_logger2();
|
|
34355
34413
|
init_runner();
|
|
34356
34414
|
init_stages();
|
|
34357
34415
|
init_git();
|
|
34358
34416
|
init_dry_run();
|
|
34359
34417
|
init_pipeline_result_handler();
|
|
34418
|
+
_iterationRunnerDeps = {
|
|
34419
|
+
loadConfigForWorkdir
|
|
34420
|
+
};
|
|
34360
34421
|
});
|
|
34361
34422
|
|
|
34362
34423
|
// src/execution/executor-types.ts
|
|
@@ -34449,6 +34510,7 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
34449
34510
|
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
34450
34511
|
const preRunCtx = {
|
|
34451
34512
|
config: ctx.config,
|
|
34513
|
+
effectiveConfig: ctx.config,
|
|
34452
34514
|
prd,
|
|
34453
34515
|
workdir: ctx.workdir,
|
|
34454
34516
|
featureDir: ctx.featureDir,
|
|
@@ -34620,7 +34682,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
34620
34682
|
var init_status_file = () => {};
|
|
34621
34683
|
|
|
34622
34684
|
// src/execution/status-writer.ts
|
|
34623
|
-
import { join as
|
|
34685
|
+
import { join as join50 } from "path";
|
|
34624
34686
|
|
|
34625
34687
|
class StatusWriter {
|
|
34626
34688
|
statusFile;
|
|
@@ -34688,7 +34750,7 @@ class StatusWriter {
|
|
|
34688
34750
|
if (!this._prd)
|
|
34689
34751
|
return;
|
|
34690
34752
|
const safeLogger = getSafeLogger();
|
|
34691
|
-
const featureStatusPath =
|
|
34753
|
+
const featureStatusPath = join50(featureDir, "status.json");
|
|
34692
34754
|
try {
|
|
34693
34755
|
const base = this.getSnapshot(totalCost, iterations);
|
|
34694
34756
|
if (!base) {
|
|
@@ -66017,7 +66079,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
66017
66079
|
init_source();
|
|
66018
66080
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
66019
66081
|
import { homedir as homedir10 } from "os";
|
|
66020
|
-
import { join as
|
|
66082
|
+
import { join as join51 } from "path";
|
|
66021
66083
|
|
|
66022
66084
|
// node_modules/commander/esm.mjs
|
|
66023
66085
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -68086,7 +68148,7 @@ async function runsShowCommand(options) {
|
|
|
68086
68148
|
// src/cli/prompts-main.ts
|
|
68087
68149
|
init_logger2();
|
|
68088
68150
|
import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
|
|
68089
|
-
import { join as
|
|
68151
|
+
import { join as join29 } from "path";
|
|
68090
68152
|
|
|
68091
68153
|
// src/pipeline/index.ts
|
|
68092
68154
|
init_runner();
|
|
@@ -68122,7 +68184,7 @@ init_prd();
|
|
|
68122
68184
|
|
|
68123
68185
|
// src/cli/prompts-tdd.ts
|
|
68124
68186
|
init_prompts2();
|
|
68125
|
-
import { join as
|
|
68187
|
+
import { join as join28 } from "path";
|
|
68126
68188
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
68127
68189
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
68128
68190
|
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(),
|
|
@@ -68141,7 +68203,7 @@ ${frontmatter}---
|
|
|
68141
68203
|
|
|
68142
68204
|
${session.prompt}`;
|
|
68143
68205
|
if (outputDir) {
|
|
68144
|
-
const promptFile =
|
|
68206
|
+
const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
|
|
68145
68207
|
await Bun.write(promptFile, fullOutput);
|
|
68146
68208
|
logger.info("cli", "Written TDD prompt file", {
|
|
68147
68209
|
storyId: story.id,
|
|
@@ -68157,7 +68219,7 @@ ${"=".repeat(80)}`);
|
|
|
68157
68219
|
}
|
|
68158
68220
|
}
|
|
68159
68221
|
if (outputDir && ctx.contextMarkdown) {
|
|
68160
|
-
const contextFile =
|
|
68222
|
+
const contextFile = join28(outputDir, `${story.id}.context.md`);
|
|
68161
68223
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
68162
68224
|
const contextOutput = `---
|
|
68163
68225
|
${frontmatter}---
|
|
@@ -68171,12 +68233,12 @@ ${ctx.contextMarkdown}`;
|
|
|
68171
68233
|
async function promptsCommand(options) {
|
|
68172
68234
|
const logger = getLogger();
|
|
68173
68235
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
68174
|
-
const naxDir =
|
|
68236
|
+
const naxDir = join29(workdir, "nax");
|
|
68175
68237
|
if (!existsSync18(naxDir)) {
|
|
68176
68238
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
68177
68239
|
}
|
|
68178
|
-
const featureDir =
|
|
68179
|
-
const prdPath =
|
|
68240
|
+
const featureDir = join29(naxDir, "features", feature);
|
|
68241
|
+
const prdPath = join29(featureDir, "prd.json");
|
|
68180
68242
|
if (!existsSync18(prdPath)) {
|
|
68181
68243
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
68182
68244
|
}
|
|
@@ -68198,6 +68260,7 @@ async function promptsCommand(options) {
|
|
|
68198
68260
|
for (const story of stories) {
|
|
68199
68261
|
const ctx = {
|
|
68200
68262
|
config: config2,
|
|
68263
|
+
effectiveConfig: config2,
|
|
68201
68264
|
prd,
|
|
68202
68265
|
story,
|
|
68203
68266
|
stories: [story],
|
|
@@ -68236,10 +68299,10 @@ ${frontmatter}---
|
|
|
68236
68299
|
|
|
68237
68300
|
${ctx.prompt}`;
|
|
68238
68301
|
if (outputDir) {
|
|
68239
|
-
const promptFile =
|
|
68302
|
+
const promptFile = join29(outputDir, `${story.id}.prompt.md`);
|
|
68240
68303
|
await Bun.write(promptFile, fullOutput);
|
|
68241
68304
|
if (ctx.contextMarkdown) {
|
|
68242
|
-
const contextFile =
|
|
68305
|
+
const contextFile = join29(outputDir, `${story.id}.context.md`);
|
|
68243
68306
|
const contextOutput = `---
|
|
68244
68307
|
${frontmatter}---
|
|
68245
68308
|
|
|
@@ -68303,7 +68366,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
68303
68366
|
}
|
|
68304
68367
|
// src/cli/prompts-init.ts
|
|
68305
68368
|
import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
|
|
68306
|
-
import { join as
|
|
68369
|
+
import { join as join30 } from "path";
|
|
68307
68370
|
var TEMPLATE_ROLES = [
|
|
68308
68371
|
{ file: "test-writer.md", role: "test-writer" },
|
|
68309
68372
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -68327,9 +68390,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
68327
68390
|
`;
|
|
68328
68391
|
async function promptsInitCommand(options) {
|
|
68329
68392
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
68330
|
-
const templatesDir =
|
|
68393
|
+
const templatesDir = join30(workdir, "nax", "templates");
|
|
68331
68394
|
mkdirSync4(templatesDir, { recursive: true });
|
|
68332
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(
|
|
68395
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join30(templatesDir, f)));
|
|
68333
68396
|
if (existingFiles.length > 0 && !force) {
|
|
68334
68397
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
68335
68398
|
Pass --force to overwrite existing templates.`);
|
|
@@ -68337,7 +68400,7 @@ async function promptsInitCommand(options) {
|
|
|
68337
68400
|
}
|
|
68338
68401
|
const written = [];
|
|
68339
68402
|
for (const template of TEMPLATE_ROLES) {
|
|
68340
|
-
const filePath =
|
|
68403
|
+
const filePath = join30(templatesDir, template.file);
|
|
68341
68404
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
68342
68405
|
const content = TEMPLATE_HEADER + roleBody;
|
|
68343
68406
|
await Bun.write(filePath, content);
|
|
@@ -68353,7 +68416,7 @@ async function promptsInitCommand(options) {
|
|
|
68353
68416
|
return written;
|
|
68354
68417
|
}
|
|
68355
68418
|
async function autoWirePromptsConfig(workdir) {
|
|
68356
|
-
const configPath =
|
|
68419
|
+
const configPath = join30(workdir, "nax.config.json");
|
|
68357
68420
|
if (!existsSync19(configPath)) {
|
|
68358
68421
|
const exampleConfig = JSON.stringify({
|
|
68359
68422
|
prompts: {
|
|
@@ -68518,7 +68581,7 @@ init_config();
|
|
|
68518
68581
|
init_logger2();
|
|
68519
68582
|
init_prd();
|
|
68520
68583
|
import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
|
|
68521
|
-
import { join as
|
|
68584
|
+
import { join as join34 } from "path";
|
|
68522
68585
|
|
|
68523
68586
|
// src/cli/diagnose-analysis.ts
|
|
68524
68587
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -68717,7 +68780,7 @@ function isProcessAlive2(pid) {
|
|
|
68717
68780
|
}
|
|
68718
68781
|
}
|
|
68719
68782
|
async function loadStatusFile2(workdir) {
|
|
68720
|
-
const statusPath =
|
|
68783
|
+
const statusPath = join34(workdir, "nax", "status.json");
|
|
68721
68784
|
if (!existsSync21(statusPath))
|
|
68722
68785
|
return null;
|
|
68723
68786
|
try {
|
|
@@ -68745,7 +68808,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
68745
68808
|
}
|
|
68746
68809
|
}
|
|
68747
68810
|
async function checkLock(workdir) {
|
|
68748
|
-
const lockFile = Bun.file(
|
|
68811
|
+
const lockFile = Bun.file(join34(workdir, "nax.lock"));
|
|
68749
68812
|
if (!await lockFile.exists())
|
|
68750
68813
|
return { lockPresent: false };
|
|
68751
68814
|
try {
|
|
@@ -68763,8 +68826,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
68763
68826
|
const logger = getLogger();
|
|
68764
68827
|
const workdir = options.workdir ?? process.cwd();
|
|
68765
68828
|
const naxSubdir = findProjectDir(workdir);
|
|
68766
|
-
let projectDir = naxSubdir ?
|
|
68767
|
-
if (!projectDir && existsSync21(
|
|
68829
|
+
let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
|
|
68830
|
+
if (!projectDir && existsSync21(join34(workdir, "nax"))) {
|
|
68768
68831
|
projectDir = workdir;
|
|
68769
68832
|
}
|
|
68770
68833
|
if (!projectDir)
|
|
@@ -68775,7 +68838,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
68775
68838
|
if (status2) {
|
|
68776
68839
|
feature = status2.run.feature;
|
|
68777
68840
|
} else {
|
|
68778
|
-
const featuresDir =
|
|
68841
|
+
const featuresDir = join34(projectDir, "nax", "features");
|
|
68779
68842
|
if (!existsSync21(featuresDir))
|
|
68780
68843
|
throw new Error("No features found in project");
|
|
68781
68844
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -68785,8 +68848,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
68785
68848
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
68786
68849
|
}
|
|
68787
68850
|
}
|
|
68788
|
-
const featureDir =
|
|
68789
|
-
const prdPath =
|
|
68851
|
+
const featureDir = join34(projectDir, "nax", "features", feature);
|
|
68852
|
+
const prdPath = join34(featureDir, "prd.json");
|
|
68790
68853
|
if (!existsSync21(prdPath))
|
|
68791
68854
|
throw new Error(`Feature not found: ${feature}`);
|
|
68792
68855
|
const prd = await loadPRD(prdPath);
|
|
@@ -68829,7 +68892,7 @@ init_interaction();
|
|
|
68829
68892
|
init_source();
|
|
68830
68893
|
init_loader2();
|
|
68831
68894
|
import { existsSync as existsSync22 } from "fs";
|
|
68832
|
-
import { join as
|
|
68895
|
+
import { join as join35 } from "path";
|
|
68833
68896
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68834
68897
|
async function generateCommand(options) {
|
|
68835
68898
|
const workdir = process.cwd();
|
|
@@ -68872,7 +68935,7 @@ async function generateCommand(options) {
|
|
|
68872
68935
|
return;
|
|
68873
68936
|
}
|
|
68874
68937
|
if (options.package) {
|
|
68875
|
-
const packageDir =
|
|
68938
|
+
const packageDir = join35(workdir, options.package);
|
|
68876
68939
|
if (dryRun) {
|
|
68877
68940
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
68878
68941
|
}
|
|
@@ -68892,8 +68955,8 @@ async function generateCommand(options) {
|
|
|
68892
68955
|
process.exit(1);
|
|
68893
68956
|
return;
|
|
68894
68957
|
}
|
|
68895
|
-
const contextPath = options.context ?
|
|
68896
|
-
const outputDir = options.output ?
|
|
68958
|
+
const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, "nax/context.md");
|
|
68959
|
+
const outputDir = options.output ? join35(workdir, options.output) : workdir;
|
|
68897
68960
|
const autoInject = !options.noAutoInject;
|
|
68898
68961
|
if (!existsSync22(contextPath)) {
|
|
68899
68962
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
@@ -68998,7 +69061,7 @@ async function generateCommand(options) {
|
|
|
68998
69061
|
// src/cli/config-display.ts
|
|
68999
69062
|
init_loader2();
|
|
69000
69063
|
import { existsSync as existsSync24 } from "fs";
|
|
69001
|
-
import { join as
|
|
69064
|
+
import { join as join37 } from "path";
|
|
69002
69065
|
|
|
69003
69066
|
// src/cli/config-descriptions.ts
|
|
69004
69067
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -69207,7 +69270,7 @@ function deepEqual(a, b) {
|
|
|
69207
69270
|
init_defaults();
|
|
69208
69271
|
init_loader2();
|
|
69209
69272
|
import { existsSync as existsSync23 } from "fs";
|
|
69210
|
-
import { join as
|
|
69273
|
+
import { join as join36 } from "path";
|
|
69211
69274
|
async function loadConfigFile(path14) {
|
|
69212
69275
|
if (!existsSync23(path14))
|
|
69213
69276
|
return null;
|
|
@@ -69229,7 +69292,7 @@ async function loadProjectConfig() {
|
|
|
69229
69292
|
const projectDir = findProjectDir();
|
|
69230
69293
|
if (!projectDir)
|
|
69231
69294
|
return null;
|
|
69232
|
-
const projectPath =
|
|
69295
|
+
const projectPath = join36(projectDir, "config.json");
|
|
69233
69296
|
return await loadConfigFile(projectPath);
|
|
69234
69297
|
}
|
|
69235
69298
|
|
|
@@ -69289,7 +69352,7 @@ async function configCommand(config2, options = {}) {
|
|
|
69289
69352
|
function determineConfigSources() {
|
|
69290
69353
|
const globalPath = globalConfigPath();
|
|
69291
69354
|
const projectDir = findProjectDir();
|
|
69292
|
-
const projectPath = projectDir ?
|
|
69355
|
+
const projectPath = projectDir ? join37(projectDir, "config.json") : null;
|
|
69293
69356
|
return {
|
|
69294
69357
|
global: fileExists(globalPath) ? globalPath : null,
|
|
69295
69358
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -69469,21 +69532,21 @@ async function diagnose(options) {
|
|
|
69469
69532
|
|
|
69470
69533
|
// src/commands/logs.ts
|
|
69471
69534
|
import { existsSync as existsSync26 } from "fs";
|
|
69472
|
-
import { join as
|
|
69535
|
+
import { join as join40 } from "path";
|
|
69473
69536
|
|
|
69474
69537
|
// src/commands/logs-formatter.ts
|
|
69475
69538
|
init_source();
|
|
69476
69539
|
init_formatter();
|
|
69477
69540
|
import { readdirSync as readdirSync7 } from "fs";
|
|
69478
|
-
import { join as
|
|
69541
|
+
import { join as join39 } from "path";
|
|
69479
69542
|
|
|
69480
69543
|
// src/commands/logs-reader.ts
|
|
69481
69544
|
import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
|
|
69482
69545
|
import { readdir as readdir3 } from "fs/promises";
|
|
69483
69546
|
import { homedir as homedir5 } from "os";
|
|
69484
|
-
import { join as
|
|
69547
|
+
import { join as join38 } from "path";
|
|
69485
69548
|
var _deps7 = {
|
|
69486
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
69549
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
|
|
69487
69550
|
};
|
|
69488
69551
|
async function resolveRunFileFromRegistry(runId) {
|
|
69489
69552
|
const runsDir = _deps7.getRunsDir();
|
|
@@ -69495,7 +69558,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
69495
69558
|
}
|
|
69496
69559
|
let matched = null;
|
|
69497
69560
|
for (const entry of entries) {
|
|
69498
|
-
const metaPath =
|
|
69561
|
+
const metaPath = join38(runsDir, entry, "meta.json");
|
|
69499
69562
|
try {
|
|
69500
69563
|
const meta3 = await Bun.file(metaPath).json();
|
|
69501
69564
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -69517,14 +69580,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
69517
69580
|
return null;
|
|
69518
69581
|
}
|
|
69519
69582
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
69520
|
-
return
|
|
69583
|
+
return join38(matched.eventsDir, specificFile ?? files[0]);
|
|
69521
69584
|
}
|
|
69522
69585
|
async function selectRunFile(runsDir) {
|
|
69523
69586
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
69524
69587
|
if (files.length === 0) {
|
|
69525
69588
|
return null;
|
|
69526
69589
|
}
|
|
69527
|
-
return
|
|
69590
|
+
return join38(runsDir, files[0]);
|
|
69528
69591
|
}
|
|
69529
69592
|
async function extractRunSummary(filePath) {
|
|
69530
69593
|
const file2 = Bun.file(filePath);
|
|
@@ -69609,7 +69672,7 @@ Runs:
|
|
|
69609
69672
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
69610
69673
|
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"));
|
|
69611
69674
|
for (const file2 of files) {
|
|
69612
|
-
const filePath =
|
|
69675
|
+
const filePath = join39(runsDir, file2);
|
|
69613
69676
|
const summary = await extractRunSummary(filePath);
|
|
69614
69677
|
const timestamp = file2.replace(".jsonl", "");
|
|
69615
69678
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -69734,7 +69797,7 @@ async function logsCommand(options) {
|
|
|
69734
69797
|
return;
|
|
69735
69798
|
}
|
|
69736
69799
|
const resolved = resolveProject({ dir: options.dir });
|
|
69737
|
-
const naxDir =
|
|
69800
|
+
const naxDir = join40(resolved.projectDir, "nax");
|
|
69738
69801
|
const configPath = resolved.configPath;
|
|
69739
69802
|
const configFile = Bun.file(configPath);
|
|
69740
69803
|
const config2 = await configFile.json();
|
|
@@ -69742,8 +69805,8 @@ async function logsCommand(options) {
|
|
|
69742
69805
|
if (!featureName) {
|
|
69743
69806
|
throw new Error("No feature specified in config.json");
|
|
69744
69807
|
}
|
|
69745
|
-
const featureDir =
|
|
69746
|
-
const runsDir =
|
|
69808
|
+
const featureDir = join40(naxDir, "features", featureName);
|
|
69809
|
+
const runsDir = join40(featureDir, "runs");
|
|
69747
69810
|
if (!existsSync26(runsDir)) {
|
|
69748
69811
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
69749
69812
|
}
|
|
@@ -69768,7 +69831,7 @@ init_config();
|
|
|
69768
69831
|
init_prd();
|
|
69769
69832
|
init_precheck();
|
|
69770
69833
|
import { existsSync as existsSync31 } from "fs";
|
|
69771
|
-
import { join as
|
|
69834
|
+
import { join as join41 } from "path";
|
|
69772
69835
|
async function precheckCommand(options) {
|
|
69773
69836
|
const resolved = resolveProject({
|
|
69774
69837
|
dir: options.dir,
|
|
@@ -69784,9 +69847,9 @@ async function precheckCommand(options) {
|
|
|
69784
69847
|
process.exit(1);
|
|
69785
69848
|
}
|
|
69786
69849
|
}
|
|
69787
|
-
const naxDir =
|
|
69788
|
-
const featureDir =
|
|
69789
|
-
const prdPath =
|
|
69850
|
+
const naxDir = join41(resolved.projectDir, "nax");
|
|
69851
|
+
const featureDir = join41(naxDir, "features", featureName);
|
|
69852
|
+
const prdPath = join41(featureDir, "prd.json");
|
|
69790
69853
|
if (!existsSync31(featureDir)) {
|
|
69791
69854
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
69792
69855
|
process.exit(1);
|
|
@@ -69810,10 +69873,10 @@ async function precheckCommand(options) {
|
|
|
69810
69873
|
init_source();
|
|
69811
69874
|
import { readdir as readdir4 } from "fs/promises";
|
|
69812
69875
|
import { homedir as homedir6 } from "os";
|
|
69813
|
-
import { join as
|
|
69876
|
+
import { join as join42 } from "path";
|
|
69814
69877
|
var DEFAULT_LIMIT = 20;
|
|
69815
69878
|
var _deps9 = {
|
|
69816
|
-
getRunsDir: () =>
|
|
69879
|
+
getRunsDir: () => join42(homedir6(), ".nax", "runs")
|
|
69817
69880
|
};
|
|
69818
69881
|
function formatDuration3(ms) {
|
|
69819
69882
|
if (ms <= 0)
|
|
@@ -69865,7 +69928,7 @@ async function runsCommand(options = {}) {
|
|
|
69865
69928
|
}
|
|
69866
69929
|
const rows = [];
|
|
69867
69930
|
for (const entry of entries) {
|
|
69868
|
-
const metaPath =
|
|
69931
|
+
const metaPath = join42(runsDir, entry, "meta.json");
|
|
69869
69932
|
let meta3;
|
|
69870
69933
|
try {
|
|
69871
69934
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -69942,7 +70005,7 @@ async function runsCommand(options = {}) {
|
|
|
69942
70005
|
|
|
69943
70006
|
// src/commands/unlock.ts
|
|
69944
70007
|
init_source();
|
|
69945
|
-
import { join as
|
|
70008
|
+
import { join as join43 } from "path";
|
|
69946
70009
|
function isProcessAlive3(pid) {
|
|
69947
70010
|
try {
|
|
69948
70011
|
process.kill(pid, 0);
|
|
@@ -69957,7 +70020,7 @@ function formatLockAge(ageMs) {
|
|
|
69957
70020
|
}
|
|
69958
70021
|
async function unlockCommand(options) {
|
|
69959
70022
|
const workdir = options.dir ?? process.cwd();
|
|
69960
|
-
const lockPath =
|
|
70023
|
+
const lockPath = join43(workdir, "nax.lock");
|
|
69961
70024
|
const lockFile = Bun.file(lockPath);
|
|
69962
70025
|
const exists = await lockFile.exists();
|
|
69963
70026
|
if (!exists) {
|
|
@@ -77792,15 +77855,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
77792
77855
|
}
|
|
77793
77856
|
return;
|
|
77794
77857
|
}
|
|
77795
|
-
const naxDir =
|
|
77858
|
+
const naxDir = join51(workdir, "nax");
|
|
77796
77859
|
if (existsSync34(naxDir) && !options.force) {
|
|
77797
77860
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
77798
77861
|
return;
|
|
77799
77862
|
}
|
|
77800
|
-
mkdirSync6(
|
|
77801
|
-
mkdirSync6(
|
|
77802
|
-
await Bun.write(
|
|
77803
|
-
await Bun.write(
|
|
77863
|
+
mkdirSync6(join51(naxDir, "features"), { recursive: true });
|
|
77864
|
+
mkdirSync6(join51(naxDir, "hooks"), { recursive: true });
|
|
77865
|
+
await Bun.write(join51(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
77866
|
+
await Bun.write(join51(naxDir, "hooks.json"), JSON.stringify({
|
|
77804
77867
|
hooks: {
|
|
77805
77868
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
77806
77869
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -77808,12 +77871,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
77808
77871
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
77809
77872
|
}
|
|
77810
77873
|
}, null, 2));
|
|
77811
|
-
await Bun.write(
|
|
77874
|
+
await Bun.write(join51(naxDir, ".gitignore"), `# nax temp files
|
|
77812
77875
|
*.tmp
|
|
77813
77876
|
.paused.json
|
|
77814
77877
|
.nax-verifier-verdict.json
|
|
77815
77878
|
`);
|
|
77816
|
-
await Bun.write(
|
|
77879
|
+
await Bun.write(join51(naxDir, "context.md"), `# Project Context
|
|
77817
77880
|
|
|
77818
77881
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
77819
77882
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -77939,8 +78002,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77939
78002
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77940
78003
|
process.exit(1);
|
|
77941
78004
|
}
|
|
77942
|
-
const featureDir =
|
|
77943
|
-
const prdPath =
|
|
78005
|
+
const featureDir = join51(naxDir, "features", options.feature);
|
|
78006
|
+
const prdPath = join51(featureDir, "prd.json");
|
|
77944
78007
|
if (options.plan && options.from) {
|
|
77945
78008
|
if (existsSync34(prdPath) && !options.force) {
|
|
77946
78009
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -77962,10 +78025,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77962
78025
|
}
|
|
77963
78026
|
}
|
|
77964
78027
|
try {
|
|
77965
|
-
const planLogDir =
|
|
78028
|
+
const planLogDir = join51(featureDir, "plan");
|
|
77966
78029
|
mkdirSync6(planLogDir, { recursive: true });
|
|
77967
78030
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77968
|
-
const planLogPath =
|
|
78031
|
+
const planLogPath = join51(planLogDir, `${planLogId}.jsonl`);
|
|
77969
78032
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77970
78033
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77971
78034
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -78003,10 +78066,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78003
78066
|
process.exit(1);
|
|
78004
78067
|
}
|
|
78005
78068
|
resetLogger();
|
|
78006
|
-
const runsDir =
|
|
78069
|
+
const runsDir = join51(featureDir, "runs");
|
|
78007
78070
|
mkdirSync6(runsDir, { recursive: true });
|
|
78008
78071
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78009
|
-
const logFilePath =
|
|
78072
|
+
const logFilePath = join51(runsDir, `${runId}.jsonl`);
|
|
78010
78073
|
const isTTY = process.stdout.isTTY ?? false;
|
|
78011
78074
|
const headlessFlag = options.headless ?? false;
|
|
78012
78075
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -78022,7 +78085,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78022
78085
|
config2.autoMode.defaultAgent = options.agent;
|
|
78023
78086
|
}
|
|
78024
78087
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
78025
|
-
const globalNaxDir =
|
|
78088
|
+
const globalNaxDir = join51(homedir10(), ".nax");
|
|
78026
78089
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
78027
78090
|
const eventEmitter = new PipelineEventEmitter;
|
|
78028
78091
|
let tuiInstance;
|
|
@@ -78045,7 +78108,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78045
78108
|
} else {
|
|
78046
78109
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
78047
78110
|
}
|
|
78048
|
-
const statusFilePath =
|
|
78111
|
+
const statusFilePath = join51(workdir, "nax", "status.json");
|
|
78049
78112
|
let parallel;
|
|
78050
78113
|
if (options.parallel !== undefined) {
|
|
78051
78114
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -78071,7 +78134,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78071
78134
|
headless: useHeadless,
|
|
78072
78135
|
skipPrecheck: options.skipPrecheck ?? false
|
|
78073
78136
|
});
|
|
78074
|
-
const latestSymlink =
|
|
78137
|
+
const latestSymlink = join51(runsDir, "latest.jsonl");
|
|
78075
78138
|
try {
|
|
78076
78139
|
if (existsSync34(latestSymlink)) {
|
|
78077
78140
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -78109,9 +78172,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78109
78172
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78110
78173
|
process.exit(1);
|
|
78111
78174
|
}
|
|
78112
|
-
const featureDir =
|
|
78175
|
+
const featureDir = join51(naxDir, "features", name);
|
|
78113
78176
|
mkdirSync6(featureDir, { recursive: true });
|
|
78114
|
-
await Bun.write(
|
|
78177
|
+
await Bun.write(join51(featureDir, "spec.md"), `# Feature: ${name}
|
|
78115
78178
|
|
|
78116
78179
|
## Overview
|
|
78117
78180
|
|
|
@@ -78119,7 +78182,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78119
78182
|
|
|
78120
78183
|
## Acceptance Criteria
|
|
78121
78184
|
`);
|
|
78122
|
-
await Bun.write(
|
|
78185
|
+
await Bun.write(join51(featureDir, "plan.md"), `# Plan: ${name}
|
|
78123
78186
|
|
|
78124
78187
|
## Architecture
|
|
78125
78188
|
|
|
@@ -78127,7 +78190,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78127
78190
|
|
|
78128
78191
|
## Dependencies
|
|
78129
78192
|
`);
|
|
78130
|
-
await Bun.write(
|
|
78193
|
+
await Bun.write(join51(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
78131
78194
|
|
|
78132
78195
|
## US-001: [Title]
|
|
78133
78196
|
|
|
@@ -78136,7 +78199,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78136
78199
|
### Acceptance Criteria
|
|
78137
78200
|
- [ ] Criterion 1
|
|
78138
78201
|
`);
|
|
78139
|
-
await Bun.write(
|
|
78202
|
+
await Bun.write(join51(featureDir, "progress.txt"), `# Progress: ${name}
|
|
78140
78203
|
|
|
78141
78204
|
Created: ${new Date().toISOString()}
|
|
78142
78205
|
|
|
@@ -78164,7 +78227,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78164
78227
|
console.error(source_default.red("nax not initialized."));
|
|
78165
78228
|
process.exit(1);
|
|
78166
78229
|
}
|
|
78167
|
-
const featuresDir =
|
|
78230
|
+
const featuresDir = join51(naxDir, "features");
|
|
78168
78231
|
if (!existsSync34(featuresDir)) {
|
|
78169
78232
|
console.log(source_default.dim("No features yet."));
|
|
78170
78233
|
return;
|
|
@@ -78179,7 +78242,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78179
78242
|
Features:
|
|
78180
78243
|
`));
|
|
78181
78244
|
for (const name of entries) {
|
|
78182
|
-
const prdPath =
|
|
78245
|
+
const prdPath = join51(featuresDir, name, "prd.json");
|
|
78183
78246
|
if (existsSync34(prdPath)) {
|
|
78184
78247
|
const prd = await loadPRD(prdPath);
|
|
78185
78248
|
const c = countStories(prd);
|
|
@@ -78210,10 +78273,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
78210
78273
|
process.exit(1);
|
|
78211
78274
|
}
|
|
78212
78275
|
const config2 = await loadConfig(workdir);
|
|
78213
|
-
const featureLogDir =
|
|
78276
|
+
const featureLogDir = join51(naxDir, "features", options.feature, "plan");
|
|
78214
78277
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
78215
78278
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78216
|
-
const planLogPath =
|
|
78279
|
+
const planLogPath = join51(featureLogDir, `${planLogId}.jsonl`);
|
|
78217
78280
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78218
78281
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78219
78282
|
try {
|
|
@@ -78250,7 +78313,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78250
78313
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78251
78314
|
process.exit(1);
|
|
78252
78315
|
}
|
|
78253
|
-
const featureDir =
|
|
78316
|
+
const featureDir = join51(naxDir, "features", options.feature);
|
|
78254
78317
|
if (!existsSync34(featureDir)) {
|
|
78255
78318
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
78256
78319
|
process.exit(1);
|
|
@@ -78266,7 +78329,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78266
78329
|
specPath: options.from,
|
|
78267
78330
|
reclassify: options.reclassify
|
|
78268
78331
|
});
|
|
78269
|
-
const prdPath =
|
|
78332
|
+
const prdPath = join51(featureDir, "prd.json");
|
|
78270
78333
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
78271
78334
|
const c = countStories(prd);
|
|
78272
78335
|
console.log(source_default.green(`
|