@nathapp/nax 0.48.3 → 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 +306 -189
- 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 +92 -24
- package/src/pipeline/types.ts +7 -0
- package/src/review/runner.ts +20 -5
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) {
|
|
@@ -24397,8 +24430,23 @@ async function runReview(config2, workdir, executionConfig) {
|
|
|
24397
24430
|
const checks3 = [];
|
|
24398
24431
|
let firstFailure;
|
|
24399
24432
|
const allUncommittedFiles = await _deps4.getUncommittedFiles(workdir);
|
|
24400
|
-
const
|
|
24401
|
-
|
|
24433
|
+
const NAX_RUNTIME_PATTERNS = [
|
|
24434
|
+
/nax\.lock$/,
|
|
24435
|
+
/nax\/metrics\.json$/,
|
|
24436
|
+
/nax\/status\.json$/,
|
|
24437
|
+
/nax\/features\/[^/]+\/status\.json$/,
|
|
24438
|
+
/nax\/features\/[^/]+\/prd\.json$/,
|
|
24439
|
+
/nax\/features\/[^/]+\/runs\//,
|
|
24440
|
+
/nax\/features\/[^/]+\/plan\//,
|
|
24441
|
+
/nax\/features\/[^/]+\/acp-sessions\.json$/,
|
|
24442
|
+
/nax\/features\/[^/]+\/interactions\//,
|
|
24443
|
+
/nax\/features\/[^/]+\/progress\.txt$/,
|
|
24444
|
+
/nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
24445
|
+
/\.nax-verifier-verdict\.json$/,
|
|
24446
|
+
/\.nax-pids$/,
|
|
24447
|
+
/\.nax-wt\//
|
|
24448
|
+
];
|
|
24449
|
+
const uncommittedFiles = allUncommittedFiles.filter((f) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(f)));
|
|
24402
24450
|
if (uncommittedFiles.length > 0) {
|
|
24403
24451
|
const fileList = uncommittedFiles.join(", ");
|
|
24404
24452
|
logger?.warn("review", `Uncommitted changes detected before review: ${fileList}`);
|
|
@@ -24562,12 +24610,13 @@ var init_review = __esm(() => {
|
|
|
24562
24610
|
init_orchestrator();
|
|
24563
24611
|
reviewStage = {
|
|
24564
24612
|
name: "review",
|
|
24565
|
-
enabled: (ctx) => ctx.config.review.enabled,
|
|
24613
|
+
enabled: (ctx) => (ctx.effectiveConfig ?? ctx.config).review.enabled,
|
|
24566
24614
|
async execute(ctx) {
|
|
24567
24615
|
const logger = getLogger();
|
|
24616
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24568
24617
|
logger.info("review", "Running review phase", { storyId: ctx.story.id });
|
|
24569
24618
|
const effectiveWorkdir = ctx.story.workdir ? join17(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24570
|
-
const result = await reviewOrchestrator.review(
|
|
24619
|
+
const result = await reviewOrchestrator.review(effectiveConfig.review, effectiveWorkdir, effectiveConfig.execution, ctx.plugins, ctx.storyGitRef, ctx.story.workdir);
|
|
24571
24620
|
ctx.reviewResult = result.builtIn;
|
|
24572
24621
|
if (!result.success) {
|
|
24573
24622
|
const allFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|
|
@@ -24575,8 +24624,8 @@ var init_review = __esm(() => {
|
|
|
24575
24624
|
ctx.reviewFindings = allFindings;
|
|
24576
24625
|
}
|
|
24577
24626
|
if (result.pluginFailed) {
|
|
24578
|
-
if (ctx.interaction && isTriggerEnabled("security-review",
|
|
24579
|
-
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);
|
|
24580
24629
|
if (!shouldContinue) {
|
|
24581
24630
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
24582
24631
|
return { action: "fail", reason: `Review failed: ${result.failureReason}` };
|
|
@@ -24587,11 +24636,11 @@ var init_review = __esm(() => {
|
|
|
24587
24636
|
logger.error("review", `Plugin reviewer failed: ${result.failureReason}`, { storyId: ctx.story.id });
|
|
24588
24637
|
return { action: "fail", reason: `Review failed: ${result.failureReason}` };
|
|
24589
24638
|
}
|
|
24590
|
-
logger.warn("review", "Review failed (built-in checks) \u2014
|
|
24639
|
+
logger.warn("review", "Review failed (built-in checks) \u2014 handing off to autofix", {
|
|
24591
24640
|
reason: result.failureReason,
|
|
24592
24641
|
storyId: ctx.story.id
|
|
24593
24642
|
});
|
|
24594
|
-
return { action: "
|
|
24643
|
+
return { action: "continue" };
|
|
24595
24644
|
}
|
|
24596
24645
|
logger.info("review", "Review passed", {
|
|
24597
24646
|
durationMs: result.builtIn.totalDurationMs,
|
|
@@ -24606,6 +24655,7 @@ var init_review = __esm(() => {
|
|
|
24606
24655
|
});
|
|
24607
24656
|
|
|
24608
24657
|
// src/pipeline/stages/autofix.ts
|
|
24658
|
+
import { join as join18 } from "path";
|
|
24609
24659
|
async function runCommand(cmd, cwd) {
|
|
24610
24660
|
const parts = cmd.split(/\s+/);
|
|
24611
24661
|
const proc = Bun.spawn(parts, { cwd, stdout: "pipe", stderr: "pipe" });
|
|
@@ -24648,7 +24698,8 @@ Commit your fixes when done.`;
|
|
|
24648
24698
|
}
|
|
24649
24699
|
async function runAgentRectification(ctx) {
|
|
24650
24700
|
const logger = getLogger();
|
|
24651
|
-
const
|
|
24701
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24702
|
+
const maxAttempts = effectiveConfig.quality.autofix?.maxAttempts ?? 2;
|
|
24652
24703
|
const failedChecks = collectFailedChecks(ctx);
|
|
24653
24704
|
if (failedChecks.length === 0) {
|
|
24654
24705
|
logger.debug("autofix", "No failed checks found \u2014 skipping agent rectification", { storyId: ctx.story.id });
|
|
@@ -24705,6 +24756,7 @@ var autofixStage, _autofixDeps;
|
|
|
24705
24756
|
var init_autofix = __esm(() => {
|
|
24706
24757
|
init_agents();
|
|
24707
24758
|
init_config();
|
|
24759
|
+
init_loader2();
|
|
24708
24760
|
init_logger2();
|
|
24709
24761
|
init_event_bus();
|
|
24710
24762
|
autofixStage = {
|
|
@@ -24714,7 +24766,7 @@ var init_autofix = __esm(() => {
|
|
|
24714
24766
|
return false;
|
|
24715
24767
|
if (ctx.reviewResult.success)
|
|
24716
24768
|
return false;
|
|
24717
|
-
const autofixEnabled = ctx.config.quality.autofix?.enabled ?? true;
|
|
24769
|
+
const autofixEnabled = (ctx.effectiveConfig ?? ctx.config).quality.autofix?.enabled ?? true;
|
|
24718
24770
|
return autofixEnabled;
|
|
24719
24771
|
},
|
|
24720
24772
|
skipReason(ctx) {
|
|
@@ -24728,12 +24780,14 @@ var init_autofix = __esm(() => {
|
|
|
24728
24780
|
if (!reviewResult || reviewResult.success) {
|
|
24729
24781
|
return { action: "continue" };
|
|
24730
24782
|
}
|
|
24731
|
-
const
|
|
24732
|
-
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;
|
|
24733
24787
|
if (lintFixCmd || formatFixCmd) {
|
|
24734
24788
|
if (lintFixCmd) {
|
|
24735
24789
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: lintFixCmd });
|
|
24736
|
-
const lintResult = await _autofixDeps.runCommand(lintFixCmd,
|
|
24790
|
+
const lintResult = await _autofixDeps.runCommand(lintFixCmd, effectiveWorkdir);
|
|
24737
24791
|
logger.debug("autofix", `lintFix exit=${lintResult.exitCode}`, { storyId: ctx.story.id });
|
|
24738
24792
|
if (lintResult.exitCode !== 0) {
|
|
24739
24793
|
logger.warn("autofix", "lintFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -24744,7 +24798,7 @@ var init_autofix = __esm(() => {
|
|
|
24744
24798
|
}
|
|
24745
24799
|
if (formatFixCmd) {
|
|
24746
24800
|
pipelineEventBus.emit({ type: "autofix:started", storyId: ctx.story.id, command: formatFixCmd });
|
|
24747
|
-
const fmtResult = await _autofixDeps.runCommand(formatFixCmd,
|
|
24801
|
+
const fmtResult = await _autofixDeps.runCommand(formatFixCmd, effectiveWorkdir);
|
|
24748
24802
|
logger.debug("autofix", `formatFix exit=${fmtResult.exitCode}`, { storyId: ctx.story.id });
|
|
24749
24803
|
if (fmtResult.exitCode !== 0) {
|
|
24750
24804
|
logger.warn("autofix", "formatFix command failed \u2014 may not have fixed all issues", {
|
|
@@ -24773,15 +24827,15 @@ var init_autofix = __esm(() => {
|
|
|
24773
24827
|
return { action: "escalate", reason: "Autofix exhausted: review still failing after fix attempts" };
|
|
24774
24828
|
}
|
|
24775
24829
|
};
|
|
24776
|
-
_autofixDeps = { runCommand, recheckReview, runAgentRectification };
|
|
24830
|
+
_autofixDeps = { runCommand, recheckReview, runAgentRectification, loadConfigForWorkdir };
|
|
24777
24831
|
});
|
|
24778
24832
|
|
|
24779
24833
|
// src/execution/progress.ts
|
|
24780
24834
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
24781
|
-
import { join as
|
|
24835
|
+
import { join as join19 } from "path";
|
|
24782
24836
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
24783
24837
|
mkdirSync2(featureDir, { recursive: true });
|
|
24784
|
-
const progressPath =
|
|
24838
|
+
const progressPath = join19(featureDir, "progress.txt");
|
|
24785
24839
|
const timestamp = new Date().toISOString();
|
|
24786
24840
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
24787
24841
|
`;
|
|
@@ -24865,7 +24919,7 @@ function estimateTokens(text) {
|
|
|
24865
24919
|
|
|
24866
24920
|
// src/constitution/loader.ts
|
|
24867
24921
|
import { existsSync as existsSync15 } from "fs";
|
|
24868
|
-
import { join as
|
|
24922
|
+
import { join as join20 } from "path";
|
|
24869
24923
|
function truncateToTokens(text, maxTokens) {
|
|
24870
24924
|
const maxChars = maxTokens * 3;
|
|
24871
24925
|
if (text.length <= maxChars) {
|
|
@@ -24887,7 +24941,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
24887
24941
|
}
|
|
24888
24942
|
let combinedContent = "";
|
|
24889
24943
|
if (!config2.skipGlobal) {
|
|
24890
|
-
const globalPath =
|
|
24944
|
+
const globalPath = join20(globalConfigDir(), config2.path);
|
|
24891
24945
|
if (existsSync15(globalPath)) {
|
|
24892
24946
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
24893
24947
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -24897,7 +24951,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
24897
24951
|
}
|
|
24898
24952
|
}
|
|
24899
24953
|
}
|
|
24900
|
-
const projectPath =
|
|
24954
|
+
const projectPath = join20(projectDir, config2.path);
|
|
24901
24955
|
if (existsSync15(projectPath)) {
|
|
24902
24956
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
24903
24957
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -25869,7 +25923,7 @@ var init_helpers = __esm(() => {
|
|
|
25869
25923
|
});
|
|
25870
25924
|
|
|
25871
25925
|
// src/pipeline/stages/context.ts
|
|
25872
|
-
import { join as
|
|
25926
|
+
import { join as join21 } from "path";
|
|
25873
25927
|
var contextStage;
|
|
25874
25928
|
var init_context2 = __esm(() => {
|
|
25875
25929
|
init_helpers();
|
|
@@ -25879,7 +25933,7 @@ var init_context2 = __esm(() => {
|
|
|
25879
25933
|
enabled: () => true,
|
|
25880
25934
|
async execute(ctx) {
|
|
25881
25935
|
const logger = getLogger();
|
|
25882
|
-
const packageWorkdir = ctx.story.workdir ?
|
|
25936
|
+
const packageWorkdir = ctx.story.workdir ? join21(ctx.workdir, ctx.story.workdir) : undefined;
|
|
25883
25937
|
const result = await buildStoryContextFull(ctx.prd, ctx.story, ctx.config, packageWorkdir);
|
|
25884
25938
|
if (result) {
|
|
25885
25939
|
ctx.contextMarkdown = result.markdown;
|
|
@@ -26011,14 +26065,14 @@ var init_isolation = __esm(() => {
|
|
|
26011
26065
|
|
|
26012
26066
|
// src/context/greenfield.ts
|
|
26013
26067
|
import { readdir } from "fs/promises";
|
|
26014
|
-
import { join as
|
|
26068
|
+
import { join as join22 } from "path";
|
|
26015
26069
|
async function scanForTestFiles(dir, testPattern, isRootCall = true) {
|
|
26016
26070
|
const results = [];
|
|
26017
26071
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
26018
26072
|
try {
|
|
26019
26073
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
26020
26074
|
for (const entry of entries) {
|
|
26021
|
-
const fullPath =
|
|
26075
|
+
const fullPath = join22(dir, entry.name);
|
|
26022
26076
|
if (entry.isDirectory()) {
|
|
26023
26077
|
if (ignoreDirs.has(entry.name))
|
|
26024
26078
|
continue;
|
|
@@ -26430,13 +26484,13 @@ function parseTestOutput(output, exitCode) {
|
|
|
26430
26484
|
|
|
26431
26485
|
// src/verification/runners.ts
|
|
26432
26486
|
import { existsSync as existsSync16 } from "fs";
|
|
26433
|
-
import { join as
|
|
26487
|
+
import { join as join23 } from "path";
|
|
26434
26488
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
26435
26489
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
26436
26490
|
return { success: true, missingFiles: [] };
|
|
26437
26491
|
const missingFiles = [];
|
|
26438
26492
|
for (const file2 of expectedFiles) {
|
|
26439
|
-
if (!existsSync16(
|
|
26493
|
+
if (!existsSync16(join23(workingDirectory, file2)))
|
|
26440
26494
|
missingFiles.push(file2);
|
|
26441
26495
|
}
|
|
26442
26496
|
if (missingFiles.length > 0) {
|
|
@@ -27121,13 +27175,13 @@ var exports_loader = {};
|
|
|
27121
27175
|
__export(exports_loader, {
|
|
27122
27176
|
loadOverride: () => loadOverride
|
|
27123
27177
|
});
|
|
27124
|
-
import { join as
|
|
27178
|
+
import { join as join24 } from "path";
|
|
27125
27179
|
async function loadOverride(role, workdir, config2) {
|
|
27126
27180
|
const overridePath = config2.prompts?.overrides?.[role];
|
|
27127
27181
|
if (!overridePath) {
|
|
27128
27182
|
return null;
|
|
27129
27183
|
}
|
|
27130
|
-
const absolutePath =
|
|
27184
|
+
const absolutePath = join24(workdir, overridePath);
|
|
27131
27185
|
const file2 = Bun.file(absolutePath);
|
|
27132
27186
|
if (!await file2.exists()) {
|
|
27133
27187
|
return null;
|
|
@@ -27949,11 +28003,11 @@ var init_tdd = __esm(() => {
|
|
|
27949
28003
|
|
|
27950
28004
|
// src/pipeline/stages/execution.ts
|
|
27951
28005
|
import { existsSync as existsSync17 } from "fs";
|
|
27952
|
-
import { join as
|
|
28006
|
+
import { join as join25 } from "path";
|
|
27953
28007
|
function resolveStoryWorkdir(repoRoot, storyWorkdir) {
|
|
27954
28008
|
if (!storyWorkdir)
|
|
27955
28009
|
return repoRoot;
|
|
27956
|
-
const resolved =
|
|
28010
|
+
const resolved = join25(repoRoot, storyWorkdir);
|
|
27957
28011
|
if (!existsSync17(resolved)) {
|
|
27958
28012
|
throw new Error(`[execution] story.workdir "${storyWorkdir}" does not exist at "${resolved}"`);
|
|
27959
28013
|
}
|
|
@@ -27983,6 +28037,9 @@ function routeTddFailure(failureCategory, isLiteMode, ctx, reviewReason) {
|
|
|
27983
28037
|
if (failureCategory === "session-failure" || failureCategory === "tests-failing" || failureCategory === "verifier-rejected") {
|
|
27984
28038
|
return { action: "escalate" };
|
|
27985
28039
|
}
|
|
28040
|
+
if (failureCategory === "greenfield-no-tests") {
|
|
28041
|
+
return { action: "escalate" };
|
|
28042
|
+
}
|
|
27986
28043
|
return {
|
|
27987
28044
|
action: "pause",
|
|
27988
28045
|
reason: reviewReason || "Three-session TDD requires review"
|
|
@@ -28423,13 +28480,14 @@ var init_prompt = __esm(() => {
|
|
|
28423
28480
|
async execute(ctx) {
|
|
28424
28481
|
const logger = getLogger();
|
|
28425
28482
|
const isBatch = ctx.stories.length > 1;
|
|
28483
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
28426
28484
|
let prompt;
|
|
28427
28485
|
if (isBatch) {
|
|
28428
|
-
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);
|
|
28429
28487
|
prompt = await builder.build();
|
|
28430
28488
|
} else {
|
|
28431
28489
|
const role = "tdd-simple";
|
|
28432
|
-
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);
|
|
28433
28491
|
prompt = await builder.build();
|
|
28434
28492
|
}
|
|
28435
28493
|
ctx.prompt = prompt;
|
|
@@ -28773,13 +28831,14 @@ var init_rectify = __esm(() => {
|
|
|
28773
28831
|
attempt: rectifyAttempt,
|
|
28774
28832
|
testOutput
|
|
28775
28833
|
});
|
|
28776
|
-
const
|
|
28834
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
28835
|
+
const testCommand = effectiveConfig.review?.commands?.test ?? effectiveConfig.quality.commands.test ?? "bun test";
|
|
28777
28836
|
const fixed = await _rectifyDeps.runRectificationLoop({
|
|
28778
28837
|
config: ctx.config,
|
|
28779
28838
|
workdir: ctx.workdir,
|
|
28780
28839
|
story: ctx.story,
|
|
28781
28840
|
testCommand,
|
|
28782
|
-
timeoutSeconds:
|
|
28841
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
28783
28842
|
testOutput
|
|
28784
28843
|
});
|
|
28785
28844
|
pipelineEventBus.emit({
|
|
@@ -29307,31 +29366,34 @@ var init_regression2 = __esm(() => {
|
|
|
29307
29366
|
regressionStage = {
|
|
29308
29367
|
name: "regression",
|
|
29309
29368
|
enabled(ctx) {
|
|
29310
|
-
const
|
|
29369
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29370
|
+
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29311
29371
|
if (mode !== "per-story")
|
|
29312
29372
|
return false;
|
|
29313
29373
|
if (ctx.verifyResult && !ctx.verifyResult.success)
|
|
29314
29374
|
return false;
|
|
29315
|
-
const gateEnabled =
|
|
29375
|
+
const gateEnabled = effectiveConfig.execution.regressionGate?.enabled ?? true;
|
|
29316
29376
|
return gateEnabled;
|
|
29317
29377
|
},
|
|
29318
29378
|
skipReason(ctx) {
|
|
29319
|
-
const
|
|
29379
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29380
|
+
const mode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29320
29381
|
if (mode !== "per-story")
|
|
29321
29382
|
return `not needed (regression mode is '${mode}', not 'per-story')`;
|
|
29322
29383
|
return "disabled (regression gate not enabled in config)";
|
|
29323
29384
|
},
|
|
29324
29385
|
async execute(ctx) {
|
|
29325
29386
|
const logger = getLogger();
|
|
29326
|
-
const
|
|
29327
|
-
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;
|
|
29328
29390
|
logger.info("regression", "Running full-suite regression gate", { storyId: ctx.story.id });
|
|
29329
29391
|
const verifyCtx = {
|
|
29330
29392
|
workdir: ctx.workdir,
|
|
29331
29393
|
testCommand,
|
|
29332
29394
|
timeoutSeconds,
|
|
29333
29395
|
storyId: ctx.story.id,
|
|
29334
|
-
acceptOnTimeout:
|
|
29396
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true,
|
|
29335
29397
|
config: ctx.config
|
|
29336
29398
|
};
|
|
29337
29399
|
const result = await _regressionStageDeps.verifyRegression(verifyCtx);
|
|
@@ -29366,7 +29428,7 @@ var init_regression2 = __esm(() => {
|
|
|
29366
29428
|
});
|
|
29367
29429
|
|
|
29368
29430
|
// src/pipeline/stages/routing.ts
|
|
29369
|
-
import { join as
|
|
29431
|
+
import { join as join26 } from "path";
|
|
29370
29432
|
async function runDecompose(story, prd, config2, _workdir, agentGetFn) {
|
|
29371
29433
|
const naxDecompose = config2.decompose;
|
|
29372
29434
|
const builderConfig = {
|
|
@@ -29440,7 +29502,7 @@ var init_routing2 = __esm(() => {
|
|
|
29440
29502
|
}
|
|
29441
29503
|
const greenfieldDetectionEnabled = ctx.config.tdd.greenfieldDetection ?? true;
|
|
29442
29504
|
if (greenfieldDetectionEnabled && routing.testStrategy.startsWith("three-session-tdd")) {
|
|
29443
|
-
const greenfieldScanDir = ctx.story.workdir ?
|
|
29505
|
+
const greenfieldScanDir = ctx.story.workdir ? join26(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29444
29506
|
const isGreenfield = await _routingDeps.isGreenfieldStory(ctx.story, greenfieldScanDir);
|
|
29445
29507
|
if (isGreenfield) {
|
|
29446
29508
|
logger.info("routing", "Greenfield detected \u2014 forcing test-after strategy", {
|
|
@@ -29537,7 +29599,7 @@ var init_crash_detector = __esm(() => {
|
|
|
29537
29599
|
});
|
|
29538
29600
|
|
|
29539
29601
|
// src/pipeline/stages/verify.ts
|
|
29540
|
-
import { join as
|
|
29602
|
+
import { join as join27 } from "path";
|
|
29541
29603
|
function coerceSmartTestRunner(val) {
|
|
29542
29604
|
if (val === undefined || val === true)
|
|
29543
29605
|
return DEFAULT_SMART_RUNNER_CONFIG2;
|
|
@@ -29551,13 +29613,30 @@ function buildScopedCommand2(testFiles, baseCommand, testScopedTemplate) {
|
|
|
29551
29613
|
}
|
|
29552
29614
|
return _smartRunnerDeps.buildSmartTestCommand(testFiles, baseCommand);
|
|
29553
29615
|
}
|
|
29616
|
+
async function readPackageName(dir) {
|
|
29617
|
+
try {
|
|
29618
|
+
const content = await Bun.file(join27(dir, "package.json")).json();
|
|
29619
|
+
return typeof content.name === "string" ? content.name : null;
|
|
29620
|
+
} catch {
|
|
29621
|
+
return null;
|
|
29622
|
+
}
|
|
29623
|
+
}
|
|
29624
|
+
async function resolvePackageTemplate(template, packageDir) {
|
|
29625
|
+
if (!template.includes("{{package}}"))
|
|
29626
|
+
return template;
|
|
29627
|
+
const name = await _verifyDeps.readPackageName(packageDir);
|
|
29628
|
+
if (name === null) {
|
|
29629
|
+
return null;
|
|
29630
|
+
}
|
|
29631
|
+
return template.replaceAll("{{package}}", name);
|
|
29632
|
+
}
|
|
29554
29633
|
var DEFAULT_SMART_RUNNER_CONFIG2, verifyStage, _verifyDeps;
|
|
29555
29634
|
var init_verify = __esm(() => {
|
|
29556
|
-
init_loader2();
|
|
29557
29635
|
init_logger2();
|
|
29558
29636
|
init_crash_detector();
|
|
29559
29637
|
init_runners();
|
|
29560
29638
|
init_smart_runner();
|
|
29639
|
+
init_scoped();
|
|
29561
29640
|
DEFAULT_SMART_RUNNER_CONFIG2 = {
|
|
29562
29641
|
enabled: true,
|
|
29563
29642
|
testFilePatterns: ["test/**/*.test.ts"],
|
|
@@ -29569,7 +29648,7 @@ var init_verify = __esm(() => {
|
|
|
29569
29648
|
skipReason: () => "not needed (full-suite gate already passed)",
|
|
29570
29649
|
async execute(ctx) {
|
|
29571
29650
|
const logger = getLogger();
|
|
29572
|
-
const effectiveConfig = ctx.
|
|
29651
|
+
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29573
29652
|
if (!effectiveConfig.quality.requireTests) {
|
|
29574
29653
|
logger.debug("verify", "Skipping verification (quality.requireTests = false)", { storyId: ctx.story.id });
|
|
29575
29654
|
return { action: "continue" };
|
|
@@ -29581,19 +29660,39 @@ var init_verify = __esm(() => {
|
|
|
29581
29660
|
return { action: "continue" };
|
|
29582
29661
|
}
|
|
29583
29662
|
logger.info("verify", "Running verification", { storyId: ctx.story.id });
|
|
29584
|
-
const effectiveWorkdir = ctx.story.workdir ?
|
|
29663
|
+
const effectiveWorkdir = ctx.story.workdir ? join27(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29585
29664
|
let effectiveCommand = testCommand;
|
|
29586
29665
|
let isFullSuite = true;
|
|
29587
|
-
const smartRunnerConfig = coerceSmartTestRunner(
|
|
29588
|
-
const regressionMode =
|
|
29589
|
-
|
|
29666
|
+
const smartRunnerConfig = coerceSmartTestRunner(effectiveConfig.execution.smartTestRunner);
|
|
29667
|
+
const regressionMode = effectiveConfig.execution.regressionGate?.mode ?? "deferred";
|
|
29668
|
+
let resolvedTestScopedTemplate = testScopedTemplate;
|
|
29669
|
+
if (testScopedTemplate && ctx.story.workdir) {
|
|
29670
|
+
const resolved = await resolvePackageTemplate(testScopedTemplate, effectiveWorkdir);
|
|
29671
|
+
resolvedTestScopedTemplate = resolved ?? undefined;
|
|
29672
|
+
}
|
|
29673
|
+
const isMonorepoOrchestrator = isMonorepoOrchestratorCommand(testCommand);
|
|
29674
|
+
if (isMonorepoOrchestrator) {
|
|
29675
|
+
if (resolvedTestScopedTemplate && ctx.story.workdir) {
|
|
29676
|
+
effectiveCommand = resolvedTestScopedTemplate;
|
|
29677
|
+
isFullSuite = false;
|
|
29678
|
+
logger.info("verify", "Monorepo orchestrator \u2014 using testScoped template", {
|
|
29679
|
+
storyId: ctx.story.id,
|
|
29680
|
+
command: effectiveCommand
|
|
29681
|
+
});
|
|
29682
|
+
} else {
|
|
29683
|
+
logger.info("verify", "Monorepo orchestrator \u2014 running full suite (no package context)", {
|
|
29684
|
+
storyId: ctx.story.id,
|
|
29685
|
+
command: effectiveCommand
|
|
29686
|
+
});
|
|
29687
|
+
}
|
|
29688
|
+
} else if (smartRunnerConfig.enabled) {
|
|
29590
29689
|
const sourceFiles = await _smartRunnerDeps.getChangedSourceFiles(effectiveWorkdir, ctx.storyGitRef, ctx.story.workdir);
|
|
29591
29690
|
const pass1Files = await _smartRunnerDeps.mapSourceToTests(sourceFiles, effectiveWorkdir);
|
|
29592
29691
|
if (pass1Files.length > 0) {
|
|
29593
29692
|
logger.info("verify", `[smart-runner] Pass 1: path convention matched ${pass1Files.length} test files`, {
|
|
29594
29693
|
storyId: ctx.story.id
|
|
29595
29694
|
});
|
|
29596
|
-
effectiveCommand = buildScopedCommand2(pass1Files, testCommand,
|
|
29695
|
+
effectiveCommand = buildScopedCommand2(pass1Files, testCommand, resolvedTestScopedTemplate);
|
|
29597
29696
|
isFullSuite = false;
|
|
29598
29697
|
} else if (smartRunnerConfig.fallback === "import-grep") {
|
|
29599
29698
|
const pass2Files = await _smartRunnerDeps.importGrepFallback(sourceFiles, effectiveWorkdir, smartRunnerConfig.testFilePatterns);
|
|
@@ -29601,7 +29700,7 @@ var init_verify = __esm(() => {
|
|
|
29601
29700
|
logger.info("verify", `[smart-runner] Pass 2: import-grep matched ${pass2Files.length} test files`, {
|
|
29602
29701
|
storyId: ctx.story.id
|
|
29603
29702
|
});
|
|
29604
|
-
effectiveCommand = buildScopedCommand2(pass2Files, testCommand,
|
|
29703
|
+
effectiveCommand = buildScopedCommand2(pass2Files, testCommand, resolvedTestScopedTemplate);
|
|
29605
29704
|
isFullSuite = false;
|
|
29606
29705
|
}
|
|
29607
29706
|
}
|
|
@@ -29624,8 +29723,8 @@ var init_verify = __esm(() => {
|
|
|
29624
29723
|
const result = await _verifyDeps.regression({
|
|
29625
29724
|
workdir: effectiveWorkdir,
|
|
29626
29725
|
command: effectiveCommand,
|
|
29627
|
-
timeoutSeconds:
|
|
29628
|
-
acceptOnTimeout:
|
|
29726
|
+
timeoutSeconds: effectiveConfig.execution.verificationTimeoutSeconds,
|
|
29727
|
+
acceptOnTimeout: effectiveConfig.execution.regressionGate?.acceptOnTimeout ?? true
|
|
29629
29728
|
});
|
|
29630
29729
|
ctx.verifyResult = {
|
|
29631
29730
|
success: result.success,
|
|
@@ -29642,7 +29741,7 @@ var init_verify = __esm(() => {
|
|
|
29642
29741
|
};
|
|
29643
29742
|
if (!result.success) {
|
|
29644
29743
|
if (result.status === "TIMEOUT") {
|
|
29645
|
-
const timeout =
|
|
29744
|
+
const timeout = effectiveConfig.execution.verificationTimeoutSeconds;
|
|
29646
29745
|
logger.error("verify", `Test suite exceeded timeout (${timeout}s). This is NOT a test failure \u2014 consider increasing execution.verificationTimeoutSeconds or scoping tests.`, {
|
|
29647
29746
|
exitCode: result.status,
|
|
29648
29747
|
storyId: ctx.story.id,
|
|
@@ -29657,10 +29756,13 @@ var init_verify = __esm(() => {
|
|
|
29657
29756
|
if (result.status !== "TIMEOUT") {
|
|
29658
29757
|
logTestOutput(logger, "verify", result.output, { storyId: ctx.story.id });
|
|
29659
29758
|
}
|
|
29660
|
-
|
|
29661
|
-
|
|
29662
|
-
|
|
29663
|
-
|
|
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" };
|
|
29664
29766
|
}
|
|
29665
29767
|
logger.info("verify", "Tests passed", { storyId: ctx.story.id });
|
|
29666
29768
|
return { action: "continue" };
|
|
@@ -29668,7 +29770,7 @@ var init_verify = __esm(() => {
|
|
|
29668
29770
|
};
|
|
29669
29771
|
_verifyDeps = {
|
|
29670
29772
|
regression,
|
|
29671
|
-
|
|
29773
|
+
readPackageName
|
|
29672
29774
|
};
|
|
29673
29775
|
});
|
|
29674
29776
|
|
|
@@ -29755,7 +29857,7 @@ __export(exports_init_context, {
|
|
|
29755
29857
|
});
|
|
29756
29858
|
import { existsSync as existsSync20 } from "fs";
|
|
29757
29859
|
import { mkdir } from "fs/promises";
|
|
29758
|
-
import { basename, join as
|
|
29860
|
+
import { basename as basename2, join as join31 } from "path";
|
|
29759
29861
|
async function findFiles(dir, maxFiles = 200) {
|
|
29760
29862
|
try {
|
|
29761
29863
|
const proc = Bun.spawnSync([
|
|
@@ -29783,7 +29885,7 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
29783
29885
|
return [];
|
|
29784
29886
|
}
|
|
29785
29887
|
async function readPackageManifest(projectRoot) {
|
|
29786
|
-
const packageJsonPath =
|
|
29888
|
+
const packageJsonPath = join31(projectRoot, "package.json");
|
|
29787
29889
|
if (!existsSync20(packageJsonPath)) {
|
|
29788
29890
|
return null;
|
|
29789
29891
|
}
|
|
@@ -29801,7 +29903,7 @@ async function readPackageManifest(projectRoot) {
|
|
|
29801
29903
|
}
|
|
29802
29904
|
}
|
|
29803
29905
|
async function readReadmeSnippet(projectRoot) {
|
|
29804
|
-
const readmePath =
|
|
29906
|
+
const readmePath = join31(projectRoot, "README.md");
|
|
29805
29907
|
if (!existsSync20(readmePath)) {
|
|
29806
29908
|
return null;
|
|
29807
29909
|
}
|
|
@@ -29819,7 +29921,7 @@ async function detectEntryPoints(projectRoot) {
|
|
|
29819
29921
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
29820
29922
|
const found = [];
|
|
29821
29923
|
for (const candidate of candidates) {
|
|
29822
|
-
const path12 =
|
|
29924
|
+
const path12 = join31(projectRoot, candidate);
|
|
29823
29925
|
if (existsSync20(path12)) {
|
|
29824
29926
|
found.push(candidate);
|
|
29825
29927
|
}
|
|
@@ -29830,7 +29932,7 @@ async function detectConfigFiles(projectRoot) {
|
|
|
29830
29932
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
29831
29933
|
const found = [];
|
|
29832
29934
|
for (const candidate of candidates) {
|
|
29833
|
-
const path12 =
|
|
29935
|
+
const path12 = join31(projectRoot, candidate);
|
|
29834
29936
|
if (existsSync20(path12)) {
|
|
29835
29937
|
found.push(candidate);
|
|
29836
29938
|
}
|
|
@@ -29843,7 +29945,7 @@ async function scanProject(projectRoot) {
|
|
|
29843
29945
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
29844
29946
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
29845
29947
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
29846
|
-
const projectName = packageManifest?.name ||
|
|
29948
|
+
const projectName = packageManifest?.name || basename2(projectRoot);
|
|
29847
29949
|
return {
|
|
29848
29950
|
projectName,
|
|
29849
29951
|
fileTree,
|
|
@@ -29991,9 +30093,9 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
29991
30093
|
}
|
|
29992
30094
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
29993
30095
|
const logger = getLogger();
|
|
29994
|
-
const packageDir =
|
|
29995
|
-
const naxDir =
|
|
29996
|
-
const contextPath =
|
|
30096
|
+
const packageDir = join31(repoRoot, packagePath);
|
|
30097
|
+
const naxDir = join31(packageDir, "nax");
|
|
30098
|
+
const contextPath = join31(naxDir, "context.md");
|
|
29997
30099
|
if (existsSync20(contextPath) && !force) {
|
|
29998
30100
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
29999
30101
|
return;
|
|
@@ -30007,8 +30109,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
30007
30109
|
}
|
|
30008
30110
|
async function initContext(projectRoot, options = {}) {
|
|
30009
30111
|
const logger = getLogger();
|
|
30010
|
-
const naxDir =
|
|
30011
|
-
const contextPath =
|
|
30112
|
+
const naxDir = join31(projectRoot, "nax");
|
|
30113
|
+
const contextPath = join31(naxDir, "context.md");
|
|
30012
30114
|
if (existsSync20(contextPath) && !options.force) {
|
|
30013
30115
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
30014
30116
|
return;
|
|
@@ -30352,11 +30454,11 @@ function getSafeLogger6() {
|
|
|
30352
30454
|
return getSafeLogger();
|
|
30353
30455
|
}
|
|
30354
30456
|
function extractPluginName(pluginPath) {
|
|
30355
|
-
const
|
|
30356
|
-
if (
|
|
30457
|
+
const basename4 = path12.basename(pluginPath);
|
|
30458
|
+
if (basename4 === "index.ts" || basename4 === "index.js" || basename4 === "index.mjs") {
|
|
30357
30459
|
return path12.basename(path12.dirname(pluginPath));
|
|
30358
30460
|
}
|
|
30359
|
-
return
|
|
30461
|
+
return basename4.replace(/\.(ts|js|mjs)$/, "");
|
|
30360
30462
|
}
|
|
30361
30463
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
|
|
30362
30464
|
const loadedPlugins = [];
|
|
@@ -31316,19 +31418,19 @@ var init_precheck = __esm(() => {
|
|
|
31316
31418
|
});
|
|
31317
31419
|
|
|
31318
31420
|
// src/hooks/runner.ts
|
|
31319
|
-
import { join as
|
|
31421
|
+
import { join as join44 } from "path";
|
|
31320
31422
|
async function loadHooksConfig(projectDir, globalDir) {
|
|
31321
31423
|
let globalHooks = { hooks: {} };
|
|
31322
31424
|
let projectHooks = { hooks: {} };
|
|
31323
31425
|
let skipGlobal = false;
|
|
31324
|
-
const projectPath =
|
|
31426
|
+
const projectPath = join44(projectDir, "hooks.json");
|
|
31325
31427
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
31326
31428
|
if (projectData) {
|
|
31327
31429
|
projectHooks = projectData;
|
|
31328
31430
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
31329
31431
|
}
|
|
31330
31432
|
if (!skipGlobal && globalDir) {
|
|
31331
|
-
const globalPath =
|
|
31433
|
+
const globalPath = join44(globalDir, "hooks.json");
|
|
31332
31434
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
31333
31435
|
if (globalData) {
|
|
31334
31436
|
globalHooks = globalData;
|
|
@@ -31819,6 +31921,7 @@ async function executeFixStory(ctx, story, prd, iterations) {
|
|
|
31819
31921
|
}), ctx.workdir);
|
|
31820
31922
|
const fixContext = {
|
|
31821
31923
|
config: ctx.config,
|
|
31924
|
+
effectiveConfig: ctx.config,
|
|
31822
31925
|
prd,
|
|
31823
31926
|
story,
|
|
31824
31927
|
stories: [story],
|
|
@@ -31852,6 +31955,7 @@ async function runAcceptanceLoop(ctx) {
|
|
|
31852
31955
|
const firstStory = prd.userStories[0];
|
|
31853
31956
|
const acceptanceContext = {
|
|
31854
31957
|
config: ctx.config,
|
|
31958
|
+
effectiveConfig: ctx.config,
|
|
31855
31959
|
prd,
|
|
31856
31960
|
story: firstStory,
|
|
31857
31961
|
stories: [firstStory],
|
|
@@ -32360,12 +32464,12 @@ __export(exports_manager, {
|
|
|
32360
32464
|
WorktreeManager: () => WorktreeManager
|
|
32361
32465
|
});
|
|
32362
32466
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
32363
|
-
import { join as
|
|
32467
|
+
import { join as join45 } from "path";
|
|
32364
32468
|
|
|
32365
32469
|
class WorktreeManager {
|
|
32366
32470
|
async create(projectRoot, storyId) {
|
|
32367
32471
|
validateStoryId(storyId);
|
|
32368
|
-
const worktreePath =
|
|
32472
|
+
const worktreePath = join45(projectRoot, ".nax-wt", storyId);
|
|
32369
32473
|
const branchName = `nax/${storyId}`;
|
|
32370
32474
|
try {
|
|
32371
32475
|
const proc = Bun.spawn(["git", "worktree", "add", worktreePath, "-b", branchName], {
|
|
@@ -32390,9 +32494,9 @@ class WorktreeManager {
|
|
|
32390
32494
|
}
|
|
32391
32495
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
32392
32496
|
}
|
|
32393
|
-
const nodeModulesSource =
|
|
32497
|
+
const nodeModulesSource = join45(projectRoot, "node_modules");
|
|
32394
32498
|
if (existsSync32(nodeModulesSource)) {
|
|
32395
|
-
const nodeModulesTarget =
|
|
32499
|
+
const nodeModulesTarget = join45(worktreePath, "node_modules");
|
|
32396
32500
|
try {
|
|
32397
32501
|
symlinkSync(nodeModulesSource, nodeModulesTarget, "dir");
|
|
32398
32502
|
} catch (error48) {
|
|
@@ -32400,9 +32504,9 @@ class WorktreeManager {
|
|
|
32400
32504
|
throw new Error(`Failed to symlink node_modules: ${errorMessage(error48)}`);
|
|
32401
32505
|
}
|
|
32402
32506
|
}
|
|
32403
|
-
const envSource =
|
|
32507
|
+
const envSource = join45(projectRoot, ".env");
|
|
32404
32508
|
if (existsSync32(envSource)) {
|
|
32405
|
-
const envTarget =
|
|
32509
|
+
const envTarget = join45(worktreePath, ".env");
|
|
32406
32510
|
try {
|
|
32407
32511
|
symlinkSync(envSource, envTarget, "file");
|
|
32408
32512
|
} catch (error48) {
|
|
@@ -32413,7 +32517,7 @@ class WorktreeManager {
|
|
|
32413
32517
|
}
|
|
32414
32518
|
async remove(projectRoot, storyId) {
|
|
32415
32519
|
validateStoryId(storyId);
|
|
32416
|
-
const worktreePath =
|
|
32520
|
+
const worktreePath = join45(projectRoot, ".nax-wt", storyId);
|
|
32417
32521
|
const branchName = `nax/${storyId}`;
|
|
32418
32522
|
try {
|
|
32419
32523
|
const proc = Bun.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -32723,6 +32827,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
32723
32827
|
try {
|
|
32724
32828
|
const pipelineContext = {
|
|
32725
32829
|
...context,
|
|
32830
|
+
effectiveConfig: context.effectiveConfig ?? context.config,
|
|
32726
32831
|
story,
|
|
32727
32832
|
stories: [story],
|
|
32728
32833
|
workdir: worktreePath,
|
|
@@ -32803,7 +32908,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
32803
32908
|
|
|
32804
32909
|
// src/execution/parallel-coordinator.ts
|
|
32805
32910
|
import os3 from "os";
|
|
32806
|
-
import { join as
|
|
32911
|
+
import { join as join46 } from "path";
|
|
32807
32912
|
function groupStoriesByDependencies(stories) {
|
|
32808
32913
|
const batches = [];
|
|
32809
32914
|
const processed = new Set;
|
|
@@ -32872,6 +32977,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32872
32977
|
});
|
|
32873
32978
|
const baseContext = {
|
|
32874
32979
|
config: config2,
|
|
32980
|
+
effectiveConfig: config2,
|
|
32875
32981
|
prd: currentPrd,
|
|
32876
32982
|
featureDir,
|
|
32877
32983
|
hooks,
|
|
@@ -32881,7 +32987,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32881
32987
|
};
|
|
32882
32988
|
const worktreePaths = new Map;
|
|
32883
32989
|
for (const story of batch) {
|
|
32884
|
-
const worktreePath =
|
|
32990
|
+
const worktreePath = join46(projectRoot, ".nax-wt", story.id);
|
|
32885
32991
|
try {
|
|
32886
32992
|
await worktreeManager.create(projectRoot, story.id);
|
|
32887
32993
|
worktreePaths.set(story.id, worktreePath);
|
|
@@ -32930,7 +33036,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
32930
33036
|
});
|
|
32931
33037
|
logger?.warn("parallel", "Worktree preserved for manual conflict resolution", {
|
|
32932
33038
|
storyId: mergeResult.storyId,
|
|
32933
|
-
worktreePath:
|
|
33039
|
+
worktreePath: join46(projectRoot, ".nax-wt", mergeResult.storyId)
|
|
32934
33040
|
});
|
|
32935
33041
|
}
|
|
32936
33042
|
}
|
|
@@ -33009,6 +33115,7 @@ async function rectifyConflictedStory(options) {
|
|
|
33009
33115
|
const routing = routeTask2(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
33010
33116
|
const pipelineContext = {
|
|
33011
33117
|
config: config2,
|
|
33118
|
+
effectiveConfig: config2,
|
|
33012
33119
|
prd,
|
|
33013
33120
|
story,
|
|
33014
33121
|
stories: [story],
|
|
@@ -33389,12 +33496,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
33389
33496
|
// src/pipeline/subscribers/events-writer.ts
|
|
33390
33497
|
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
33391
33498
|
import { homedir as homedir7 } from "os";
|
|
33392
|
-
import { basename as
|
|
33499
|
+
import { basename as basename5, join as join47 } from "path";
|
|
33393
33500
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
33394
33501
|
const logger = getSafeLogger();
|
|
33395
|
-
const project =
|
|
33396
|
-
const eventsDir =
|
|
33397
|
-
const eventsFile =
|
|
33502
|
+
const project = basename5(workdir);
|
|
33503
|
+
const eventsDir = join47(homedir7(), ".nax", "events", project);
|
|
33504
|
+
const eventsFile = join47(eventsDir, "events.jsonl");
|
|
33398
33505
|
let dirReady = false;
|
|
33399
33506
|
const write = (line) => {
|
|
33400
33507
|
(async () => {
|
|
@@ -33554,12 +33661,12 @@ var init_interaction2 = __esm(() => {
|
|
|
33554
33661
|
// src/pipeline/subscribers/registry.ts
|
|
33555
33662
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
33556
33663
|
import { homedir as homedir8 } from "os";
|
|
33557
|
-
import { basename as
|
|
33664
|
+
import { basename as basename6, join as join48 } from "path";
|
|
33558
33665
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
33559
33666
|
const logger = getSafeLogger();
|
|
33560
|
-
const project =
|
|
33561
|
-
const runDir =
|
|
33562
|
-
const metaFile =
|
|
33667
|
+
const project = basename6(workdir);
|
|
33668
|
+
const runDir = join48(homedir8(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
33669
|
+
const metaFile = join48(runDir, "meta.json");
|
|
33563
33670
|
const unsub = bus.on("run:started", (_ev) => {
|
|
33564
33671
|
(async () => {
|
|
33565
33672
|
try {
|
|
@@ -33569,8 +33676,8 @@ function wireRegistry(bus, feature, runId, workdir) {
|
|
|
33569
33676
|
project,
|
|
33570
33677
|
feature,
|
|
33571
33678
|
workdir,
|
|
33572
|
-
statusPath:
|
|
33573
|
-
eventsDir:
|
|
33679
|
+
statusPath: join48(workdir, "nax", "features", feature, "status.json"),
|
|
33680
|
+
eventsDir: join48(workdir, "nax", "features", feature, "runs"),
|
|
33574
33681
|
registeredAt: new Date().toISOString()
|
|
33575
33682
|
};
|
|
33576
33683
|
await writeFile(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -34199,6 +34306,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
34199
34306
|
});
|
|
34200
34307
|
|
|
34201
34308
|
// src/execution/iteration-runner.ts
|
|
34309
|
+
import { join as join49 } from "path";
|
|
34202
34310
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
34203
34311
|
const logger = getSafeLogger();
|
|
34204
34312
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
@@ -34224,8 +34332,10 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34224
34332
|
const storyStartTime = Date.now();
|
|
34225
34333
|
const storyGitRef = await captureGitRef(ctx.workdir);
|
|
34226
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;
|
|
34227
34336
|
const pipelineContext = {
|
|
34228
34337
|
config: ctx.config,
|
|
34338
|
+
effectiveConfig,
|
|
34229
34339
|
prd,
|
|
34230
34340
|
story,
|
|
34231
34341
|
stories: storiesToExecute,
|
|
@@ -34296,13 +34406,18 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
34296
34406
|
reason: pipelineResult.reason
|
|
34297
34407
|
};
|
|
34298
34408
|
}
|
|
34409
|
+
var _iterationRunnerDeps;
|
|
34299
34410
|
var init_iteration_runner = __esm(() => {
|
|
34411
|
+
init_loader2();
|
|
34300
34412
|
init_logger2();
|
|
34301
34413
|
init_runner();
|
|
34302
34414
|
init_stages();
|
|
34303
34415
|
init_git();
|
|
34304
34416
|
init_dry_run();
|
|
34305
34417
|
init_pipeline_result_handler();
|
|
34418
|
+
_iterationRunnerDeps = {
|
|
34419
|
+
loadConfigForWorkdir
|
|
34420
|
+
};
|
|
34306
34421
|
});
|
|
34307
34422
|
|
|
34308
34423
|
// src/execution/executor-types.ts
|
|
@@ -34395,6 +34510,7 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
34395
34510
|
logger?.info("execution", "Running pre-run pipeline (acceptance test setup)");
|
|
34396
34511
|
const preRunCtx = {
|
|
34397
34512
|
config: ctx.config,
|
|
34513
|
+
effectiveConfig: ctx.config,
|
|
34398
34514
|
prd,
|
|
34399
34515
|
workdir: ctx.workdir,
|
|
34400
34516
|
featureDir: ctx.featureDir,
|
|
@@ -34566,7 +34682,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
34566
34682
|
var init_status_file = () => {};
|
|
34567
34683
|
|
|
34568
34684
|
// src/execution/status-writer.ts
|
|
34569
|
-
import { join as
|
|
34685
|
+
import { join as join50 } from "path";
|
|
34570
34686
|
|
|
34571
34687
|
class StatusWriter {
|
|
34572
34688
|
statusFile;
|
|
@@ -34634,7 +34750,7 @@ class StatusWriter {
|
|
|
34634
34750
|
if (!this._prd)
|
|
34635
34751
|
return;
|
|
34636
34752
|
const safeLogger = getSafeLogger();
|
|
34637
|
-
const featureStatusPath =
|
|
34753
|
+
const featureStatusPath = join50(featureDir, "status.json");
|
|
34638
34754
|
try {
|
|
34639
34755
|
const base = this.getSnapshot(totalCost, iterations);
|
|
34640
34756
|
if (!base) {
|
|
@@ -65963,7 +66079,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
65963
66079
|
init_source();
|
|
65964
66080
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
65965
66081
|
import { homedir as homedir10 } from "os";
|
|
65966
|
-
import { join as
|
|
66082
|
+
import { join as join51 } from "path";
|
|
65967
66083
|
|
|
65968
66084
|
// node_modules/commander/esm.mjs
|
|
65969
66085
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -68032,7 +68148,7 @@ async function runsShowCommand(options) {
|
|
|
68032
68148
|
// src/cli/prompts-main.ts
|
|
68033
68149
|
init_logger2();
|
|
68034
68150
|
import { existsSync as existsSync18, mkdirSync as mkdirSync3 } from "fs";
|
|
68035
|
-
import { join as
|
|
68151
|
+
import { join as join29 } from "path";
|
|
68036
68152
|
|
|
68037
68153
|
// src/pipeline/index.ts
|
|
68038
68154
|
init_runner();
|
|
@@ -68068,7 +68184,7 @@ init_prd();
|
|
|
68068
68184
|
|
|
68069
68185
|
// src/cli/prompts-tdd.ts
|
|
68070
68186
|
init_prompts2();
|
|
68071
|
-
import { join as
|
|
68187
|
+
import { join as join28 } from "path";
|
|
68072
68188
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
68073
68189
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
68074
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(),
|
|
@@ -68087,7 +68203,7 @@ ${frontmatter}---
|
|
|
68087
68203
|
|
|
68088
68204
|
${session.prompt}`;
|
|
68089
68205
|
if (outputDir) {
|
|
68090
|
-
const promptFile =
|
|
68206
|
+
const promptFile = join28(outputDir, `${story.id}.${session.role}.md`);
|
|
68091
68207
|
await Bun.write(promptFile, fullOutput);
|
|
68092
68208
|
logger.info("cli", "Written TDD prompt file", {
|
|
68093
68209
|
storyId: story.id,
|
|
@@ -68103,7 +68219,7 @@ ${"=".repeat(80)}`);
|
|
|
68103
68219
|
}
|
|
68104
68220
|
}
|
|
68105
68221
|
if (outputDir && ctx.contextMarkdown) {
|
|
68106
|
-
const contextFile =
|
|
68222
|
+
const contextFile = join28(outputDir, `${story.id}.context.md`);
|
|
68107
68223
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
68108
68224
|
const contextOutput = `---
|
|
68109
68225
|
${frontmatter}---
|
|
@@ -68117,12 +68233,12 @@ ${ctx.contextMarkdown}`;
|
|
|
68117
68233
|
async function promptsCommand(options) {
|
|
68118
68234
|
const logger = getLogger();
|
|
68119
68235
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
68120
|
-
const naxDir =
|
|
68236
|
+
const naxDir = join29(workdir, "nax");
|
|
68121
68237
|
if (!existsSync18(naxDir)) {
|
|
68122
68238
|
throw new Error(`nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
68123
68239
|
}
|
|
68124
|
-
const featureDir =
|
|
68125
|
-
const prdPath =
|
|
68240
|
+
const featureDir = join29(naxDir, "features", feature);
|
|
68241
|
+
const prdPath = join29(featureDir, "prd.json");
|
|
68126
68242
|
if (!existsSync18(prdPath)) {
|
|
68127
68243
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
68128
68244
|
}
|
|
@@ -68144,6 +68260,7 @@ async function promptsCommand(options) {
|
|
|
68144
68260
|
for (const story of stories) {
|
|
68145
68261
|
const ctx = {
|
|
68146
68262
|
config: config2,
|
|
68263
|
+
effectiveConfig: config2,
|
|
68147
68264
|
prd,
|
|
68148
68265
|
story,
|
|
68149
68266
|
stories: [story],
|
|
@@ -68182,10 +68299,10 @@ ${frontmatter}---
|
|
|
68182
68299
|
|
|
68183
68300
|
${ctx.prompt}`;
|
|
68184
68301
|
if (outputDir) {
|
|
68185
|
-
const promptFile =
|
|
68302
|
+
const promptFile = join29(outputDir, `${story.id}.prompt.md`);
|
|
68186
68303
|
await Bun.write(promptFile, fullOutput);
|
|
68187
68304
|
if (ctx.contextMarkdown) {
|
|
68188
|
-
const contextFile =
|
|
68305
|
+
const contextFile = join29(outputDir, `${story.id}.context.md`);
|
|
68189
68306
|
const contextOutput = `---
|
|
68190
68307
|
${frontmatter}---
|
|
68191
68308
|
|
|
@@ -68249,7 +68366,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
68249
68366
|
}
|
|
68250
68367
|
// src/cli/prompts-init.ts
|
|
68251
68368
|
import { existsSync as existsSync19, mkdirSync as mkdirSync4 } from "fs";
|
|
68252
|
-
import { join as
|
|
68369
|
+
import { join as join30 } from "path";
|
|
68253
68370
|
var TEMPLATE_ROLES = [
|
|
68254
68371
|
{ file: "test-writer.md", role: "test-writer" },
|
|
68255
68372
|
{ file: "implementer.md", role: "implementer", variant: "standard" },
|
|
@@ -68273,9 +68390,9 @@ var TEMPLATE_HEADER = `<!--
|
|
|
68273
68390
|
`;
|
|
68274
68391
|
async function promptsInitCommand(options) {
|
|
68275
68392
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
68276
|
-
const templatesDir =
|
|
68393
|
+
const templatesDir = join30(workdir, "nax", "templates");
|
|
68277
68394
|
mkdirSync4(templatesDir, { recursive: true });
|
|
68278
|
-
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)));
|
|
68279
68396
|
if (existingFiles.length > 0 && !force) {
|
|
68280
68397
|
console.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
68281
68398
|
Pass --force to overwrite existing templates.`);
|
|
@@ -68283,7 +68400,7 @@ async function promptsInitCommand(options) {
|
|
|
68283
68400
|
}
|
|
68284
68401
|
const written = [];
|
|
68285
68402
|
for (const template of TEMPLATE_ROLES) {
|
|
68286
|
-
const filePath =
|
|
68403
|
+
const filePath = join30(templatesDir, template.file);
|
|
68287
68404
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
68288
68405
|
const content = TEMPLATE_HEADER + roleBody;
|
|
68289
68406
|
await Bun.write(filePath, content);
|
|
@@ -68299,7 +68416,7 @@ async function promptsInitCommand(options) {
|
|
|
68299
68416
|
return written;
|
|
68300
68417
|
}
|
|
68301
68418
|
async function autoWirePromptsConfig(workdir) {
|
|
68302
|
-
const configPath =
|
|
68419
|
+
const configPath = join30(workdir, "nax.config.json");
|
|
68303
68420
|
if (!existsSync19(configPath)) {
|
|
68304
68421
|
const exampleConfig = JSON.stringify({
|
|
68305
68422
|
prompts: {
|
|
@@ -68464,7 +68581,7 @@ init_config();
|
|
|
68464
68581
|
init_logger2();
|
|
68465
68582
|
init_prd();
|
|
68466
68583
|
import { existsSync as existsSync21, readdirSync as readdirSync5 } from "fs";
|
|
68467
|
-
import { join as
|
|
68584
|
+
import { join as join34 } from "path";
|
|
68468
68585
|
|
|
68469
68586
|
// src/cli/diagnose-analysis.ts
|
|
68470
68587
|
function detectFailurePattern(story, prd, status) {
|
|
@@ -68663,7 +68780,7 @@ function isProcessAlive2(pid) {
|
|
|
68663
68780
|
}
|
|
68664
68781
|
}
|
|
68665
68782
|
async function loadStatusFile2(workdir) {
|
|
68666
|
-
const statusPath =
|
|
68783
|
+
const statusPath = join34(workdir, "nax", "status.json");
|
|
68667
68784
|
if (!existsSync21(statusPath))
|
|
68668
68785
|
return null;
|
|
68669
68786
|
try {
|
|
@@ -68691,7 +68808,7 @@ async function countCommitsSince(workdir, since) {
|
|
|
68691
68808
|
}
|
|
68692
68809
|
}
|
|
68693
68810
|
async function checkLock(workdir) {
|
|
68694
|
-
const lockFile = Bun.file(
|
|
68811
|
+
const lockFile = Bun.file(join34(workdir, "nax.lock"));
|
|
68695
68812
|
if (!await lockFile.exists())
|
|
68696
68813
|
return { lockPresent: false };
|
|
68697
68814
|
try {
|
|
@@ -68709,8 +68826,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
68709
68826
|
const logger = getLogger();
|
|
68710
68827
|
const workdir = options.workdir ?? process.cwd();
|
|
68711
68828
|
const naxSubdir = findProjectDir(workdir);
|
|
68712
|
-
let projectDir = naxSubdir ?
|
|
68713
|
-
if (!projectDir && existsSync21(
|
|
68829
|
+
let projectDir = naxSubdir ? join34(naxSubdir, "..") : null;
|
|
68830
|
+
if (!projectDir && existsSync21(join34(workdir, "nax"))) {
|
|
68714
68831
|
projectDir = workdir;
|
|
68715
68832
|
}
|
|
68716
68833
|
if (!projectDir)
|
|
@@ -68721,7 +68838,7 @@ async function diagnoseCommand(options = {}) {
|
|
|
68721
68838
|
if (status2) {
|
|
68722
68839
|
feature = status2.run.feature;
|
|
68723
68840
|
} else {
|
|
68724
|
-
const featuresDir =
|
|
68841
|
+
const featuresDir = join34(projectDir, "nax", "features");
|
|
68725
68842
|
if (!existsSync21(featuresDir))
|
|
68726
68843
|
throw new Error("No features found in project");
|
|
68727
68844
|
const features = readdirSync5(featuresDir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
@@ -68731,8 +68848,8 @@ async function diagnoseCommand(options = {}) {
|
|
|
68731
68848
|
logger.info("diagnose", "No feature specified, using first found", { feature });
|
|
68732
68849
|
}
|
|
68733
68850
|
}
|
|
68734
|
-
const featureDir =
|
|
68735
|
-
const prdPath =
|
|
68851
|
+
const featureDir = join34(projectDir, "nax", "features", feature);
|
|
68852
|
+
const prdPath = join34(featureDir, "prd.json");
|
|
68736
68853
|
if (!existsSync21(prdPath))
|
|
68737
68854
|
throw new Error(`Feature not found: ${feature}`);
|
|
68738
68855
|
const prd = await loadPRD(prdPath);
|
|
@@ -68775,7 +68892,7 @@ init_interaction();
|
|
|
68775
68892
|
init_source();
|
|
68776
68893
|
init_loader2();
|
|
68777
68894
|
import { existsSync as existsSync22 } from "fs";
|
|
68778
|
-
import { join as
|
|
68895
|
+
import { join as join35 } from "path";
|
|
68779
68896
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
68780
68897
|
async function generateCommand(options) {
|
|
68781
68898
|
const workdir = process.cwd();
|
|
@@ -68818,7 +68935,7 @@ async function generateCommand(options) {
|
|
|
68818
68935
|
return;
|
|
68819
68936
|
}
|
|
68820
68937
|
if (options.package) {
|
|
68821
|
-
const packageDir =
|
|
68938
|
+
const packageDir = join35(workdir, options.package);
|
|
68822
68939
|
if (dryRun) {
|
|
68823
68940
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
68824
68941
|
}
|
|
@@ -68838,8 +68955,8 @@ async function generateCommand(options) {
|
|
|
68838
68955
|
process.exit(1);
|
|
68839
68956
|
return;
|
|
68840
68957
|
}
|
|
68841
|
-
const contextPath = options.context ?
|
|
68842
|
-
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;
|
|
68843
68960
|
const autoInject = !options.noAutoInject;
|
|
68844
68961
|
if (!existsSync22(contextPath)) {
|
|
68845
68962
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
@@ -68944,7 +69061,7 @@ async function generateCommand(options) {
|
|
|
68944
69061
|
// src/cli/config-display.ts
|
|
68945
69062
|
init_loader2();
|
|
68946
69063
|
import { existsSync as existsSync24 } from "fs";
|
|
68947
|
-
import { join as
|
|
69064
|
+
import { join as join37 } from "path";
|
|
68948
69065
|
|
|
68949
69066
|
// src/cli/config-descriptions.ts
|
|
68950
69067
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -69153,7 +69270,7 @@ function deepEqual(a, b) {
|
|
|
69153
69270
|
init_defaults();
|
|
69154
69271
|
init_loader2();
|
|
69155
69272
|
import { existsSync as existsSync23 } from "fs";
|
|
69156
|
-
import { join as
|
|
69273
|
+
import { join as join36 } from "path";
|
|
69157
69274
|
async function loadConfigFile(path14) {
|
|
69158
69275
|
if (!existsSync23(path14))
|
|
69159
69276
|
return null;
|
|
@@ -69175,7 +69292,7 @@ async function loadProjectConfig() {
|
|
|
69175
69292
|
const projectDir = findProjectDir();
|
|
69176
69293
|
if (!projectDir)
|
|
69177
69294
|
return null;
|
|
69178
|
-
const projectPath =
|
|
69295
|
+
const projectPath = join36(projectDir, "config.json");
|
|
69179
69296
|
return await loadConfigFile(projectPath);
|
|
69180
69297
|
}
|
|
69181
69298
|
|
|
@@ -69235,7 +69352,7 @@ async function configCommand(config2, options = {}) {
|
|
|
69235
69352
|
function determineConfigSources() {
|
|
69236
69353
|
const globalPath = globalConfigPath();
|
|
69237
69354
|
const projectDir = findProjectDir();
|
|
69238
|
-
const projectPath = projectDir ?
|
|
69355
|
+
const projectPath = projectDir ? join37(projectDir, "config.json") : null;
|
|
69239
69356
|
return {
|
|
69240
69357
|
global: fileExists(globalPath) ? globalPath : null,
|
|
69241
69358
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -69415,21 +69532,21 @@ async function diagnose(options) {
|
|
|
69415
69532
|
|
|
69416
69533
|
// src/commands/logs.ts
|
|
69417
69534
|
import { existsSync as existsSync26 } from "fs";
|
|
69418
|
-
import { join as
|
|
69535
|
+
import { join as join40 } from "path";
|
|
69419
69536
|
|
|
69420
69537
|
// src/commands/logs-formatter.ts
|
|
69421
69538
|
init_source();
|
|
69422
69539
|
init_formatter();
|
|
69423
69540
|
import { readdirSync as readdirSync7 } from "fs";
|
|
69424
|
-
import { join as
|
|
69541
|
+
import { join as join39 } from "path";
|
|
69425
69542
|
|
|
69426
69543
|
// src/commands/logs-reader.ts
|
|
69427
69544
|
import { existsSync as existsSync25, readdirSync as readdirSync6 } from "fs";
|
|
69428
69545
|
import { readdir as readdir3 } from "fs/promises";
|
|
69429
69546
|
import { homedir as homedir5 } from "os";
|
|
69430
|
-
import { join as
|
|
69547
|
+
import { join as join38 } from "path";
|
|
69431
69548
|
var _deps7 = {
|
|
69432
|
-
getRunsDir: () => process.env.NAX_RUNS_DIR ??
|
|
69549
|
+
getRunsDir: () => process.env.NAX_RUNS_DIR ?? join38(homedir5(), ".nax", "runs")
|
|
69433
69550
|
};
|
|
69434
69551
|
async function resolveRunFileFromRegistry(runId) {
|
|
69435
69552
|
const runsDir = _deps7.getRunsDir();
|
|
@@ -69441,7 +69558,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
69441
69558
|
}
|
|
69442
69559
|
let matched = null;
|
|
69443
69560
|
for (const entry of entries) {
|
|
69444
|
-
const metaPath =
|
|
69561
|
+
const metaPath = join38(runsDir, entry, "meta.json");
|
|
69445
69562
|
try {
|
|
69446
69563
|
const meta3 = await Bun.file(metaPath).json();
|
|
69447
69564
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -69463,14 +69580,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
69463
69580
|
return null;
|
|
69464
69581
|
}
|
|
69465
69582
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
69466
|
-
return
|
|
69583
|
+
return join38(matched.eventsDir, specificFile ?? files[0]);
|
|
69467
69584
|
}
|
|
69468
69585
|
async function selectRunFile(runsDir) {
|
|
69469
69586
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
69470
69587
|
if (files.length === 0) {
|
|
69471
69588
|
return null;
|
|
69472
69589
|
}
|
|
69473
|
-
return
|
|
69590
|
+
return join38(runsDir, files[0]);
|
|
69474
69591
|
}
|
|
69475
69592
|
async function extractRunSummary(filePath) {
|
|
69476
69593
|
const file2 = Bun.file(filePath);
|
|
@@ -69555,7 +69672,7 @@ Runs:
|
|
|
69555
69672
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
69556
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"));
|
|
69557
69674
|
for (const file2 of files) {
|
|
69558
|
-
const filePath =
|
|
69675
|
+
const filePath = join39(runsDir, file2);
|
|
69559
69676
|
const summary = await extractRunSummary(filePath);
|
|
69560
69677
|
const timestamp = file2.replace(".jsonl", "");
|
|
69561
69678
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -69680,7 +69797,7 @@ async function logsCommand(options) {
|
|
|
69680
69797
|
return;
|
|
69681
69798
|
}
|
|
69682
69799
|
const resolved = resolveProject({ dir: options.dir });
|
|
69683
|
-
const naxDir =
|
|
69800
|
+
const naxDir = join40(resolved.projectDir, "nax");
|
|
69684
69801
|
const configPath = resolved.configPath;
|
|
69685
69802
|
const configFile = Bun.file(configPath);
|
|
69686
69803
|
const config2 = await configFile.json();
|
|
@@ -69688,8 +69805,8 @@ async function logsCommand(options) {
|
|
|
69688
69805
|
if (!featureName) {
|
|
69689
69806
|
throw new Error("No feature specified in config.json");
|
|
69690
69807
|
}
|
|
69691
|
-
const featureDir =
|
|
69692
|
-
const runsDir =
|
|
69808
|
+
const featureDir = join40(naxDir, "features", featureName);
|
|
69809
|
+
const runsDir = join40(featureDir, "runs");
|
|
69693
69810
|
if (!existsSync26(runsDir)) {
|
|
69694
69811
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
69695
69812
|
}
|
|
@@ -69714,7 +69831,7 @@ init_config();
|
|
|
69714
69831
|
init_prd();
|
|
69715
69832
|
init_precheck();
|
|
69716
69833
|
import { existsSync as existsSync31 } from "fs";
|
|
69717
|
-
import { join as
|
|
69834
|
+
import { join as join41 } from "path";
|
|
69718
69835
|
async function precheckCommand(options) {
|
|
69719
69836
|
const resolved = resolveProject({
|
|
69720
69837
|
dir: options.dir,
|
|
@@ -69730,9 +69847,9 @@ async function precheckCommand(options) {
|
|
|
69730
69847
|
process.exit(1);
|
|
69731
69848
|
}
|
|
69732
69849
|
}
|
|
69733
|
-
const naxDir =
|
|
69734
|
-
const featureDir =
|
|
69735
|
-
const prdPath =
|
|
69850
|
+
const naxDir = join41(resolved.projectDir, "nax");
|
|
69851
|
+
const featureDir = join41(naxDir, "features", featureName);
|
|
69852
|
+
const prdPath = join41(featureDir, "prd.json");
|
|
69736
69853
|
if (!existsSync31(featureDir)) {
|
|
69737
69854
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
69738
69855
|
process.exit(1);
|
|
@@ -69756,10 +69873,10 @@ async function precheckCommand(options) {
|
|
|
69756
69873
|
init_source();
|
|
69757
69874
|
import { readdir as readdir4 } from "fs/promises";
|
|
69758
69875
|
import { homedir as homedir6 } from "os";
|
|
69759
|
-
import { join as
|
|
69876
|
+
import { join as join42 } from "path";
|
|
69760
69877
|
var DEFAULT_LIMIT = 20;
|
|
69761
69878
|
var _deps9 = {
|
|
69762
|
-
getRunsDir: () =>
|
|
69879
|
+
getRunsDir: () => join42(homedir6(), ".nax", "runs")
|
|
69763
69880
|
};
|
|
69764
69881
|
function formatDuration3(ms) {
|
|
69765
69882
|
if (ms <= 0)
|
|
@@ -69811,7 +69928,7 @@ async function runsCommand(options = {}) {
|
|
|
69811
69928
|
}
|
|
69812
69929
|
const rows = [];
|
|
69813
69930
|
for (const entry of entries) {
|
|
69814
|
-
const metaPath =
|
|
69931
|
+
const metaPath = join42(runsDir, entry, "meta.json");
|
|
69815
69932
|
let meta3;
|
|
69816
69933
|
try {
|
|
69817
69934
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -69888,7 +70005,7 @@ async function runsCommand(options = {}) {
|
|
|
69888
70005
|
|
|
69889
70006
|
// src/commands/unlock.ts
|
|
69890
70007
|
init_source();
|
|
69891
|
-
import { join as
|
|
70008
|
+
import { join as join43 } from "path";
|
|
69892
70009
|
function isProcessAlive3(pid) {
|
|
69893
70010
|
try {
|
|
69894
70011
|
process.kill(pid, 0);
|
|
@@ -69903,7 +70020,7 @@ function formatLockAge(ageMs) {
|
|
|
69903
70020
|
}
|
|
69904
70021
|
async function unlockCommand(options) {
|
|
69905
70022
|
const workdir = options.dir ?? process.cwd();
|
|
69906
|
-
const lockPath =
|
|
70023
|
+
const lockPath = join43(workdir, "nax.lock");
|
|
69907
70024
|
const lockFile = Bun.file(lockPath);
|
|
69908
70025
|
const exists = await lockFile.exists();
|
|
69909
70026
|
if (!exists) {
|
|
@@ -77738,15 +77855,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
77738
77855
|
}
|
|
77739
77856
|
return;
|
|
77740
77857
|
}
|
|
77741
|
-
const naxDir =
|
|
77858
|
+
const naxDir = join51(workdir, "nax");
|
|
77742
77859
|
if (existsSync34(naxDir) && !options.force) {
|
|
77743
77860
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
77744
77861
|
return;
|
|
77745
77862
|
}
|
|
77746
|
-
mkdirSync6(
|
|
77747
|
-
mkdirSync6(
|
|
77748
|
-
await Bun.write(
|
|
77749
|
-
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({
|
|
77750
77867
|
hooks: {
|
|
77751
77868
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
77752
77869
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -77754,12 +77871,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
77754
77871
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
77755
77872
|
}
|
|
77756
77873
|
}, null, 2));
|
|
77757
|
-
await Bun.write(
|
|
77874
|
+
await Bun.write(join51(naxDir, ".gitignore"), `# nax temp files
|
|
77758
77875
|
*.tmp
|
|
77759
77876
|
.paused.json
|
|
77760
77877
|
.nax-verifier-verdict.json
|
|
77761
77878
|
`);
|
|
77762
|
-
await Bun.write(
|
|
77879
|
+
await Bun.write(join51(naxDir, "context.md"), `# Project Context
|
|
77763
77880
|
|
|
77764
77881
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
77765
77882
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -77885,8 +78002,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77885
78002
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
77886
78003
|
process.exit(1);
|
|
77887
78004
|
}
|
|
77888
|
-
const featureDir =
|
|
77889
|
-
const prdPath =
|
|
78005
|
+
const featureDir = join51(naxDir, "features", options.feature);
|
|
78006
|
+
const prdPath = join51(featureDir, "prd.json");
|
|
77890
78007
|
if (options.plan && options.from) {
|
|
77891
78008
|
if (existsSync34(prdPath) && !options.force) {
|
|
77892
78009
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -77908,10 +78025,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77908
78025
|
}
|
|
77909
78026
|
}
|
|
77910
78027
|
try {
|
|
77911
|
-
const planLogDir =
|
|
78028
|
+
const planLogDir = join51(featureDir, "plan");
|
|
77912
78029
|
mkdirSync6(planLogDir, { recursive: true });
|
|
77913
78030
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77914
|
-
const planLogPath =
|
|
78031
|
+
const planLogPath = join51(planLogDir, `${planLogId}.jsonl`);
|
|
77915
78032
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
77916
78033
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
77917
78034
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -77949,10 +78066,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77949
78066
|
process.exit(1);
|
|
77950
78067
|
}
|
|
77951
78068
|
resetLogger();
|
|
77952
|
-
const runsDir =
|
|
78069
|
+
const runsDir = join51(featureDir, "runs");
|
|
77953
78070
|
mkdirSync6(runsDir, { recursive: true });
|
|
77954
78071
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
77955
|
-
const logFilePath =
|
|
78072
|
+
const logFilePath = join51(runsDir, `${runId}.jsonl`);
|
|
77956
78073
|
const isTTY = process.stdout.isTTY ?? false;
|
|
77957
78074
|
const headlessFlag = options.headless ?? false;
|
|
77958
78075
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -77968,7 +78085,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77968
78085
|
config2.autoMode.defaultAgent = options.agent;
|
|
77969
78086
|
}
|
|
77970
78087
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
77971
|
-
const globalNaxDir =
|
|
78088
|
+
const globalNaxDir = join51(homedir10(), ".nax");
|
|
77972
78089
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
77973
78090
|
const eventEmitter = new PipelineEventEmitter;
|
|
77974
78091
|
let tuiInstance;
|
|
@@ -77991,7 +78108,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
77991
78108
|
} else {
|
|
77992
78109
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
77993
78110
|
}
|
|
77994
|
-
const statusFilePath =
|
|
78111
|
+
const statusFilePath = join51(workdir, "nax", "status.json");
|
|
77995
78112
|
let parallel;
|
|
77996
78113
|
if (options.parallel !== undefined) {
|
|
77997
78114
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -78017,7 +78134,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78017
78134
|
headless: useHeadless,
|
|
78018
78135
|
skipPrecheck: options.skipPrecheck ?? false
|
|
78019
78136
|
});
|
|
78020
|
-
const latestSymlink =
|
|
78137
|
+
const latestSymlink = join51(runsDir, "latest.jsonl");
|
|
78021
78138
|
try {
|
|
78022
78139
|
if (existsSync34(latestSymlink)) {
|
|
78023
78140
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -78055,9 +78172,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78055
78172
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78056
78173
|
process.exit(1);
|
|
78057
78174
|
}
|
|
78058
|
-
const featureDir =
|
|
78175
|
+
const featureDir = join51(naxDir, "features", name);
|
|
78059
78176
|
mkdirSync6(featureDir, { recursive: true });
|
|
78060
|
-
await Bun.write(
|
|
78177
|
+
await Bun.write(join51(featureDir, "spec.md"), `# Feature: ${name}
|
|
78061
78178
|
|
|
78062
78179
|
## Overview
|
|
78063
78180
|
|
|
@@ -78065,7 +78182,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78065
78182
|
|
|
78066
78183
|
## Acceptance Criteria
|
|
78067
78184
|
`);
|
|
78068
|
-
await Bun.write(
|
|
78185
|
+
await Bun.write(join51(featureDir, "plan.md"), `# Plan: ${name}
|
|
78069
78186
|
|
|
78070
78187
|
## Architecture
|
|
78071
78188
|
|
|
@@ -78073,7 +78190,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78073
78190
|
|
|
78074
78191
|
## Dependencies
|
|
78075
78192
|
`);
|
|
78076
|
-
await Bun.write(
|
|
78193
|
+
await Bun.write(join51(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
78077
78194
|
|
|
78078
78195
|
## US-001: [Title]
|
|
78079
78196
|
|
|
@@ -78082,7 +78199,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78082
78199
|
### Acceptance Criteria
|
|
78083
78200
|
- [ ] Criterion 1
|
|
78084
78201
|
`);
|
|
78085
|
-
await Bun.write(
|
|
78202
|
+
await Bun.write(join51(featureDir, "progress.txt"), `# Progress: ${name}
|
|
78086
78203
|
|
|
78087
78204
|
Created: ${new Date().toISOString()}
|
|
78088
78205
|
|
|
@@ -78110,7 +78227,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78110
78227
|
console.error(source_default.red("nax not initialized."));
|
|
78111
78228
|
process.exit(1);
|
|
78112
78229
|
}
|
|
78113
|
-
const featuresDir =
|
|
78230
|
+
const featuresDir = join51(naxDir, "features");
|
|
78114
78231
|
if (!existsSync34(featuresDir)) {
|
|
78115
78232
|
console.log(source_default.dim("No features yet."));
|
|
78116
78233
|
return;
|
|
@@ -78125,7 +78242,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78125
78242
|
Features:
|
|
78126
78243
|
`));
|
|
78127
78244
|
for (const name of entries) {
|
|
78128
|
-
const prdPath =
|
|
78245
|
+
const prdPath = join51(featuresDir, name, "prd.json");
|
|
78129
78246
|
if (existsSync34(prdPath)) {
|
|
78130
78247
|
const prd = await loadPRD(prdPath);
|
|
78131
78248
|
const c = countStories(prd);
|
|
@@ -78156,10 +78273,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
78156
78273
|
process.exit(1);
|
|
78157
78274
|
}
|
|
78158
78275
|
const config2 = await loadConfig(workdir);
|
|
78159
|
-
const featureLogDir =
|
|
78276
|
+
const featureLogDir = join51(naxDir, "features", options.feature, "plan");
|
|
78160
78277
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
78161
78278
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78162
|
-
const planLogPath =
|
|
78279
|
+
const planLogPath = join51(featureLogDir, `${planLogId}.jsonl`);
|
|
78163
78280
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78164
78281
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78165
78282
|
try {
|
|
@@ -78196,7 +78313,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78196
78313
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78197
78314
|
process.exit(1);
|
|
78198
78315
|
}
|
|
78199
|
-
const featureDir =
|
|
78316
|
+
const featureDir = join51(naxDir, "features", options.feature);
|
|
78200
78317
|
if (!existsSync34(featureDir)) {
|
|
78201
78318
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
78202
78319
|
process.exit(1);
|
|
@@ -78212,7 +78329,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78212
78329
|
specPath: options.from,
|
|
78213
78330
|
reclassify: options.reclassify
|
|
78214
78331
|
});
|
|
78215
|
-
const prdPath =
|
|
78332
|
+
const prdPath = join51(featureDir, "prd.json");
|
|
78216
78333
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
78217
78334
|
const c = countStories(prd);
|
|
78218
78335
|
console.log(source_default.green(`
|