@nathapp/nax 0.48.4 → 0.49.1
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/CHANGELOG.md +8 -0
- package/dist/nax.js +283 -184
- package/package.json +1 -1
- package/src/acceptance/generator.ts +7 -5
- package/src/agents/acp/adapter.ts +7 -2
- package/src/agents/acp/parser.ts +1 -1
- package/src/agents/acp/spawn-client.ts +2 -0
- package/src/agents/types.ts +6 -0
- package/src/cli/plan.ts +11 -1
- 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/routing.ts +20 -1
- package/src/pipeline/stages/verify.ts +23 -20
- package/src/pipeline/types.ts +7 -0
- package/src/utils/git.ts +10 -2
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,14 @@ 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, {
|
|
18733
|
+
model: options.modelDef.model,
|
|
18734
|
+
config: options.config
|
|
18735
|
+
});
|
|
18736
|
+
const testCode = extractTestCode(rawOutput);
|
|
18735
18737
|
const refinedJsonContent = JSON.stringify(refinedCriteria.map((c, i) => ({
|
|
18736
18738
|
acId: `AC-${i + 1}`,
|
|
18737
18739
|
original: c.original,
|
|
@@ -19171,6 +19173,8 @@ class SpawnAcpSession {
|
|
|
19171
19173
|
"acpx",
|
|
19172
19174
|
"--cwd",
|
|
19173
19175
|
this.cwd,
|
|
19176
|
+
"--format",
|
|
19177
|
+
"json",
|
|
19174
19178
|
...this.permissionMode === "approve-all" ? ["--approve-all"] : [],
|
|
19175
19179
|
"--model",
|
|
19176
19180
|
this.model,
|
|
@@ -19743,7 +19747,11 @@ class AcpAgentAdapter {
|
|
|
19743
19747
|
await client.start();
|
|
19744
19748
|
let session = null;
|
|
19745
19749
|
try {
|
|
19746
|
-
session = await client.createSession({
|
|
19750
|
+
session = await client.createSession({
|
|
19751
|
+
agentName: this.name,
|
|
19752
|
+
permissionMode,
|
|
19753
|
+
sessionName: _options?.sessionName
|
|
19754
|
+
});
|
|
19747
19755
|
let timeoutId;
|
|
19748
19756
|
const timeoutPromise = new Promise((_, reject) => {
|
|
19749
19757
|
timeoutId = setTimeout(() => reject(new Error(`complete() timed out after ${timeoutMs}ms`)), timeoutMs);
|
|
@@ -20697,17 +20705,49 @@ var init_json_file = __esm(() => {
|
|
|
20697
20705
|
|
|
20698
20706
|
// src/config/merge.ts
|
|
20699
20707
|
function mergePackageConfig(root, packageOverride) {
|
|
20700
|
-
const
|
|
20701
|
-
if (!
|
|
20708
|
+
const hasAnyMergeableField = packageOverride.execution !== undefined || packageOverride.review !== undefined || packageOverride.acceptance !== undefined || packageOverride.quality !== undefined || packageOverride.context !== undefined;
|
|
20709
|
+
if (!hasAnyMergeableField) {
|
|
20702
20710
|
return root;
|
|
20703
20711
|
}
|
|
20704
20712
|
return {
|
|
20705
20713
|
...root,
|
|
20714
|
+
execution: {
|
|
20715
|
+
...root.execution,
|
|
20716
|
+
...packageOverride.execution,
|
|
20717
|
+
smartTestRunner: packageOverride.execution?.smartTestRunner ?? root.execution.smartTestRunner,
|
|
20718
|
+
regressionGate: {
|
|
20719
|
+
...root.execution.regressionGate,
|
|
20720
|
+
...packageOverride.execution?.regressionGate
|
|
20721
|
+
},
|
|
20722
|
+
verificationTimeoutSeconds: packageOverride.execution?.verificationTimeoutSeconds ?? root.execution.verificationTimeoutSeconds
|
|
20723
|
+
},
|
|
20724
|
+
review: {
|
|
20725
|
+
...root.review,
|
|
20726
|
+
...packageOverride.review,
|
|
20727
|
+
commands: {
|
|
20728
|
+
...root.review.commands,
|
|
20729
|
+
...packageOverride.review?.commands
|
|
20730
|
+
}
|
|
20731
|
+
},
|
|
20732
|
+
acceptance: {
|
|
20733
|
+
...root.acceptance,
|
|
20734
|
+
...packageOverride.acceptance
|
|
20735
|
+
},
|
|
20706
20736
|
quality: {
|
|
20707
20737
|
...root.quality,
|
|
20738
|
+
requireTests: packageOverride.quality?.requireTests ?? root.quality.requireTests,
|
|
20739
|
+
requireTypecheck: packageOverride.quality?.requireTypecheck ?? root.quality.requireTypecheck,
|
|
20740
|
+
requireLint: packageOverride.quality?.requireLint ?? root.quality.requireLint,
|
|
20708
20741
|
commands: {
|
|
20709
20742
|
...root.quality.commands,
|
|
20710
|
-
...
|
|
20743
|
+
...packageOverride.quality?.commands
|
|
20744
|
+
}
|
|
20745
|
+
},
|
|
20746
|
+
context: {
|
|
20747
|
+
...root.context,
|
|
20748
|
+
testCoverage: {
|
|
20749
|
+
...root.context.testCoverage,
|
|
20750
|
+
...packageOverride.context?.testCoverage
|
|
20711
20751
|
}
|
|
20712
20752
|
}
|
|
20713
20753
|
};
|
|
@@ -22210,7 +22250,7 @@ var package_default;
|
|
|
22210
22250
|
var init_package = __esm(() => {
|
|
22211
22251
|
package_default = {
|
|
22212
22252
|
name: "@nathapp/nax",
|
|
22213
|
-
version: "0.
|
|
22253
|
+
version: "0.49.1",
|
|
22214
22254
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22215
22255
|
type: "module",
|
|
22216
22256
|
bin: {
|
|
@@ -22283,8 +22323,8 @@ var init_version = __esm(() => {
|
|
|
22283
22323
|
NAX_VERSION = package_default.version;
|
|
22284
22324
|
NAX_COMMIT = (() => {
|
|
22285
22325
|
try {
|
|
22286
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22287
|
-
return "
|
|
22326
|
+
if (/^[0-9a-f]{6,10}$/.test("635a552"))
|
|
22327
|
+
return "635a552";
|
|
22288
22328
|
} catch {}
|
|
22289
22329
|
try {
|
|
22290
22330
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -23933,7 +23973,8 @@ var init_acceptance2 = __esm(() => {
|
|
|
23933
23973
|
acceptanceStage = {
|
|
23934
23974
|
name: "acceptance",
|
|
23935
23975
|
enabled(ctx) {
|
|
23936
|
-
|
|
23976
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
23977
|
+
if (!effectiveConfig.acceptance.enabled) {
|
|
23937
23978
|
return false;
|
|
23938
23979
|
}
|
|
23939
23980
|
if (!areAllStoriesComplete(ctx)) {
|
|
@@ -23943,12 +23984,13 @@ var init_acceptance2 = __esm(() => {
|
|
|
23943
23984
|
},
|
|
23944
23985
|
async execute(ctx) {
|
|
23945
23986
|
const logger = getLogger();
|
|
23987
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
23946
23988
|
logger.info("acceptance", "Running acceptance tests");
|
|
23947
23989
|
if (!ctx.featureDir) {
|
|
23948
23990
|
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
|
|
23949
23991
|
return { action: "continue" };
|
|
23950
23992
|
}
|
|
23951
|
-
const testPath = path4.join(ctx.featureDir,
|
|
23993
|
+
const testPath = path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath);
|
|
23952
23994
|
const testFile = Bun.file(testPath);
|
|
23953
23995
|
const exists = await testFile.exists();
|
|
23954
23996
|
if (!exists) {
|
|
@@ -24577,12 +24619,13 @@ var init_review = __esm(() => {
|
|
|
24577
24619
|
init_orchestrator();
|
|
24578
24620
|
reviewStage = {
|
|
24579
24621
|
name: "review",
|
|
24580
|
-
enabled: (ctx) => ctx.config.review.enabled,
|
|
24622
|
+
enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
|
|
24581
24623
|
async execute(ctx) {
|
|
24582
24624
|
const logger = getLogger();
|
|
24625
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24583
24626
|
logger.info("review", "Running review phase", { storyId: ctx.story.id });
|
|
24584
24627
|
const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24585
|
-
const result = await reviewOrchestrator.review(
|
|
24628
|
+
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
|
|
24586
24629
|
ctx.reviewResult = result.builtIn;
|
|
24587
24630
|
if (!result.success) {
|
|
24588
24631
|
const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -24590,8 +24633,8 @@ var init_review = __esm(() => {
|
|
|
24590
24633
|
ctx.reviewFindings = allFindings;
|
|
24591
24634
|
}
|
|
24592
24635
|
if (result.pluginFailed) {
|
|
24593
|
-
if (ctx.interaction && isTriggerEnabled("security-review",
|
|
24594
|
-
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id },
|
|
24636
|
+
if (ctx.interaction && isTriggerEnabled("security-review", effectiveConfig)) {
|
|
24637
|
+
const shouldContinue = await _reviewDeps.checkSecurityReview({ featureName: ctx.prd.feature, storyId: ctx.story.id }, effectiveConfig, ctx.interaction);
|
|
24595
24638
|
if (!shouldContinue) {
|
|
24596
24639
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
24597
24640
|
return { action: "fail", reason: `Review failed: ${result.failureReason}` };
|
|
@@ -24602,11 +24645,11 @@ var init_review = __esm(() => {
|
|
|
24602
24645
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
24603
24646
|
return { action: "fail", reason: `Review failed: ${result.failureReason}` };
|
|
24604
24647
|
}
|
|
24605
|
-
logger.warn("review", "Review failed (built-in checks) \u2014
|
|
24648
|
+
logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
|
|
24606
24649
|
reason: result.failureReason,
|
|
24607
24650
|
storyId: ctx.story.id
|
|
24608
24651
|
});
|
|
24609
|
-
return { action: "
|
|
24652
|
+
return { action: "continue" };
|
|
24610
24653
|
}
|
|
24611
24654
|
logger.info("review", "Review passed", {
|
|
24612
24655
|
durationMs: result.builtIn.totalDurationMs,
|
|
@@ -24621,6 +24664,7 @@ var init_review = __esm(() => {
|
|
|
24621
24664
|
});
|
|
24622
24665
|
|
|
24623
24666
|
// src/pipeline/stages/autofix.ts
|
|
24667
|
+
import { join as join18 } from "path";
|
|
24624
24668
|
async function runCommand(cmd, cwd) {
|
|
24625
24669
|
const parts = cmd.split(/\s+/);
|
|
24626
24670
|
const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
@@ -24663,7 +24707,8 @@ Commit your fixes when done.`;
|
|
|
24663
24707
|
}
|
|
24664
24708
|
async function runAgentRectification(ctx) {
|
|
24665
24709
|
const logger = getLogger();
|
|
24666
|
-
const
|
|
24710
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24711
|
+
const maxAttempts = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
|
|
24667
24712
|
const failedChecks = collectFailedChecks(ctx);
|
|
24668
24713
|
if (failedChecks.length === 0) {
|
|
24669
24714
|
logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
|
|
@@ -24720,6 +24765,7 @@ var autofixStage, _autofixDeps;
|
|
|
24720
24765
|
var init_autofix = __esm(() => {
|
|
24721
24766
|
init_agents();
|
|
24722
24767
|
init_config();
|
|
24768
|
+
init_loader2();
|
|
24723
24769
|
init_logger2();
|
|
24724
24770
|
init_event_bus();
|
|
24725
24771
|
autofixStage = {
|
|
@@ -24729,7 +24775,7 @@ var init_autofix = __esm(() => {
|
|
|
24729
24775
|
return false;
|
|
24730
24776
|
if (ctx.reviewResult.success)
|
|
24731
24777
|
return false;
|
|
24732
|
-
const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
|
|
24778
|
+
const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
|
|
24733
24779
|
return autofixEnabled;
|
|
24734
24780
|
},
|
|
24735
24781
|
skipReason(ctx) {
|
|
@@ -24743,12 +24789,14 @@ var init_autofix = __esm(() => {
|
|
|
24743
24789
|
if (!reviewResult || reviewResult.success) {
|
|
24744
24790
|
return { action: "continue" };
|
|
24745
24791
|
}
|
|
24746
|
-
const
|
|
24747
|
-
const
|
|
24792
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24793
|
+
const lintFixCmd = effectiveConfig.quality.commands.lintFix;
|
|
24794
|
+
const formatFixCmd = effectiveConfig.quality.commands.formatFix;
|
|
24795
|
+
const effectiveWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24748
24796
|
if (lintFixCmd || formatFixCmd) {
|
|
24749
24797
|
if (lintFixCmd) {
|
|
24750
24798
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
|
|
24751
|
-
const lintResult = await _autofixDeps.runCommand(lintFixCmd,
|
|
24799
|
+
const lintResult = await _autofixDeps.runCommand(lintFixCmd, effectiveWorkdir);
|
|
24752
24800
|
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id });
|
|
24753
24801
|
if (lintResult.exitCode !== 0) {
|
|
24754
24802
|
logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -24759,7 +24807,7 @@ var init_autofix = __esm(() => {
|
|
|
24759
24807
|
}
|
|
24760
24808
|
if (formatFixCmd) {
|
|
24761
24809
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
|
|
24762
|
-
const fmtResult = await _autofixDeps.runCommand(formatFixCmd,
|
|
24810
|
+
const fmtResult = await _autofixDeps.runCommand(formatFixCmd, effectiveWorkdir);
|
|
24763
24811
|
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, { storyId: ctx.story.id });
|
|
24764
24812
|
if (fmtResult.exitCode !== 0) {
|
|
24765
24813
|
logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -24788,15 +24836,15 @@ var init_autofix = __esm(() => {
|
|
|
24788
24836
|
return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
|
|
24789
24837
|
}
|
|
24790
24838
|
};
|
|
24791
|
-
_autofixDeps = { runCommand, recheckReview, runAgentRectification };
|
|
24839
|
+
_autofixDeps = { runCommand, recheckReview, runAgentRectification, loadConfigForWorkdir };
|
|
24792
24840
|
});
|
|
24793
24841
|
|
|
24794
24842
|
// src/execution/progress.ts
|
|
24795
24843
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
24796
|
-
import { join as
|
|
24844
|
+
import { join as join19 } from "path";
|
|
24797
24845
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
24798
24846
|
mkdirSync2(featureDir, { recursive: true });
|
|
24799
|
-
const progressPath =
|
|
24847
|
+
const progressPath = join19(featureDir, "progress.txt");
|
|
24800
24848
|
const timestamp = new Date().toISOString();
|
|
24801
24849
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
24802
24850
|
`;
|
|
@@ -24880,7 +24928,7 @@ function estimateTokens(text) {
|
|
|
24880
24928
|
|
|
24881
24929
|
// src/constitution/loader.ts
|
|
24882
24930
|
import { existsSync as existsSync15 } from "fs";
|
|
24883
|
-
import { join as
|
|
24931
|
+
import { join as join20 } from "path";
|
|
24884
24932
|
function truncateToTokens(text, maxTokens) {
|
|
24885
24933
|
const maxChars = maxTokens * 3;
|
|
24886
24934
|
if (text.length <= maxChars) {
|
|
@@ -24902,7 +24950,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
24902
24950
|
}
|
|
24903
24951
|
let combinedContent = "";
|
|
24904
24952
|
if (!config2.skipGlobal) {
|
|
24905
|
-
const globalPath =
|
|
24953
|
+
const globalPath = join20(globalConfigDir(), config2.path);
|
|
24906
24954
|
if (existsSync15(globalPath)) {
|
|
24907
24955
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
24908
24956
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -24912,7 +24960,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
24912
24960
|
}
|
|
24913
24961
|
}
|
|
24914
24962
|
}
|
|
24915
|
-
const projectPath =
|
|
24963
|
+
const projectPath = join20(projectDir, config2.path);
|
|
24916
24964
|
if (existsSync15(projectPath)) {
|
|
24917
24965
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
24918
24966
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -25884,7 +25932,7 @@ var init_helpers = __esm(() => {
|
|
|
25884
25932
|
});
|
|
25885
25933
|
|
|
25886
25934
|
// src/pipeline/stages/context.ts
|
|
25887
|
-
import { join as
|
|
25935
|
+
import { join as join21 } from "path";
|
|
25888
25936
|
var contextStage;
|
|
25889
25937
|
var init_context2 = __esm(() => {
|
|
25890
25938
|
init_helpers();
|
|
@@ -25894,7 +25942,7 @@ var init_context2 = __esm(() => {
|
|
|
25894
25942
|
enabled: () => true,
|
|
25895
25943
|
async execute(ctx) {
|
|
25896
25944
|
const logger = getLogger();
|
|
25897
|
-
const packageWorkdir = ctx.story.workdir ?
|
|
25945
|
+
const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
|
|
25898
25946
|
const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
25899
25947
|
if (result) {
|
|
25900
25948
|
ctx.contextMarkdown = result.markdown;
|
|
@@ -26026,14 +26074,14 @@ var init_isolation = __esm(() => {
|
|
|
26026
26074
|
|
|
26027
26075
|
// src/context/greenfield.ts
|
|
26028
26076
|
import { readdir } from "fs/promises";
|
|
26029
|
-
import { join as
|
|
26077
|
+
import { join as join22 } from "path";
|
|
26030
26078
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
26031
26079
|
const results = [];
|
|
26032
26080
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
26033
26081
|
try {
|
|
26034
26082
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
26035
26083
|
for (const entry of entries) {
|
|
26036
|
-
const fullPath =
|
|
26084
|
+
const fullPath = join22(dir, entry.name);
|
|
26037
26085
|
if (entry.isDirectory()) {
|
|
26038
26086
|
if (ignoreDirs.has(entry.name))
|
|
26039
26087
|
continue;
|
|
@@ -26139,7 +26187,9 @@ async function autoCommitIfDirty(workdir, stage, role, storyId) {
|
|
|
26139
26187
|
return gitRoot;
|
|
26140
26188
|
}
|
|
26141
26189
|
})();
|
|
26142
|
-
|
|
26190
|
+
const isAtRoot = realWorkdir === realGitRoot;
|
|
26191
|
+
const isSubdir = realGitRoot && realWorkdir.startsWith(`${realGitRoot}/`);
|
|
26192
|
+
if (!isAtRoot && !isSubdir)
|
|
26143
26193
|
return;
|
|
26144
26194
|
const statusProc = _gitDeps.spawn(["git", "status", "--porcelain"], {
|
|
26145
26195
|
cwd: workdir,
|
|
@@ -26156,7 +26206,8 @@ async function autoCommitIfDirty(workdir, stage, role, storyId) {
|
|
|
26156
26206
|
dirtyFiles: statusOutput.trim().split(`
|
|
26157
26207
|
`).length
|
|
26158
26208
|
});
|
|
26159
|
-
const
|
|
26209
|
+
const addArgs = isSubdir ? ["git", "add", "."] : ["git", "add", "-A"];
|
|
26210
|
+
const addProc = _gitDeps.spawn(addArgs, { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
26160
26211
|
await addProc.exited;
|
|
26161
26212
|
const commitProc = _gitDeps.spawn(["git", "commit", "-m", `chore(${storyId}): auto-commit after ${role} session`], {
|
|
26162
26213
|
cwd: workdir,
|
|
@@ -26445,13 +26496,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
26445
26496
|
|
|
26446
26497
|
// src/verification/runners.ts
|
|
26447
26498
|
import { existsSync as existsSync16 } from "fs";
|
|
26448
|
-
import { join as
|
|
26499
|
+
import { join as join23 } from "path";
|
|
26449
26500
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
26450
26501
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
26451
26502
|
return { success: true, missingFiles: [] };
|
|
26452
26503
|
const missingFiles = [];
|
|
26453
26504
|
for (const file2 of expectedFiles) {
|
|
26454
|
-
if (!existsSync16(
|
|
26505
|
+
if (!existsSync16(join23(workingDirectory, file2)))
|
|
26455
26506
|
missingFiles.push(file2);
|
|
26456
26507
|
}
|
|
26457
26508
|
if (missingFiles.length > 0) {
|
|
@@ -27136,13 +27187,13 @@ var exports_loader = {};
|
|
|
27136
27187
|
__export(exports_loader, {
|
|
27137
27188
|
loadOverride: () => loadOverride
|
|
27138
27189
|
});
|
|
27139
|
-
import { join as
|
|
27190
|
+
import { join as join24 } from "path";
|
|
27140
27191
|
async function loadOverride(role, workdir, config2) {
|
|
27141
27192
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
27142
27193
|
if (!overridePath) {
|
|
27143
27194
|
return null;
|
|
27144
27195
|
}
|
|
27145
|
-
const absolutePath =
|
|
27196
|
+
const absolutePath = join24(workdir, overridePath);
|
|
27146
27197
|
const file2 = Bun.file(absolutePath);
|
|
27147
27198
|
if (!await file2.exists()) {
|
|
27148
27199
|
return null;
|
|
@@ -27964,11 +28015,11 @@ var init_tdd = __esm(() => {
|
|
|
27964
28015
|
|
|
27965
28016
|
// src/pipeline/stages/execution.ts
|
|
27966
28017
|
import { existsSync as existsSync17 } from "fs";
|
|
27967
|
-
import { join as
|
|
28018
|
+
import { join as join25 } from "path";
|
|
27968
28019
|
function resolveStoryWorkdir(repoRoot, storyWorkdir) {
|
|
27969
28020
|
if (!storyWorkdir)
|
|
27970
28021
|
return repoRoot;
|
|
27971
|
-
const resolved =
|
|
28022
|
+
const resolved = join25(repoRoot, storyWorkdir);
|
|
27972
28023
|
if (!existsSync17(resolved)) {
|
|
27973
28024
|
throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
|
|
27974
28025
|
}
|
|
@@ -27998,6 +28049,9 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
|
|
|
27998
28049
|
if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
|
|
27999
28050
|
return { action: "escalate" };
|
|
28000
28051
|
}
|
|
28052
|
+
if (failureCategory === "greenfield-no-tests") {
|
|
28053
|
+
return { action: "escalate" };
|
|
28054
|
+
}
|
|
28001
28055
|
return {
|
|
28002
28056
|
action: "pause",
|
|
28003
28057
|
reason: reviewReason || "Three-session TDD requires review"
|
|
@@ -28438,13 +28492,14 @@ var init_prompt = __esm(() => {
|
|
|
28438
28492
|
async execute(ctx) {
|
|
28439
28493
|
const logger = getLogger();
|
|
28440
28494
|
const isBatch = ctx.stories.length > 1;
|
|
28495
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
28441
28496
|
let prompt;
|
|
28442
28497
|
if (isBatch) {
|
|
28443
|
-
const builder = PromptBuilder.for("batch").withLoader(ctx.workdir, ctx.config).stories(ctx.stories).context(ctx.contextMarkdown).constitution(ctx.constitution?.content).testCommand(
|
|
28498
|
+
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
28499
|
prompt = await builder.build();
|
|
28445
28500
|
} else {
|
|
28446
28501
|
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(
|
|
28502
|
+
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
28503
|
prompt = await builder.build();
|
|
28449
28504
|
}
|
|
28450
28505
|
ctx.prompt = prompt;
|
|
@@ -28788,13 +28843,14 @@ var init_rectify = __esm(() => {
|
|
|
28788
28843
|
attempt: rectifyAttempt,
|
|
28789
28844
|
testOutput
|
|
28790
28845
|
});
|
|
28791
|
-
const
|
|
28846
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
28847
|
+
const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
|
|
28792
28848
|
const fixed = await _rectifyDeps.runRectificationLoop({
|
|
28793
28849
|
config: ctx.config,
|
|
28794
28850
|
workdir: ctx.workdir,
|
|
28795
28851
|
story: ctx.story,
|
|
28796
28852
|
testCommand,
|
|
28797
|
-
timeoutSeconds:
|
|
28853
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
28798
28854
|
testOutput
|
|
28799
28855
|
});
|
|
28800
28856
|
pipelineEventBus.emit({
|
|
@@ -29322,31 +29378,34 @@ var init_regression2 = __esm(() => {
|
|
|
29322
29378
|
regressionStage = {
|
|
29323
29379
|
name: "regression",
|
|
29324
29380
|
enabled(ctx) {
|
|
29325
|
-
const
|
|
29381
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29382
|
+
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29326
29383
|
if (mode !== "per-story")
|
|
29327
29384
|
return false;
|
|
29328
29385
|
if (ctx.verifyResult && !ctx.verifyResult.success)
|
|
29329
29386
|
return false;
|
|
29330
|
-
const gateEnabled =
|
|
29387
|
+
const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
|
|
29331
29388
|
return gateEnabled;
|
|
29332
29389
|
},
|
|
29333
29390
|
skipReason(ctx) {
|
|
29334
|
-
const
|
|
29391
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29392
|
+
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29335
29393
|
if (mode !== "per-story")
|
|
29336
29394
|
return `not needed (regression mode is '${mode}', not 'per-story')`;
|
|
29337
29395
|
return "disabled (regression gate not enabled in config)";
|
|
29338
29396
|
},
|
|
29339
29397
|
async execute(ctx) {
|
|
29340
29398
|
const logger = getLogger();
|
|
29341
|
-
const
|
|
29342
|
-
const
|
|
29399
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29400
|
+
const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
|
|
29401
|
+
const timeoutSeconds = effectiveConfig.execution.regressionGate?.timeoutSeconds ?? 120;
|
|
29343
29402
|
logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
|
|
29344
29403
|
const verifyCtx = {
|
|
29345
29404
|
workdir: ctx.workdir,
|
|
29346
29405
|
testCommand,
|
|
29347
29406
|
timeoutSeconds,
|
|
29348
29407
|
storyId: ctx.story.id,
|
|
29349
|
-
acceptOnTimeout:
|
|
29408
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
|
|
29350
29409
|
config: ctx.config
|
|
29351
29410
|
};
|
|
29352
29411
|
const result = await _regressionStageDeps.verifyRegression(verifyCtx);
|
|
@@ -29381,7 +29440,7 @@ var init_regression2 = __esm(() => {
|
|
|
29381
29440
|
});
|
|
29382
29441
|
|
|
29383
29442
|
// src/pipeline/stages/routing.ts
|
|
29384
|
-
import { join as
|
|
29443
|
+
import { join as join26 } from "path";
|
|
29385
29444
|
async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
29386
29445
|
const naxDecompose = config2.decompose;
|
|
29387
29446
|
const builderConfig = {
|
|
@@ -29393,9 +29452,24 @@ async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
|
29393
29452
|
if (!agent) {
|
|
29394
29453
|
throw new Error(`[decompose] Agent "${config2.autoMode.defaultAgent}" not found \u2014 cannot decompose`);
|
|
29395
29454
|
}
|
|
29455
|
+
const decomposeTier = naxDecompose?.model ?? "balanced";
|
|
29456
|
+
let decomposeModel;
|
|
29457
|
+
try {
|
|
29458
|
+
const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
29459
|
+
const models = config2.models;
|
|
29460
|
+
const entry = models[decomposeTier] ?? models.balanced;
|
|
29461
|
+
if (entry)
|
|
29462
|
+
decomposeModel = resolveModel2(entry).model;
|
|
29463
|
+
} catch {}
|
|
29464
|
+
const storySessionName = `nax-decompose-${story.id.toLowerCase()}`;
|
|
29396
29465
|
const adapter = {
|
|
29397
29466
|
async decompose(prompt) {
|
|
29398
|
-
return agent.complete(prompt, {
|
|
29467
|
+
return agent.complete(prompt, {
|
|
29468
|
+
model: decomposeModel,
|
|
29469
|
+
jsonMode: true,
|
|
29470
|
+
config: config2,
|
|
29471
|
+
sessionName: storySessionName
|
|
29472
|
+
});
|
|
29399
29473
|
}
|
|
29400
29474
|
};
|
|
29401
29475
|
return DecomposeBuilder.for(story).prd(prd).config(builderConfig).decompose(adapter);
|
|
@@ -29455,7 +29529,7 @@ var init_routing2 = __esm(() => {
|
|
|
29455
29529
|
}
|
|
29456
29530
|
const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
|
|
29457
29531
|
if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
|
|
29458
|
-
const greenfieldScanDir = ctx.story.workdir ?
|
|
29532
|
+
const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29459
29533
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
|
|
29460
29534
|
if (isGreenfield) {
|
|
29461
29535
|
logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
|
|
@@ -29552,7 +29626,7 @@ var init_crash_detector = __esm(() => {
|
|
|
29552
29626
|
});
|
|
29553
29627
|
|
|
29554
29628
|
// src/pipeline/stages/verify.ts
|
|
29555
|
-
import { join as
|
|
29629
|
+
import { join as join27 } from "path";
|
|
29556
29630
|
function coerceSmartTestRunner(val) {
|
|
29557
29631
|
if (val === undefined || val === true)
|
|
29558
29632
|
return DEFAULT_SMART_RUNNER_CONFIG2;
|
|
@@ -29568,7 +29642,7 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
29568
29642
|
}
|
|
29569
29643
|
async function readPackageName(dir) {
|
|
29570
29644
|
try {
|
|
29571
|
-
const content = await Bun.file(
|
|
29645
|
+
const content = await Bun.file(join27(dir, "package.json")).json();
|
|
29572
29646
|
return typeof content.name === "string" ? content.name : null;
|
|
29573
29647
|
} catch {
|
|
29574
29648
|
return null;
|
|
@@ -29585,7 +29659,6 @@ async function resolvePackageTemplate(template, packageDir) {
|
|
|
29585
29659
|
}
|
|
29586
29660
|
var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
|
|
29587
29661
|
var init_verify = __esm(() => {
|
|
29588
|
-
init_loader2();
|
|
29589
29662
|
init_logger2();
|
|
29590
29663
|
init_crash_detector();
|
|
29591
29664
|
init_runners();
|
|
@@ -29602,7 +29675,7 @@ var init_verify = __esm(() => {
|
|
|
29602
29675
|
skipReason: () => "not needed (full-suite gate already passed)",
|
|
29603
29676
|
async execute(ctx) {
|
|
29604
29677
|
const logger = getLogger();
|
|
29605
|
-
const effectiveConfig = ctx.
|
|
29678
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29606
29679
|
if (!effectiveConfig.quality.requireTests) {
|
|
29607
29680
|
logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
|
|
29608
29681
|
return { action: "continue" };
|
|
@@ -29614,11 +29687,11 @@ var init_verify = __esm(() => {
|
|
|
29614
29687
|
return { action: "continue" };
|
|
29615
29688
|
}
|
|
29616
29689
|
logger.info("verify", "Running verification", { storyId: ctx.story.id });
|
|
29617
|
-
const effectiveWorkdir = ctx.story.workdir ?
|
|
29690
|
+
const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29618
29691
|
let effectiveCommand = testCommand;
|
|
29619
29692
|
let isFullSuite = true;
|
|
29620
|
-
const smartRunnerConfig = coerceSmartTestRunner(
|
|
29621
|
-
const regressionMode =
|
|
29693
|
+
const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
|
|
29694
|
+
const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29622
29695
|
let resolvedTestScopedTemplate = testScopedTemplate;
|
|
29623
29696
|
if (testScopedTemplate && ctx.story.workdir) {
|
|
29624
29697
|
const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
|
|
@@ -29677,8 +29750,8 @@ var init_verify = __esm(() => {
|
|
|
29677
29750
|
const result = await _verifyDeps.regression({
|
|
29678
29751
|
workdir: effectiveWorkdir,
|
|
29679
29752
|
command: effectiveCommand,
|
|
29680
|
-
timeoutSeconds:
|
|
29681
|
-
acceptOnTimeout:
|
|
29753
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
29754
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
|
|
29682
29755
|
});
|
|
29683
29756
|
ctx.verifyResult = {
|
|
29684
29757
|
success: result.success,
|
|
@@ -29695,7 +29768,7 @@ var init_verify = __esm(() => {
|
|
|
29695
29768
|
};
|
|
29696
29769
|
if (!result.success) {
|
|
29697
29770
|
if (result.status === "TIMEOUT") {
|
|
29698
|
-
const timeout =
|
|
29771
|
+
const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
|
|
29699
29772
|
logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
|
|
29700
29773
|
exitCode: result.status,
|
|
29701
29774
|
storyId: ctx.story.id,
|
|
@@ -29710,10 +29783,13 @@ var init_verify = __esm(() => {
|
|
|
29710
29783
|
if (result.status !== "TIMEOUT") {
|
|
29711
29784
|
logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
|
|
29712
29785
|
}
|
|
29713
|
-
|
|
29714
|
-
|
|
29715
|
-
|
|
29716
|
-
|
|
29786
|
+
if (result.status === "TIMEOUT" || detectRuntimeCrash(result.output)) {
|
|
29787
|
+
return {
|
|
29788
|
+
action: "escalate",
|
|
29789
|
+
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"})`
|
|
29790
|
+
};
|
|
29791
|
+
}
|
|
29792
|
+
return { action: "continue" };
|
|
29717
29793
|
}
|
|
29718
29794
|
logger.info("verify", "Tests passed", { storyId: ctx.story.id });
|
|
29719
29795
|
return { action: "continue" };
|
|
@@ -29721,7 +29797,6 @@ var init_verify = __esm(() => {
|
|
|
29721
29797
|
};
|
|
29722
29798
|
_verifyDeps = {
|
|
29723
29799
|
regression,
|
|
29724
|
-
loadConfigForWorkdir,
|
|
29725
29800
|
readPackageName
|
|
29726
29801
|
};
|
|
29727
29802
|
});
|
|
@@ -29809,7 +29884,7 @@ __export(exports_init_context, {
|
|
|
29809
29884
|
});
|
|
29810
29885
|
import { existsSync as existsSync20 } from "fs";
|
|
29811
29886
|
import { mkdir } from "fs/promises";
|
|
29812
|
-
import { basename as basename2, join as
|
|
29887
|
+
import { basename as basename2, join as join31 } from "path";
|
|
29813
29888
|
async function findFiles(dir, maxFiles = 200) {
|
|
29814
29889
|
try {
|
|
29815
29890
|
const proc = Bun.spawnSync([
|
|
@@ -29837,7 +29912,7 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
29837
29912
|
return [];
|
|
29838
29913
|
}
|
|
29839
29914
|
async function readPackageManifest(projectRoot) {
|
|
29840
|
-
const packageJsonPath =
|
|
29915
|
+
const packageJsonPath = join31(projectRoot, "package.json");
|
|
29841
29916
|
if (!existsSync20(packageJsonPath)) {
|
|
29842
29917
|
return null;
|
|
29843
29918
|
}
|
|
@@ -29855,7 +29930,7 @@ async function readPackageManifest(projectRoot) {
|
|
|
29855
29930
|
}
|
|
29856
29931
|
}
|
|
29857
29932
|
async function readReadmeSnippet(projectRoot) {
|
|
29858
|
-
const readmePath =
|
|
29933
|
+
const readmePath = join31(projectRoot, "README.md");
|
|
29859
29934
|
if (!existsSync20(readmePath)) {
|
|
29860
29935
|
return null;
|
|
29861
29936
|
}
|
|
@@ -29873,7 +29948,7 @@ async function detectEntryPoints(projectRoot) {
|
|
|
29873
29948
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
29874
29949
|
const found = [];
|
|
29875
29950
|
for (const candidate of candidates) {
|
|
29876
|
-
const path12 =
|
|
29951
|
+
const path12 = join31(projectRoot, candidate);
|
|
29877
29952
|
if (existsSync20(path12)) {
|
|
29878
29953
|
found.push(candidate);
|
|
29879
29954
|
}
|
|
@@ -29884,7 +29959,7 @@ async function detectConfigFiles(projectRoot) {
|
|
|
29884
29959
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
29885
29960
|
const found = [];
|
|
29886
29961
|
for (const candidate of candidates) {
|
|
29887
|
-
const path12 =
|
|
29962
|
+
const path12 = join31(projectRoot, candidate);
|
|
29888
29963
|
if (existsSync20(path12)) {
|
|
29889
29964
|
found.push(candidate);
|
|
29890
29965
|
}
|
|
@@ -30045,9 +30120,9 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
30045
30120
|
}
|
|
30046
30121
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
30047
30122
|
const logger = getLogger();
|
|
30048
|
-
const packageDir =
|
|
30049
|
-
const naxDir =
|
|
30050
|
-
const contextPath =
|
|
30123
|
+
const packageDir = join31(repoRoot, packagePath);
|
|
30124
|
+
const naxDir = join31(packageDir, "nax");
|
|
30125
|
+
const contextPath = join31(naxDir, "context.md");
|
|
30051
30126
|
if (existsSync20(contextPath) && !force) {
|
|
30052
30127
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
30053
30128
|
return;
|
|
@@ -30061,8 +30136,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
30061
30136
|
}
|
|
30062
30137
|
async function initContext(projectRoot, options = {}) {
|
|
30063
30138
|
const logger = getLogger();
|
|
30064
|
-
const naxDir =
|
|
30065
|
-
const contextPath =
|
|
30139
|
+
const naxDir = join31(projectRoot, "nax");
|
|
30140
|
+
const contextPath = join31(naxDir, "context.md");
|
|
30066
30141
|
if (existsSync20(contextPath) && !options.force) {
|
|
30067
30142
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
30068
30143
|
return;
|
|
@@ -31370,19 +31445,19 @@ var init_precheck = __esm(() => {
|
|
|
31370
31445
|
});
|
|
31371
31446
|
|
|
31372
31447
|
// src/hooks/runner.ts
|
|
31373
|
-
import { join as
|
|
31448
|
+
import { join as join44 } from "path";
|
|
31374
31449
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
31375
31450
|
let globalHooks = { hooks: {} };
|
|
31376
31451
|
let projectHooks = { hooks: {} };
|
|
31377
31452
|
let skipGlobal = false;
|
|
31378
|
-
const projectPath =
|
|
31453
|
+
const projectPath = join44(projectDir, "hooks.json");
|
|
31379
31454
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
31380
31455
|
if (projectData) {
|
|
31381
31456
|
projectHooks = projectData;
|
|
31382
31457
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
31383
31458
|
}
|
|
31384
31459
|
if (!skipGlobal && globalDir) {
|
|
31385
|
-
const globalPath =
|
|
31460
|
+
const globalPath = join44(globalDir, "hooks.json");
|
|
31386
31461
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
31387
31462
|
if (globalData) {
|
|
31388
31463
|
globalHooks = globalData;
|
|
@@ -31873,6 +31948,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
31873
31948
|
}), ctx.workdir);
|
|
31874
31949
|
const fixContext = {
|
|
31875
31950
|
config: ctx.config,
|
|
31951
|
+
effectiveConfig: ctx.config,
|
|
31876
31952
|
prd,
|
|
31877
31953
|
story,
|
|
31878
31954
|
stories: [story],
|
|
@@ -31906,6 +31982,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
31906
31982
|
const firstStory = prd.userStories[0];
|
|
31907
31983
|
const acceptanceContext = {
|
|
31908
31984
|
config: ctx.config,
|
|
31985
|
+
effectiveConfig: ctx.config,
|
|
31909
31986
|
prd,
|
|
31910
31987
|
story: firstStory,
|
|
31911
31988
|
stories: [firstStory],
|
|
@@ -32414,12 +32491,12 @@ __export(exports_manager, {
|
|
|
32414
32491
|
WorktreeManager: () => WorktreeManager
|
|
32415
32492
|
});
|
|
32416
32493
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
32417
|
-
import { join as
|
|
32494
|
+
import { join as join45 } from "path";
|
|
32418
32495
|
|
|
32419
32496
|
class WorktreeManager {
|
|
32420
32497
|
async create(projectRoot, storyId) {
|
|
32421
32498
|
validateStoryId(storyId);
|
|
32422
|
-
const worktreePath =
|
|
32499
|
+
const worktreePath = join45(projectRoot, ".nax-wt", storyId);
|
|
32423
32500
|
const branchName = `nax/${storyId}`;
|
|
32424
32501
|
try {
|
|
32425
32502
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -32444,9 +32521,9 @@ class WorktreeManager {
|
|
|
32444
32521
|
}
|
|
32445
32522
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
32446
32523
|
}
|
|
32447
|
-
const nodeModulesSource =
|
|
32524
|
+
const nodeModulesSource = join45(projectRoot, "node_modules");
|
|
32448
32525
|
if (existsSync32(nodeModulesSource)) {
|
|
32449
|
-
const nodeModulesTarget =
|
|
32526
|
+
const nodeModulesTarget = join45(worktreePath, "node_modules");
|
|
32450
32527
|
try {
|
|
32451
32528
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
32452
32529
|
} catch (error48) {
|
|
@@ -32454,9 +32531,9 @@ class WorktreeManager {
|
|
|
32454
32531
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
32455
32532
|
}
|
|
32456
32533
|
}
|
|
32457
|
-
const envSource =
|
|
32534
|
+
const envSource = join45(projectRoot, ".env");
|
|
32458
32535
|
if (existsSync32(envSource)) {
|
|
32459
|
-
const envTarget =
|
|
32536
|
+
const envTarget = join45(worktreePath, ".env");
|
|
32460
32537
|
try {
|
|
32461
32538
|
symlinkSync(envSource, envTarget, "file");
|
|
32462
32539
|
} catch (error48) {
|
|
@@ -32467,7 +32544,7 @@ class WorktreeManager {
|
|
|
32467
32544
|
}
|
|
32468
32545
|
async remove(projectRoot, storyId) {
|
|
32469
32546
|
validateStoryId(storyId);
|
|
32470
|
-
const worktreePath =
|
|
32547
|
+
const worktreePath = join45(projectRoot, ".nax-wt", storyId);
|
|
32471
32548
|
const branchName = `nax/${storyId}`;
|
|
32472
32549
|
try {
|
|
32473
32550
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -32777,6 +32854,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
32777
32854
|
try {
|
|
32778
32855
|
const pipelineContext = {
|
|
32779
32856
|
...context,
|
|
32857
|
+
effectiveConfig: context.effectiveConfig ?? context.config,
|
|
32780
32858
|
story,
|
|
32781
32859
|
stories: [story],
|
|
32782
32860
|
workdir: worktreePath,
|
|
@@ -32857,7 +32935,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
32857
32935
|
|
|
32858
32936
|
// src/execution/parallel-coordinator.ts
|
|
32859
32937
|
import os3 from "os";
|
|
32860
|
-
import { join as
|
|
32938
|
+
import { join as join46 } from "path";
|
|
32861
32939
|
function groupStoriesByDependencies(stories) {
|
|
32862
32940
|
const batches = [];
|
|
32863
32941
|
const processed = new Set;
|
|
@@ -32926,6 +33004,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32926
33004
|
});
|
|
32927
33005
|
const baseContext = {
|
|
32928
33006
|
config: config2,
|
|
33007
|
+
effectiveConfig: config2,
|
|
32929
33008
|
prd: currentPrd,
|
|
32930
33009
|
featureDir,
|
|
32931
33010
|
hooks,
|
|
@@ -32935,7 +33014,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32935
33014
|
};
|
|
32936
33015
|
const worktreePaths = new Map;
|
|
32937
33016
|
for (const story of batch) {
|
|
32938
|
-
const worktreePath =
|
|
33017
|
+
const worktreePath = join46(projectRoot, ".nax-wt", story.id);
|
|
32939
33018
|
try {
|
|
32940
33019
|
await worktreeManager.create(projectRoot, story.id);
|
|
32941
33020
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -32984,7 +33063,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32984
33063
|
});
|
|
32985
33064
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
32986
33065
|
storyId: mergeResult.storyId,
|
|
32987
|
-
worktreePath:
|
|
33066
|
+
worktreePath: join46(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
32988
33067
|
});
|
|
32989
33068
|
}
|
|
32990
33069
|
}
|
|
@@ -33063,6 +33142,7 @@ async function rectifyConflictedStory(options) {
|
|
|
33063
33142
|
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
33064
33143
|
const pipelineContext = {
|
|
33065
33144
|
config: config2,
|
|
33145
|
+
effectiveConfig: config2,
|
|
33066
33146
|
prd,
|
|
33067
33147
|
story,
|
|
33068
33148
|
stories: [story],
|
|
@@ -33443,12 +33523,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
33443
33523
|
// src/pipeline/subscribers/events-writer.ts
|
|
33444
33524
|
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
33445
33525
|
import { homedir as homedir7 } from "os";
|
|
33446
|
-
import { basename as basename5, join as
|
|
33526
|
+
import { basename as basename5, join as join47 } from "path";
|
|
33447
33527
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
33448
33528
|
const logger = getSafeLogger();
|
|
33449
33529
|
const project = basename5(workdir);
|
|
33450
|
-
const eventsDir =
|
|
33451
|
-
const eventsFile =
|
|
33530
|
+
const eventsDir = join47(homedir7(), ".nax", "events", project);
|
|
33531
|
+
const eventsFile = join47(eventsDir, "events.jsonl");
|
|
33452
33532
|
let dirReady = false;
|
|
33453
33533
|
const write = (line) => {
|
|
33454
33534
|
(async () => {
|
|
@@ -33608,12 +33688,12 @@ var init_interaction2 = __esm(() => {
|
|
|
33608
33688
|
// src/pipeline/subscribers/registry.ts
|
|
33609
33689
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
33610
33690
|
import { homedir as homedir8 } from "os";
|
|
33611
|
-
import { basename as basename6, join as
|
|
33691
|
+
import { basename as basename6, join as join48 } from "path";
|
|
33612
33692
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
33613
33693
|
const logger = getSafeLogger();
|
|
33614
33694
|
const project = basename6(workdir);
|
|
33615
|
-
const runDir =
|
|
33616
|
-
const metaFile =
|
|
33695
|
+
const runDir = join48(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
33696
|
+
const metaFile = join48(runDir, "meta.json");
|
|
33617
33697
|
const unsub = bus.on("run:started", (_ev) => {
|
|
33618
33698
|
(async () => {
|
|
33619
33699
|
try {
|
|
@@ -33623,8 +33703,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
33623
33703
|
project,
|
|
33624
33704
|
feature,
|
|
33625
33705
|
workdir,
|
|
33626
|
-
statusPath:
|
|
33627
|
-
eventsDir:
|
|
33706
|
+
statusPath: join48(workdir, "nax", "features", feature, "status.json"),
|
|
33707
|
+
eventsDir: join48(workdir, "nax", "features", feature, "runs"),
|
|
33628
33708
|
registeredAt: new Date().toISOString()
|
|
33629
33709
|
};
|
|
33630
33710
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -34253,6 +34333,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
34253
34333
|
});
|
|
34254
34334
|
|
|
34255
34335
|
// src/execution/iteration-runner.ts
|
|
34336
|
+
import { join as join49 } from "path";
|
|
34256
34337
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34257
34338
|
const logger = getSafeLogger();
|
|
34258
34339
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -34278,8 +34359,10 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34278
34359
|
const storyStartTime = Date.now();
|
|
34279
34360
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
34280
34361
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
34362
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join49(ctx.workdir, "nax", "config.json"), story.workdir) : ctx.config;
|
|
34281
34363
|
const pipelineContext = {
|
|
34282
34364
|
config: ctx.config,
|
|
34365
|
+
effectiveConfig,
|
|
34283
34366
|
prd,
|
|
34284
34367
|
story,
|
|
34285
34368
|
stories: storiesToExecute,
|
|
@@ -34350,13 +34433,18 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34350
34433
|
reason: pipelineResult.reason
|
|
34351
34434
|
};
|
|
34352
34435
|
}
|
|
34436
|
+
var _iterationRunnerDeps;
|
|
34353
34437
|
var init_iteration_runner = __esm(() => {
|
|
34438
|
+
init_loader2();
|
|
34354
34439
|
init_logger2();
|
|
34355
34440
|
init_runner();
|
|
34356
34441
|
init_stages();
|
|
34357
34442
|
init_git();
|
|
34358
34443
|
init_dry_run();
|
|
34359
34444
|
init_pipeline_result_handler();
|
|
34445
|
+
_iterationRunnerDeps = {
|
|
34446
|
+
loadConfigForWorkdir
|
|
34447
|
+
};
|
|
34360
34448
|
});
|
|
34361
34449
|
|
|
34362
34450
|
// src/execution/executor-types.ts
|
|
@@ -34449,6 +34537,7 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
34449
34537
|
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
34450
34538
|
const preRunCtx = {
|
|
34451
34539
|
config: ctx.config,
|
|
34540
|
+
effectiveConfig: ctx.config,
|
|
34452
34541
|
prd,
|
|
34453
34542
|
workdir: ctx.workdir,
|
|
34454
34543
|
featureDir: ctx.featureDir,
|
|
@@ -34620,7 +34709,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
34620
34709
|
var init_status_file = () => {};
|
|
34621
34710
|
|
|
34622
34711
|
// src/execution/status-writer.ts
|
|
34623
|
-
import { join as
|
|
34712
|
+
import { join as join50 } from "path";
|
|
34624
34713
|
|
|
34625
34714
|
class StatusWriter {
|
|
34626
34715
|
statusFile;
|
|
@@ -34688,7 +34777,7 @@ class StatusWriter {
|
|
|
34688
34777
|
if (!this._prd)
|
|
34689
34778
|
return;
|
|
34690
34779
|
const safeLogger = getSafeLogger();
|
|
34691
|
-
const featureStatusPath =
|
|
34780
|
+
const featureStatusPath = join50(featureDir, "status.json");
|
|
34692
34781
|
try {
|
|
34693
34782
|
const base = this.getSnapshot(totalCost, iterations);
|
|
34694
34783
|
if (!base) {
|
|
@@ -66017,7 +66106,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
66017
66106
|
init_source();
|
|
66018
66107
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
66019
66108
|
import { homedir as homedir10 } from "os";
|
|
66020
|
-
import { join as
|
|
66109
|
+
import { join as join51 } from "path";
|
|
66021
66110
|
|
|
66022
66111
|
// node_modules/commander/esm.mjs
|
|
66023
66112
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -67243,7 +67332,16 @@ async function planCommand(workdir, config2, options) {
|
|
|
67243
67332
|
const cliAdapter = _deps2.getAgent(agentName);
|
|
67244
67333
|
if (!cliAdapter)
|
|
67245
67334
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
67246
|
-
|
|
67335
|
+
let autoModel;
|
|
67336
|
+
try {
|
|
67337
|
+
const planTier = config2?.plan?.model ?? "balanced";
|
|
67338
|
+
const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
67339
|
+
const models = config2?.models;
|
|
67340
|
+
const entry = models?.[planTier] ?? models?.balanced;
|
|
67341
|
+
if (entry)
|
|
67342
|
+
autoModel = resolveModel2(entry).model;
|
|
67343
|
+
} catch {}
|
|
67344
|
+
rawResponse = await cliAdapter.complete(prompt, { model: autoModel, jsonMode: true, workdir, config: config2 });
|
|
67247
67345
|
try {
|
|
67248
67346
|
const envelope = JSON.parse(rawResponse);
|
|
67249
67347
|
if (envelope?.type === "result" && typeof envelope?.result === "string") {
|
|
@@ -68086,7 +68184,7 @@ async function runsShowCommand(options) {
|
|
|
68086
68184
|
// src/cli/prompts-main.ts
|
|
68087
68185
|
init_logger2();
|
|
68088
68186
|
import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
|
|
68089
|
-
import { join as
|
|
68187
|
+
import { join as join29 } from "path";
|
|
68090
68188
|
|
|
68091
68189
|
// src/pipeline/index.ts
|
|
68092
68190
|
init_runner();
|
|
@@ -68122,7 +68220,7 @@ init_prd();
|
|
|
68122
68220
|
|
|
68123
68221
|
// src/cli/prompts-tdd.ts
|
|
68124
68222
|
init_prompts2();
|
|
68125
|
-
import { join as
|
|
68223
|
+
import { join as join28 } from "path";
|
|
68126
68224
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
68127
68225
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
68128
68226
|
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 +68239,7 @@ ${frontmatter}---
|
|
|
68141
68239
|
|
|
68142
68240
|
${session.prompt}`;
|
|
68143
68241
|
if (outputDir) {
|
|
68144
|
-
const promptFile =
|
|
68242
|
+
const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
|
|
68145
68243
|
await Bun.write(promptFile, fullOutput);
|
|
68146
68244
|
logger.info("cli", "Written TDD prompt file", {
|
|
68147
68245
|
storyId: story.id,
|
|
@@ -68157,7 +68255,7 @@ ${"=".repeat(80)}`);
|
|
|
68157
68255
|
}
|
|
68158
68256
|
}
|
|
68159
68257
|
if (outputDir && ctx.contextMarkdown) {
|
|
68160
|
-
const contextFile =
|
|
68258
|
+
const contextFile = join28(outputDir, `${story.id}.context.md`);
|
|
68161
68259
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
68162
68260
|
const contextOutput = `---
|
|
68163
68261
|
${frontmatter}---
|
|
@@ -68171,12 +68269,12 @@ ${ctx.contextMarkdown}`;
|
|
|
68171
68269
|
async function promptsCommand(options) {
|
|
68172
68270
|
const logger = getLogger();
|
|
68173
68271
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
68174
|
-
const naxDir =
|
|
68272
|
+
const naxDir = join29(workdir, "nax");
|
|
68175
68273
|
if (!existsSync18(naxDir)) {
|
|
68176
68274
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
68177
68275
|
}
|
|
68178
|
-
const featureDir =
|
|
68179
|
-
const prdPath =
|
|
68276
|
+
const featureDir = join29(naxDir, "features", feature);
|
|
68277
|
+
const prdPath = join29(featureDir, "prd.json");
|
|
68180
68278
|
if (!existsSync18(prdPath)) {
|
|
68181
68279
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
68182
68280
|
}
|
|
@@ -68198,6 +68296,7 @@ async function promptsCommand(options) {
|
|
|
68198
68296
|
for (const story of stories) {
|
|
68199
68297
|
const ctx = {
|
|
68200
68298
|
config: config2,
|
|
68299
|
+
effectiveConfig: config2,
|
|
68201
68300
|
prd,
|
|
68202
68301
|
story,
|
|
68203
68302
|
stories: [story],
|
|
@@ -68236,10 +68335,10 @@ ${frontmatter}---
|
|
|
68236
68335
|
|
|
68237
68336
|
${ctx.prompt}`;
|
|
68238
68337
|
if (outputDir) {
|
|
68239
|
-
const promptFile =
|
|
68338
|
+
const promptFile = join29(outputDir, `${story.id}.prompt.md`);
|
|
68240
68339
|
await Bun.write(promptFile, fullOutput);
|
|
68241
68340
|
if (ctx.contextMarkdown) {
|
|
68242
|
-
const contextFile =
|
|
68341
|
+
const contextFile = join29(outputDir, `${story.id}.context.md`);
|
|
68243
68342
|
const contextOutput = `---
|
|
68244
68343
|
${frontmatter}---
|
|
68245
68344
|
|
|
@@ -68303,7 +68402,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
68303
68402
|
}
|
|
68304
68403
|
// src/cli/prompts-init.ts
|
|
68305
68404
|
import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
|
|
68306
|
-
import { join as
|
|
68405
|
+
import { join as join30 } from "path";
|
|
68307
68406
|
var TEMPLATE_ROLES = [
|
|
68308
68407
|
{ file: "test-writer.md", role: "test-writer" },
|
|
68309
68408
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -68327,9 +68426,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
68327
68426
|
`;
|
|
68328
68427
|
async function promptsInitCommand(options) {
|
|
68329
68428
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
68330
|
-
const templatesDir =
|
|
68429
|
+
const templatesDir = join30(workdir, "nax", "templates");
|
|
68331
68430
|
mkdirSync4(templatesDir, { recursive: true });
|
|
68332
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(
|
|
68431
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync19(join30(templatesDir, f)));
|
|
68333
68432
|
if (existingFiles.length > 0 && !force) {
|
|
68334
68433
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
68335
68434
|
Pass --force to overwrite existing templates.`);
|
|
@@ -68337,7 +68436,7 @@ async function promptsInitCommand(options) {
|
|
|
68337
68436
|
}
|
|
68338
68437
|
const written = [];
|
|
68339
68438
|
for (const template of TEMPLATE_ROLES) {
|
|
68340
|
-
const filePath =
|
|
68439
|
+
const filePath = join30(templatesDir, template.file);
|
|
68341
68440
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
68342
68441
|
const content = TEMPLATE_HEADER + roleBody;
|
|
68343
68442
|
await Bun.write(filePath, content);
|
|
@@ -68353,7 +68452,7 @@ async function promptsInitCommand(options) {
|
|
|
68353
68452
|
return written;
|
|
68354
68453
|
}
|
|
68355
68454
|
async function autoWirePromptsConfig(workdir) {
|
|
68356
|
-
const configPath =
|
|
68455
|
+
const configPath = join30(workdir, "nax.config.json");
|
|
68357
68456
|
if (!existsSync19(configPath)) {
|
|
68358
68457
|
const exampleConfig = JSON.stringify({
|
|
68359
68458
|
prompts: {
|
|
@@ -68518,7 +68617,7 @@ init_config();
|
|
|
68518
68617
|
init_logger2();
|
|
68519
68618
|
init_prd();
|
|
68520
68619
|
import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
|
|
68521
|
-
import { join as
|
|
68620
|
+
import { join as join34 } from "path";
|
|
68522
68621
|
|
|
68523
68622
|
// src/cli/diagnose-analysis.ts
|
|
68524
68623
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -68717,7 +68816,7 @@ function isProcessAlive2(pid) {
|
|
|
68717
68816
|
}
|
|
68718
68817
|
}
|
|
68719
68818
|
async function loadStatusFile2(workdir) {
|
|
68720
|
-
const statusPath =
|
|
68819
|
+
const statusPath = join34(workdir, "nax", "status.json");
|
|
68721
68820
|
if (!existsSync21(statusPath))
|
|
68722
68821
|
return null;
|
|
68723
68822
|
try {
|
|
@@ -68745,7 +68844,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
68745
68844
|
}
|
|
68746
68845
|
}
|
|
68747
68846
|
async function checkLock(workdir) {
|
|
68748
|
-
const lockFile = Bun.file(
|
|
68847
|
+
const lockFile = Bun.file(join34(workdir, "nax.lock"));
|
|
68749
68848
|
if (!await lockFile.exists())
|
|
68750
68849
|
return { lockPresent: false };
|
|
68751
68850
|
try {
|
|
@@ -68763,8 +68862,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
68763
68862
|
const logger = getLogger();
|
|
68764
68863
|
const workdir = options.workdir ?? process.cwd();
|
|
68765
68864
|
const naxSubdir = findProjectDir(workdir);
|
|
68766
|
-
let projectDir = naxSubdir ?
|
|
68767
|
-
if (!projectDir && existsSync21(
|
|
68865
|
+
let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
|
|
68866
|
+
if (!projectDir && existsSync21(join34(workdir, "nax"))) {
|
|
68768
68867
|
projectDir = workdir;
|
|
68769
68868
|
}
|
|
68770
68869
|
if (!projectDir)
|
|
@@ -68775,7 +68874,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
68775
68874
|
if (status2) {
|
|
68776
68875
|
feature = status2.run.feature;
|
|
68777
68876
|
} else {
|
|
68778
|
-
const featuresDir =
|
|
68877
|
+
const featuresDir = join34(projectDir, "nax", "features");
|
|
68779
68878
|
if (!existsSync21(featuresDir))
|
|
68780
68879
|
throw new Error("No features found in project");
|
|
68781
68880
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -68785,8 +68884,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
68785
68884
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
68786
68885
|
}
|
|
68787
68886
|
}
|
|
68788
|
-
const featureDir =
|
|
68789
|
-
const prdPath =
|
|
68887
|
+
const featureDir = join34(projectDir, "nax", "features", feature);
|
|
68888
|
+
const prdPath = join34(featureDir, "prd.json");
|
|
68790
68889
|
if (!existsSync21(prdPath))
|
|
68791
68890
|
throw new Error(`Feature not found: ${feature}`);
|
|
68792
68891
|
const prd = await loadPRD(prdPath);
|
|
@@ -68829,7 +68928,7 @@ init_interaction();
|
|
|
68829
68928
|
init_source();
|
|
68830
68929
|
init_loader2();
|
|
68831
68930
|
import { existsSync as existsSync22 } from "fs";
|
|
68832
|
-
import { join as
|
|
68931
|
+
import { join as join35 } from "path";
|
|
68833
68932
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68834
68933
|
async function generateCommand(options) {
|
|
68835
68934
|
const workdir = process.cwd();
|
|
@@ -68872,7 +68971,7 @@ async function generateCommand(options) {
|
|
|
68872
68971
|
return;
|
|
68873
68972
|
}
|
|
68874
68973
|
if (options.package) {
|
|
68875
|
-
const packageDir =
|
|
68974
|
+
const packageDir = join35(workdir, options.package);
|
|
68876
68975
|
if (dryRun) {
|
|
68877
68976
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
68878
68977
|
}
|
|
@@ -68892,8 +68991,8 @@ async function generateCommand(options) {
|
|
|
68892
68991
|
process.exit(1);
|
|
68893
68992
|
return;
|
|
68894
68993
|
}
|
|
68895
|
-
const contextPath = options.context ?
|
|
68896
|
-
const outputDir = options.output ?
|
|
68994
|
+
const contextPath = options.context ? join35(workdir, options.context) : join35(workdir, "nax/context.md");
|
|
68995
|
+
const outputDir = options.output ? join35(workdir, options.output) : workdir;
|
|
68897
68996
|
const autoInject = !options.noAutoInject;
|
|
68898
68997
|
if (!existsSync22(contextPath)) {
|
|
68899
68998
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
@@ -68998,7 +69097,7 @@ async function generateCommand(options) {
|
|
|
68998
69097
|
// src/cli/config-display.ts
|
|
68999
69098
|
init_loader2();
|
|
69000
69099
|
import { existsSync as existsSync24 } from "fs";
|
|
69001
|
-
import { join as
|
|
69100
|
+
import { join as join37 } from "path";
|
|
69002
69101
|
|
|
69003
69102
|
// src/cli/config-descriptions.ts
|
|
69004
69103
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -69207,7 +69306,7 @@ function deepEqual(a, b) {
|
|
|
69207
69306
|
init_defaults();
|
|
69208
69307
|
init_loader2();
|
|
69209
69308
|
import { existsSync as existsSync23 } from "fs";
|
|
69210
|
-
import { join as
|
|
69309
|
+
import { join as join36 } from "path";
|
|
69211
69310
|
async function loadConfigFile(path14) {
|
|
69212
69311
|
if (!existsSync23(path14))
|
|
69213
69312
|
return null;
|
|
@@ -69229,7 +69328,7 @@ async function loadProjectConfig() {
|
|
|
69229
69328
|
const projectDir = findProjectDir();
|
|
69230
69329
|
if (!projectDir)
|
|
69231
69330
|
return null;
|
|
69232
|
-
const projectPath =
|
|
69331
|
+
const projectPath = join36(projectDir, "config.json");
|
|
69233
69332
|
return await loadConfigFile(projectPath);
|
|
69234
69333
|
}
|
|
69235
69334
|
|
|
@@ -69289,7 +69388,7 @@ async function configCommand(config2, options = {}) {
|
|
|
69289
69388
|
function determineConfigSources() {
|
|
69290
69389
|
const globalPath = globalConfigPath();
|
|
69291
69390
|
const projectDir = findProjectDir();
|
|
69292
|
-
const projectPath = projectDir ?
|
|
69391
|
+
const projectPath = projectDir ? join37(projectDir, "config.json") : null;
|
|
69293
69392
|
return {
|
|
69294
69393
|
global: fileExists(globalPath) ? globalPath : null,
|
|
69295
69394
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -69469,21 +69568,21 @@ async function diagnose(options) {
|
|
|
69469
69568
|
|
|
69470
69569
|
// src/commands/logs.ts
|
|
69471
69570
|
import { existsSync as existsSync26 } from "fs";
|
|
69472
|
-
import { join as
|
|
69571
|
+
import { join as join40 } from "path";
|
|
69473
69572
|
|
|
69474
69573
|
// src/commands/logs-formatter.ts
|
|
69475
69574
|
init_source();
|
|
69476
69575
|
init_formatter();
|
|
69477
69576
|
import { readdirSync as readdirSync7 } from "fs";
|
|
69478
|
-
import { join as
|
|
69577
|
+
import { join as join39 } from "path";
|
|
69479
69578
|
|
|
69480
69579
|
// src/commands/logs-reader.ts
|
|
69481
69580
|
import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
|
|
69482
69581
|
import { readdir as readdir3 } from "fs/promises";
|
|
69483
69582
|
import { homedir as homedir5 } from "os";
|
|
69484
|
-
import { join as
|
|
69583
|
+
import { join as join38 } from "path";
|
|
69485
69584
|
var _deps7 = {
|
|
69486
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
69585
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
|
|
69487
69586
|
};
|
|
69488
69587
|
async function resolveRunFileFromRegistry(runId) {
|
|
69489
69588
|
const runsDir = _deps7.getRunsDir();
|
|
@@ -69495,7 +69594,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
69495
69594
|
}
|
|
69496
69595
|
let matched = null;
|
|
69497
69596
|
for (const entry of entries) {
|
|
69498
|
-
const metaPath =
|
|
69597
|
+
const metaPath = join38(runsDir, entry, "meta.json");
|
|
69499
69598
|
try {
|
|
69500
69599
|
const meta3 = await Bun.file(metaPath).json();
|
|
69501
69600
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -69517,14 +69616,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
69517
69616
|
return null;
|
|
69518
69617
|
}
|
|
69519
69618
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
69520
|
-
return
|
|
69619
|
+
return join38(matched.eventsDir, specificFile ?? files[0]);
|
|
69521
69620
|
}
|
|
69522
69621
|
async function selectRunFile(runsDir) {
|
|
69523
69622
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
69524
69623
|
if (files.length === 0) {
|
|
69525
69624
|
return null;
|
|
69526
69625
|
}
|
|
69527
|
-
return
|
|
69626
|
+
return join38(runsDir, files[0]);
|
|
69528
69627
|
}
|
|
69529
69628
|
async function extractRunSummary(filePath) {
|
|
69530
69629
|
const file2 = Bun.file(filePath);
|
|
@@ -69609,7 +69708,7 @@ Runs:
|
|
|
69609
69708
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
69610
69709
|
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
69710
|
for (const file2 of files) {
|
|
69612
|
-
const filePath =
|
|
69711
|
+
const filePath = join39(runsDir, file2);
|
|
69613
69712
|
const summary = await extractRunSummary(filePath);
|
|
69614
69713
|
const timestamp = file2.replace(".jsonl", "");
|
|
69615
69714
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -69734,7 +69833,7 @@ async function logsCommand(options) {
|
|
|
69734
69833
|
return;
|
|
69735
69834
|
}
|
|
69736
69835
|
const resolved = resolveProject({ dir: options.dir });
|
|
69737
|
-
const naxDir =
|
|
69836
|
+
const naxDir = join40(resolved.projectDir, "nax");
|
|
69738
69837
|
const configPath = resolved.configPath;
|
|
69739
69838
|
const configFile = Bun.file(configPath);
|
|
69740
69839
|
const config2 = await configFile.json();
|
|
@@ -69742,8 +69841,8 @@ async function logsCommand(options) {
|
|
|
69742
69841
|
if (!featureName) {
|
|
69743
69842
|
throw new Error("No feature specified in config.json");
|
|
69744
69843
|
}
|
|
69745
|
-
const featureDir =
|
|
69746
|
-
const runsDir =
|
|
69844
|
+
const featureDir = join40(naxDir, "features", featureName);
|
|
69845
|
+
const runsDir = join40(featureDir, "runs");
|
|
69747
69846
|
if (!existsSync26(runsDir)) {
|
|
69748
69847
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
69749
69848
|
}
|
|
@@ -69768,7 +69867,7 @@ init_config();
|
|
|
69768
69867
|
init_prd();
|
|
69769
69868
|
init_precheck();
|
|
69770
69869
|
import { existsSync as existsSync31 } from "fs";
|
|
69771
|
-
import { join as
|
|
69870
|
+
import { join as join41 } from "path";
|
|
69772
69871
|
async function precheckCommand(options) {
|
|
69773
69872
|
const resolved = resolveProject({
|
|
69774
69873
|
dir: options.dir,
|
|
@@ -69784,9 +69883,9 @@ async function precheckCommand(options) {
|
|
|
69784
69883
|
process.exit(1);
|
|
69785
69884
|
}
|
|
69786
69885
|
}
|
|
69787
|
-
const naxDir =
|
|
69788
|
-
const featureDir =
|
|
69789
|
-
const prdPath =
|
|
69886
|
+
const naxDir = join41(resolved.projectDir, "nax");
|
|
69887
|
+
const featureDir = join41(naxDir, "features", featureName);
|
|
69888
|
+
const prdPath = join41(featureDir, "prd.json");
|
|
69790
69889
|
if (!existsSync31(featureDir)) {
|
|
69791
69890
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
69792
69891
|
process.exit(1);
|
|
@@ -69810,10 +69909,10 @@ async function precheckCommand(options) {
|
|
|
69810
69909
|
init_source();
|
|
69811
69910
|
import { readdir as readdir4 } from "fs/promises";
|
|
69812
69911
|
import { homedir as homedir6 } from "os";
|
|
69813
|
-
import { join as
|
|
69912
|
+
import { join as join42 } from "path";
|
|
69814
69913
|
var DEFAULT_LIMIT = 20;
|
|
69815
69914
|
var _deps9 = {
|
|
69816
|
-
getRunsDir: () =>
|
|
69915
|
+
getRunsDir: () => join42(homedir6(), ".nax", "runs")
|
|
69817
69916
|
};
|
|
69818
69917
|
function formatDuration3(ms) {
|
|
69819
69918
|
if (ms <= 0)
|
|
@@ -69865,7 +69964,7 @@ async function runsCommand(options = {}) {
|
|
|
69865
69964
|
}
|
|
69866
69965
|
const rows = [];
|
|
69867
69966
|
for (const entry of entries) {
|
|
69868
|
-
const metaPath =
|
|
69967
|
+
const metaPath = join42(runsDir, entry, "meta.json");
|
|
69869
69968
|
let meta3;
|
|
69870
69969
|
try {
|
|
69871
69970
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -69942,7 +70041,7 @@ async function runsCommand(options = {}) {
|
|
|
69942
70041
|
|
|
69943
70042
|
// src/commands/unlock.ts
|
|
69944
70043
|
init_source();
|
|
69945
|
-
import { join as
|
|
70044
|
+
import { join as join43 } from "path";
|
|
69946
70045
|
function isProcessAlive3(pid) {
|
|
69947
70046
|
try {
|
|
69948
70047
|
process.kill(pid, 0);
|
|
@@ -69957,7 +70056,7 @@ function formatLockAge(ageMs) {
|
|
|
69957
70056
|
}
|
|
69958
70057
|
async function unlockCommand(options) {
|
|
69959
70058
|
const workdir = options.dir ?? process.cwd();
|
|
69960
|
-
const lockPath =
|
|
70059
|
+
const lockPath = join43(workdir, "nax.lock");
|
|
69961
70060
|
const lockFile = Bun.file(lockPath);
|
|
69962
70061
|
const exists = await lockFile.exists();
|
|
69963
70062
|
if (!exists) {
|
|
@@ -77792,15 +77891,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
77792
77891
|
}
|
|
77793
77892
|
return;
|
|
77794
77893
|
}
|
|
77795
|
-
const naxDir =
|
|
77894
|
+
const naxDir = join51(workdir, "nax");
|
|
77796
77895
|
if (existsSync34(naxDir) && !options.force) {
|
|
77797
77896
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
77798
77897
|
return;
|
|
77799
77898
|
}
|
|
77800
|
-
mkdirSync6(
|
|
77801
|
-
mkdirSync6(
|
|
77802
|
-
await Bun.write(
|
|
77803
|
-
await Bun.write(
|
|
77899
|
+
mkdirSync6(join51(naxDir, "features"), { recursive: true });
|
|
77900
|
+
mkdirSync6(join51(naxDir, "hooks"), { recursive: true });
|
|
77901
|
+
await Bun.write(join51(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
77902
|
+
await Bun.write(join51(naxDir, "hooks.json"), JSON.stringify({
|
|
77804
77903
|
hooks: {
|
|
77805
77904
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
77806
77905
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -77808,12 +77907,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
77808
77907
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
77809
77908
|
}
|
|
77810
77909
|
}, null, 2));
|
|
77811
|
-
await Bun.write(
|
|
77910
|
+
await Bun.write(join51(naxDir, ".gitignore"), `# nax temp files
|
|
77812
77911
|
*.tmp
|
|
77813
77912
|
.paused.json
|
|
77814
77913
|
.nax-verifier-verdict.json
|
|
77815
77914
|
`);
|
|
77816
|
-
await Bun.write(
|
|
77915
|
+
await Bun.write(join51(naxDir, "context.md"), `# Project Context
|
|
77817
77916
|
|
|
77818
77917
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
77819
77918
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -77939,8 +78038,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77939
78038
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77940
78039
|
process.exit(1);
|
|
77941
78040
|
}
|
|
77942
|
-
const featureDir =
|
|
77943
|
-
const prdPath =
|
|
78041
|
+
const featureDir = join51(naxDir, "features", options.feature);
|
|
78042
|
+
const prdPath = join51(featureDir, "prd.json");
|
|
77944
78043
|
if (options.plan && options.from) {
|
|
77945
78044
|
if (existsSync34(prdPath) && !options.force) {
|
|
77946
78045
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -77962,10 +78061,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77962
78061
|
}
|
|
77963
78062
|
}
|
|
77964
78063
|
try {
|
|
77965
|
-
const planLogDir =
|
|
78064
|
+
const planLogDir = join51(featureDir, "plan");
|
|
77966
78065
|
mkdirSync6(planLogDir, { recursive: true });
|
|
77967
78066
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77968
|
-
const planLogPath =
|
|
78067
|
+
const planLogPath = join51(planLogDir, `${planLogId}.jsonl`);
|
|
77969
78068
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77970
78069
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77971
78070
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -78003,10 +78102,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78003
78102
|
process.exit(1);
|
|
78004
78103
|
}
|
|
78005
78104
|
resetLogger();
|
|
78006
|
-
const runsDir =
|
|
78105
|
+
const runsDir = join51(featureDir, "runs");
|
|
78007
78106
|
mkdirSync6(runsDir, { recursive: true });
|
|
78008
78107
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78009
|
-
const logFilePath =
|
|
78108
|
+
const logFilePath = join51(runsDir, `${runId}.jsonl`);
|
|
78010
78109
|
const isTTY = process.stdout.isTTY ?? false;
|
|
78011
78110
|
const headlessFlag = options.headless ?? false;
|
|
78012
78111
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -78022,7 +78121,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78022
78121
|
config2.autoMode.defaultAgent = options.agent;
|
|
78023
78122
|
}
|
|
78024
78123
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
78025
|
-
const globalNaxDir =
|
|
78124
|
+
const globalNaxDir = join51(homedir10(), ".nax");
|
|
78026
78125
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
78027
78126
|
const eventEmitter = new PipelineEventEmitter;
|
|
78028
78127
|
let tuiInstance;
|
|
@@ -78045,7 +78144,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78045
78144
|
} else {
|
|
78046
78145
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
78047
78146
|
}
|
|
78048
|
-
const statusFilePath =
|
|
78147
|
+
const statusFilePath = join51(workdir, "nax", "status.json");
|
|
78049
78148
|
let parallel;
|
|
78050
78149
|
if (options.parallel !== undefined) {
|
|
78051
78150
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -78071,7 +78170,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78071
78170
|
headless: useHeadless,
|
|
78072
78171
|
skipPrecheck: options.skipPrecheck ?? false
|
|
78073
78172
|
});
|
|
78074
|
-
const latestSymlink =
|
|
78173
|
+
const latestSymlink = join51(runsDir, "latest.jsonl");
|
|
78075
78174
|
try {
|
|
78076
78175
|
if (existsSync34(latestSymlink)) {
|
|
78077
78176
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -78109,9 +78208,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78109
78208
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78110
78209
|
process.exit(1);
|
|
78111
78210
|
}
|
|
78112
|
-
const featureDir =
|
|
78211
|
+
const featureDir = join51(naxDir, "features", name);
|
|
78113
78212
|
mkdirSync6(featureDir, { recursive: true });
|
|
78114
|
-
await Bun.write(
|
|
78213
|
+
await Bun.write(join51(featureDir, "spec.md"), `# Feature: ${name}
|
|
78115
78214
|
|
|
78116
78215
|
## Overview
|
|
78117
78216
|
|
|
@@ -78119,7 +78218,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78119
78218
|
|
|
78120
78219
|
## Acceptance Criteria
|
|
78121
78220
|
`);
|
|
78122
|
-
await Bun.write(
|
|
78221
|
+
await Bun.write(join51(featureDir, "plan.md"), `# Plan: ${name}
|
|
78123
78222
|
|
|
78124
78223
|
## Architecture
|
|
78125
78224
|
|
|
@@ -78127,7 +78226,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78127
78226
|
|
|
78128
78227
|
## Dependencies
|
|
78129
78228
|
`);
|
|
78130
|
-
await Bun.write(
|
|
78229
|
+
await Bun.write(join51(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
78131
78230
|
|
|
78132
78231
|
## US-001: [Title]
|
|
78133
78232
|
|
|
@@ -78136,7 +78235,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78136
78235
|
### Acceptance Criteria
|
|
78137
78236
|
- [ ] Criterion 1
|
|
78138
78237
|
`);
|
|
78139
|
-
await Bun.write(
|
|
78238
|
+
await Bun.write(join51(featureDir, "progress.txt"), `# Progress: ${name}
|
|
78140
78239
|
|
|
78141
78240
|
Created: ${new Date().toISOString()}
|
|
78142
78241
|
|
|
@@ -78164,7 +78263,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78164
78263
|
console.error(source_default.red("nax not initialized."));
|
|
78165
78264
|
process.exit(1);
|
|
78166
78265
|
}
|
|
78167
|
-
const featuresDir =
|
|
78266
|
+
const featuresDir = join51(naxDir, "features");
|
|
78168
78267
|
if (!existsSync34(featuresDir)) {
|
|
78169
78268
|
console.log(source_default.dim("No features yet."));
|
|
78170
78269
|
return;
|
|
@@ -78179,7 +78278,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78179
78278
|
Features:
|
|
78180
78279
|
`));
|
|
78181
78280
|
for (const name of entries) {
|
|
78182
|
-
const prdPath =
|
|
78281
|
+
const prdPath = join51(featuresDir, name, "prd.json");
|
|
78183
78282
|
if (existsSync34(prdPath)) {
|
|
78184
78283
|
const prd = await loadPRD(prdPath);
|
|
78185
78284
|
const c = countStories(prd);
|
|
@@ -78210,10 +78309,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
78210
78309
|
process.exit(1);
|
|
78211
78310
|
}
|
|
78212
78311
|
const config2 = await loadConfig(workdir);
|
|
78213
|
-
const featureLogDir =
|
|
78312
|
+
const featureLogDir = join51(naxDir, "features", options.feature, "plan");
|
|
78214
78313
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
78215
78314
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78216
|
-
const planLogPath =
|
|
78315
|
+
const planLogPath = join51(featureLogDir, `${planLogId}.jsonl`);
|
|
78217
78316
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78218
78317
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78219
78318
|
try {
|
|
@@ -78250,7 +78349,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78250
78349
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78251
78350
|
process.exit(1);
|
|
78252
78351
|
}
|
|
78253
|
-
const featureDir =
|
|
78352
|
+
const featureDir = join51(naxDir, "features", options.feature);
|
|
78254
78353
|
if (!existsSync34(featureDir)) {
|
|
78255
78354
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
78256
78355
|
process.exit(1);
|
|
@@ -78266,7 +78365,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78266
78365
|
specPath: options.from,
|
|
78267
78366
|
reclassify: options.reclassify
|
|
78268
78367
|
});
|
|
78269
|
-
const prdPath =
|
|
78368
|
+
const prdPath = join51(featureDir, "prd.json");
|
|
78270
78369
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
78271
78370
|
const c = countStories(prd);
|
|
78272
78371
|
console.log(source_default.green(`
|