@nathapp/nax 0.49.6 → 0.50.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/CHANGELOG.md +14 -0
- package/dist/nax.js +271 -84
- package/package.json +1 -1
- package/src/acceptance/generator.ts +48 -7
- package/src/cli/plan.ts +46 -13
- package/src/config/test-strategy.ts +17 -16
- package/src/context/builder.ts +25 -0
- package/src/context/parent-context.ts +39 -0
- package/src/decompose/apply.ts +5 -1
- package/src/execution/escalation/tier-escalation.ts +1 -1
- package/src/execution/escalation/tier-outcome.ts +2 -2
- package/src/execution/lifecycle/run-initialization.ts +47 -13
- package/src/execution/parallel-coordinator.ts +3 -3
- package/src/execution/pipeline-result-handler.ts +30 -1
- package/src/pipeline/stages/autofix.ts +10 -2
- package/src/prd/index.ts +9 -1
- package/src/prd/types.ts +6 -0
- package/src/routing/router.ts +1 -1
- package/src/utils/git.ts +23 -0
package/dist/nax.js
CHANGED
|
@@ -3256,29 +3256,30 @@ function resolveTestStrategy(raw) {
|
|
|
3256
3256
|
}
|
|
3257
3257
|
var VALID_TEST_STRATEGIES, COMPLEXITY_GUIDE = `## Complexity Classification Guide
|
|
3258
3258
|
|
|
3259
|
-
- simple: \u226450 LOC, single-file change, purely additive, no new dependencies \u2192
|
|
3260
|
-
- medium: 50\u2013200 LOC, 2\u20135 files, standard patterns, clear requirements \u2192 tdd-
|
|
3259
|
+
- simple: \u226450 LOC, single-file change, purely additive, no new dependencies \u2192 tdd-simple
|
|
3260
|
+
- medium: 50\u2013200 LOC, 2\u20135 files, standard patterns, clear requirements \u2192 three-session-tdd-lite
|
|
3261
3261
|
- complex: 200\u2013500 LOC, multiple modules, new abstractions or integrations \u2192 three-session-tdd
|
|
3262
|
-
- expert: 500+ LOC, architectural changes, cross-cutting concerns, high risk \u2192 three-session-tdd
|
|
3262
|
+
- expert: 500+ LOC, architectural changes, cross-cutting concerns, high risk \u2192 three-session-tdd
|
|
3263
3263
|
|
|
3264
3264
|
### Security Override
|
|
3265
3265
|
|
|
3266
3266
|
Security-critical functions (authentication, cryptography, tokens, sessions, credentials,
|
|
3267
|
-
password hashing, access control) must
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
-
|
|
3271
|
-
- tdd
|
|
3272
|
-
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3267
|
+
password hashing, access control) must use three-session-tdd regardless of complexity.`, TEST_STRATEGY_GUIDE = `## Test Strategy Guide
|
|
3268
|
+
|
|
3269
|
+
- tdd-simple: Simple stories (\u226450 LOC). Write failing tests first, then implement to pass them \u2014 all in one session.
|
|
3270
|
+
- three-session-tdd-lite: Medium stories, or complex stories involving UI/CLI/integration. 3 sessions: (1) test-writer writes failing tests and may create minimal src/ stubs for imports, (2) implementer makes tests pass and may replace stubs, (3) verifier confirms correctness.
|
|
3271
|
+
- three-session-tdd: Complex/expert stories or security-critical code. 3 sessions with strict isolation: (1) test-writer writes failing tests \u2014 no src/ changes allowed, (2) implementer makes them pass without modifying test files, (3) verifier confirms correctness.
|
|
3272
|
+
- test-after: Only when explicitly configured (tddStrategy: "off"). Write tests after implementation. Not auto-assigned.`, GROUPING_RULES = `## Story Rules
|
|
3273
|
+
|
|
3274
|
+
- Every story must produce code changes verifiable by tests or review.
|
|
3275
|
+
- NEVER create stories for analysis, planning, documentation, or migration plans.
|
|
3276
|
+
Your analysis belongs in the "analysis" field, not in a story.
|
|
3277
|
+
- NEVER create stories whose primary purpose is writing tests, achieving coverage
|
|
3278
|
+
targets, or running validation/regression suites. Each story's testStrategy
|
|
3279
|
+
handles test creation as part of implementation. Testing is a built-in pipeline
|
|
3280
|
+
stage, not a user story. No exceptions.
|
|
3275
3281
|
- Combine small, related tasks into a single "simple" or "medium" story.
|
|
3276
|
-
|
|
3277
|
-
- Do NOT create standalone stories purely for test coverage or testing.
|
|
3278
|
-
Each story's testStrategy already handles testing (tdd-simple writes tests first,
|
|
3279
|
-
three-session-tdd uses separate test-writer session, test-after writes tests after).
|
|
3280
|
-
Only create a dedicated test story for unique integration/E2E test logic that spans
|
|
3281
|
-
multiple stories and cannot be covered by individual story test strategies.
|
|
3282
|
+
Do NOT create separate stories for every single file or function unless complex.
|
|
3282
3283
|
- Aim for coherent units of value. Maximum recommended stories: 10-15 per feature.`;
|
|
3283
3284
|
var init_test_strategy = __esm(() => {
|
|
3284
3285
|
VALID_TEST_STRATEGIES = [
|
|
@@ -18744,6 +18745,17 @@ IMPORTANT: Output raw TypeScript code only. Do NOT use markdown code fences (\`\
|
|
|
18744
18745
|
config: options.config
|
|
18745
18746
|
});
|
|
18746
18747
|
const testCode = extractTestCode(rawOutput);
|
|
18748
|
+
if (!testCode) {
|
|
18749
|
+
logger.warn("acceptance", "LLM returned non-code output for acceptance tests \u2014 falling back to skeleton", {
|
|
18750
|
+
outputPreview: rawOutput.slice(0, 200)
|
|
18751
|
+
});
|
|
18752
|
+
const skeletonCriteria = refinedCriteria.map((c, i) => ({
|
|
18753
|
+
id: `AC-${i + 1}`,
|
|
18754
|
+
text: c.refined,
|
|
18755
|
+
lineNumber: i + 1
|
|
18756
|
+
}));
|
|
18757
|
+
return { testCode: generateSkeletonTests(options.featureName, skeletonCriteria), criteria: skeletonCriteria };
|
|
18758
|
+
}
|
|
18747
18759
|
const refinedJsonContent = JSON.stringify(refinedCriteria.map((c, i) => ({
|
|
18748
18760
|
acId: `AC-${i + 1}`,
|
|
18749
18761
|
original: c.original,
|
|
@@ -18870,6 +18882,15 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18870
18882
|
config: options.config
|
|
18871
18883
|
});
|
|
18872
18884
|
const testCode = extractTestCode(output);
|
|
18885
|
+
if (!testCode) {
|
|
18886
|
+
logger.warn("acceptance", "LLM returned non-code output for acceptance tests \u2014 falling back to skeleton", {
|
|
18887
|
+
outputPreview: output.slice(0, 200)
|
|
18888
|
+
});
|
|
18889
|
+
return {
|
|
18890
|
+
testCode: generateSkeletonTests(options.featureName, criteria),
|
|
18891
|
+
criteria
|
|
18892
|
+
};
|
|
18893
|
+
}
|
|
18873
18894
|
return {
|
|
18874
18895
|
testCode,
|
|
18875
18896
|
criteria
|
|
@@ -18883,15 +18904,30 @@ async function generateAcceptanceTests(adapter, options) {
|
|
|
18883
18904
|
}
|
|
18884
18905
|
}
|
|
18885
18906
|
function extractTestCode(output) {
|
|
18907
|
+
let code;
|
|
18886
18908
|
const fenceMatch = output.match(/```(?:typescript|ts)?\s*([\s\S]*?)\s*```/);
|
|
18887
18909
|
if (fenceMatch) {
|
|
18888
|
-
|
|
18910
|
+
code = fenceMatch[1].trim();
|
|
18911
|
+
}
|
|
18912
|
+
if (!code) {
|
|
18913
|
+
const importMatch = output.match(/import\s+{[\s\S]+/);
|
|
18914
|
+
if (importMatch) {
|
|
18915
|
+
code = importMatch[0].trim();
|
|
18916
|
+
}
|
|
18889
18917
|
}
|
|
18890
|
-
|
|
18891
|
-
|
|
18892
|
-
|
|
18918
|
+
if (!code) {
|
|
18919
|
+
const describeMatch = output.match(/describe\s*\([\s\S]+/);
|
|
18920
|
+
if (describeMatch) {
|
|
18921
|
+
code = describeMatch[0].trim();
|
|
18922
|
+
}
|
|
18923
|
+
}
|
|
18924
|
+
if (!code)
|
|
18925
|
+
return null;
|
|
18926
|
+
const hasTestKeyword = /\b(?:describe|test|it|expect)\s*\(/.test(code);
|
|
18927
|
+
if (!hasTestKeyword) {
|
|
18928
|
+
return null;
|
|
18893
18929
|
}
|
|
18894
|
-
return
|
|
18930
|
+
return code;
|
|
18895
18931
|
}
|
|
18896
18932
|
function generateSkeletonTests(featureName, criteria) {
|
|
18897
18933
|
const tests = criteria.map((ac) => {
|
|
@@ -20363,7 +20399,8 @@ function applyDecomposition(prd, result) {
|
|
|
20363
20399
|
const originalIndex = prd.userStories.findIndex((s) => s.id === parentStoryId);
|
|
20364
20400
|
if (originalIndex === -1)
|
|
20365
20401
|
return;
|
|
20366
|
-
prd.userStories[originalIndex]
|
|
20402
|
+
const parentStory = prd.userStories[originalIndex];
|
|
20403
|
+
parentStory.status = "decomposed";
|
|
20367
20404
|
const newStories = subStories.map((sub) => ({
|
|
20368
20405
|
id: sub.id,
|
|
20369
20406
|
title: sub.title,
|
|
@@ -20375,7 +20412,8 @@ function applyDecomposition(prd, result) {
|
|
|
20375
20412
|
passes: false,
|
|
20376
20413
|
escalations: [],
|
|
20377
20414
|
attempts: 0,
|
|
20378
|
-
parentStoryId: sub.parentStoryId
|
|
20415
|
+
parentStoryId: sub.parentStoryId,
|
|
20416
|
+
...parentStory.workdir !== undefined && { workdir: parentStory.workdir }
|
|
20379
20417
|
}));
|
|
20380
20418
|
prd.userStories.splice(originalIndex + 1, 0, ...newStories);
|
|
20381
20419
|
}
|
|
@@ -22255,7 +22293,7 @@ function markStoryPassed(prd, storyId) {
|
|
|
22255
22293
|
story.status = "passed";
|
|
22256
22294
|
}
|
|
22257
22295
|
}
|
|
22258
|
-
function markStoryFailed(prd, storyId, failureCategory) {
|
|
22296
|
+
function markStoryFailed(prd, storyId, failureCategory, failureStage) {
|
|
22259
22297
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
22260
22298
|
if (story) {
|
|
22261
22299
|
story.status = "failed";
|
|
@@ -22263,6 +22301,9 @@ function markStoryFailed(prd, storyId, failureCategory) {
|
|
|
22263
22301
|
if (failureCategory !== undefined) {
|
|
22264
22302
|
story.failureCategory = failureCategory;
|
|
22265
22303
|
}
|
|
22304
|
+
if (failureStage !== undefined) {
|
|
22305
|
+
story.failureStage = failureStage;
|
|
22306
|
+
}
|
|
22266
22307
|
}
|
|
22267
22308
|
}
|
|
22268
22309
|
function markStorySkipped(prd, storyId) {
|
|
@@ -22289,7 +22330,7 @@ var package_default;
|
|
|
22289
22330
|
var init_package = __esm(() => {
|
|
22290
22331
|
package_default = {
|
|
22291
22332
|
name: "@nathapp/nax",
|
|
22292
|
-
version: "0.
|
|
22333
|
+
version: "0.50.0",
|
|
22293
22334
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22294
22335
|
type: "module",
|
|
22295
22336
|
bin: {
|
|
@@ -22362,8 +22403,8 @@ var init_version = __esm(() => {
|
|
|
22362
22403
|
NAX_VERSION = package_default.version;
|
|
22363
22404
|
NAX_COMMIT = (() => {
|
|
22364
22405
|
try {
|
|
22365
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22366
|
-
return "
|
|
22406
|
+
if (/^[0-9a-f]{6,10}$/.test("0eeefb4"))
|
|
22407
|
+
return "0eeefb4";
|
|
22367
22408
|
} catch {}
|
|
22368
22409
|
try {
|
|
22369
22410
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -24755,6 +24796,9 @@ ${c.output}
|
|
|
24755
24796
|
\`\`\``).join(`
|
|
24756
24797
|
|
|
24757
24798
|
`);
|
|
24799
|
+
const scopeConstraint = story.workdir ? `
|
|
24800
|
+
|
|
24801
|
+
IMPORTANT: Only modify files within \`${story.workdir}/\`. Do NOT touch files outside this directory.` : "";
|
|
24758
24802
|
return `You are fixing lint/typecheck errors from a code review.
|
|
24759
24803
|
|
|
24760
24804
|
Story: ${story.title} (${story.id})
|
|
@@ -24765,7 +24809,7 @@ ${errors3}
|
|
|
24765
24809
|
|
|
24766
24810
|
Fix ALL errors listed above. Do NOT change test files or test behavior.
|
|
24767
24811
|
Do NOT add new features \u2014 only fix the quality check errors.
|
|
24768
|
-
Commit your fixes when done
|
|
24812
|
+
Commit your fixes when done.${scopeConstraint}`;
|
|
24769
24813
|
}
|
|
24770
24814
|
async function runAgentRectification(ctx) {
|
|
24771
24815
|
const logger = getLogger();
|
|
@@ -24792,9 +24836,10 @@ async function runAgentRectification(ctx) {
|
|
|
24792
24836
|
const prompt = buildReviewRectificationPrompt(failedChecks, ctx.story);
|
|
24793
24837
|
const modelTier = ctx.story.routing?.modelTier ?? ctx.config.autoMode.escalation.tierOrder[0]?.tier ?? "balanced";
|
|
24794
24838
|
const modelDef = resolveModel(ctx.config.models[modelTier]);
|
|
24839
|
+
const rectificationWorkdir = ctx.story.workdir ? join18(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
24795
24840
|
await agent.run({
|
|
24796
24841
|
prompt,
|
|
24797
|
-
workdir:
|
|
24842
|
+
workdir: rectificationWorkdir,
|
|
24798
24843
|
modelTier,
|
|
24799
24844
|
modelDef,
|
|
24800
24845
|
timeoutSeconds: ctx.config.execution.sessionTimeoutSeconds,
|
|
@@ -25348,6 +25393,32 @@ var init_elements = __esm(() => {
|
|
|
25348
25393
|
init_logger2();
|
|
25349
25394
|
});
|
|
25350
25395
|
|
|
25396
|
+
// src/context/parent-context.ts
|
|
25397
|
+
function getParentOutputFiles(story, allStories) {
|
|
25398
|
+
if (!story.dependencies || story.dependencies.length === 0)
|
|
25399
|
+
return [];
|
|
25400
|
+
const parentFiles = [];
|
|
25401
|
+
for (const depId of story.dependencies) {
|
|
25402
|
+
const parent = allStories.find((s) => s.id === depId);
|
|
25403
|
+
if (parent?.outputFiles) {
|
|
25404
|
+
parentFiles.push(...parent.outputFiles);
|
|
25405
|
+
}
|
|
25406
|
+
}
|
|
25407
|
+
const unique = [...new Set(parentFiles)];
|
|
25408
|
+
return unique.filter((f) => !NOISE_PATTERNS.some((p) => p.test(f))).slice(0, MAX_PARENT_FILES);
|
|
25409
|
+
}
|
|
25410
|
+
var MAX_PARENT_FILES = 10, NOISE_PATTERNS;
|
|
25411
|
+
var init_parent_context = __esm(() => {
|
|
25412
|
+
NOISE_PATTERNS = [
|
|
25413
|
+
/\.test\.(ts|js|tsx|jsx)$/,
|
|
25414
|
+
/\.spec\.(ts|js|tsx|jsx)$/,
|
|
25415
|
+
/package-lock\.json$/,
|
|
25416
|
+
/bun\.lockb?$/,
|
|
25417
|
+
/\.gitignore$/,
|
|
25418
|
+
/^nax\//
|
|
25419
|
+
];
|
|
25420
|
+
});
|
|
25421
|
+
|
|
25351
25422
|
// src/context/test-scanner.ts
|
|
25352
25423
|
import path6 from "path";
|
|
25353
25424
|
var {Glob } = globalThis.Bun;
|
|
@@ -25695,6 +25766,18 @@ async function buildContext(storyContext, budget) {
|
|
|
25695
25766
|
}
|
|
25696
25767
|
}
|
|
25697
25768
|
elements.push(createStoryContext(currentStory, 80));
|
|
25769
|
+
if (prd.analysis) {
|
|
25770
|
+
const analysisContent = `The following analysis was performed during the planning phase. Use it to understand the codebase context before implementing:
|
|
25771
|
+
|
|
25772
|
+
${prd.analysis}`;
|
|
25773
|
+
elements.push({
|
|
25774
|
+
type: "planning-analysis",
|
|
25775
|
+
label: "Planning Analysis",
|
|
25776
|
+
content: analysisContent,
|
|
25777
|
+
priority: 88,
|
|
25778
|
+
tokens: estimateTokens(analysisContent)
|
|
25779
|
+
});
|
|
25780
|
+
}
|
|
25698
25781
|
addDependencyElements(elements, currentStory, prd);
|
|
25699
25782
|
await addTestCoverageElement(elements, storyContext, currentStory);
|
|
25700
25783
|
await addFileElements(elements, storyContext, currentStory);
|
|
@@ -25755,6 +25838,15 @@ async function addFileElements(elements, storyContext, story) {
|
|
|
25755
25838
|
if (fileInjection !== "keyword")
|
|
25756
25839
|
return;
|
|
25757
25840
|
let contextFiles = getContextFiles(story);
|
|
25841
|
+
const parentFiles = getParentOutputFiles(story, storyContext.prd?.userStories ?? []);
|
|
25842
|
+
if (parentFiles.length > 0) {
|
|
25843
|
+
const logger = getLogger();
|
|
25844
|
+
logger.info("context", "Injecting parent output files for context chaining", {
|
|
25845
|
+
storyId: story.id,
|
|
25846
|
+
parentFiles
|
|
25847
|
+
});
|
|
25848
|
+
contextFiles = [...new Set([...contextFiles, ...parentFiles])];
|
|
25849
|
+
}
|
|
25758
25850
|
if (contextFiles.length === 0 && storyContext.config?.context?.autoDetect?.enabled !== false && storyContext.workdir) {
|
|
25759
25851
|
const autoDetectConfig = storyContext.config?.context?.autoDetect;
|
|
25760
25852
|
try {
|
|
@@ -25822,6 +25914,7 @@ var init_builder3 = __esm(() => {
|
|
|
25822
25914
|
init_prd();
|
|
25823
25915
|
init_auto_detect();
|
|
25824
25916
|
init_elements();
|
|
25917
|
+
init_parent_context();
|
|
25825
25918
|
init_test_scanner();
|
|
25826
25919
|
init_elements();
|
|
25827
25920
|
_deps5 = {
|
|
@@ -26291,6 +26384,22 @@ async function autoCommitIfDirty(workdir, stage, role, storyId) {
|
|
|
26291
26384
|
await commitProc.exited;
|
|
26292
26385
|
} catch {}
|
|
26293
26386
|
}
|
|
26387
|
+
async function captureOutputFiles(workdir, baseRef, scopePrefix) {
|
|
26388
|
+
if (!baseRef)
|
|
26389
|
+
return [];
|
|
26390
|
+
try {
|
|
26391
|
+
const args = ["diff", "--name-only", `${baseRef}..HEAD`];
|
|
26392
|
+
if (scopePrefix)
|
|
26393
|
+
args.push("--", `${scopePrefix}/`);
|
|
26394
|
+
const proc = _gitDeps.spawn(["git", ...args], { cwd: workdir, stdout: "pipe", stderr: "pipe" });
|
|
26395
|
+
const output = await new Response(proc.stdout).text();
|
|
26396
|
+
await proc.exited;
|
|
26397
|
+
return output.trim().split(`
|
|
26398
|
+
`).filter(Boolean);
|
|
26399
|
+
} catch {
|
|
26400
|
+
return [];
|
|
26401
|
+
}
|
|
26402
|
+
}
|
|
26294
26403
|
var _gitDeps, GIT_TIMEOUT_MS = 1e4;
|
|
26295
26404
|
var init_git = __esm(() => {
|
|
26296
26405
|
init_logger2();
|
|
@@ -33141,7 +33250,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33141
33250
|
worktreePath
|
|
33142
33251
|
});
|
|
33143
33252
|
} catch (error48) {
|
|
33144
|
-
markStoryFailed(currentPrd, story.id);
|
|
33253
|
+
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
33145
33254
|
logger?.error("parallel", "Failed to create worktree", {
|
|
33146
33255
|
storyId: story.id,
|
|
33147
33256
|
error: errorMessage(error48)
|
|
@@ -33169,7 +33278,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33169
33278
|
retryCount: mergeResult.retryCount
|
|
33170
33279
|
});
|
|
33171
33280
|
} else {
|
|
33172
|
-
markStoryFailed(currentPrd, mergeResult.storyId);
|
|
33281
|
+
markStoryFailed(currentPrd, mergeResult.storyId, undefined, undefined);
|
|
33173
33282
|
batchResult.mergeConflicts.push({
|
|
33174
33283
|
storyId: mergeResult.storyId,
|
|
33175
33284
|
conflictFiles: mergeResult.conflictFiles || [],
|
|
@@ -33187,7 +33296,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
33187
33296
|
}
|
|
33188
33297
|
}
|
|
33189
33298
|
for (const { story, error: error48 } of batchResult.failed) {
|
|
33190
|
-
markStoryFailed(currentPrd, story.id);
|
|
33299
|
+
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
33191
33300
|
logger?.error("parallel", "Cleaning up failed story worktree", {
|
|
33192
33301
|
storyId: story.id,
|
|
33193
33302
|
error: error48
|
|
@@ -34124,7 +34233,7 @@ async function handleNoTierAvailable(ctx, failureCategory) {
|
|
|
34124
34233
|
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
34125
34234
|
}
|
|
34126
34235
|
const failedPrd = { ...ctx.prd };
|
|
34127
|
-
markStoryFailed(failedPrd, ctx.story.id, failureCategory);
|
|
34236
|
+
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
34128
34237
|
await savePRD(failedPrd, ctx.prdPath);
|
|
34129
34238
|
logger?.error("execution", "Story failed - execution failed", {
|
|
34130
34239
|
storyId: ctx.story.id
|
|
@@ -34164,7 +34273,7 @@ async function handleMaxAttemptsReached(ctx, failureCategory) {
|
|
|
34164
34273
|
return { outcome: "paused", prdDirty: true, prd: pausedPrd };
|
|
34165
34274
|
}
|
|
34166
34275
|
const failedPrd = { ...ctx.prd };
|
|
34167
|
-
markStoryFailed(failedPrd, ctx.story.id, failureCategory);
|
|
34276
|
+
markStoryFailed(failedPrd, ctx.story.id, failureCategory, undefined);
|
|
34168
34277
|
await savePRD(failedPrd, ctx.prdPath);
|
|
34169
34278
|
logger?.error("execution", "Story failed - max attempts reached", {
|
|
34170
34279
|
storyId: ctx.story.id,
|
|
@@ -34329,6 +34438,17 @@ var init_escalation = __esm(() => {
|
|
|
34329
34438
|
});
|
|
34330
34439
|
|
|
34331
34440
|
// src/execution/pipeline-result-handler.ts
|
|
34441
|
+
function filterOutputFiles(files) {
|
|
34442
|
+
const NOISE = [
|
|
34443
|
+
/\.test\.(ts|js|tsx|jsx)$/,
|
|
34444
|
+
/\.spec\.(ts|js|tsx|jsx)$/,
|
|
34445
|
+
/package-lock\.json$/,
|
|
34446
|
+
/bun\.lock(b?)$/,
|
|
34447
|
+
/\.gitignore$/,
|
|
34448
|
+
/^nax\//
|
|
34449
|
+
];
|
|
34450
|
+
return files.filter((f) => !NOISE.some((p) => p.test(f))).slice(0, 15);
|
|
34451
|
+
}
|
|
34332
34452
|
async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
34333
34453
|
const logger = getSafeLogger();
|
|
34334
34454
|
const costDelta = pipelineResult.context.agentResult?.estimatedCost || 0;
|
|
@@ -34357,6 +34477,17 @@ async function handlePipelineSuccess(ctx, pipelineResult) {
|
|
|
34357
34477
|
testStrategy: ctx.routing.testStrategy
|
|
34358
34478
|
});
|
|
34359
34479
|
}
|
|
34480
|
+
if (ctx.storyGitRef) {
|
|
34481
|
+
for (const completedStory of ctx.storiesToExecute) {
|
|
34482
|
+
try {
|
|
34483
|
+
const rawFiles = await captureOutputFiles(ctx.workdir, ctx.storyGitRef, completedStory.workdir);
|
|
34484
|
+
const filtered = filterOutputFiles(rawFiles);
|
|
34485
|
+
if (filtered.length > 0) {
|
|
34486
|
+
completedStory.outputFiles = filtered;
|
|
34487
|
+
}
|
|
34488
|
+
} catch {}
|
|
34489
|
+
}
|
|
34490
|
+
}
|
|
34360
34491
|
const updatedCounts = countStories(prd);
|
|
34361
34492
|
logger?.info("progress", "Progress update", {
|
|
34362
34493
|
totalStories: updatedCounts.total,
|
|
@@ -34393,7 +34524,7 @@ async function handlePipelineFailure(ctx, pipelineResult) {
|
|
|
34393
34524
|
prdDirty = true;
|
|
34394
34525
|
break;
|
|
34395
34526
|
case "fail":
|
|
34396
|
-
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory);
|
|
34527
|
+
markStoryFailed(prd, ctx.story.id, pipelineResult.context.tddFailureCategory, pipelineResult.stoppedAtStage);
|
|
34397
34528
|
await savePRD(prd, ctx.prdPath);
|
|
34398
34529
|
prdDirty = true;
|
|
34399
34530
|
logger?.error("pipeline", "Story failed", { storyId: ctx.story.id, reason: pipelineResult.reason });
|
|
@@ -34447,6 +34578,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
34447
34578
|
init_logger2();
|
|
34448
34579
|
init_event_bus();
|
|
34449
34580
|
init_prd();
|
|
34581
|
+
init_git();
|
|
34450
34582
|
init_escalation();
|
|
34451
34583
|
init_progress();
|
|
34452
34584
|
});
|
|
@@ -35101,25 +35233,44 @@ var init_precheck_runner = __esm(() => {
|
|
|
35101
35233
|
var exports_run_initialization = {};
|
|
35102
35234
|
__export(exports_run_initialization, {
|
|
35103
35235
|
logActiveProtocol: () => logActiveProtocol,
|
|
35104
|
-
initializeRun: () => initializeRun
|
|
35236
|
+
initializeRun: () => initializeRun,
|
|
35237
|
+
_reconcileDeps: () => _reconcileDeps
|
|
35105
35238
|
});
|
|
35106
|
-
|
|
35239
|
+
import { join as join51 } from "path";
|
|
35240
|
+
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
35107
35241
|
const logger = getSafeLogger();
|
|
35108
35242
|
let reconciledCount = 0;
|
|
35109
35243
|
let modified = false;
|
|
35110
35244
|
for (const story of prd.userStories) {
|
|
35111
|
-
if (story.status
|
|
35112
|
-
|
|
35113
|
-
|
|
35114
|
-
|
|
35115
|
-
|
|
35116
|
-
|
|
35117
|
-
|
|
35118
|
-
|
|
35119
|
-
|
|
35120
|
-
|
|
35245
|
+
if (story.status !== "failed")
|
|
35246
|
+
continue;
|
|
35247
|
+
const hasCommits = await _reconcileDeps.hasCommitsForStory(workdir, story.id);
|
|
35248
|
+
if (!hasCommits)
|
|
35249
|
+
continue;
|
|
35250
|
+
if (story.failureStage === "review" || story.failureStage === "autofix") {
|
|
35251
|
+
const effectiveWorkdir = story.workdir ? join51(workdir, story.workdir) : workdir;
|
|
35252
|
+
try {
|
|
35253
|
+
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
35254
|
+
if (!reviewResult.success) {
|
|
35255
|
+
logger?.warn("reconciliation", "Review still fails \u2014 not reconciling story", {
|
|
35256
|
+
storyId: story.id,
|
|
35257
|
+
failureReason: reviewResult.failureReason
|
|
35258
|
+
});
|
|
35259
|
+
continue;
|
|
35260
|
+
}
|
|
35261
|
+
logger?.info("reconciliation", "Review now passes \u2014 reconciling story", { storyId: story.id });
|
|
35262
|
+
} catch {
|
|
35263
|
+
logger?.warn("reconciliation", "Review check errored \u2014 not reconciling story", { storyId: story.id });
|
|
35264
|
+
continue;
|
|
35121
35265
|
}
|
|
35122
35266
|
}
|
|
35267
|
+
logger?.warn("reconciliation", "Failed story has commits in git history, marking as passed", {
|
|
35268
|
+
storyId: story.id,
|
|
35269
|
+
title: story.title
|
|
35270
|
+
});
|
|
35271
|
+
markStoryPassed(prd, story.id);
|
|
35272
|
+
reconciledCount++;
|
|
35273
|
+
modified = true;
|
|
35123
35274
|
}
|
|
35124
35275
|
if (reconciledCount > 0) {
|
|
35125
35276
|
logger?.info("reconciliation", `Reconciled ${reconciledCount} failed stories from git history`);
|
|
@@ -35169,7 +35320,7 @@ async function initializeRun(ctx) {
|
|
|
35169
35320
|
const logger = getSafeLogger();
|
|
35170
35321
|
await checkAgentInstalled(ctx.config, ctx.dryRun, ctx.agentGetFn);
|
|
35171
35322
|
let prd = await loadPRD(ctx.prdPath);
|
|
35172
|
-
prd = await reconcileState(prd, ctx.prdPath, ctx.workdir);
|
|
35323
|
+
prd = await reconcileState(prd, ctx.prdPath, ctx.workdir, ctx.config);
|
|
35173
35324
|
const counts = countStories(prd);
|
|
35174
35325
|
validateStoryCount(counts, ctx.config);
|
|
35175
35326
|
logger?.info("execution", "Run initialization complete", {
|
|
@@ -35179,11 +35330,17 @@ async function initializeRun(ctx) {
|
|
|
35179
35330
|
});
|
|
35180
35331
|
return { prd, storyCounts: counts };
|
|
35181
35332
|
}
|
|
35333
|
+
var _reconcileDeps;
|
|
35182
35334
|
var init_run_initialization = __esm(() => {
|
|
35183
35335
|
init_errors3();
|
|
35184
35336
|
init_logger2();
|
|
35185
35337
|
init_prd();
|
|
35338
|
+
init_runner2();
|
|
35186
35339
|
init_git();
|
|
35340
|
+
_reconcileDeps = {
|
|
35341
|
+
hasCommitsForStory: (workdir, storyId) => hasCommitsForStory(workdir, storyId),
|
|
35342
|
+
runReview: (reviewConfig, workdir, executionConfig) => runReview(reviewConfig, workdir, executionConfig)
|
|
35343
|
+
};
|
|
35187
35344
|
});
|
|
35188
35345
|
|
|
35189
35346
|
// src/execution/lifecycle/run-setup.ts
|
|
@@ -66225,7 +66382,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
66225
66382
|
init_source();
|
|
66226
66383
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
66227
66384
|
import { homedir as homedir10 } from "os";
|
|
66228
|
-
import { join as
|
|
66385
|
+
import { join as join52 } from "path";
|
|
66229
66386
|
|
|
66230
66387
|
// node_modules/commander/esm.mjs
|
|
66231
66388
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -67651,14 +67808,48 @@ For each user story, set the "workdir" field to the relevant package path (e.g.
|
|
|
67651
67808
|
"workdir": "string \u2014 optional, relative path to package (e.g. \\"packages/api\\"). Omit for root-level stories.",` : "";
|
|
67652
67809
|
return `You are a senior software architect generating a product requirements document (PRD) as JSON.
|
|
67653
67810
|
|
|
67811
|
+
## Step 1: Understand the Spec
|
|
67812
|
+
|
|
67813
|
+
Read the spec carefully. Identify the goal, scope, constraints, and what "done" looks like.
|
|
67814
|
+
|
|
67654
67815
|
## Spec
|
|
67655
67816
|
|
|
67656
67817
|
${specContent}
|
|
67657
67818
|
|
|
67819
|
+
## Step 2: Analyze
|
|
67820
|
+
|
|
67821
|
+
Examine the codebase context below.
|
|
67822
|
+
|
|
67823
|
+
If the codebase has existing code (refactoring, enhancement, bug fix):
|
|
67824
|
+
- Which existing files need modification?
|
|
67825
|
+
- Which files import from or depend on them?
|
|
67826
|
+
- What tests cover the affected code?
|
|
67827
|
+
- What are the risks (breaking changes, backward compatibility)?
|
|
67828
|
+
- What is the migration path?
|
|
67829
|
+
|
|
67830
|
+
If this is a greenfield project (empty or minimal codebase):
|
|
67831
|
+
- What is the target architecture?
|
|
67832
|
+
- What are the key technical decisions (framework, patterns, conventions)?
|
|
67833
|
+
- What should be built first (dependency order)?
|
|
67834
|
+
|
|
67835
|
+
Record ALL findings in the "analysis" field of the output JSON. This analysis is provided to every implementation agent as context \u2014 be thorough.
|
|
67836
|
+
|
|
67658
67837
|
## Codebase Context
|
|
67659
67838
|
|
|
67660
67839
|
${codebaseContext}${monorepoHint}
|
|
67661
67840
|
|
|
67841
|
+
## Step 3: Generate Implementation Stories
|
|
67842
|
+
|
|
67843
|
+
Based on your Step 2 analysis, create stories that produce CODE CHANGES.
|
|
67844
|
+
|
|
67845
|
+
${GROUPING_RULES}
|
|
67846
|
+
|
|
67847
|
+
For each story, set "contextFiles" to the key source files the agent should read before implementing (max 5 per story). Use your Step 2 analysis to identify the most relevant files. Leave empty for greenfield stories with no existing files to reference.
|
|
67848
|
+
|
|
67849
|
+
${COMPLEXITY_GUIDE}
|
|
67850
|
+
|
|
67851
|
+
${TEST_STRATEGY_GUIDE}
|
|
67852
|
+
|
|
67662
67853
|
## Output Schema
|
|
67663
67854
|
|
|
67664
67855
|
Generate a JSON object with this exact structure (no markdown, no explanation \u2014 JSON only):
|
|
@@ -67666,6 +67857,7 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
67666
67857
|
{
|
|
67667
67858
|
"project": "string \u2014 project name",
|
|
67668
67859
|
"feature": "string \u2014 feature name",
|
|
67860
|
+
"analysis": "string \u2014 your Step 2 analysis: key files, impact areas, risks, architecture decisions, migration notes. All implementation agents will receive this.",
|
|
67669
67861
|
"branchName": "string \u2014 git branch (e.g. feat/my-feature)",
|
|
67670
67862
|
"createdAt": "ISO 8601 timestamp",
|
|
67671
67863
|
"updatedAt": "ISO 8601 timestamp",
|
|
@@ -67675,13 +67867,14 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
67675
67867
|
"title": "string \u2014 concise story title",
|
|
67676
67868
|
"description": "string \u2014 detailed description of the story",
|
|
67677
67869
|
"acceptanceCriteria": ["string \u2014 each AC line"],
|
|
67870
|
+
"contextFiles": ["string \u2014 key source files the agent should read (max 5, relative paths)"],
|
|
67678
67871
|
"tags": ["string \u2014 routing tags, e.g. feature, security, api"],
|
|
67679
67872
|
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
67680
67873
|
"status": "pending",
|
|
67681
67874
|
"passes": false,
|
|
67682
67875
|
"routing": {
|
|
67683
67876
|
"complexity": "simple | medium | complex | expert",
|
|
67684
|
-
"testStrategy": "
|
|
67877
|
+
"testStrategy": "tdd-simple | three-session-tdd-lite | three-session-tdd | test-after",
|
|
67685
67878
|
"reasoning": "string \u2014 brief classification rationale"
|
|
67686
67879
|
},
|
|
67687
67880
|
"escalations": [],
|
|
@@ -67690,12 +67883,6 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
67690
67883
|
]
|
|
67691
67884
|
}
|
|
67692
67885
|
|
|
67693
|
-
${COMPLEXITY_GUIDE}
|
|
67694
|
-
|
|
67695
|
-
${TEST_STRATEGY_GUIDE}
|
|
67696
|
-
|
|
67697
|
-
${GROUPING_RULES}
|
|
67698
|
-
|
|
67699
67886
|
${outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
|
|
67700
67887
|
Do NOT output the JSON to the conversation. Write the file, then reply with a brief confirmation.` : "Output ONLY the JSON object. Do not wrap in markdown code blocks."}`;
|
|
67701
67888
|
}
|
|
@@ -78012,15 +78199,15 @@ Next: nax generate --package ${options.package}`));
|
|
|
78012
78199
|
}
|
|
78013
78200
|
return;
|
|
78014
78201
|
}
|
|
78015
|
-
const naxDir =
|
|
78202
|
+
const naxDir = join52(workdir, "nax");
|
|
78016
78203
|
if (existsSync34(naxDir) && !options.force) {
|
|
78017
78204
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
78018
78205
|
return;
|
|
78019
78206
|
}
|
|
78020
|
-
mkdirSync6(
|
|
78021
|
-
mkdirSync6(
|
|
78022
|
-
await Bun.write(
|
|
78023
|
-
await Bun.write(
|
|
78207
|
+
mkdirSync6(join52(naxDir, "features"), { recursive: true });
|
|
78208
|
+
mkdirSync6(join52(naxDir, "hooks"), { recursive: true });
|
|
78209
|
+
await Bun.write(join52(naxDir, "config.json"), JSON.stringify(DEFAULT_CONFIG, null, 2));
|
|
78210
|
+
await Bun.write(join52(naxDir, "hooks.json"), JSON.stringify({
|
|
78024
78211
|
hooks: {
|
|
78025
78212
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
78026
78213
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -78028,12 +78215,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
78028
78215
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
78029
78216
|
}
|
|
78030
78217
|
}, null, 2));
|
|
78031
|
-
await Bun.write(
|
|
78218
|
+
await Bun.write(join52(naxDir, ".gitignore"), `# nax temp files
|
|
78032
78219
|
*.tmp
|
|
78033
78220
|
.paused.json
|
|
78034
78221
|
.nax-verifier-verdict.json
|
|
78035
78222
|
`);
|
|
78036
|
-
await Bun.write(
|
|
78223
|
+
await Bun.write(join52(naxDir, "context.md"), `# Project Context
|
|
78037
78224
|
|
|
78038
78225
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
78039
78226
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -78159,8 +78346,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78159
78346
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78160
78347
|
process.exit(1);
|
|
78161
78348
|
}
|
|
78162
|
-
const featureDir =
|
|
78163
|
-
const prdPath =
|
|
78349
|
+
const featureDir = join52(naxDir, "features", options.feature);
|
|
78350
|
+
const prdPath = join52(featureDir, "prd.json");
|
|
78164
78351
|
if (options.plan && options.from) {
|
|
78165
78352
|
if (existsSync34(prdPath) && !options.force) {
|
|
78166
78353
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -78182,10 +78369,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78182
78369
|
}
|
|
78183
78370
|
}
|
|
78184
78371
|
try {
|
|
78185
|
-
const planLogDir =
|
|
78372
|
+
const planLogDir = join52(featureDir, "plan");
|
|
78186
78373
|
mkdirSync6(planLogDir, { recursive: true });
|
|
78187
78374
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78188
|
-
const planLogPath =
|
|
78375
|
+
const planLogPath = join52(planLogDir, `${planLogId}.jsonl`);
|
|
78189
78376
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78190
78377
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78191
78378
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -78223,10 +78410,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78223
78410
|
process.exit(1);
|
|
78224
78411
|
}
|
|
78225
78412
|
resetLogger();
|
|
78226
|
-
const runsDir =
|
|
78413
|
+
const runsDir = join52(featureDir, "runs");
|
|
78227
78414
|
mkdirSync6(runsDir, { recursive: true });
|
|
78228
78415
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78229
|
-
const logFilePath =
|
|
78416
|
+
const logFilePath = join52(runsDir, `${runId}.jsonl`);
|
|
78230
78417
|
const isTTY = process.stdout.isTTY ?? false;
|
|
78231
78418
|
const headlessFlag = options.headless ?? false;
|
|
78232
78419
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -78242,7 +78429,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78242
78429
|
config2.autoMode.defaultAgent = options.agent;
|
|
78243
78430
|
}
|
|
78244
78431
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
78245
|
-
const globalNaxDir =
|
|
78432
|
+
const globalNaxDir = join52(homedir10(), ".nax");
|
|
78246
78433
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
78247
78434
|
const eventEmitter = new PipelineEventEmitter;
|
|
78248
78435
|
let tuiInstance;
|
|
@@ -78265,7 +78452,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78265
78452
|
} else {
|
|
78266
78453
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
78267
78454
|
}
|
|
78268
|
-
const statusFilePath =
|
|
78455
|
+
const statusFilePath = join52(workdir, "nax", "status.json");
|
|
78269
78456
|
let parallel;
|
|
78270
78457
|
if (options.parallel !== undefined) {
|
|
78271
78458
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -78291,7 +78478,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
78291
78478
|
headless: useHeadless,
|
|
78292
78479
|
skipPrecheck: options.skipPrecheck ?? false
|
|
78293
78480
|
});
|
|
78294
|
-
const latestSymlink =
|
|
78481
|
+
const latestSymlink = join52(runsDir, "latest.jsonl");
|
|
78295
78482
|
try {
|
|
78296
78483
|
if (existsSync34(latestSymlink)) {
|
|
78297
78484
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -78329,9 +78516,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78329
78516
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78330
78517
|
process.exit(1);
|
|
78331
78518
|
}
|
|
78332
|
-
const featureDir =
|
|
78519
|
+
const featureDir = join52(naxDir, "features", name);
|
|
78333
78520
|
mkdirSync6(featureDir, { recursive: true });
|
|
78334
|
-
await Bun.write(
|
|
78521
|
+
await Bun.write(join52(featureDir, "spec.md"), `# Feature: ${name}
|
|
78335
78522
|
|
|
78336
78523
|
## Overview
|
|
78337
78524
|
|
|
@@ -78339,7 +78526,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78339
78526
|
|
|
78340
78527
|
## Acceptance Criteria
|
|
78341
78528
|
`);
|
|
78342
|
-
await Bun.write(
|
|
78529
|
+
await Bun.write(join52(featureDir, "plan.md"), `# Plan: ${name}
|
|
78343
78530
|
|
|
78344
78531
|
## Architecture
|
|
78345
78532
|
|
|
@@ -78347,7 +78534,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78347
78534
|
|
|
78348
78535
|
## Dependencies
|
|
78349
78536
|
`);
|
|
78350
|
-
await Bun.write(
|
|
78537
|
+
await Bun.write(join52(featureDir, "tasks.md"), `# Tasks: ${name}
|
|
78351
78538
|
|
|
78352
78539
|
## US-001: [Title]
|
|
78353
78540
|
|
|
@@ -78356,7 +78543,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
78356
78543
|
### Acceptance Criteria
|
|
78357
78544
|
- [ ] Criterion 1
|
|
78358
78545
|
`);
|
|
78359
|
-
await Bun.write(
|
|
78546
|
+
await Bun.write(join52(featureDir, "progress.txt"), `# Progress: ${name}
|
|
78360
78547
|
|
|
78361
78548
|
Created: ${new Date().toISOString()}
|
|
78362
78549
|
|
|
@@ -78384,7 +78571,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78384
78571
|
console.error(source_default.red("nax not initialized."));
|
|
78385
78572
|
process.exit(1);
|
|
78386
78573
|
}
|
|
78387
|
-
const featuresDir =
|
|
78574
|
+
const featuresDir = join52(naxDir, "features");
|
|
78388
78575
|
if (!existsSync34(featuresDir)) {
|
|
78389
78576
|
console.log(source_default.dim("No features yet."));
|
|
78390
78577
|
return;
|
|
@@ -78399,7 +78586,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
78399
78586
|
Features:
|
|
78400
78587
|
`));
|
|
78401
78588
|
for (const name of entries) {
|
|
78402
|
-
const prdPath =
|
|
78589
|
+
const prdPath = join52(featuresDir, name, "prd.json");
|
|
78403
78590
|
if (existsSync34(prdPath)) {
|
|
78404
78591
|
const prd = await loadPRD(prdPath);
|
|
78405
78592
|
const c = countStories(prd);
|
|
@@ -78430,10 +78617,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
78430
78617
|
process.exit(1);
|
|
78431
78618
|
}
|
|
78432
78619
|
const config2 = await loadConfig(workdir);
|
|
78433
|
-
const featureLogDir =
|
|
78620
|
+
const featureLogDir = join52(naxDir, "features", options.feature, "plan");
|
|
78434
78621
|
mkdirSync6(featureLogDir, { recursive: true });
|
|
78435
78622
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
78436
|
-
const planLogPath =
|
|
78623
|
+
const planLogPath = join52(featureLogDir, `${planLogId}.jsonl`);
|
|
78437
78624
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
78438
78625
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
78439
78626
|
try {
|
|
@@ -78470,7 +78657,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78470
78657
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
78471
78658
|
process.exit(1);
|
|
78472
78659
|
}
|
|
78473
|
-
const featureDir =
|
|
78660
|
+
const featureDir = join52(naxDir, "features", options.feature);
|
|
78474
78661
|
if (!existsSync34(featureDir)) {
|
|
78475
78662
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
78476
78663
|
process.exit(1);
|
|
@@ -78486,7 +78673,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
78486
78673
|
specPath: options.from,
|
|
78487
78674
|
reclassify: options.reclassify
|
|
78488
78675
|
});
|
|
78489
|
-
const prdPath =
|
|
78676
|
+
const prdPath = join52(featureDir, "prd.json");
|
|
78490
78677
|
await Bun.write(prdPath, JSON.stringify(prd, null, 2));
|
|
78491
78678
|
const c = countStories(prd);
|
|
78492
78679
|
console.log(source_default.green(`
|