@nathapp/nax 0.68.7 → 0.68.8
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 +588 -327
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -27497,6 +27497,9 @@ function getContextFiles(story) {
|
|
|
27497
27497
|
const files = story.contextFiles ?? story.relevantFiles ?? [];
|
|
27498
27498
|
return files.map((f) => typeof f === "string" ? f : f.path);
|
|
27499
27499
|
}
|
|
27500
|
+
function getExpectedFiles(story) {
|
|
27501
|
+
return story.expectedFiles ?? [];
|
|
27502
|
+
}
|
|
27500
27503
|
function isStalled(prd) {
|
|
27501
27504
|
const remaining = prd.userStories.filter((s) => s.status !== "passed" && s.status !== "skipped");
|
|
27502
27505
|
if (remaining.length === 0)
|
|
@@ -27772,6 +27775,24 @@ function validateStory(raw, index, allIds) {
|
|
|
27772
27775
|
}
|
|
27773
27776
|
}
|
|
27774
27777
|
}
|
|
27778
|
+
const rawExpectedFiles = s.expectedFiles;
|
|
27779
|
+
const expectedFiles = [];
|
|
27780
|
+
if (Array.isArray(rawExpectedFiles)) {
|
|
27781
|
+
for (const f of rawExpectedFiles) {
|
|
27782
|
+
if (typeof f !== "string")
|
|
27783
|
+
continue;
|
|
27784
|
+
const trimmed = f.trim();
|
|
27785
|
+
if (trimmed === "")
|
|
27786
|
+
continue;
|
|
27787
|
+
if (trimmed.startsWith("/")) {
|
|
27788
|
+
throw new Error(`[schema] story[${index}].expectedFiles entry must be relative (no absolute paths): "${trimmed}"`);
|
|
27789
|
+
}
|
|
27790
|
+
if (trimmed.includes("..")) {
|
|
27791
|
+
throw new Error(`[schema] story[${index}].expectedFiles entry must not contain '..': "${trimmed}"`);
|
|
27792
|
+
}
|
|
27793
|
+
expectedFiles.push(trimmed);
|
|
27794
|
+
}
|
|
27795
|
+
}
|
|
27775
27796
|
const VALID_VERIFIED_BY_KINDS = ["test", "symbol", "file"];
|
|
27776
27797
|
let verifiedBy;
|
|
27777
27798
|
if (s.verifiedBy !== undefined && s.verifiedBy !== null) {
|
|
@@ -27805,6 +27826,7 @@ function validateStory(raw, index, allIds) {
|
|
|
27805
27826
|
},
|
|
27806
27827
|
...workdir !== undefined ? { workdir } : {},
|
|
27807
27828
|
...contextFiles.length > 0 ? { contextFiles } : {},
|
|
27829
|
+
...expectedFiles.length > 0 ? { expectedFiles } : {},
|
|
27808
27830
|
...suggestedCriteria !== undefined ? { suggestedCriteria } : {},
|
|
27809
27831
|
...verifiedBy !== undefined ? { verifiedBy } : {},
|
|
27810
27832
|
...intent !== undefined ? { intent } : {}
|
|
@@ -29366,6 +29388,12 @@ function renderErrorSection(sections, byType) {
|
|
|
29366
29388
|
|
|
29367
29389
|
// src/context/builder.ts
|
|
29368
29390
|
import path2 from "path";
|
|
29391
|
+
function readContextMessage(relativeFilePath) {
|
|
29392
|
+
return `_Path: \`${relativeFilePath}\` \u2014 read this file before implementing._`;
|
|
29393
|
+
}
|
|
29394
|
+
function createIntentMessage(relativeFilePath) {
|
|
29395
|
+
return `_Path: \`${relativeFilePath}\` \u2014 this file does not exist yet; you will CREATE it as part of this story._`;
|
|
29396
|
+
}
|
|
29369
29397
|
function sortContextElements(elements) {
|
|
29370
29398
|
return [...elements].sort((a, b) => {
|
|
29371
29399
|
if (a.priority !== b.priority)
|
|
@@ -29493,7 +29521,6 @@ async function addTestCoverageElement(elements, storyContext, story) {
|
|
|
29493
29521
|
}
|
|
29494
29522
|
}
|
|
29495
29523
|
async function addFileElements(elements, storyContext, story) {
|
|
29496
|
-
const MAX_FILES3 = 5;
|
|
29497
29524
|
const fileInjection = storyContext.config?.context?.fileInjection;
|
|
29498
29525
|
let contextFiles = getContextFiles(story);
|
|
29499
29526
|
const parentFiles = getParentOutputFiles(story, storyContext.prd?.userStories ?? []);
|
|
@@ -29530,26 +29557,51 @@ async function addFileElements(elements, storyContext, story) {
|
|
|
29530
29557
|
});
|
|
29531
29558
|
}
|
|
29532
29559
|
}
|
|
29533
|
-
|
|
29560
|
+
const expectedFiles = getExpectedFiles(story);
|
|
29561
|
+
if (contextFiles.length === 0 && expectedFiles.length === 0)
|
|
29534
29562
|
return;
|
|
29535
|
-
const filesToLoad = contextFiles.slice(0, MAX_FILES3);
|
|
29536
29563
|
const { workdir } = storyContext;
|
|
29537
29564
|
if (!workdir) {
|
|
29538
29565
|
getLogger().warn("context", "workdir not set \u2014 cannot load context files", { storyId: story.id });
|
|
29539
29566
|
return;
|
|
29540
29567
|
}
|
|
29568
|
+
const expectedSet = new Set(expectedFiles);
|
|
29569
|
+
const surfaced = new Set;
|
|
29570
|
+
const filesToLoad = contextFiles.slice(0, FILE_INJECTION_MAX_FILES);
|
|
29541
29571
|
for (let i = 0;i < filesToLoad.length; i++) {
|
|
29542
29572
|
const relativeFilePath = filesToLoad[i];
|
|
29573
|
+
surfaced.add(relativeFilePath);
|
|
29543
29574
|
const absolutePath = path2.resolve(workdir, relativeFilePath);
|
|
29544
|
-
|
|
29545
|
-
|
|
29546
|
-
getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
|
|
29575
|
+
if (await Bun.file(absolutePath).exists()) {
|
|
29576
|
+
elements.push(createFileContext(relativeFilePath, readContextMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
|
|
29547
29577
|
continue;
|
|
29548
29578
|
}
|
|
29549
|
-
|
|
29579
|
+
if (expectedSet.has(relativeFilePath)) {
|
|
29580
|
+
elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
|
|
29581
|
+
getLogger().debug("context", "Context file does not exist yet \u2014 treated as to-be-created", {
|
|
29582
|
+
storyId: story.id,
|
|
29583
|
+
filePath: relativeFilePath
|
|
29584
|
+
});
|
|
29585
|
+
} else {
|
|
29586
|
+
getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
|
|
29587
|
+
}
|
|
29550
29588
|
}
|
|
29589
|
+
await addCreateIntentElements(elements, workdir, expectedFiles, surfaced);
|
|
29551
29590
|
}
|
|
29552
|
-
|
|
29591
|
+
async function addCreateIntentElements(elements, workdir, expectedFiles, surfaced) {
|
|
29592
|
+
let idx = 0;
|
|
29593
|
+
for (const relativeFilePath of expectedFiles.slice(0, FILE_INJECTION_MAX_FILES)) {
|
|
29594
|
+
if (surfaced.has(relativeFilePath))
|
|
29595
|
+
continue;
|
|
29596
|
+
const absolutePath = path2.resolve(workdir, relativeFilePath);
|
|
29597
|
+
if (await Bun.file(absolutePath).exists())
|
|
29598
|
+
continue;
|
|
29599
|
+
elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - FILE_INJECTION_MAX_FILES - idx));
|
|
29600
|
+
surfaced.add(relativeFilePath);
|
|
29601
|
+
idx++;
|
|
29602
|
+
}
|
|
29603
|
+
}
|
|
29604
|
+
var _contextBuilderDeps, FILE_INJECTION_MAX_FILES = 5, FILE_CONTEXT_PRIORITY_BASE = 60;
|
|
29553
29605
|
var init_builder = __esm(() => {
|
|
29554
29606
|
init_logger2();
|
|
29555
29607
|
init_prd();
|
|
@@ -34222,6 +34274,7 @@ ${outputFormat}`, overridable: false }
|
|
|
34222
34274
|
});
|
|
34223
34275
|
|
|
34224
34276
|
// src/operations/plan-refine.ts
|
|
34277
|
+
import { join as join20 } from "path";
|
|
34225
34278
|
function hasToken(text, tokens) {
|
|
34226
34279
|
const lower = text.toLowerCase();
|
|
34227
34280
|
return tokens.some((token) => lower.includes(token));
|
|
@@ -34284,6 +34337,53 @@ async function readSpecDriftViolations(input) {
|
|
|
34284
34337
|
return [];
|
|
34285
34338
|
}
|
|
34286
34339
|
}
|
|
34340
|
+
async function normalizeStoryFiles(story, workdir, fileExists) {
|
|
34341
|
+
const contextFiles = story.contextFiles ?? [];
|
|
34342
|
+
if (contextFiles.length === 0)
|
|
34343
|
+
return { story, changed: false };
|
|
34344
|
+
const logger = getSafeLogger();
|
|
34345
|
+
const expected = new Set(getExpectedFiles(story));
|
|
34346
|
+
const kept = [];
|
|
34347
|
+
const moved = [];
|
|
34348
|
+
for (const entry of contextFiles) {
|
|
34349
|
+
const filePath = typeof entry === "string" ? entry : entry.path;
|
|
34350
|
+
const factId = typeof entry === "string" ? undefined : entry.factId;
|
|
34351
|
+
if (expected.has(filePath) || await fileExists(join20(workdir, filePath))) {
|
|
34352
|
+
kept.push(entry);
|
|
34353
|
+
continue;
|
|
34354
|
+
}
|
|
34355
|
+
if (factId) {
|
|
34356
|
+
logger?.warn("plan", "Context file cites a manifest fact but is absent on disk", {
|
|
34357
|
+
storyId: story.id,
|
|
34358
|
+
filePath,
|
|
34359
|
+
factId
|
|
34360
|
+
});
|
|
34361
|
+
kept.push(entry);
|
|
34362
|
+
continue;
|
|
34363
|
+
}
|
|
34364
|
+
moved.push(filePath);
|
|
34365
|
+
}
|
|
34366
|
+
if (moved.length === 0)
|
|
34367
|
+
return { story, changed: false };
|
|
34368
|
+
const newExpected = [...getExpectedFiles(story)];
|
|
34369
|
+
for (const filePath of moved) {
|
|
34370
|
+
if (!newExpected.includes(filePath))
|
|
34371
|
+
newExpected.push(filePath);
|
|
34372
|
+
}
|
|
34373
|
+
logger?.info("plan", "Moved absent contextFiles entries to expectedFiles (story creates them)", {
|
|
34374
|
+
storyId: story.id,
|
|
34375
|
+
moved
|
|
34376
|
+
});
|
|
34377
|
+
return { story: { ...story, contextFiles: kept, expectedFiles: newExpected }, changed: true };
|
|
34378
|
+
}
|
|
34379
|
+
async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
|
|
34380
|
+
if (!workdir)
|
|
34381
|
+
return prd;
|
|
34382
|
+
const results = await Promise.all(prd.userStories.map((story) => normalizeStoryFiles(story, workdir, fileExists)));
|
|
34383
|
+
if (!results.some((r) => r.changed))
|
|
34384
|
+
return prd;
|
|
34385
|
+
return { ...prd, userStories: results.map((r) => r.story) };
|
|
34386
|
+
}
|
|
34287
34387
|
var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
|
|
34288
34388
|
var init_plan_refine = __esm(() => {
|
|
34289
34389
|
init_retry();
|
|
@@ -34405,7 +34505,7 @@ ${outputFormat}`,
|
|
|
34405
34505
|
if (ctx.config.plan.specGuard) {
|
|
34406
34506
|
warnOnSpecDrift(validated, input.featureName);
|
|
34407
34507
|
}
|
|
34408
|
-
return validated;
|
|
34508
|
+
return await normalizeCreatedContextFiles(validated, input.workdir, ctx.fileExists);
|
|
34409
34509
|
},
|
|
34410
34510
|
recover: async (input, ctx) => {
|
|
34411
34511
|
const content = await ctx.readFile(input.outputPath);
|
|
@@ -35391,11 +35491,11 @@ function extractTestCode(output) {
|
|
|
35391
35491
|
|
|
35392
35492
|
// src/acceptance/generator.ts
|
|
35393
35493
|
import { existsSync as existsSync5 } from "fs";
|
|
35394
|
-
import { join as
|
|
35494
|
+
import { join as join21 } from "path";
|
|
35395
35495
|
function resolvePytestBin(packageDir) {
|
|
35396
35496
|
if (packageDir) {
|
|
35397
35497
|
for (const venvDir of [".venv", "venv", "env"]) {
|
|
35398
|
-
const candidate =
|
|
35498
|
+
const candidate = join21(packageDir, venvDir, "bin", "pytest");
|
|
35399
35499
|
if (existsSync5(candidate))
|
|
35400
35500
|
return candidate;
|
|
35401
35501
|
}
|
|
@@ -38004,14 +38104,14 @@ var init_plan_critic_llm = __esm(() => {
|
|
|
38004
38104
|
|
|
38005
38105
|
// src/context/greenfield.ts
|
|
38006
38106
|
import { readdir as readdir2 } from "fs/promises";
|
|
38007
|
-
import { join as
|
|
38107
|
+
import { join as join22 } from "path";
|
|
38008
38108
|
async function scanForTestFiles(dir, testPatterns, isRootCall = true) {
|
|
38009
38109
|
const results = [];
|
|
38010
38110
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
38011
38111
|
try {
|
|
38012
38112
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
38013
38113
|
for (const entry of entries) {
|
|
38014
|
-
const fullPath =
|
|
38114
|
+
const fullPath = join22(dir, entry.name);
|
|
38015
38115
|
if (entry.isDirectory()) {
|
|
38016
38116
|
if (ignoreDirs.has(entry.name))
|
|
38017
38117
|
continue;
|
|
@@ -38216,13 +38316,13 @@ __export(exports_runners, {
|
|
|
38216
38316
|
_regressionRunnerDeps: () => _regressionRunnerDeps
|
|
38217
38317
|
});
|
|
38218
38318
|
import { existsSync as existsSync6 } from "fs";
|
|
38219
|
-
import { join as
|
|
38319
|
+
import { join as join23 } from "path";
|
|
38220
38320
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
38221
38321
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
38222
38322
|
return { success: true, missingFiles: [] };
|
|
38223
38323
|
const missingFiles = [];
|
|
38224
38324
|
for (const file3 of expectedFiles) {
|
|
38225
|
-
if (!existsSync6(
|
|
38325
|
+
if (!existsSync6(join23(workingDirectory, file3)))
|
|
38226
38326
|
missingFiles.push(file3);
|
|
38227
38327
|
}
|
|
38228
38328
|
if (missingFiles.length > 0) {
|
|
@@ -38668,7 +38768,7 @@ var init_apply_test_edit_declarations = __esm(() => {
|
|
|
38668
38768
|
});
|
|
38669
38769
|
|
|
38670
38770
|
// src/operations/validate-mock-structure-files.ts
|
|
38671
|
-
import { join as
|
|
38771
|
+
import { join as join24 } from "path";
|
|
38672
38772
|
async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
|
|
38673
38773
|
const fileExists = deps?.fileExists ?? defaultFileExists;
|
|
38674
38774
|
const valid = [];
|
|
@@ -38681,7 +38781,7 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
|
|
|
38681
38781
|
const files = d.files ?? [d.file];
|
|
38682
38782
|
let allValid = true;
|
|
38683
38783
|
for (const file3 of files) {
|
|
38684
|
-
const absolutePath =
|
|
38784
|
+
const absolutePath = join24(packageDir, file3);
|
|
38685
38785
|
const exists = await fileExists(absolutePath);
|
|
38686
38786
|
if (!exists) {
|
|
38687
38787
|
allValid = false;
|
|
@@ -39975,7 +40075,7 @@ var init_lint_parsing = __esm(() => {
|
|
|
39975
40075
|
});
|
|
39976
40076
|
|
|
39977
40077
|
// src/review/scoped-lint.ts
|
|
39978
|
-
import { join as
|
|
40078
|
+
import { join as join25, relative as relative10 } from "path";
|
|
39979
40079
|
function shellQuotePath4(path5) {
|
|
39980
40080
|
return `'${path5.replaceAll("'", "'\\''")}'`;
|
|
39981
40081
|
}
|
|
@@ -40023,7 +40123,7 @@ function uniqueFiles(files) {
|
|
|
40023
40123
|
async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
|
|
40024
40124
|
const inScope = [];
|
|
40025
40125
|
for (const relPath of files) {
|
|
40026
|
-
const absPath =
|
|
40126
|
+
const absPath = join25(workdir, relPath);
|
|
40027
40127
|
const exists = await _scopedLintDeps.fileExists(absPath);
|
|
40028
40128
|
if (!exists)
|
|
40029
40129
|
continue;
|
|
@@ -42177,7 +42277,7 @@ Output ONLY the JSON object. Do not include markdown fences or explanation.`;
|
|
|
42177
42277
|
buildRefineContinuation(outputFilePath, specGuard = false) {
|
|
42178
42278
|
const specGuardItems = specGuard ? `
|
|
42179
42279
|
#### orphan-acs
|
|
42180
|
-
Every acceptance criterion in the PRD must trace back to a requirement stated in the spec. An AC that introduces scope the spec never mentions \u2014 new enum values, new status codes, new config keys, extra validation rules, invented helper behaviour \u2014 is scope bleed from candidate-PRD merging. Delete it, or reduce it to exactly what the spec says.
|
|
42280
|
+
Every acceptance criterion in the PRD must trace back to a requirement stated in the spec. An AC that introduces scope the spec never mentions \u2014 new enum values, new status codes, new config keys, extra validation rules, invented helper behaviour \u2014 is scope bleed from candidate-PRD merging. Delete it, or reduce it to exactly what the spec says. **\`suggestedCriteria\` entries are exempt from this rule** \u2014 they are intentionally out-of-spec edge cases and must be preserved unchanged.
|
|
42181
42281
|
|
|
42182
42282
|
#### no-behavior-degradation
|
|
42183
42283
|
No acceptance criterion may use a deprecated verification tag (\`[grep]\`, \`[file]\`, \`[verbatim]\`) or contain a shell-command pattern (\`grep -\`, \`wc\`, \`|\` inside a backtick span). These signal a file-content check that the agent cannot implement as a runtime test. Rewrite any such AC as a behavioural assertion: what the function returns, throws, or emits \u2014 not what the source file contains.` : "";
|
|
@@ -42308,9 +42408,9 @@ Based on your Step 2 analysis, create stories that produce CODE CHANGES.
|
|
|
42308
42408
|
|
|
42309
42409
|
${buildSharedQualityRules(specContent, projectProfile)}
|
|
42310
42410
|
|
|
42311
|
-
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.
|
|
42411
|
+
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. Set "expectedFiles" to the NEW files the story creates.
|
|
42312
42412
|
|
|
42313
|
-
|
|
42413
|
+
${CONTEXT_VS_EXPECTED_FILES_RULE}`;
|
|
42314
42414
|
const suggestedCriteriaField = specContent.trim() ? `
|
|
42315
42415
|
"suggestedCriteria": ["string \u2014 optional. Behavioral edge cases or negative paths you identified that are NOT in the spec. Plain assertions only \u2014 observable outputs, return values, state changes, or error conditions. No implementation details or vague descriptions. Omit this field if empty."],` : "";
|
|
42316
42416
|
const outputDirective = outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
|
|
@@ -42332,7 +42432,8 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
42332
42432
|
"title": "string \u2014 concise story title",
|
|
42333
42433
|
"description": "string \u2014 detailed description of the story",
|
|
42334
42434
|
"acceptanceCriteria": ["string \u2014 behavioral, testable criteria. Format: 'When [X], then [Y]'. One assertion per AC. Never include quality gates."],${suggestedCriteriaField}
|
|
42335
|
-
"contextFiles": ["string \u2014
|
|
42435
|
+
"contextFiles": ["string \u2014 EXISTING source files the agent should read (max 5, relative paths)"],
|
|
42436
|
+
${EXPECTED_FILES_SCHEMA_FIELD}
|
|
42336
42437
|
"tags": ["string \u2014 routing tags, e.g. feature, security, api"],
|
|
42337
42438
|
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
42338
42439
|
"status": "pending",
|
|
@@ -42405,9 +42506,9 @@ Every concrete claim referencing existing code must cite [F-NNN] or [S-NNN] from
|
|
|
42405
42506
|
|
|
42406
42507
|
${buildSharedQualityRules(input.specContent, input.projectProfile)}
|
|
42407
42508
|
|
|
42408
|
-
For each story, set "contextFiles" to the key source files the implementer should read before starting (max 5 per story). Cite manifest factIds where relevant.
|
|
42509
|
+
For each story, set "contextFiles" to the key source files the implementer should read before starting (max 5 per story). Cite manifest factIds where relevant. Set "expectedFiles" to the NEW files the story creates.
|
|
42409
42510
|
|
|
42410
|
-
|
|
42511
|
+
${CONTEXT_VS_EXPECTED_FILES_RULE}
|
|
42411
42512
|
|
|
42412
42513
|
## Output Schema
|
|
42413
42514
|
|
|
@@ -42423,7 +42524,8 @@ Produce a JSON object with this exact structure. Field names are mandatory \u201
|
|
|
42423
42524
|
"title": "string \u2014 concise story title",
|
|
42424
42525
|
"description": "string \u2014 detailed description of what to implement",
|
|
42425
42526
|
"acceptanceCriteria": ["string \u2014 behavioral criterion, format: 'When [X], then [Y]'. One assertion per item."],${suggestedCriteriaField}
|
|
42426
|
-
"contextFiles": ["string \u2014 relative paths the implementer should read (max 5)"],
|
|
42527
|
+
"contextFiles": ["string \u2014 EXISTING relative paths the implementer should read (max 5)"],
|
|
42528
|
+
${EXPECTED_FILES_SCHEMA_FIELD}
|
|
42427
42529
|
"tags": ["string"],
|
|
42428
42530
|
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
42429
42531
|
"routing": {
|
|
@@ -42464,6 +42566,9 @@ ${rows.join(`
|
|
|
42464
42566
|
`)}
|
|
42465
42567
|
`;
|
|
42466
42568
|
}
|
|
42569
|
+
var CONTEXT_VS_EXPECTED_FILES_RULE = `**\`contextFiles\` rule \u2014 existing files only.** Only list paths that already exist in the repo today. The pipeline verifies every \`contextFiles\` entry against the filesystem; a path that does not exist is treated as a missing-context warning.
|
|
42570
|
+
|
|
42571
|
+
**\`expectedFiles\` rule \u2014 files this story CREATES.** List every NEW file the story authors (relative paths). A file the story will create belongs here, NEVER in \`contextFiles\` \u2014 these are the story's outputs, not files to read first. A single path may appear in \`contextFiles\` (an existing sibling to mirror) AND \`expectedFiles\` (the new file itself), but the same path must never be in both.`, EXPECTED_FILES_SCHEMA_FIELD = `"expectedFiles": ["string \u2014 NEW files this story creates (relative paths, omit if none)"],`;
|
|
42467
42572
|
var init_plan_builder = __esm(() => {
|
|
42468
42573
|
init_config();
|
|
42469
42574
|
});
|
|
@@ -43392,7 +43497,7 @@ var init_call = __esm(() => {
|
|
|
43392
43497
|
|
|
43393
43498
|
// src/runtime/cost-aggregator.ts
|
|
43394
43499
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
43395
|
-
import { join as
|
|
43500
|
+
import { join as join26 } from "path";
|
|
43396
43501
|
function makeCorrelationId() {
|
|
43397
43502
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
43398
43503
|
}
|
|
@@ -43583,7 +43688,7 @@ class CostAggregator {
|
|
|
43583
43688
|
if (events.length === 0 && errors3.length === 0)
|
|
43584
43689
|
return;
|
|
43585
43690
|
mkdirSync2(this._drainDir, { recursive: true });
|
|
43586
|
-
const path5 =
|
|
43691
|
+
const path5 = join26(this._drainDir, `${this._runId}.jsonl`);
|
|
43587
43692
|
const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
|
|
43588
43693
|
await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
|
|
43589
43694
|
`)}
|
|
@@ -43623,7 +43728,7 @@ var init_cost_aggregator = __esm(() => {
|
|
|
43623
43728
|
// src/runtime/prompt-auditor.ts
|
|
43624
43729
|
import { appendFileSync } from "fs";
|
|
43625
43730
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
43626
|
-
import { join as
|
|
43731
|
+
import { join as join27 } from "path";
|
|
43627
43732
|
function createNoOpPromptAuditor() {
|
|
43628
43733
|
return {
|
|
43629
43734
|
record() {},
|
|
@@ -43689,8 +43794,8 @@ class PromptAuditor {
|
|
|
43689
43794
|
_jsonlPath;
|
|
43690
43795
|
_featureDir;
|
|
43691
43796
|
constructor(runId, flushDir, featureName) {
|
|
43692
|
-
this._featureDir =
|
|
43693
|
-
this._jsonlPath =
|
|
43797
|
+
this._featureDir = join27(flushDir, featureName);
|
|
43798
|
+
this._jsonlPath = join27(this._featureDir, `${runId}.jsonl`);
|
|
43694
43799
|
}
|
|
43695
43800
|
record(entry) {
|
|
43696
43801
|
this._enqueue(entry);
|
|
@@ -43739,7 +43844,7 @@ class PromptAuditor {
|
|
|
43739
43844
|
const auditEntry = entry;
|
|
43740
43845
|
const filename = deriveTxtFilename(auditEntry);
|
|
43741
43846
|
try {
|
|
43742
|
-
await _promptAuditorDeps.write(
|
|
43847
|
+
await _promptAuditorDeps.write(join27(this._featureDir, filename), buildTxtContent(auditEntry));
|
|
43743
43848
|
} catch (err) {
|
|
43744
43849
|
throw tagAuditError(err, "txt");
|
|
43745
43850
|
}
|
|
@@ -44851,7 +44956,7 @@ var init_pid_registry = __esm(() => {
|
|
|
44851
44956
|
// src/session/manager-deps.ts
|
|
44852
44957
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
44853
44958
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
44854
|
-
import { isAbsolute as isAbsolute9, join as
|
|
44959
|
+
import { isAbsolute as isAbsolute9, join as join28, relative as relative11, sep as sep2 } from "path";
|
|
44855
44960
|
function resolveProjectDirFromScratchDir(scratchDir) {
|
|
44856
44961
|
const marker = `${sep2}.nax${sep2}features${sep2}`;
|
|
44857
44962
|
const markerIdx = scratchDir.lastIndexOf(marker);
|
|
@@ -44872,7 +44977,7 @@ var init_manager_deps = __esm(() => {
|
|
|
44872
44977
|
now: () => new Date().toISOString(),
|
|
44873
44978
|
nowMs: () => Date.now(),
|
|
44874
44979
|
uuid: () => randomUUID3(),
|
|
44875
|
-
sessionScratchDir: (projectDir, featureName, sessionId) =>
|
|
44980
|
+
sessionScratchDir: (projectDir, featureName, sessionId) => join28(projectDir, ".nax", "features", featureName, "sessions", sessionId),
|
|
44876
44981
|
writeDescriptor: async (scratchDir, descriptor, projectDir) => {
|
|
44877
44982
|
await mkdir5(scratchDir, { recursive: true });
|
|
44878
44983
|
const { handle: _handle, ...persistable } = descriptor;
|
|
@@ -44883,7 +44988,7 @@ var init_manager_deps = __esm(() => {
|
|
|
44883
44988
|
persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
|
|
44884
44989
|
}
|
|
44885
44990
|
}
|
|
44886
|
-
await Bun.write(
|
|
44991
|
+
await Bun.write(join28(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
|
|
44887
44992
|
}
|
|
44888
44993
|
};
|
|
44889
44994
|
});
|
|
@@ -45634,7 +45739,7 @@ __export(exports_runtime, {
|
|
|
45634
45739
|
CostAggregator: () => CostAggregator,
|
|
45635
45740
|
AgentStreamEventBus: () => AgentStreamEventBus
|
|
45636
45741
|
});
|
|
45637
|
-
import { basename as basename5, join as
|
|
45742
|
+
import { basename as basename5, join as join29 } from "path";
|
|
45638
45743
|
function createRuntime(config2, workdir, opts) {
|
|
45639
45744
|
const runId = crypto.randomUUID();
|
|
45640
45745
|
const controller = new AbortController;
|
|
@@ -45650,10 +45755,10 @@ function createRuntime(config2, workdir, opts) {
|
|
|
45650
45755
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
45651
45756
|
const globalDir = globalOutputDir();
|
|
45652
45757
|
const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
|
|
45653
|
-
const costDir =
|
|
45758
|
+
const costDir = join29(outputDir, "cost");
|
|
45654
45759
|
const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
|
|
45655
45760
|
const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
|
|
45656
|
-
const auditDir = config2.agent?.promptAudit?.dir ??
|
|
45761
|
+
const auditDir = config2.agent?.promptAudit?.dir ?? join29(outputDir, "prompt-audit");
|
|
45657
45762
|
let promptAuditor;
|
|
45658
45763
|
if (opts?.promptAuditor) {
|
|
45659
45764
|
promptAuditor = opts.promptAuditor;
|
|
@@ -45804,9 +45909,9 @@ async function allSettledBounded(tasks, limit) {
|
|
|
45804
45909
|
|
|
45805
45910
|
// src/context/injector.ts
|
|
45806
45911
|
import { existsSync as existsSync8 } from "fs";
|
|
45807
|
-
import { join as
|
|
45912
|
+
import { join as join30 } from "path";
|
|
45808
45913
|
async function detectNode(workdir) {
|
|
45809
|
-
const pkgPath =
|
|
45914
|
+
const pkgPath = join30(workdir, "package.json");
|
|
45810
45915
|
if (!existsSync8(pkgPath))
|
|
45811
45916
|
return null;
|
|
45812
45917
|
try {
|
|
@@ -45823,7 +45928,7 @@ async function detectNode(workdir) {
|
|
|
45823
45928
|
}
|
|
45824
45929
|
}
|
|
45825
45930
|
async function detectGo(workdir) {
|
|
45826
|
-
const goMod =
|
|
45931
|
+
const goMod = join30(workdir, "go.mod");
|
|
45827
45932
|
if (!existsSync8(goMod))
|
|
45828
45933
|
return null;
|
|
45829
45934
|
try {
|
|
@@ -45847,7 +45952,7 @@ async function detectGo(workdir) {
|
|
|
45847
45952
|
}
|
|
45848
45953
|
}
|
|
45849
45954
|
async function detectRust(workdir) {
|
|
45850
|
-
const cargoPath =
|
|
45955
|
+
const cargoPath = join30(workdir, "Cargo.toml");
|
|
45851
45956
|
if (!existsSync8(cargoPath))
|
|
45852
45957
|
return null;
|
|
45853
45958
|
try {
|
|
@@ -45863,8 +45968,8 @@ async function detectRust(workdir) {
|
|
|
45863
45968
|
}
|
|
45864
45969
|
}
|
|
45865
45970
|
async function detectPython(workdir) {
|
|
45866
|
-
const pyproject =
|
|
45867
|
-
const requirements =
|
|
45971
|
+
const pyproject = join30(workdir, "pyproject.toml");
|
|
45972
|
+
const requirements = join30(workdir, "requirements.txt");
|
|
45868
45973
|
if (!existsSync8(pyproject) && !existsSync8(requirements))
|
|
45869
45974
|
return null;
|
|
45870
45975
|
try {
|
|
@@ -45883,7 +45988,7 @@ async function detectPython(workdir) {
|
|
|
45883
45988
|
}
|
|
45884
45989
|
}
|
|
45885
45990
|
async function detectPhp(workdir) {
|
|
45886
|
-
const composerPath =
|
|
45991
|
+
const composerPath = join30(workdir, "composer.json");
|
|
45887
45992
|
if (!existsSync8(composerPath))
|
|
45888
45993
|
return null;
|
|
45889
45994
|
try {
|
|
@@ -45896,7 +46001,7 @@ async function detectPhp(workdir) {
|
|
|
45896
46001
|
}
|
|
45897
46002
|
}
|
|
45898
46003
|
async function detectRuby(workdir) {
|
|
45899
|
-
const gemfile =
|
|
46004
|
+
const gemfile = join30(workdir, "Gemfile");
|
|
45900
46005
|
if (!existsSync8(gemfile))
|
|
45901
46006
|
return null;
|
|
45902
46007
|
try {
|
|
@@ -45908,9 +46013,9 @@ async function detectRuby(workdir) {
|
|
|
45908
46013
|
}
|
|
45909
46014
|
}
|
|
45910
46015
|
async function detectJvm(workdir) {
|
|
45911
|
-
const pom =
|
|
45912
|
-
const gradle =
|
|
45913
|
-
const gradleKts =
|
|
46016
|
+
const pom = join30(workdir, "pom.xml");
|
|
46017
|
+
const gradle = join30(workdir, "build.gradle");
|
|
46018
|
+
const gradleKts = join30(workdir, "build.gradle.kts");
|
|
45914
46019
|
if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
|
|
45915
46020
|
return null;
|
|
45916
46021
|
try {
|
|
@@ -45918,7 +46023,7 @@ async function detectJvm(workdir) {
|
|
|
45918
46023
|
const content2 = await Bun.file(pom).text();
|
|
45919
46024
|
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
45920
46025
|
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
45921
|
-
const lang2 = existsSync8(
|
|
46026
|
+
const lang2 = existsSync8(join30(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
45922
46027
|
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
45923
46028
|
}
|
|
45924
46029
|
const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
|
|
@@ -46172,7 +46277,7 @@ var init_windsurf = __esm(() => {
|
|
|
46172
46277
|
|
|
46173
46278
|
// src/context/generator.ts
|
|
46174
46279
|
import { existsSync as existsSync9 } from "fs";
|
|
46175
|
-
import { join as
|
|
46280
|
+
import { join as join31, relative as relative12 } from "path";
|
|
46176
46281
|
async function loadContextContent(options, config2) {
|
|
46177
46282
|
if (!_generatorDeps.existsSync(options.contextPath)) {
|
|
46178
46283
|
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
@@ -46190,7 +46295,7 @@ async function generateFor(agent, options, config2) {
|
|
|
46190
46295
|
try {
|
|
46191
46296
|
const context = await loadContextContent(options, config2);
|
|
46192
46297
|
const content = generator.generate(context);
|
|
46193
|
-
const outputPath =
|
|
46298
|
+
const outputPath = join31(options.outputDir, generator.outputFile);
|
|
46194
46299
|
validateFilePath(outputPath, options.outputDir);
|
|
46195
46300
|
if (!options.dryRun) {
|
|
46196
46301
|
await _generatorDeps.writeFile(outputPath, content);
|
|
@@ -46208,7 +46313,7 @@ async function generateAll(options, config2, agentFilter) {
|
|
|
46208
46313
|
for (const [agentKey, generator] of entries) {
|
|
46209
46314
|
try {
|
|
46210
46315
|
const content = generator.generate(context);
|
|
46211
|
-
const outputPath =
|
|
46316
|
+
const outputPath = join31(options.outputDir, generator.outputFile);
|
|
46212
46317
|
validateFilePath(outputPath, options.outputDir);
|
|
46213
46318
|
if (!options.dryRun) {
|
|
46214
46319
|
await _generatorDeps.writeFile(outputPath, content);
|
|
@@ -46228,7 +46333,7 @@ async function discoverPackages(repoRoot) {
|
|
|
46228
46333
|
const glob = new Bun.Glob(pattern);
|
|
46229
46334
|
for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
|
|
46230
46335
|
const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
|
|
46231
|
-
const pkgAbsolute =
|
|
46336
|
+
const pkgAbsolute = join31(repoRoot, pkgRelative);
|
|
46232
46337
|
if (!seen.has(pkgAbsolute)) {
|
|
46233
46338
|
seen.add(pkgAbsolute);
|
|
46234
46339
|
packages.push(pkgAbsolute);
|
|
@@ -46260,14 +46365,14 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
46260
46365
|
}
|
|
46261
46366
|
}
|
|
46262
46367
|
}
|
|
46263
|
-
const turboPath =
|
|
46368
|
+
const turboPath = join31(repoRoot, "turbo.json");
|
|
46264
46369
|
try {
|
|
46265
46370
|
const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
|
|
46266
46371
|
if (Array.isArray(turbo.packages)) {
|
|
46267
46372
|
await resolveGlobs(turbo.packages);
|
|
46268
46373
|
}
|
|
46269
46374
|
} catch {}
|
|
46270
|
-
const pkgPath =
|
|
46375
|
+
const pkgPath = join31(repoRoot, "package.json");
|
|
46271
46376
|
try {
|
|
46272
46377
|
const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
|
|
46273
46378
|
const ws = pkg.workspaces;
|
|
@@ -46275,7 +46380,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
46275
46380
|
if (patterns.length > 0)
|
|
46276
46381
|
await resolveGlobs(patterns);
|
|
46277
46382
|
} catch {}
|
|
46278
|
-
const pnpmPath =
|
|
46383
|
+
const pnpmPath = join31(repoRoot, "pnpm-workspace.yaml");
|
|
46279
46384
|
try {
|
|
46280
46385
|
const raw = await _generatorDeps.readTextFile(pnpmPath);
|
|
46281
46386
|
const lines = raw.split(`
|
|
@@ -46301,7 +46406,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
46301
46406
|
async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
|
|
46302
46407
|
const resolvedRepoRoot = repoRoot ?? packageDir;
|
|
46303
46408
|
const relativePkgPath = relative12(resolvedRepoRoot, packageDir);
|
|
46304
|
-
const contextPath =
|
|
46409
|
+
const contextPath = join31(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
|
|
46305
46410
|
if (!_generatorDeps.existsSync(contextPath)) {
|
|
46306
46411
|
return [
|
|
46307
46412
|
{
|
|
@@ -46369,7 +46474,7 @@ var init_generator2 = __esm(() => {
|
|
|
46369
46474
|
});
|
|
46370
46475
|
|
|
46371
46476
|
// src/analyze/scanner.ts
|
|
46372
|
-
import { join as
|
|
46477
|
+
import { join as join32 } from "path";
|
|
46373
46478
|
function resolveFrameworkAndRunner(language, pkg) {
|
|
46374
46479
|
if (language === "go")
|
|
46375
46480
|
return { framework: "", testRunner: "go-test" };
|
|
@@ -46391,7 +46496,7 @@ async function scanSourceRoots(workdir) {
|
|
|
46391
46496
|
});
|
|
46392
46497
|
try {
|
|
46393
46498
|
const language = await deps.detectLanguage(workdir);
|
|
46394
|
-
const pkg = await deps.readPackageJson(
|
|
46499
|
+
const pkg = await deps.readPackageJson(join32(workdir, "package.json"));
|
|
46395
46500
|
const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
|
|
46396
46501
|
return [{ path: ".", language, framework, testRunner }];
|
|
46397
46502
|
} catch {
|
|
@@ -46409,9 +46514,9 @@ async function scanSourceRoots(workdir) {
|
|
|
46409
46514
|
packages = packages.slice(0, MAX_SOURCE_ROOTS);
|
|
46410
46515
|
}
|
|
46411
46516
|
return Promise.all(packages.map(async (pkgPath) => {
|
|
46412
|
-
const pkgDir = pkgPath === "." ? workdir :
|
|
46517
|
+
const pkgDir = pkgPath === "." ? workdir : join32(workdir, pkgPath);
|
|
46413
46518
|
const language = await deps.detectLanguage(pkgDir);
|
|
46414
|
-
const pkg = await deps.readPackageJson(
|
|
46519
|
+
const pkg = await deps.readPackageJson(join32(pkgDir, "package.json"));
|
|
46415
46520
|
const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
|
|
46416
46521
|
return { path: pkgPath, language, framework, testRunner };
|
|
46417
46522
|
}));
|
|
@@ -46444,7 +46549,7 @@ var init_analyze = __esm(() => {
|
|
|
46444
46549
|
});
|
|
46445
46550
|
|
|
46446
46551
|
// src/debate/pre-phase/grounder.ts
|
|
46447
|
-
import { join as
|
|
46552
|
+
import { join as join33 } from "path";
|
|
46448
46553
|
async function buildCodebaseContext(workdir) {
|
|
46449
46554
|
const roots = await _grounderDeps.scanSourceRoots(workdir);
|
|
46450
46555
|
return buildSourceRootsSection(normalizeRoots(workdir, roots));
|
|
@@ -46456,7 +46561,7 @@ function normalizeRoots(workdir, roots) {
|
|
|
46456
46561
|
}));
|
|
46457
46562
|
}
|
|
46458
46563
|
async function writeManifestArtifact(ctx, manifest) {
|
|
46459
|
-
const manifestPath =
|
|
46564
|
+
const manifestPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
|
|
46460
46565
|
await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
|
|
46461
46566
|
}
|
|
46462
46567
|
var _grounderDeps, grounderStrategy = async (ctx) => {
|
|
@@ -46799,7 +46904,7 @@ function formatSpecDeltas(blockers, manifest) {
|
|
|
46799
46904
|
|
|
46800
46905
|
// src/debate/verifiers/checks.ts
|
|
46801
46906
|
import { existsSync as defaultExistsSync } from "fs";
|
|
46802
|
-
import { join as
|
|
46907
|
+
import { join as join34 } from "path";
|
|
46803
46908
|
function checkFilesExist(prd, workdir, deps) {
|
|
46804
46909
|
const existsSync10 = deps?.existsSync ?? defaultExistsSync;
|
|
46805
46910
|
const findings = [];
|
|
@@ -46809,7 +46914,7 @@ function checkFilesExist(prd, workdir, deps) {
|
|
|
46809
46914
|
for (const entry of story.contextFiles) {
|
|
46810
46915
|
const filePath = typeof entry === "string" ? entry : entry.path;
|
|
46811
46916
|
const factId = typeof entry === "string" ? undefined : entry.factId;
|
|
46812
|
-
const absPath =
|
|
46917
|
+
const absPath = join34(workdir, filePath);
|
|
46813
46918
|
if (existsSync10(absPath))
|
|
46814
46919
|
continue;
|
|
46815
46920
|
if (factId) {
|
|
@@ -46920,7 +47025,7 @@ var init_checks3 = () => {};
|
|
|
46920
47025
|
|
|
46921
47026
|
// src/debate/verifiers/plan-checklist.ts
|
|
46922
47027
|
import { existsSync as existsSync10 } from "fs";
|
|
46923
|
-
import { join as
|
|
47028
|
+
import { join as join35 } from "path";
|
|
46924
47029
|
function parsePrd(output) {
|
|
46925
47030
|
if (!output)
|
|
46926
47031
|
return null;
|
|
@@ -46931,7 +47036,7 @@ function parsePrd(output) {
|
|
|
46931
47036
|
}
|
|
46932
47037
|
}
|
|
46933
47038
|
async function loadManifest(ctx) {
|
|
46934
|
-
const manifestPath =
|
|
47039
|
+
const manifestPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
|
|
46935
47040
|
const raw = await _planChecklistDeps.readFile(manifestPath);
|
|
46936
47041
|
if (!raw)
|
|
46937
47042
|
return null;
|
|
@@ -46944,7 +47049,7 @@ async function loadManifest(ctx) {
|
|
|
46944
47049
|
}
|
|
46945
47050
|
}
|
|
46946
47051
|
async function emitSpecDeltas(ctx, blockers, manifest) {
|
|
46947
|
-
const artifactPath =
|
|
47052
|
+
const artifactPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
|
|
46948
47053
|
const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
|
|
46949
47054
|
await _planChecklistDeps.write(artifactPath, content);
|
|
46950
47055
|
return artifactPath;
|
|
@@ -47290,7 +47395,7 @@ var init_runner_plan_helpers = __esm(() => {
|
|
|
47290
47395
|
});
|
|
47291
47396
|
|
|
47292
47397
|
// src/debate/runner-plan.ts
|
|
47293
|
-
import { join as
|
|
47398
|
+
import { join as join36 } from "path";
|
|
47294
47399
|
async function runPlan(ctx, taskContext, outputFormat, opts) {
|
|
47295
47400
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
47296
47401
|
const config2 = ctx.stageConfig;
|
|
@@ -47349,7 +47454,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
|
|
|
47349
47454
|
sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
|
|
47350
47455
|
proposers: ctx.stageConfig.proposers
|
|
47351
47456
|
});
|
|
47352
|
-
const outputPaths = resolved.map((_, i) =>
|
|
47457
|
+
const outputPaths = resolved.map((_, i) => join36(opts.outputDir, `prd-debate-${i}.json`));
|
|
47353
47458
|
const successful = [];
|
|
47354
47459
|
let rebuttalList;
|
|
47355
47460
|
if (selectorKind === "verifier-pick") {
|
|
@@ -49592,9 +49697,9 @@ function validateFeatureName(feature) {
|
|
|
49592
49697
|
|
|
49593
49698
|
// src/plan/critic.ts
|
|
49594
49699
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
49595
|
-
import { dirname as dirname7, join as
|
|
49700
|
+
import { dirname as dirname7, join as join39 } from "path";
|
|
49596
49701
|
async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
|
|
49597
|
-
const path7 =
|
|
49702
|
+
const path7 = join39(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
|
|
49598
49703
|
await mkdir6(dirname7(path7), { recursive: true });
|
|
49599
49704
|
await Bun.write(path7, formatSpecDeltas(findings, manifest));
|
|
49600
49705
|
return path7;
|
|
@@ -50807,9 +50912,9 @@ __export(exports_plan_decompose, {
|
|
|
50807
50912
|
runReplanLoop: () => runReplanLoop,
|
|
50808
50913
|
planDecomposeCommand: () => planDecomposeCommand
|
|
50809
50914
|
});
|
|
50810
|
-
import { join as
|
|
50915
|
+
import { join as join40 } from "path";
|
|
50811
50916
|
async function planDecomposeCommand(workdir, config2, options) {
|
|
50812
|
-
const prdPath =
|
|
50917
|
+
const prdPath = join40(workdir, ".nax", "features", options.feature, "prd.json");
|
|
50813
50918
|
if (!_planDeps.existsSync(prdPath)) {
|
|
50814
50919
|
throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
|
|
50815
50920
|
stage: "decompose",
|
|
@@ -50983,7 +51088,7 @@ var init_plan_decompose = __esm(() => {
|
|
|
50983
51088
|
|
|
50984
51089
|
// src/cli/plan-runtime.ts
|
|
50985
51090
|
import { existsSync as existsSync15 } from "fs";
|
|
50986
|
-
import { join as
|
|
51091
|
+
import { join as join41 } from "path";
|
|
50987
51092
|
function isRuntimeWithAgentManager(value) {
|
|
50988
51093
|
return typeof value === "object" && value !== null && "agentManager" in value;
|
|
50989
51094
|
}
|
|
@@ -51035,7 +51140,7 @@ var init_plan_runtime = __esm(() => {
|
|
|
51035
51140
|
writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
|
|
51036
51141
|
scanSourceRoots: (workdir) => scanSourceRoots(workdir),
|
|
51037
51142
|
createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
|
|
51038
|
-
readPackageJson: (workdir) => Bun.file(
|
|
51143
|
+
readPackageJson: (workdir) => Bun.file(join41(workdir, "package.json")).json().catch(() => null),
|
|
51039
51144
|
spawnSync: (cmd, opts) => {
|
|
51040
51145
|
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
|
|
51041
51146
|
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
@@ -51420,7 +51525,7 @@ var init_metrics = __esm(() => {
|
|
|
51420
51525
|
|
|
51421
51526
|
// src/commands/common.ts
|
|
51422
51527
|
import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
|
|
51423
|
-
import { join as
|
|
51528
|
+
import { join as join42, resolve as resolve13 } from "path";
|
|
51424
51529
|
function resolveProject(options = {}) {
|
|
51425
51530
|
const { dir, feature } = options;
|
|
51426
51531
|
let projectRoot;
|
|
@@ -51428,12 +51533,12 @@ function resolveProject(options = {}) {
|
|
|
51428
51533
|
let configPath;
|
|
51429
51534
|
if (dir) {
|
|
51430
51535
|
projectRoot = realpathSync3(resolve13(dir));
|
|
51431
|
-
naxDir =
|
|
51536
|
+
naxDir = join42(projectRoot, ".nax");
|
|
51432
51537
|
if (!existsSync16(naxDir)) {
|
|
51433
51538
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
51434
51539
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
51435
51540
|
}
|
|
51436
|
-
configPath =
|
|
51541
|
+
configPath = join42(naxDir, "config.json");
|
|
51437
51542
|
if (!existsSync16(configPath)) {
|
|
51438
51543
|
throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
|
|
51439
51544
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
@@ -51441,17 +51546,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
|
51441
51546
|
} else {
|
|
51442
51547
|
const found = findProjectRoot(process.cwd());
|
|
51443
51548
|
if (!found) {
|
|
51444
|
-
const cwdNaxDir =
|
|
51549
|
+
const cwdNaxDir = join42(process.cwd(), ".nax");
|
|
51445
51550
|
if (existsSync16(cwdNaxDir)) {
|
|
51446
|
-
const cwdConfigPath =
|
|
51551
|
+
const cwdConfigPath = join42(cwdNaxDir, "config.json");
|
|
51447
51552
|
throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
|
|
51448
51553
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
51449
51554
|
}
|
|
51450
51555
|
throw new NaxError("No nax project found. Run this command from within a nax project directory, or use -d flag to specify the project path.", "PROJECT_NOT_FOUND", { cwd: process.cwd() });
|
|
51451
51556
|
}
|
|
51452
51557
|
projectRoot = found;
|
|
51453
|
-
naxDir =
|
|
51454
|
-
configPath =
|
|
51558
|
+
naxDir = join42(projectRoot, ".nax");
|
|
51559
|
+
configPath = join42(naxDir, "config.json");
|
|
51455
51560
|
}
|
|
51456
51561
|
let featureDir;
|
|
51457
51562
|
if (feature) {
|
|
@@ -51460,8 +51565,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
|
|
|
51460
51565
|
} catch (error48) {
|
|
51461
51566
|
throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
|
|
51462
51567
|
}
|
|
51463
|
-
const featuresDir =
|
|
51464
|
-
featureDir =
|
|
51568
|
+
const featuresDir = join42(naxDir, "features");
|
|
51569
|
+
featureDir = join42(featuresDir, feature);
|
|
51465
51570
|
if (!existsSync16(featureDir)) {
|
|
51466
51571
|
const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
51467
51572
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
@@ -51494,7 +51599,7 @@ async function resolveProjectAsync(options = {}) {
|
|
|
51494
51599
|
}
|
|
51495
51600
|
const isPlainName = !dir.includes("/") && !dir.includes("\\");
|
|
51496
51601
|
if (isPlainName) {
|
|
51497
|
-
const registryIdentityPath =
|
|
51602
|
+
const registryIdentityPath = join42(globalConfigDir(), dir, ".identity");
|
|
51498
51603
|
const identityFile = Bun.file(registryIdentityPath);
|
|
51499
51604
|
if (await identityFile.exists()) {
|
|
51500
51605
|
try {
|
|
@@ -51526,12 +51631,12 @@ function findProjectRoot(startDir) {
|
|
|
51526
51631
|
let current = resolve13(startDir);
|
|
51527
51632
|
let depth = 0;
|
|
51528
51633
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
51529
|
-
const naxDir =
|
|
51530
|
-
const configPath =
|
|
51634
|
+
const naxDir = join42(current, ".nax");
|
|
51635
|
+
const configPath = join42(naxDir, "config.json");
|
|
51531
51636
|
if (existsSync16(configPath)) {
|
|
51532
51637
|
return realpathSync3(current);
|
|
51533
51638
|
}
|
|
51534
|
-
const parent =
|
|
51639
|
+
const parent = join42(current, "..");
|
|
51535
51640
|
if (parent === current) {
|
|
51536
51641
|
break;
|
|
51537
51642
|
}
|
|
@@ -52690,10 +52795,10 @@ var init_effectiveness = __esm(() => {
|
|
|
52690
52795
|
|
|
52691
52796
|
// src/execution/progress.ts
|
|
52692
52797
|
import { appendFile as appendFile2, mkdir as mkdir7 } from "fs/promises";
|
|
52693
|
-
import { join as
|
|
52798
|
+
import { join as join45 } from "path";
|
|
52694
52799
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
52695
52800
|
await mkdir7(featureDir, { recursive: true });
|
|
52696
|
-
const progressPath =
|
|
52801
|
+
const progressPath = join45(featureDir, "progress.txt");
|
|
52697
52802
|
const timestamp = new Date().toISOString();
|
|
52698
52803
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
52699
52804
|
`;
|
|
@@ -52887,7 +52992,7 @@ var init_completion = __esm(() => {
|
|
|
52887
52992
|
|
|
52888
52993
|
// src/constitution/loader.ts
|
|
52889
52994
|
import { existsSync as existsSync19 } from "fs";
|
|
52890
|
-
import { join as
|
|
52995
|
+
import { join as join46 } from "path";
|
|
52891
52996
|
function truncateToTokens(text, maxTokens) {
|
|
52892
52997
|
const maxChars = maxTokens * 3;
|
|
52893
52998
|
if (text.length <= maxChars) {
|
|
@@ -52909,7 +53014,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
52909
53014
|
}
|
|
52910
53015
|
let combinedContent = "";
|
|
52911
53016
|
if (!config2.skipGlobal) {
|
|
52912
|
-
const globalPath =
|
|
53017
|
+
const globalPath = join46(globalConfigDir(), config2.path);
|
|
52913
53018
|
if (existsSync19(globalPath)) {
|
|
52914
53019
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
52915
53020
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -52919,7 +53024,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
52919
53024
|
}
|
|
52920
53025
|
}
|
|
52921
53026
|
}
|
|
52922
|
-
const projectPath =
|
|
53027
|
+
const projectPath = join46(projectDir, config2.path);
|
|
52923
53028
|
if (existsSync19(projectPath)) {
|
|
52924
53029
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
52925
53030
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -53762,6 +53867,9 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
53762
53867
|
});
|
|
53763
53868
|
}
|
|
53764
53869
|
logUnifiedReviewPhaseStart(ctx.storyId, opName);
|
|
53870
|
+
if (ctx.storyId) {
|
|
53871
|
+
pipelineEventBus.emit({ type: "story:step", storyId: ctx.storyId, step: opName });
|
|
53872
|
+
}
|
|
53765
53873
|
const phaseStartedAt = Date.now();
|
|
53766
53874
|
const scope = ctx.runtime.costAggregator.openScope();
|
|
53767
53875
|
try {
|
|
@@ -54116,6 +54224,7 @@ var init_story_orchestrator = __esm(() => {
|
|
|
54116
54224
|
init_logger2();
|
|
54117
54225
|
init_operations();
|
|
54118
54226
|
init_call();
|
|
54227
|
+
init_event_bus();
|
|
54119
54228
|
init_prepare_inputs();
|
|
54120
54229
|
init_git();
|
|
54121
54230
|
_storyOrchestratorDeps = {
|
|
@@ -54182,7 +54291,7 @@ var init_story_orchestrator = __esm(() => {
|
|
|
54182
54291
|
});
|
|
54183
54292
|
|
|
54184
54293
|
// src/execution/build-plan-for-strategy.ts
|
|
54185
|
-
import { join as
|
|
54294
|
+
import { join as join47 } from "path";
|
|
54186
54295
|
function requiresInitialRefCapture(strategy) {
|
|
54187
54296
|
return isThreeSessionStrategy(strategy);
|
|
54188
54297
|
}
|
|
@@ -54228,7 +54337,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
54228
54337
|
}
|
|
54229
54338
|
if (shouldRunRectification(config2) && inputs.rectification) {
|
|
54230
54339
|
const sink = makeDeclarationSink();
|
|
54231
|
-
const packageDir =
|
|
54340
|
+
const packageDir = join47(ctx.packageDir, story.workdir ?? "");
|
|
54232
54341
|
const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
|
|
54233
54342
|
const strategies = [];
|
|
54234
54343
|
if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
|
|
@@ -55780,7 +55889,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
55780
55889
|
}
|
|
55781
55890
|
|
|
55782
55891
|
// src/cli/prompts-tdd.ts
|
|
55783
|
-
import { join as
|
|
55892
|
+
import { join as join48 } from "path";
|
|
55784
55893
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
55785
55894
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
55786
55895
|
TddPromptBuilder.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(),
|
|
@@ -55799,7 +55908,7 @@ ${frontmatter}---
|
|
|
55799
55908
|
|
|
55800
55909
|
${session.prompt}`;
|
|
55801
55910
|
if (outputDir) {
|
|
55802
|
-
const promptFile =
|
|
55911
|
+
const promptFile = join48(outputDir, `${story.id}.${session.role}.md`);
|
|
55803
55912
|
await Bun.write(promptFile, fullOutput);
|
|
55804
55913
|
logger.info("cli", "Written TDD prompt file", {
|
|
55805
55914
|
storyId: story.id,
|
|
@@ -55815,7 +55924,7 @@ ${"=".repeat(80)}`);
|
|
|
55815
55924
|
}
|
|
55816
55925
|
}
|
|
55817
55926
|
if (outputDir && ctx.contextMarkdown) {
|
|
55818
|
-
const contextFile =
|
|
55927
|
+
const contextFile = join48(outputDir, `${story.id}.context.md`);
|
|
55819
55928
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
55820
55929
|
const contextOutput = `---
|
|
55821
55930
|
${frontmatter}---
|
|
@@ -55830,16 +55939,16 @@ var init_prompts_tdd = __esm(() => {
|
|
|
55830
55939
|
|
|
55831
55940
|
// src/cli/prompts-main.ts
|
|
55832
55941
|
import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
|
|
55833
|
-
import { join as
|
|
55942
|
+
import { join as join49 } from "path";
|
|
55834
55943
|
async function promptsCommand(options) {
|
|
55835
55944
|
const logger = getLogger();
|
|
55836
55945
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
55837
|
-
const naxDir =
|
|
55946
|
+
const naxDir = join49(workdir, ".nax");
|
|
55838
55947
|
if (!existsSync20(naxDir)) {
|
|
55839
55948
|
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
55840
55949
|
}
|
|
55841
|
-
const featureDir =
|
|
55842
|
-
const prdPath =
|
|
55950
|
+
const featureDir = join49(naxDir, "features", feature);
|
|
55951
|
+
const prdPath = join49(featureDir, "prd.json");
|
|
55843
55952
|
if (!existsSync20(prdPath)) {
|
|
55844
55953
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
55845
55954
|
}
|
|
@@ -55906,10 +56015,10 @@ ${frontmatter}---
|
|
|
55906
56015
|
|
|
55907
56016
|
${ctx.prompt}`;
|
|
55908
56017
|
if (outputDir) {
|
|
55909
|
-
const promptFile =
|
|
56018
|
+
const promptFile = join49(outputDir, `${story.id}.prompt.md`);
|
|
55910
56019
|
await Bun.write(promptFile, fullOutput);
|
|
55911
56020
|
if (ctx.contextMarkdown) {
|
|
55912
|
-
const contextFile =
|
|
56021
|
+
const contextFile = join49(outputDir, `${story.id}.context.md`);
|
|
55913
56022
|
const contextOutput = `---
|
|
55914
56023
|
${frontmatter}---
|
|
55915
56024
|
|
|
@@ -55945,12 +56054,12 @@ var init_prompts_main = __esm(() => {
|
|
|
55945
56054
|
|
|
55946
56055
|
// src/cli/prompts-init.ts
|
|
55947
56056
|
import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
|
|
55948
|
-
import { join as
|
|
56057
|
+
import { join as join50 } from "path";
|
|
55949
56058
|
async function promptsInitCommand(options) {
|
|
55950
56059
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
55951
|
-
const templatesDir =
|
|
56060
|
+
const templatesDir = join50(workdir, ".nax", "templates");
|
|
55952
56061
|
mkdirSync4(templatesDir, { recursive: true });
|
|
55953
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(
|
|
56062
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join50(templatesDir, f)));
|
|
55954
56063
|
if (existingFiles.length > 0 && !force) {
|
|
55955
56064
|
_promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
55956
56065
|
Pass --force to overwrite existing templates.`);
|
|
@@ -55958,7 +56067,7 @@ async function promptsInitCommand(options) {
|
|
|
55958
56067
|
}
|
|
55959
56068
|
const written = [];
|
|
55960
56069
|
for (const template of TEMPLATE_ROLES) {
|
|
55961
|
-
const filePath =
|
|
56070
|
+
const filePath = join50(templatesDir, template.file);
|
|
55962
56071
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
55963
56072
|
const content = TEMPLATE_HEADER + roleBody;
|
|
55964
56073
|
await Bun.write(filePath, content);
|
|
@@ -55974,7 +56083,7 @@ async function promptsInitCommand(options) {
|
|
|
55974
56083
|
return written;
|
|
55975
56084
|
}
|
|
55976
56085
|
async function autoWirePromptsConfig(workdir) {
|
|
55977
|
-
const configPath =
|
|
56086
|
+
const configPath = join50(workdir, "nax.config.json");
|
|
55978
56087
|
if (!existsSync21(configPath)) {
|
|
55979
56088
|
const exampleConfig = JSON.stringify({
|
|
55980
56089
|
prompts: {
|
|
@@ -56144,7 +56253,7 @@ __export(exports_init_context, {
|
|
|
56144
56253
|
});
|
|
56145
56254
|
import { existsSync as existsSync22 } from "fs";
|
|
56146
56255
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
56147
|
-
import { basename as basename9, join as
|
|
56256
|
+
import { basename as basename9, join as join51 } from "path";
|
|
56148
56257
|
async function findFiles(dir, maxFiles = 200) {
|
|
56149
56258
|
try {
|
|
56150
56259
|
const proc = Bun.spawnSync([
|
|
@@ -56172,7 +56281,7 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
56172
56281
|
return [];
|
|
56173
56282
|
}
|
|
56174
56283
|
async function readPackageManifest(projectRoot) {
|
|
56175
|
-
const packageJsonPath =
|
|
56284
|
+
const packageJsonPath = join51(projectRoot, "package.json");
|
|
56176
56285
|
if (!existsSync22(packageJsonPath)) {
|
|
56177
56286
|
return null;
|
|
56178
56287
|
}
|
|
@@ -56190,7 +56299,7 @@ async function readPackageManifest(projectRoot) {
|
|
|
56190
56299
|
}
|
|
56191
56300
|
}
|
|
56192
56301
|
async function readReadmeSnippet(projectRoot) {
|
|
56193
|
-
const readmePath =
|
|
56302
|
+
const readmePath = join51(projectRoot, "README.md");
|
|
56194
56303
|
if (!existsSync22(readmePath)) {
|
|
56195
56304
|
return null;
|
|
56196
56305
|
}
|
|
@@ -56208,7 +56317,7 @@ async function detectEntryPoints(projectRoot) {
|
|
|
56208
56317
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
56209
56318
|
const found = [];
|
|
56210
56319
|
for (const candidate of candidates) {
|
|
56211
|
-
const path13 =
|
|
56320
|
+
const path13 = join51(projectRoot, candidate);
|
|
56212
56321
|
if (existsSync22(path13)) {
|
|
56213
56322
|
found.push(candidate);
|
|
56214
56323
|
}
|
|
@@ -56219,7 +56328,7 @@ async function detectConfigFiles(projectRoot) {
|
|
|
56219
56328
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
56220
56329
|
const found = [];
|
|
56221
56330
|
for (const candidate of candidates) {
|
|
56222
|
-
const path13 =
|
|
56331
|
+
const path13 = join51(projectRoot, candidate);
|
|
56223
56332
|
if (existsSync22(path13)) {
|
|
56224
56333
|
found.push(candidate);
|
|
56225
56334
|
}
|
|
@@ -56380,8 +56489,8 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
56380
56489
|
}
|
|
56381
56490
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
56382
56491
|
const logger = getLogger();
|
|
56383
|
-
const naxDir =
|
|
56384
|
-
const contextPath =
|
|
56492
|
+
const naxDir = join51(repoRoot, ".nax", "mono", packagePath);
|
|
56493
|
+
const contextPath = join51(naxDir, "context.md");
|
|
56385
56494
|
if (existsSync22(contextPath) && !force) {
|
|
56386
56495
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
56387
56496
|
return;
|
|
@@ -56395,8 +56504,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
56395
56504
|
}
|
|
56396
56505
|
async function initContext(projectRoot, options = {}) {
|
|
56397
56506
|
const logger = getLogger();
|
|
56398
|
-
const naxDir =
|
|
56399
|
-
const contextPath =
|
|
56507
|
+
const naxDir = join51(projectRoot, ".nax");
|
|
56508
|
+
const contextPath = join51(naxDir, "context.md");
|
|
56400
56509
|
if (existsSync22(contextPath) && !options.force) {
|
|
56401
56510
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
56402
56511
|
return;
|
|
@@ -56426,9 +56535,9 @@ var init_init_context = __esm(() => {
|
|
|
56426
56535
|
|
|
56427
56536
|
// src/cli/init-detect.ts
|
|
56428
56537
|
import { existsSync as existsSync23, readFileSync } from "fs";
|
|
56429
|
-
import { join as
|
|
56538
|
+
import { join as join52 } from "path";
|
|
56430
56539
|
function readPackageJson(projectRoot) {
|
|
56431
|
-
const pkgPath =
|
|
56540
|
+
const pkgPath = join52(projectRoot, "package.json");
|
|
56432
56541
|
if (!existsSync23(pkgPath))
|
|
56433
56542
|
return;
|
|
56434
56543
|
try {
|
|
@@ -56471,41 +56580,41 @@ function detectStack(projectRoot) {
|
|
|
56471
56580
|
};
|
|
56472
56581
|
}
|
|
56473
56582
|
function detectRuntime(projectRoot) {
|
|
56474
|
-
if (existsSync23(
|
|
56583
|
+
if (existsSync23(join52(projectRoot, "bun.lockb")) || existsSync23(join52(projectRoot, "bunfig.toml"))) {
|
|
56475
56584
|
return "bun";
|
|
56476
56585
|
}
|
|
56477
|
-
if (existsSync23(
|
|
56586
|
+
if (existsSync23(join52(projectRoot, "package-lock.json")) || existsSync23(join52(projectRoot, "yarn.lock")) || existsSync23(join52(projectRoot, "pnpm-lock.yaml"))) {
|
|
56478
56587
|
return "node";
|
|
56479
56588
|
}
|
|
56480
56589
|
return "unknown";
|
|
56481
56590
|
}
|
|
56482
56591
|
function detectLanguage2(projectRoot) {
|
|
56483
|
-
if (existsSync23(
|
|
56592
|
+
if (existsSync23(join52(projectRoot, "tsconfig.json")))
|
|
56484
56593
|
return "typescript";
|
|
56485
|
-
if (existsSync23(
|
|
56594
|
+
if (existsSync23(join52(projectRoot, "pyproject.toml")) || existsSync23(join52(projectRoot, "setup.py"))) {
|
|
56486
56595
|
return "python";
|
|
56487
56596
|
}
|
|
56488
|
-
if (existsSync23(
|
|
56597
|
+
if (existsSync23(join52(projectRoot, "Cargo.toml")))
|
|
56489
56598
|
return "rust";
|
|
56490
|
-
if (existsSync23(
|
|
56599
|
+
if (existsSync23(join52(projectRoot, "go.mod")))
|
|
56491
56600
|
return "go";
|
|
56492
56601
|
return "unknown";
|
|
56493
56602
|
}
|
|
56494
56603
|
function detectLinter(projectRoot) {
|
|
56495
|
-
if (existsSync23(
|
|
56604
|
+
if (existsSync23(join52(projectRoot, "biome.json")) || existsSync23(join52(projectRoot, "biome.jsonc"))) {
|
|
56496
56605
|
return "biome";
|
|
56497
56606
|
}
|
|
56498
|
-
if (existsSync23(
|
|
56607
|
+
if (existsSync23(join52(projectRoot, ".eslintrc.json")) || existsSync23(join52(projectRoot, ".eslintrc.js")) || existsSync23(join52(projectRoot, "eslint.config.js"))) {
|
|
56499
56608
|
return "eslint";
|
|
56500
56609
|
}
|
|
56501
56610
|
return "unknown";
|
|
56502
56611
|
}
|
|
56503
56612
|
function detectMonorepo(projectRoot) {
|
|
56504
|
-
if (existsSync23(
|
|
56613
|
+
if (existsSync23(join52(projectRoot, "turbo.json")))
|
|
56505
56614
|
return "turborepo";
|
|
56506
|
-
if (existsSync23(
|
|
56615
|
+
if (existsSync23(join52(projectRoot, "nx.json")))
|
|
56507
56616
|
return "nx";
|
|
56508
|
-
if (existsSync23(
|
|
56617
|
+
if (existsSync23(join52(projectRoot, "pnpm-workspace.yaml")))
|
|
56509
56618
|
return "pnpm-workspaces";
|
|
56510
56619
|
const pkg = readPackageJson(projectRoot);
|
|
56511
56620
|
if (pkg?.workspaces)
|
|
@@ -56649,7 +56758,7 @@ __export(exports_init, {
|
|
|
56649
56758
|
});
|
|
56650
56759
|
import { existsSync as existsSync24 } from "fs";
|
|
56651
56760
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
56652
|
-
import { join as
|
|
56761
|
+
import { join as join53 } from "path";
|
|
56653
56762
|
function validateProjectName(name) {
|
|
56654
56763
|
if (!name)
|
|
56655
56764
|
return { valid: false, error: "name must be non-empty" };
|
|
@@ -56685,7 +56794,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
|
|
|
56685
56794
|
}
|
|
56686
56795
|
async function updateGitignore(projectRoot) {
|
|
56687
56796
|
const logger = getLogger();
|
|
56688
|
-
const gitignorePath =
|
|
56797
|
+
const gitignorePath = join53(projectRoot, ".gitignore");
|
|
56689
56798
|
let existing = "";
|
|
56690
56799
|
if (existsSync24(gitignorePath)) {
|
|
56691
56800
|
existing = await Bun.file(gitignorePath).text();
|
|
@@ -56771,7 +56880,7 @@ async function initGlobal() {
|
|
|
56771
56880
|
await mkdir9(globalDir, { recursive: true });
|
|
56772
56881
|
logger.info("init", "Created global config directory", { path: globalDir });
|
|
56773
56882
|
}
|
|
56774
|
-
const configPath =
|
|
56883
|
+
const configPath = join53(globalDir, "config.json");
|
|
56775
56884
|
if (!existsSync24(configPath)) {
|
|
56776
56885
|
await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
|
|
56777
56886
|
`);
|
|
@@ -56779,14 +56888,14 @@ async function initGlobal() {
|
|
|
56779
56888
|
} else {
|
|
56780
56889
|
logger.info("init", "Global config already exists", { path: configPath });
|
|
56781
56890
|
}
|
|
56782
|
-
const constitutionPath =
|
|
56891
|
+
const constitutionPath = join53(globalDir, "constitution.md");
|
|
56783
56892
|
if (!existsSync24(constitutionPath)) {
|
|
56784
56893
|
await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
|
|
56785
56894
|
logger.info("init", "Created global constitution", { path: constitutionPath });
|
|
56786
56895
|
} else {
|
|
56787
56896
|
logger.info("init", "Global constitution already exists", { path: constitutionPath });
|
|
56788
56897
|
}
|
|
56789
|
-
const hooksDir =
|
|
56898
|
+
const hooksDir = join53(globalDir, "hooks");
|
|
56790
56899
|
if (!existsSync24(hooksDir)) {
|
|
56791
56900
|
await mkdir9(hooksDir, { recursive: true });
|
|
56792
56901
|
logger.info("init", "Created global hooks directory", { path: hooksDir });
|
|
@@ -56819,7 +56928,7 @@ async function initProject(projectRoot, options) {
|
|
|
56819
56928
|
if (detectedName && !options?.force) {
|
|
56820
56929
|
const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
|
|
56821
56930
|
if (collision.collision && collision.existing) {
|
|
56822
|
-
const configPath2 =
|
|
56931
|
+
const configPath2 = join53(projectDir, "config.json");
|
|
56823
56932
|
throw new NaxError([
|
|
56824
56933
|
`Project name collision: "${detectedName}"`,
|
|
56825
56934
|
` This project: ${projectRoot}`,
|
|
@@ -56847,7 +56956,7 @@ async function initProject(projectRoot, options) {
|
|
|
56847
56956
|
linter: stack.linter,
|
|
56848
56957
|
monorepo: stack.monorepo
|
|
56849
56958
|
});
|
|
56850
|
-
const configPath =
|
|
56959
|
+
const configPath = join53(projectDir, "config.json");
|
|
56851
56960
|
if (!existsSync24(configPath)) {
|
|
56852
56961
|
await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
|
|
56853
56962
|
`);
|
|
@@ -56856,14 +56965,14 @@ async function initProject(projectRoot, options) {
|
|
|
56856
56965
|
logger.info("init", "Project config already exists", { path: configPath });
|
|
56857
56966
|
}
|
|
56858
56967
|
await initContext(projectRoot, { ai: options?.ai, force: options?.force });
|
|
56859
|
-
const constitutionPath =
|
|
56968
|
+
const constitutionPath = join53(projectDir, "constitution.md");
|
|
56860
56969
|
if (!existsSync24(constitutionPath) || options?.force) {
|
|
56861
56970
|
await Bun.write(constitutionPath, buildConstitution(stack));
|
|
56862
56971
|
logger.info("init", "Created project constitution", { path: constitutionPath });
|
|
56863
56972
|
} else {
|
|
56864
56973
|
logger.info("init", "Project constitution already exists", { path: constitutionPath });
|
|
56865
56974
|
}
|
|
56866
|
-
const hooksDir =
|
|
56975
|
+
const hooksDir = join53(projectDir, "hooks");
|
|
56867
56976
|
if (!existsSync24(hooksDir)) {
|
|
56868
56977
|
await mkdir9(hooksDir, { recursive: true });
|
|
56869
56978
|
logger.info("init", "Created project hooks directory", { path: hooksDir });
|
|
@@ -58298,12 +58407,12 @@ var init_loader4 = __esm(() => {
|
|
|
58298
58407
|
});
|
|
58299
58408
|
|
|
58300
58409
|
// src/utils/paths.ts
|
|
58301
|
-
import { join as
|
|
58410
|
+
import { join as join64 } from "path";
|
|
58302
58411
|
function getRunsDir() {
|
|
58303
|
-
return process.env.NAX_RUNS_DIR ??
|
|
58412
|
+
return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
|
|
58304
58413
|
}
|
|
58305
58414
|
function getEventsRootDir() {
|
|
58306
|
-
return
|
|
58415
|
+
return join64(globalConfigDir(), "events");
|
|
58307
58416
|
}
|
|
58308
58417
|
var init_paths3 = __esm(() => {
|
|
58309
58418
|
init_paths();
|
|
@@ -58363,7 +58472,7 @@ var init_command_argv = __esm(() => {
|
|
|
58363
58472
|
});
|
|
58364
58473
|
|
|
58365
58474
|
// src/hooks/runner.ts
|
|
58366
|
-
import { join as
|
|
58475
|
+
import { join as join71 } from "path";
|
|
58367
58476
|
function createDrainDeadline2(deadlineMs) {
|
|
58368
58477
|
let timeoutId;
|
|
58369
58478
|
const promise2 = new Promise((resolve16) => {
|
|
@@ -58382,14 +58491,14 @@ async function loadHooksConfig(projectDir, globalDir) {
|
|
|
58382
58491
|
let globalHooks = { hooks: {} };
|
|
58383
58492
|
let projectHooks = { hooks: {} };
|
|
58384
58493
|
let skipGlobal = false;
|
|
58385
|
-
const projectPath =
|
|
58494
|
+
const projectPath = join71(projectDir, "hooks.json");
|
|
58386
58495
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
58387
58496
|
if (projectData) {
|
|
58388
58497
|
projectHooks = projectData;
|
|
58389
58498
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
58390
58499
|
}
|
|
58391
58500
|
if (!skipGlobal && globalDir) {
|
|
58392
|
-
const globalPath =
|
|
58501
|
+
const globalPath = join71(globalDir, "hooks.json");
|
|
58393
58502
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
58394
58503
|
if (globalData) {
|
|
58395
58504
|
globalHooks = globalData;
|
|
@@ -58559,7 +58668,7 @@ var package_default;
|
|
|
58559
58668
|
var init_package = __esm(() => {
|
|
58560
58669
|
package_default = {
|
|
58561
58670
|
name: "@nathapp/nax",
|
|
58562
|
-
version: "0.68.
|
|
58671
|
+
version: "0.68.8",
|
|
58563
58672
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
58564
58673
|
type: "module",
|
|
58565
58674
|
bin: {
|
|
@@ -58654,8 +58763,8 @@ var init_version = __esm(() => {
|
|
|
58654
58763
|
NAX_VERSION = package_default.version;
|
|
58655
58764
|
NAX_COMMIT = (() => {
|
|
58656
58765
|
try {
|
|
58657
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
58658
|
-
return "
|
|
58766
|
+
if (/^[0-9a-f]{6,10}$/.test("00515ea8"))
|
|
58767
|
+
return "00515ea8";
|
|
58659
58768
|
} catch {}
|
|
58660
58769
|
try {
|
|
58661
58770
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -59532,15 +59641,15 @@ var init_acceptance_loop = __esm(() => {
|
|
|
59532
59641
|
|
|
59533
59642
|
// src/session/scratch-purge.ts
|
|
59534
59643
|
import { mkdir as mkdir13, rename, rm } from "fs/promises";
|
|
59535
|
-
import { dirname as dirname12, join as
|
|
59644
|
+
import { dirname as dirname12, join as join72 } from "path";
|
|
59536
59645
|
async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
|
|
59537
|
-
const sessionsDir =
|
|
59646
|
+
const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
|
|
59538
59647
|
const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
|
|
59539
59648
|
const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
|
|
59540
59649
|
let purged = 0;
|
|
59541
59650
|
for (const sessionId of sessionIds) {
|
|
59542
|
-
const sessionDir =
|
|
59543
|
-
const descriptorPath =
|
|
59651
|
+
const sessionDir = join72(sessionsDir, sessionId);
|
|
59652
|
+
const descriptorPath = join72(sessionDir, "descriptor.json");
|
|
59544
59653
|
if (!await _scratchPurgeDeps.fileExists(descriptorPath))
|
|
59545
59654
|
continue;
|
|
59546
59655
|
let lastActivityAt;
|
|
@@ -59556,7 +59665,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
|
|
|
59556
59665
|
if (new Date(lastActivityAt).getTime() >= cutoffMs)
|
|
59557
59666
|
continue;
|
|
59558
59667
|
if (archiveInsteadOfDelete) {
|
|
59559
|
-
const archiveDest =
|
|
59668
|
+
const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
|
|
59560
59669
|
await _scratchPurgeDeps.move(sessionDir, archiveDest);
|
|
59561
59670
|
} else {
|
|
59562
59671
|
await _scratchPurgeDeps.remove(sessionDir);
|
|
@@ -59936,6 +60045,7 @@ async function handleRunCompletion(options) {
|
|
|
59936
60045
|
const regressionMode = config2.execution.regressionGate?.mode;
|
|
59937
60046
|
if (options.skipRegression) {} else if (regressionMode === "deferred" && config2.quality.commands.test) {
|
|
59938
60047
|
statusWriter.setPostRunPhase("regression", { status: "running" });
|
|
60048
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "regression" });
|
|
59939
60049
|
const regressionResult = await _runCompletionDeps.runDeferredRegression({
|
|
59940
60050
|
config: config2,
|
|
59941
60051
|
prd,
|
|
@@ -59950,6 +60060,7 @@ async function handleRunCompletion(options) {
|
|
|
59950
60060
|
});
|
|
59951
60061
|
if (regressionResult.success) {
|
|
59952
60062
|
statusWriter.setPostRunPhase("regression", { status: "passed", lastRunAt });
|
|
60063
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: true });
|
|
59953
60064
|
} else {
|
|
59954
60065
|
statusWriter.setPostRunPhase("regression", {
|
|
59955
60066
|
status: "failed",
|
|
@@ -59957,6 +60068,7 @@ async function handleRunCompletion(options) {
|
|
|
59957
60068
|
affectedStories: regressionResult.affectedStories,
|
|
59958
60069
|
lastRunAt
|
|
59959
60070
|
});
|
|
60071
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: false });
|
|
59960
60072
|
for (const storyId of regressionResult.affectedStories) {
|
|
59961
60073
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
59962
60074
|
if (story) {
|
|
@@ -60021,6 +60133,9 @@ async function handleRunCompletion(options) {
|
|
|
60021
60133
|
}
|
|
60022
60134
|
let pluginGateFailed = false;
|
|
60023
60135
|
const deferredReview = options.deferredReview;
|
|
60136
|
+
if (deferredReview !== undefined) {
|
|
60137
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "review", passed: !deferredReview.anyFailed });
|
|
60138
|
+
}
|
|
60024
60139
|
if (deferredReview?.anyFailed) {
|
|
60025
60140
|
const failedReviewers = deferredReview.reviewerResults.filter((r) => !r.passed).map((r) => r.name);
|
|
60026
60141
|
pluginGateFailed = config2.review.pluginMode === "gating";
|
|
@@ -60288,12 +60403,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
|
|
|
60288
60403
|
|
|
60289
60404
|
// src/pipeline/subscribers/events-writer.ts
|
|
60290
60405
|
import { appendFile as appendFile4, mkdir as mkdir14 } from "fs/promises";
|
|
60291
|
-
import { basename as basename13, join as
|
|
60406
|
+
import { basename as basename13, join as join73 } from "path";
|
|
60292
60407
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
60293
60408
|
const logger = getSafeLogger();
|
|
60294
60409
|
const project = basename13(workdir);
|
|
60295
|
-
const eventsDir =
|
|
60296
|
-
const eventsFile =
|
|
60410
|
+
const eventsDir = join73(getEventsRootDir(), project);
|
|
60411
|
+
const eventsFile = join73(eventsDir, "events.jsonl");
|
|
60297
60412
|
let dirReady = false;
|
|
60298
60413
|
const write = (line) => {
|
|
60299
60414
|
return (async () => {
|
|
@@ -60474,12 +60589,12 @@ var init_interaction2 = __esm(() => {
|
|
|
60474
60589
|
|
|
60475
60590
|
// src/pipeline/subscribers/registry.ts
|
|
60476
60591
|
import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
|
|
60477
|
-
import { basename as basename14, join as
|
|
60592
|
+
import { basename as basename14, join as join74 } from "path";
|
|
60478
60593
|
function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
60479
60594
|
const logger = getSafeLogger();
|
|
60480
60595
|
const project = basename14(workdir);
|
|
60481
|
-
const runDir =
|
|
60482
|
-
const metaFile =
|
|
60596
|
+
const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
|
|
60597
|
+
const metaFile = join74(runDir, "meta.json");
|
|
60483
60598
|
const unsub = bus.on("run:started", (_ev) => {
|
|
60484
60599
|
return (async () => {
|
|
60485
60600
|
try {
|
|
@@ -60489,8 +60604,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
|
60489
60604
|
project,
|
|
60490
60605
|
feature,
|
|
60491
60606
|
workdir,
|
|
60492
|
-
statusPath:
|
|
60493
|
-
eventsDir:
|
|
60607
|
+
statusPath: join74(outputDir, "features", feature, "status.json"),
|
|
60608
|
+
eventsDir: join74(outputDir, "features", feature, "runs"),
|
|
60494
60609
|
registeredAt: new Date().toISOString()
|
|
60495
60610
|
};
|
|
60496
60611
|
await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -60736,7 +60851,7 @@ var init_types9 = __esm(() => {
|
|
|
60736
60851
|
|
|
60737
60852
|
// src/worktree/dependencies.ts
|
|
60738
60853
|
import { existsSync as existsSync31 } from "fs";
|
|
60739
|
-
import { join as
|
|
60854
|
+
import { join as join75 } from "path";
|
|
60740
60855
|
async function prepareWorktreeDependencies(options) {
|
|
60741
60856
|
const mode = options.config.execution.worktreeDependencies.mode;
|
|
60742
60857
|
const resolvedCwd = resolveDependencyCwd(options);
|
|
@@ -60750,7 +60865,7 @@ async function prepareWorktreeDependencies(options) {
|
|
|
60750
60865
|
}
|
|
60751
60866
|
}
|
|
60752
60867
|
function resolveDependencyCwd(options) {
|
|
60753
|
-
return options.storyWorkdir ?
|
|
60868
|
+
return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
|
|
60754
60869
|
}
|
|
60755
60870
|
function resolveInheritedDependencies(options, resolvedCwd) {
|
|
60756
60871
|
if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
|
|
@@ -60760,7 +60875,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
|
|
|
60760
60875
|
}
|
|
60761
60876
|
function hasDependencyManifests(worktreeRoot, resolvedCwd) {
|
|
60762
60877
|
const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
|
|
60763
|
-
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(
|
|
60878
|
+
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
|
|
60764
60879
|
}
|
|
60765
60880
|
async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
|
|
60766
60881
|
const setupCommand = config2.execution.worktreeDependencies.setupCommand;
|
|
@@ -60824,13 +60939,13 @@ __export(exports_manager, {
|
|
|
60824
60939
|
});
|
|
60825
60940
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
60826
60941
|
import { mkdir as mkdir16 } from "fs/promises";
|
|
60827
|
-
import { join as
|
|
60942
|
+
import { join as join76 } from "path";
|
|
60828
60943
|
|
|
60829
60944
|
class WorktreeManager {
|
|
60830
60945
|
async ensureGitExcludes(projectRoot) {
|
|
60831
60946
|
const logger = getSafeLogger();
|
|
60832
|
-
const infoDir =
|
|
60833
|
-
const excludePath =
|
|
60947
|
+
const infoDir = join76(projectRoot, ".git", "info");
|
|
60948
|
+
const excludePath = join76(infoDir, "exclude");
|
|
60834
60949
|
try {
|
|
60835
60950
|
await mkdir16(infoDir, { recursive: true });
|
|
60836
60951
|
let existing = "";
|
|
@@ -60857,7 +60972,7 @@ ${missing.join(`
|
|
|
60857
60972
|
}
|
|
60858
60973
|
async create(projectRoot, storyId) {
|
|
60859
60974
|
validateStoryId(storyId);
|
|
60860
|
-
const worktreePath =
|
|
60975
|
+
const worktreePath = join76(projectRoot, ".nax-wt", storyId);
|
|
60861
60976
|
const branchName = `nax/${storyId}`;
|
|
60862
60977
|
try {
|
|
60863
60978
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -60898,9 +61013,9 @@ ${missing.join(`
|
|
|
60898
61013
|
}
|
|
60899
61014
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
60900
61015
|
}
|
|
60901
|
-
const envSource =
|
|
61016
|
+
const envSource = join76(projectRoot, ".env");
|
|
60902
61017
|
if (existsSync32(envSource)) {
|
|
60903
|
-
const envTarget =
|
|
61018
|
+
const envTarget = join76(worktreePath, ".env");
|
|
60904
61019
|
try {
|
|
60905
61020
|
symlinkSync(envSource, envTarget, "file");
|
|
60906
61021
|
} catch (error48) {
|
|
@@ -60911,7 +61026,7 @@ ${missing.join(`
|
|
|
60911
61026
|
}
|
|
60912
61027
|
async remove(projectRoot, storyId) {
|
|
60913
61028
|
validateStoryId(storyId);
|
|
60914
|
-
const worktreePath =
|
|
61029
|
+
const worktreePath = join76(projectRoot, ".nax-wt", storyId);
|
|
60915
61030
|
const branchName = `nax/${storyId}`;
|
|
60916
61031
|
try {
|
|
60917
61032
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -61723,10 +61838,10 @@ var init_merge_conflict_rectify = __esm(() => {
|
|
|
61723
61838
|
});
|
|
61724
61839
|
|
|
61725
61840
|
// src/execution/pipeline-result-handler.ts
|
|
61726
|
-
import { join as
|
|
61841
|
+
import { join as join77 } from "path";
|
|
61727
61842
|
async function removeWorktreeDirectory(projectRoot, storyId) {
|
|
61728
61843
|
const logger = getSafeLogger();
|
|
61729
|
-
const worktreePath =
|
|
61844
|
+
const worktreePath = join77(projectRoot, ".nax-wt", storyId);
|
|
61730
61845
|
try {
|
|
61731
61846
|
const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
61732
61847
|
cwd: projectRoot,
|
|
@@ -61943,7 +62058,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
61943
62058
|
|
|
61944
62059
|
// src/execution/iteration-runner.ts
|
|
61945
62060
|
import { existsSync as existsSync33 } from "fs";
|
|
61946
|
-
import { join as
|
|
62061
|
+
import { join as join78 } from "path";
|
|
61947
62062
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
61948
62063
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
61949
62064
|
if (ctx.dryRun) {
|
|
@@ -61968,7 +62083,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
61968
62083
|
const storyStartTime = Date.now();
|
|
61969
62084
|
let effectiveWorkdir = ctx.workdir;
|
|
61970
62085
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
61971
|
-
const worktreePath =
|
|
62086
|
+
const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
|
|
61972
62087
|
const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
|
|
61973
62088
|
if (!worktreeExists) {
|
|
61974
62089
|
await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
|
|
@@ -61988,7 +62103,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
61988
62103
|
}
|
|
61989
62104
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
61990
62105
|
const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
|
|
61991
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
62106
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
61992
62107
|
let dependencyContext;
|
|
61993
62108
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
61994
62109
|
try {
|
|
@@ -62015,7 +62130,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
62015
62130
|
};
|
|
62016
62131
|
}
|
|
62017
62132
|
}
|
|
62018
|
-
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ?
|
|
62133
|
+
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ? join78(effectiveWorkdir, story.workdir) : effectiveWorkdir : story.workdir ? join78(ctx.workdir, story.workdir) : ctx.workdir;
|
|
62019
62134
|
const pipelineContext = {
|
|
62020
62135
|
config: effectiveConfig,
|
|
62021
62136
|
rootConfig: ctx.config,
|
|
@@ -62217,7 +62332,7 @@ __export(exports_parallel_worker, {
|
|
|
62217
62332
|
buildWorktreePipelineContext: () => buildWorktreePipelineContext,
|
|
62218
62333
|
_parallelWorkerDeps: () => _parallelWorkerDeps
|
|
62219
62334
|
});
|
|
62220
|
-
import { join as
|
|
62335
|
+
import { join as join79 } from "path";
|
|
62221
62336
|
function buildWorktreePipelineContext(base, _story) {
|
|
62222
62337
|
return { ...base, prd: structuredClone(base.prd) };
|
|
62223
62338
|
}
|
|
@@ -62240,7 +62355,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
|
|
|
62240
62355
|
story,
|
|
62241
62356
|
stories: [story],
|
|
62242
62357
|
projectDir: context.projectDir,
|
|
62243
|
-
workdir: dependencyContext.cwd ?? (story.workdir ?
|
|
62358
|
+
workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
|
|
62244
62359
|
worktreeDependencyContext: dependencyContext,
|
|
62245
62360
|
routing,
|
|
62246
62361
|
storyGitRef: storyGitRef ?? undefined
|
|
@@ -62617,6 +62732,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
62617
62732
|
if (isComplete(prd)) {
|
|
62618
62733
|
logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
|
|
62619
62734
|
const naxIgnoreIndex = await getRunNaxIgnoreIndex(prd);
|
|
62735
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
|
|
62620
62736
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
|
|
62621
62737
|
return buildResult2("completed");
|
|
62622
62738
|
}
|
|
@@ -62670,6 +62786,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
62670
62786
|
return buildResult2("pre-merge-aborted");
|
|
62671
62787
|
}
|
|
62672
62788
|
logger?.debug("execution", "Running deferred review");
|
|
62789
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
|
|
62673
62790
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
|
|
62674
62791
|
logger?.debug("execution", "Deferred review done \u2014 returning completed");
|
|
62675
62792
|
return buildResult2("completed");
|
|
@@ -63125,7 +63242,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
63125
63242
|
var init_status_file = () => {};
|
|
63126
63243
|
|
|
63127
63244
|
// src/execution/status-writer.ts
|
|
63128
|
-
import { join as
|
|
63245
|
+
import { join as join80 } from "path";
|
|
63129
63246
|
|
|
63130
63247
|
class StatusWriter {
|
|
63131
63248
|
statusFile;
|
|
@@ -63244,7 +63361,7 @@ class StatusWriter {
|
|
|
63244
63361
|
if (!this._prd)
|
|
63245
63362
|
return;
|
|
63246
63363
|
const safeLogger = getSafeLogger();
|
|
63247
|
-
const featureStatusPath =
|
|
63364
|
+
const featureStatusPath = join80(featureDir, "status.json");
|
|
63248
63365
|
const write = async () => {
|
|
63249
63366
|
try {
|
|
63250
63367
|
const base = this.getSnapshot(totalCost, iterations);
|
|
@@ -63678,7 +63795,7 @@ __export(exports_run_initialization, {
|
|
|
63678
63795
|
initializeRun: () => initializeRun,
|
|
63679
63796
|
_reconcileDeps: () => _reconcileDeps
|
|
63680
63797
|
});
|
|
63681
|
-
import { join as
|
|
63798
|
+
import { join as join81 } from "path";
|
|
63682
63799
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
63683
63800
|
const logger = getSafeLogger();
|
|
63684
63801
|
let reconciledCount = 0;
|
|
@@ -63695,7 +63812,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
63695
63812
|
});
|
|
63696
63813
|
continue;
|
|
63697
63814
|
}
|
|
63698
|
-
const effectiveWorkdir = story.workdir ?
|
|
63815
|
+
const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
|
|
63699
63816
|
try {
|
|
63700
63817
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
63701
63818
|
if (!reviewResult.success) {
|
|
@@ -93488,7 +93605,7 @@ __export(exports_curator, {
|
|
|
93488
93605
|
});
|
|
93489
93606
|
import { readdirSync as readdirSync8 } from "fs";
|
|
93490
93607
|
import { unlink as unlink4 } from "fs/promises";
|
|
93491
|
-
import { basename as basename15, join as
|
|
93608
|
+
import { basename as basename15, join as join83 } from "path";
|
|
93492
93609
|
function getProjectKey(config2, projectDir) {
|
|
93493
93610
|
return config2.name?.trim() || basename15(projectDir);
|
|
93494
93611
|
}
|
|
@@ -93571,7 +93688,7 @@ async function curatorStatus(options) {
|
|
|
93571
93688
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93572
93689
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93573
93690
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93574
|
-
const runsDir =
|
|
93691
|
+
const runsDir = join83(outputDir, "runs");
|
|
93575
93692
|
const runIds = listRunIds(runsDir);
|
|
93576
93693
|
let runId;
|
|
93577
93694
|
if (options.run) {
|
|
@@ -93588,8 +93705,8 @@ async function curatorStatus(options) {
|
|
|
93588
93705
|
runId = runIds[runIds.length - 1];
|
|
93589
93706
|
}
|
|
93590
93707
|
console.log(`Run: ${runId}`);
|
|
93591
|
-
const runDir =
|
|
93592
|
-
const observationsPath =
|
|
93708
|
+
const runDir = join83(runsDir, runId);
|
|
93709
|
+
const observationsPath = join83(runDir, "observations.jsonl");
|
|
93593
93710
|
const observations = await parseObservations(observationsPath);
|
|
93594
93711
|
const counts = new Map;
|
|
93595
93712
|
for (const obs of observations) {
|
|
@@ -93599,7 +93716,7 @@ async function curatorStatus(options) {
|
|
|
93599
93716
|
for (const [kind, count] of counts.entries()) {
|
|
93600
93717
|
console.log(` ${kind}: ${count}`);
|
|
93601
93718
|
}
|
|
93602
|
-
const proposalsPath =
|
|
93719
|
+
const proposalsPath = join83(runDir, "curator-proposals.md");
|
|
93603
93720
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
93604
93721
|
if (proposalText !== null) {
|
|
93605
93722
|
console.log("");
|
|
@@ -93613,8 +93730,8 @@ async function curatorCommit(options) {
|
|
|
93613
93730
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93614
93731
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93615
93732
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93616
|
-
const runDir =
|
|
93617
|
-
const proposalsPath =
|
|
93733
|
+
const runDir = join83(outputDir, "runs", options.runId);
|
|
93734
|
+
const proposalsPath = join83(runDir, "curator-proposals.md");
|
|
93618
93735
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
93619
93736
|
if (proposalText === null) {
|
|
93620
93737
|
console.log(`curator-proposals.md not found for run ${options.runId}.`);
|
|
@@ -93630,7 +93747,7 @@ async function curatorCommit(options) {
|
|
|
93630
93747
|
const dropFileState = new Map;
|
|
93631
93748
|
const skippedDrops = new Set;
|
|
93632
93749
|
for (const drop2 of drops) {
|
|
93633
|
-
const targetPath =
|
|
93750
|
+
const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
|
|
93634
93751
|
if (!dropFileState.has(targetPath)) {
|
|
93635
93752
|
const fileExists2 = await Bun.file(targetPath).exists();
|
|
93636
93753
|
const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
|
|
@@ -93664,7 +93781,7 @@ async function curatorCommit(options) {
|
|
|
93664
93781
|
if (skippedDrops.has(drop2)) {
|
|
93665
93782
|
continue;
|
|
93666
93783
|
}
|
|
93667
|
-
const targetPath =
|
|
93784
|
+
const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
|
|
93668
93785
|
const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
|
|
93669
93786
|
const filtered = filterDropContent(existing, drop2.description);
|
|
93670
93787
|
await _curatorCmdDeps.writeFile(targetPath, filtered);
|
|
@@ -93673,7 +93790,7 @@ async function curatorCommit(options) {
|
|
|
93673
93790
|
}
|
|
93674
93791
|
const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
|
|
93675
93792
|
for (const add2 of adds) {
|
|
93676
|
-
const targetPath =
|
|
93793
|
+
const targetPath = join83(resolved.projectDir, add2.canonicalFile);
|
|
93677
93794
|
const content = buildAddContent(add2);
|
|
93678
93795
|
await _curatorCmdDeps.appendFile(targetPath, content);
|
|
93679
93796
|
modifiedFiles.add(targetPath);
|
|
@@ -93710,7 +93827,7 @@ async function curatorDryrun(options) {
|
|
|
93710
93827
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93711
93828
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93712
93829
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93713
|
-
const runsDir =
|
|
93830
|
+
const runsDir = join83(outputDir, "runs");
|
|
93714
93831
|
const runIds = listRunIds(runsDir);
|
|
93715
93832
|
if (runIds.length === 0) {
|
|
93716
93833
|
console.log("No runs found.");
|
|
@@ -93721,7 +93838,7 @@ async function curatorDryrun(options) {
|
|
|
93721
93838
|
console.log(`Run ${options.run} not found in ${runsDir}.`);
|
|
93722
93839
|
return;
|
|
93723
93840
|
}
|
|
93724
|
-
const observationsPath =
|
|
93841
|
+
const observationsPath = join83(runsDir, runId, "observations.jsonl");
|
|
93725
93842
|
const observations = await parseObservations(observationsPath);
|
|
93726
93843
|
const thresholds = getThresholds(config2);
|
|
93727
93844
|
const proposals = runHeuristics(observations, thresholds);
|
|
@@ -93762,12 +93879,12 @@ async function curatorGc(options) {
|
|
|
93762
93879
|
await _curatorCmdDeps.writeFile(rollupPath, newContent);
|
|
93763
93880
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93764
93881
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93765
|
-
const perRunsDir =
|
|
93882
|
+
const perRunsDir = join83(outputDir, "runs");
|
|
93766
93883
|
for (const runId of uniqueRunIds) {
|
|
93767
93884
|
if (!keepSet.has(runId)) {
|
|
93768
|
-
const runDir =
|
|
93769
|
-
await _curatorCmdDeps.removeFile(
|
|
93770
|
-
await _curatorCmdDeps.removeFile(
|
|
93885
|
+
const runDir = join83(perRunsDir, runId);
|
|
93886
|
+
await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
|
|
93887
|
+
await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
|
|
93771
93888
|
}
|
|
93772
93889
|
}
|
|
93773
93890
|
console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
|
|
@@ -93812,7 +93929,7 @@ var init_curator2 = __esm(() => {
|
|
|
93812
93929
|
init_source();
|
|
93813
93930
|
import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
|
|
93814
93931
|
import { homedir as homedir3 } from "os";
|
|
93815
|
-
import { basename as basename16, join as
|
|
93932
|
+
import { basename as basename16, join as join84 } from "path";
|
|
93816
93933
|
|
|
93817
93934
|
// node_modules/commander/esm.mjs
|
|
93818
93935
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -93836,12 +93953,12 @@ init_errors();
|
|
|
93836
93953
|
init_operations();
|
|
93837
93954
|
|
|
93838
93955
|
// src/plan/strategies/context-builder.ts
|
|
93839
|
-
import { join as
|
|
93956
|
+
import { join as join38 } from "path";
|
|
93840
93957
|
init_config();
|
|
93841
93958
|
init_errors();
|
|
93842
93959
|
init_interaction();
|
|
93843
93960
|
async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
93844
|
-
const naxDir =
|
|
93961
|
+
const naxDir = join38(workdir, ".nax");
|
|
93845
93962
|
if (!deps.existsSync(naxDir)) {
|
|
93846
93963
|
throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
|
|
93847
93964
|
stage: "plan",
|
|
@@ -93849,8 +93966,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
93849
93966
|
});
|
|
93850
93967
|
}
|
|
93851
93968
|
validateFeatureName(options.feature);
|
|
93852
|
-
const outputDir =
|
|
93853
|
-
const outputPath =
|
|
93969
|
+
const outputDir = join38(naxDir, "features", options.feature);
|
|
93970
|
+
const outputPath = join38(outputDir, "prd.json");
|
|
93854
93971
|
const [specContent, sourceRoots, pkg] = await Promise.all([
|
|
93855
93972
|
deps.readFile(options.from),
|
|
93856
93973
|
deps.scanSourceRoots(workdir),
|
|
@@ -93865,7 +93982,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
93865
93982
|
...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
|
|
93866
93983
|
];
|
|
93867
93984
|
const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
|
|
93868
|
-
const packageJson = await deps.readPackageJsonAt(
|
|
93985
|
+
const packageJson = await deps.readPackageJsonAt(join38(workdir, relativePath, "package.json"));
|
|
93869
93986
|
return buildPackageSummary(relativePath, packageJson);
|
|
93870
93987
|
}));
|
|
93871
93988
|
const projectName = detectProjectName(workdir, pkg);
|
|
@@ -94182,7 +94299,8 @@ class RefinePlanStrategy {
|
|
|
94182
94299
|
packages: ctx.relativePackages,
|
|
94183
94300
|
packageDetails: ctx.packageDetails,
|
|
94184
94301
|
projectProfile: ctx.config.project,
|
|
94185
|
-
specGuard: ctx.config.plan.specGuard ?? false
|
|
94302
|
+
specGuard: ctx.config.plan.specGuard ?? false,
|
|
94303
|
+
workdir: ctx.workdir
|
|
94186
94304
|
});
|
|
94187
94305
|
return writeOrRecoverPrd(ctx, prd);
|
|
94188
94306
|
} catch (err) {
|
|
@@ -94467,7 +94585,7 @@ init_interaction();
|
|
|
94467
94585
|
init_prd();
|
|
94468
94586
|
init_runtime();
|
|
94469
94587
|
import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
|
|
94470
|
-
import { basename as basename7, join as
|
|
94588
|
+
import { basename as basename7, join as join43, resolve as resolve14 } from "path";
|
|
94471
94589
|
var _statusFeaturesDeps = {
|
|
94472
94590
|
projectOutputDir,
|
|
94473
94591
|
loadConfig
|
|
@@ -94481,7 +94599,7 @@ function isPidAlive(pid) {
|
|
|
94481
94599
|
}
|
|
94482
94600
|
}
|
|
94483
94601
|
async function loadStatusFile(featureDir) {
|
|
94484
|
-
const statusPath =
|
|
94602
|
+
const statusPath = join43(featureDir, "status.json");
|
|
94485
94603
|
if (!existsSync17(statusPath)) {
|
|
94486
94604
|
return null;
|
|
94487
94605
|
}
|
|
@@ -94496,7 +94614,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
94496
94614
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94497
94615
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94498
94616
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94499
|
-
const statusPath =
|
|
94617
|
+
const statusPath = join43(outputDir, "status.json");
|
|
94500
94618
|
if (!existsSync17(statusPath)) {
|
|
94501
94619
|
return null;
|
|
94502
94620
|
}
|
|
@@ -94508,7 +94626,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
94508
94626
|
}
|
|
94509
94627
|
}
|
|
94510
94628
|
async function getFeatureSummary(featureName, featureDir) {
|
|
94511
|
-
const prdPath =
|
|
94629
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
94512
94630
|
if (!existsSync17(prdPath)) {
|
|
94513
94631
|
return {
|
|
94514
94632
|
name: featureName,
|
|
@@ -94551,7 +94669,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
94551
94669
|
};
|
|
94552
94670
|
}
|
|
94553
94671
|
}
|
|
94554
|
-
const runsDir =
|
|
94672
|
+
const runsDir = join43(featureDir, "runs");
|
|
94555
94673
|
if (existsSync17(runsDir)) {
|
|
94556
94674
|
const runs = readdirSync3(runsDir, { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".jsonl") && e.name !== "latest.jsonl").map((e) => e.name).sort().reverse();
|
|
94557
94675
|
if (runs.length > 0) {
|
|
@@ -94565,7 +94683,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94565
94683
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94566
94684
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94567
94685
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94568
|
-
const featuresDir =
|
|
94686
|
+
const featuresDir = join43(outputDir, "features");
|
|
94569
94687
|
if (!existsSync17(featuresDir)) {
|
|
94570
94688
|
console.log(source_default.dim("No features found."));
|
|
94571
94689
|
return;
|
|
@@ -94606,7 +94724,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94606
94724
|
console.log();
|
|
94607
94725
|
}
|
|
94608
94726
|
}
|
|
94609
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
94727
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join43(featuresDir, name))));
|
|
94610
94728
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
94611
94729
|
`));
|
|
94612
94730
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -94632,7 +94750,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94632
94750
|
console.log();
|
|
94633
94751
|
}
|
|
94634
94752
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
94635
|
-
const prdPath =
|
|
94753
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
94636
94754
|
if (!existsSync17(prdPath)) {
|
|
94637
94755
|
console.log(source_default.bold(`
|
|
94638
94756
|
\uD83D\uDCCA ${featureName}
|
|
@@ -94778,7 +94896,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
94778
94896
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94779
94897
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94780
94898
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94781
|
-
featureDir =
|
|
94899
|
+
featureDir = join43(outputDir, "features", options.feature);
|
|
94782
94900
|
} else {
|
|
94783
94901
|
const resolved = resolveProject({ feature: options.feature });
|
|
94784
94902
|
if (!resolved.featureDir) {
|
|
@@ -94798,7 +94916,7 @@ init_errors();
|
|
|
94798
94916
|
init_logger2();
|
|
94799
94917
|
init_runtime();
|
|
94800
94918
|
import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
|
|
94801
|
-
import { basename as basename8, join as
|
|
94919
|
+
import { basename as basename8, join as join44 } from "path";
|
|
94802
94920
|
async function resolveOutputDir2(workdir, override) {
|
|
94803
94921
|
if (override)
|
|
94804
94922
|
return override;
|
|
@@ -94822,7 +94940,7 @@ async function runsListCommand(options) {
|
|
|
94822
94940
|
const logger = getLogger();
|
|
94823
94941
|
const { feature, workdir } = options;
|
|
94824
94942
|
const outputDir = await resolveOutputDir2(workdir, options.outputDir);
|
|
94825
|
-
const runsDir =
|
|
94943
|
+
const runsDir = join44(outputDir, "features", feature, "runs");
|
|
94826
94944
|
if (!existsSync18(runsDir)) {
|
|
94827
94945
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
94828
94946
|
return;
|
|
@@ -94834,7 +94952,7 @@ async function runsListCommand(options) {
|
|
|
94834
94952
|
}
|
|
94835
94953
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
94836
94954
|
for (const file3 of files.sort().reverse()) {
|
|
94837
|
-
const logPath =
|
|
94955
|
+
const logPath = join44(runsDir, file3);
|
|
94838
94956
|
const entries = await parseRunLog(logPath);
|
|
94839
94957
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
94840
94958
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -94861,7 +94979,7 @@ async function runsShowCommand(options) {
|
|
|
94861
94979
|
const logger = getLogger();
|
|
94862
94980
|
const { runId, feature, workdir } = options;
|
|
94863
94981
|
const outputDir = await resolveOutputDir2(workdir, options.outputDir);
|
|
94864
|
-
const logPath =
|
|
94982
|
+
const logPath = join44(outputDir, "features", feature, "runs", `${runId}.jsonl`);
|
|
94865
94983
|
if (!existsSync18(logPath)) {
|
|
94866
94984
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
94867
94985
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -94976,7 +95094,7 @@ init_source();
|
|
|
94976
95094
|
init_loader();
|
|
94977
95095
|
init_generator2();
|
|
94978
95096
|
import { existsSync as existsSync25 } from "fs";
|
|
94979
|
-
import { join as
|
|
95097
|
+
import { join as join58 } from "path";
|
|
94980
95098
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
94981
95099
|
async function generateCommand(options) {
|
|
94982
95100
|
const workdir = options.dir ?? process.cwd();
|
|
@@ -95019,7 +95137,7 @@ async function generateCommand(options) {
|
|
|
95019
95137
|
return;
|
|
95020
95138
|
}
|
|
95021
95139
|
if (options.package) {
|
|
95022
|
-
const packageDir =
|
|
95140
|
+
const packageDir = join58(workdir, options.package);
|
|
95023
95141
|
if (dryRun) {
|
|
95024
95142
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
95025
95143
|
}
|
|
@@ -95039,8 +95157,8 @@ async function generateCommand(options) {
|
|
|
95039
95157
|
process.exit(1);
|
|
95040
95158
|
return;
|
|
95041
95159
|
}
|
|
95042
|
-
const contextPath = options.context ?
|
|
95043
|
-
const outputDir = options.output ?
|
|
95160
|
+
const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
|
|
95161
|
+
const outputDir = options.output ? join58(workdir, options.output) : workdir;
|
|
95044
95162
|
const autoInject = !options.noAutoInject;
|
|
95045
95163
|
if (!existsSync25(contextPath)) {
|
|
95046
95164
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
@@ -95146,7 +95264,7 @@ async function generateCommand(options) {
|
|
|
95146
95264
|
// src/cli/config-display.ts
|
|
95147
95265
|
init_loader();
|
|
95148
95266
|
import { existsSync as existsSync27 } from "fs";
|
|
95149
|
-
import { join as
|
|
95267
|
+
import { join as join60 } from "path";
|
|
95150
95268
|
|
|
95151
95269
|
// src/cli/config-descriptions.ts
|
|
95152
95270
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -95398,7 +95516,7 @@ function deepEqual(a, b) {
|
|
|
95398
95516
|
init_defaults();
|
|
95399
95517
|
init_loader();
|
|
95400
95518
|
import { existsSync as existsSync26 } from "fs";
|
|
95401
|
-
import { join as
|
|
95519
|
+
import { join as join59 } from "path";
|
|
95402
95520
|
async function loadConfigFile(path18) {
|
|
95403
95521
|
if (!existsSync26(path18))
|
|
95404
95522
|
return null;
|
|
@@ -95420,7 +95538,7 @@ async function loadProjectConfig() {
|
|
|
95420
95538
|
const projectDir = findProjectDir();
|
|
95421
95539
|
if (!projectDir)
|
|
95422
95540
|
return null;
|
|
95423
|
-
const projectPath =
|
|
95541
|
+
const projectPath = join59(projectDir, "config.json");
|
|
95424
95542
|
return await loadConfigFile(projectPath);
|
|
95425
95543
|
}
|
|
95426
95544
|
|
|
@@ -95480,7 +95598,7 @@ async function configCommand(config2, options = {}) {
|
|
|
95480
95598
|
function determineConfigSources() {
|
|
95481
95599
|
const globalPath = globalConfigPath();
|
|
95482
95600
|
const projectDir = findProjectDir();
|
|
95483
|
-
const projectPath = projectDir ?
|
|
95601
|
+
const projectPath = projectDir ? join60(projectDir, "config.json") : null;
|
|
95484
95602
|
return {
|
|
95485
95603
|
global: fileExists(globalPath) ? globalPath : null,
|
|
95486
95604
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -95629,15 +95747,15 @@ init_paths();
|
|
|
95629
95747
|
init_profile();
|
|
95630
95748
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
95631
95749
|
import { readdirSync as readdirSync5 } from "fs";
|
|
95632
|
-
import { join as
|
|
95750
|
+
import { join as join61 } from "path";
|
|
95633
95751
|
var _profileCLIDeps = {
|
|
95634
95752
|
env: process.env
|
|
95635
95753
|
};
|
|
95636
95754
|
var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
|
|
95637
95755
|
var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
|
|
95638
95756
|
async function profileListCommand(startDir) {
|
|
95639
|
-
const globalProfilesDir =
|
|
95640
|
-
const projectProfilesDir =
|
|
95757
|
+
const globalProfilesDir = join61(globalConfigDir(), "profiles");
|
|
95758
|
+
const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
|
|
95641
95759
|
const globalProfiles = scanProfileDir(globalProfilesDir);
|
|
95642
95760
|
const projectProfiles = scanProfileDir(projectProfilesDir);
|
|
95643
95761
|
const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
@@ -95696,7 +95814,7 @@ function maskProfileValues(obj) {
|
|
|
95696
95814
|
return result;
|
|
95697
95815
|
}
|
|
95698
95816
|
async function profileUseCommand(profileName, startDir) {
|
|
95699
|
-
const configPath =
|
|
95817
|
+
const configPath = join61(projectConfigDir(startDir), "config.json");
|
|
95700
95818
|
const configFile = Bun.file(configPath);
|
|
95701
95819
|
let existing = {};
|
|
95702
95820
|
if (await configFile.exists()) {
|
|
@@ -95715,8 +95833,8 @@ async function profileCurrentCommand(startDir) {
|
|
|
95715
95833
|
return resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
95716
95834
|
}
|
|
95717
95835
|
async function profileCreateCommand(profileName, startDir) {
|
|
95718
|
-
const profilesDir =
|
|
95719
|
-
const profilePath =
|
|
95836
|
+
const profilesDir = join61(projectConfigDir(startDir), "profiles");
|
|
95837
|
+
const profilePath = join61(profilesDir, `${profileName}.json`);
|
|
95720
95838
|
const profileFile = Bun.file(profilePath);
|
|
95721
95839
|
if (await profileFile.exists()) {
|
|
95722
95840
|
throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
|
|
@@ -95838,7 +95956,7 @@ async function contextInspectCommand(options) {
|
|
|
95838
95956
|
init_canonical_loader();
|
|
95839
95957
|
init_errors();
|
|
95840
95958
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
95841
|
-
import { basename as basename12, join as
|
|
95959
|
+
import { basename as basename12, join as join62 } from "path";
|
|
95842
95960
|
var _rulesCLIDeps = {
|
|
95843
95961
|
readFile: async (path18) => Bun.file(path18).text(),
|
|
95844
95962
|
writeFile: async (path18, content) => {
|
|
@@ -95847,7 +95965,7 @@ var _rulesCLIDeps = {
|
|
|
95847
95965
|
fileExists: async (path18) => Bun.file(path18).exists(),
|
|
95848
95966
|
globInDir: (dir) => {
|
|
95849
95967
|
try {
|
|
95850
|
-
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) =>
|
|
95968
|
+
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
|
|
95851
95969
|
} catch {
|
|
95852
95970
|
return [];
|
|
95853
95971
|
}
|
|
@@ -95896,7 +96014,7 @@ ${r.content}`).join(`
|
|
|
95896
96014
|
`);
|
|
95897
96015
|
const shimContent = `${header + body}
|
|
95898
96016
|
`;
|
|
95899
|
-
const shimPath =
|
|
96017
|
+
const shimPath = join62(workdir, shimFileName);
|
|
95900
96018
|
if (options.dryRun) {
|
|
95901
96019
|
console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
|
|
95902
96020
|
return;
|
|
@@ -95925,14 +96043,14 @@ function neutralizeContent(content) {
|
|
|
95925
96043
|
}
|
|
95926
96044
|
async function collectMigrationSources(workdir) {
|
|
95927
96045
|
const sources = [];
|
|
95928
|
-
const claudeMdPath =
|
|
96046
|
+
const claudeMdPath = join62(workdir, "CLAUDE.md");
|
|
95929
96047
|
if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
|
|
95930
96048
|
const content = await _rulesCLIDeps.readFile(claudeMdPath);
|
|
95931
96049
|
if (content.trim()) {
|
|
95932
96050
|
sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
|
|
95933
96051
|
}
|
|
95934
96052
|
}
|
|
95935
|
-
const rulesDir =
|
|
96053
|
+
const rulesDir = join62(workdir, ".claude", "rules");
|
|
95936
96054
|
const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
|
|
95937
96055
|
for (const filePath of ruleFiles) {
|
|
95938
96056
|
try {
|
|
@@ -95952,7 +96070,7 @@ async function rulesMigrateCommand(options) {
|
|
|
95952
96070
|
console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
|
|
95953
96071
|
return;
|
|
95954
96072
|
}
|
|
95955
|
-
const targetDir =
|
|
96073
|
+
const targetDir = join62(workdir, CANONICAL_RULES_DIR);
|
|
95956
96074
|
if (!options.dryRun) {
|
|
95957
96075
|
try {
|
|
95958
96076
|
await _rulesCLIDeps.mkdir(targetDir);
|
|
@@ -95963,7 +96081,7 @@ async function rulesMigrateCommand(options) {
|
|
|
95963
96081
|
let written = 0;
|
|
95964
96082
|
let skipped = 0;
|
|
95965
96083
|
for (const { sourcePath, targetFileName, content } of sources) {
|
|
95966
|
-
const targetPath =
|
|
96084
|
+
const targetPath = join62(targetDir, targetFileName);
|
|
95967
96085
|
if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
|
|
95968
96086
|
console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
|
|
95969
96087
|
skipped++;
|
|
@@ -96002,7 +96120,7 @@ function collectCanonicalRuleRoots(workdir) {
|
|
|
96002
96120
|
const packageRel = normalized.slice(0, idx);
|
|
96003
96121
|
if (!packageRel)
|
|
96004
96122
|
continue;
|
|
96005
|
-
roots.add(
|
|
96123
|
+
roots.add(join62(workdir, packageRel));
|
|
96006
96124
|
}
|
|
96007
96125
|
return [...roots].sort();
|
|
96008
96126
|
}
|
|
@@ -96024,7 +96142,7 @@ init_logger2();
|
|
|
96024
96142
|
init_detect2();
|
|
96025
96143
|
init_workspace();
|
|
96026
96144
|
init_common();
|
|
96027
|
-
import { join as
|
|
96145
|
+
import { join as join63 } from "path";
|
|
96028
96146
|
function resolveEffective(detected, configPatterns) {
|
|
96029
96147
|
if (configPatterns !== undefined)
|
|
96030
96148
|
return "config";
|
|
@@ -96109,7 +96227,7 @@ async function detectCommand(options) {
|
|
|
96109
96227
|
const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
96110
96228
|
const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
|
|
96111
96229
|
const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
96112
|
-
const pkgConfigPath =
|
|
96230
|
+
const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
|
|
96113
96231
|
const pkgRaw = await loadRawConfig(pkgConfigPath);
|
|
96114
96232
|
const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
|
|
96115
96233
|
const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
|
|
@@ -96163,13 +96281,13 @@ async function detectCommand(options) {
|
|
|
96163
96281
|
if (rootDetected.confidence === "empty") {
|
|
96164
96282
|
console.log(source_default.yellow(" root: skipped (empty detection)"));
|
|
96165
96283
|
} else {
|
|
96166
|
-
const rootConfigPath =
|
|
96284
|
+
const rootConfigPath = join63(workdir, ".nax", "config.json");
|
|
96167
96285
|
try {
|
|
96168
96286
|
const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
|
|
96169
96287
|
if (status === "skipped") {
|
|
96170
96288
|
console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
|
|
96171
96289
|
} else {
|
|
96172
|
-
console.log(source_default.green(` root: ${status} \u2192 ${
|
|
96290
|
+
console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
|
|
96173
96291
|
}
|
|
96174
96292
|
} catch (err) {
|
|
96175
96293
|
console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
|
|
@@ -96182,13 +96300,13 @@ async function detectCommand(options) {
|
|
|
96182
96300
|
console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
|
|
96183
96301
|
continue;
|
|
96184
96302
|
}
|
|
96185
|
-
const pkgConfigPath =
|
|
96303
|
+
const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
|
|
96186
96304
|
try {
|
|
96187
96305
|
const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
|
|
96188
96306
|
if (status === "skipped") {
|
|
96189
96307
|
console.log(source_default.dim(` ${dir}: skipped (already set)`));
|
|
96190
96308
|
} else {
|
|
96191
|
-
console.log(source_default.green(` ${dir}: ${status} \u2192 ${
|
|
96309
|
+
console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
|
|
96192
96310
|
}
|
|
96193
96311
|
} catch (err) {
|
|
96194
96312
|
console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
|
|
@@ -96206,19 +96324,19 @@ async function detectCommand(options) {
|
|
|
96206
96324
|
// src/commands/logs.ts
|
|
96207
96325
|
init_common();
|
|
96208
96326
|
import { existsSync as existsSync29 } from "fs";
|
|
96209
|
-
import { join as
|
|
96327
|
+
import { join as join67 } from "path";
|
|
96210
96328
|
|
|
96211
96329
|
// src/commands/logs-formatter.ts
|
|
96212
96330
|
init_source();
|
|
96213
96331
|
init_formatter();
|
|
96214
96332
|
import { readdirSync as readdirSync7 } from "fs";
|
|
96215
|
-
import { join as
|
|
96333
|
+
import { join as join66 } from "path";
|
|
96216
96334
|
|
|
96217
96335
|
// src/commands/logs-reader.ts
|
|
96218
96336
|
init_paths3();
|
|
96219
96337
|
import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
|
|
96220
96338
|
import { readdir as readdir4 } from "fs/promises";
|
|
96221
|
-
import { join as
|
|
96339
|
+
import { join as join65 } from "path";
|
|
96222
96340
|
var _logsReaderDeps = {
|
|
96223
96341
|
getRunsDir
|
|
96224
96342
|
};
|
|
@@ -96232,7 +96350,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
96232
96350
|
}
|
|
96233
96351
|
let matched = null;
|
|
96234
96352
|
for (const entry of entries) {
|
|
96235
|
-
const metaPath =
|
|
96353
|
+
const metaPath = join65(runsDir, entry, "meta.json");
|
|
96236
96354
|
try {
|
|
96237
96355
|
const meta3 = await Bun.file(metaPath).json();
|
|
96238
96356
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -96254,14 +96372,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
96254
96372
|
return null;
|
|
96255
96373
|
}
|
|
96256
96374
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
96257
|
-
return
|
|
96375
|
+
return join65(matched.eventsDir, specificFile ?? files[0]);
|
|
96258
96376
|
}
|
|
96259
96377
|
async function selectRunFile(runsDir) {
|
|
96260
96378
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
96261
96379
|
if (files.length === 0) {
|
|
96262
96380
|
return null;
|
|
96263
96381
|
}
|
|
96264
|
-
return
|
|
96382
|
+
return join65(runsDir, files[0]);
|
|
96265
96383
|
}
|
|
96266
96384
|
async function extractRunSummary(filePath) {
|
|
96267
96385
|
const file3 = Bun.file(filePath);
|
|
@@ -96347,7 +96465,7 @@ Runs:
|
|
|
96347
96465
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
96348
96466
|
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"));
|
|
96349
96467
|
for (const file3 of files) {
|
|
96350
|
-
const filePath =
|
|
96468
|
+
const filePath = join66(runsDir, file3);
|
|
96351
96469
|
const summary = await extractRunSummary(filePath);
|
|
96352
96470
|
const timestamp = file3.replace(".jsonl", "");
|
|
96353
96471
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -96461,7 +96579,7 @@ async function logsCommand(options) {
|
|
|
96461
96579
|
return;
|
|
96462
96580
|
}
|
|
96463
96581
|
const resolved = resolveProject({ dir: options.dir });
|
|
96464
|
-
const naxDir =
|
|
96582
|
+
const naxDir = join67(resolved.projectDir, ".nax");
|
|
96465
96583
|
const configPath = resolved.configPath;
|
|
96466
96584
|
const configFile = Bun.file(configPath);
|
|
96467
96585
|
const config2 = await configFile.json();
|
|
@@ -96469,8 +96587,8 @@ async function logsCommand(options) {
|
|
|
96469
96587
|
if (!featureName) {
|
|
96470
96588
|
throw new Error("No feature specified in config.json");
|
|
96471
96589
|
}
|
|
96472
|
-
const featureDir =
|
|
96473
|
-
const runsDir =
|
|
96590
|
+
const featureDir = join67(naxDir, "features", featureName);
|
|
96591
|
+
const runsDir = join67(featureDir, "runs");
|
|
96474
96592
|
if (!existsSync29(runsDir)) {
|
|
96475
96593
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
96476
96594
|
}
|
|
@@ -96496,7 +96614,7 @@ init_prd();
|
|
|
96496
96614
|
init_precheck();
|
|
96497
96615
|
init_common();
|
|
96498
96616
|
import { existsSync as existsSync30 } from "fs";
|
|
96499
|
-
import { join as
|
|
96617
|
+
import { join as join68 } from "path";
|
|
96500
96618
|
async function precheckCommand(options) {
|
|
96501
96619
|
const resolved = resolveProject({
|
|
96502
96620
|
dir: options.dir,
|
|
@@ -96518,9 +96636,9 @@ async function precheckCommand(options) {
|
|
|
96518
96636
|
process.exit(1);
|
|
96519
96637
|
}
|
|
96520
96638
|
}
|
|
96521
|
-
const naxDir =
|
|
96522
|
-
const featureDir =
|
|
96523
|
-
const prdPath =
|
|
96639
|
+
const naxDir = join68(resolved.projectDir, ".nax");
|
|
96640
|
+
const featureDir = join68(naxDir, "features", featureName);
|
|
96641
|
+
const prdPath = join68(featureDir, "prd.json");
|
|
96524
96642
|
if (!existsSync30(featureDir)) {
|
|
96525
96643
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
96526
96644
|
process.exit(1);
|
|
@@ -96543,7 +96661,7 @@ async function precheckCommand(options) {
|
|
|
96543
96661
|
init_source();
|
|
96544
96662
|
init_paths3();
|
|
96545
96663
|
import { readdir as readdir5 } from "fs/promises";
|
|
96546
|
-
import { join as
|
|
96664
|
+
import { join as join69 } from "path";
|
|
96547
96665
|
var DEFAULT_LIMIT = 20;
|
|
96548
96666
|
var _runsCmdDeps = {
|
|
96549
96667
|
getRunsDir
|
|
@@ -96598,7 +96716,7 @@ async function runsCommand(options = {}) {
|
|
|
96598
96716
|
}
|
|
96599
96717
|
const rows = [];
|
|
96600
96718
|
for (const entry of entries) {
|
|
96601
|
-
const metaPath =
|
|
96719
|
+
const metaPath = join69(runsDir, entry, "meta.json");
|
|
96602
96720
|
let meta3;
|
|
96603
96721
|
try {
|
|
96604
96722
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -96675,7 +96793,7 @@ async function runsCommand(options = {}) {
|
|
|
96675
96793
|
|
|
96676
96794
|
// src/commands/unlock.ts
|
|
96677
96795
|
init_source();
|
|
96678
|
-
import { join as
|
|
96796
|
+
import { join as join70 } from "path";
|
|
96679
96797
|
function isProcessAlive2(pid) {
|
|
96680
96798
|
try {
|
|
96681
96799
|
process.kill(pid, 0);
|
|
@@ -96690,7 +96808,7 @@ function formatLockAge(ageMs) {
|
|
|
96690
96808
|
}
|
|
96691
96809
|
async function unlockCommand(options) {
|
|
96692
96810
|
const workdir = options.dir ?? process.cwd();
|
|
96693
|
-
const lockPath =
|
|
96811
|
+
const lockPath = join70(workdir, "nax.lock");
|
|
96694
96812
|
const lockFile = Bun.file(lockPath);
|
|
96695
96813
|
const exists = await lockFile.exists();
|
|
96696
96814
|
if (!exists) {
|
|
@@ -96742,6 +96860,7 @@ init_acceptance2();
|
|
|
96742
96860
|
init_config();
|
|
96743
96861
|
init_hooks();
|
|
96744
96862
|
init_logger2();
|
|
96863
|
+
init_pipeline();
|
|
96745
96864
|
init_prd();
|
|
96746
96865
|
init_git();
|
|
96747
96866
|
init_crash_recovery();
|
|
@@ -96775,6 +96894,7 @@ async function runCompletionPhase(options) {
|
|
|
96775
96894
|
logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
|
|
96776
96895
|
} else if (options.config.acceptance.enabled && isComplete(options.prd)) {
|
|
96777
96896
|
options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
|
|
96897
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
|
|
96778
96898
|
const acceptanceTestPaths = options.featureDir ? await Promise.all(groupStoriesByPackage(options.prd, options.workdir, options.feature, options.config.acceptance.testPath, options.config.project?.language).map(async (g) => {
|
|
96779
96899
|
const relativeWorkdir = path19.relative(options.workdir, g.packageDir);
|
|
96780
96900
|
let groupConfig = options.config;
|
|
@@ -96821,6 +96941,7 @@ async function runCompletionPhase(options) {
|
|
|
96821
96941
|
const lastRunAt = new Date().toISOString();
|
|
96822
96942
|
if (acceptanceResult.success) {
|
|
96823
96943
|
options.statusWriter.setPostRunPhase("acceptance", { status: "passed", lastRunAt });
|
|
96944
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: true });
|
|
96824
96945
|
} else {
|
|
96825
96946
|
acceptancePassed = false;
|
|
96826
96947
|
options.statusWriter.setPostRunPhase("acceptance", {
|
|
@@ -96829,6 +96950,7 @@ async function runCompletionPhase(options) {
|
|
|
96829
96950
|
retries: acceptanceResult.retries ?? 0,
|
|
96830
96951
|
lastRunAt
|
|
96831
96952
|
});
|
|
96953
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: false });
|
|
96832
96954
|
}
|
|
96833
96955
|
Object.assign(options, {
|
|
96834
96956
|
prd: acceptanceResult.prd,
|
|
@@ -103725,6 +103847,7 @@ var MAX_ESCALATION_DISPLAY = 5;
|
|
|
103725
103847
|
function LiveActivityPanel({
|
|
103726
103848
|
focused = false,
|
|
103727
103849
|
activeCalls,
|
|
103850
|
+
storySteps,
|
|
103728
103851
|
runSummary,
|
|
103729
103852
|
runErrored,
|
|
103730
103853
|
escalationLog = []
|
|
@@ -103783,7 +103906,8 @@ function LiveActivityPanel({
|
|
|
103783
103906
|
paddingX: 1,
|
|
103784
103907
|
paddingY: 1,
|
|
103785
103908
|
children: activeCallList.map((call) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ActiveCallRow, {
|
|
103786
|
-
call
|
|
103909
|
+
call,
|
|
103910
|
+
step: call.storyId ? storySteps?.[call.storyId] : undefined
|
|
103787
103911
|
}, call.callId, false, undefined, this))
|
|
103788
103912
|
}, undefined, false, undefined, this),
|
|
103789
103913
|
hasEscalations && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
@@ -103799,7 +103923,29 @@ function LiveActivityPanel({
|
|
|
103799
103923
|
}, `${entry.storyId}-${entry.at}`, false, undefined, this))
|
|
103800
103924
|
]
|
|
103801
103925
|
}, undefined, true, undefined, this),
|
|
103802
|
-
!hasActiveCalls &&
|
|
103926
|
+
!hasActiveCalls && storySteps && Object.keys(storySteps).length > 0 && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103927
|
+
flexDirection: "column",
|
|
103928
|
+
paddingX: 1,
|
|
103929
|
+
paddingY: 1,
|
|
103930
|
+
children: Object.entries(storySteps).map(([sid, step]) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103931
|
+
flexDirection: "row",
|
|
103932
|
+
gap: 1,
|
|
103933
|
+
children: [
|
|
103934
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103935
|
+
children: sid
|
|
103936
|
+
}, undefined, false, undefined, this),
|
|
103937
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103938
|
+
color: "yellow",
|
|
103939
|
+
children: [
|
|
103940
|
+
"[",
|
|
103941
|
+
step,
|
|
103942
|
+
"]"
|
|
103943
|
+
]
|
|
103944
|
+
}, undefined, true, undefined, this)
|
|
103945
|
+
]
|
|
103946
|
+
}, sid, true, undefined, this))
|
|
103947
|
+
}, undefined, false, undefined, this),
|
|
103948
|
+
!hasActiveCalls && !hasSummary && !hasError && (!storySteps || Object.keys(storySteps).length === 0) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103803
103949
|
paddingX: 1,
|
|
103804
103950
|
paddingY: 1,
|
|
103805
103951
|
children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
@@ -103810,7 +103956,7 @@ function LiveActivityPanel({
|
|
|
103810
103956
|
]
|
|
103811
103957
|
}, undefined, true, undefined, this);
|
|
103812
103958
|
}
|
|
103813
|
-
function ActiveCallRow({ call }) {
|
|
103959
|
+
function ActiveCallRow({ call, step }) {
|
|
103814
103960
|
return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103815
103961
|
flexDirection: "column",
|
|
103816
103962
|
marginBottom: 1,
|
|
@@ -103826,14 +103972,21 @@ function ActiveCallRow({ call }) {
|
|
|
103826
103972
|
call.storyId && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103827
103973
|
children: call.storyId
|
|
103828
103974
|
}, undefined, false, undefined, this),
|
|
103829
|
-
|
|
103975
|
+
step ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103976
|
+
color: "yellow",
|
|
103977
|
+
children: [
|
|
103978
|
+
"[",
|
|
103979
|
+
step,
|
|
103980
|
+
"]"
|
|
103981
|
+
]
|
|
103982
|
+
}, undefined, true, undefined, this) : call.stage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103830
103983
|
dimColor: true,
|
|
103831
103984
|
children: [
|
|
103832
103985
|
"[",
|
|
103833
103986
|
call.stage,
|
|
103834
103987
|
"]"
|
|
103835
103988
|
]
|
|
103836
|
-
}, undefined, true, undefined, this)
|
|
103989
|
+
}, undefined, true, undefined, this) : null
|
|
103837
103990
|
]
|
|
103838
103991
|
}, undefined, true, undefined, this),
|
|
103839
103992
|
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
@@ -104040,7 +104193,7 @@ function getStatusIcon(status) {
|
|
|
104040
104193
|
return "\u23F8\uFE0F";
|
|
104041
104194
|
}
|
|
104042
104195
|
}
|
|
104043
|
-
function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight }) {
|
|
104196
|
+
function StoriesPanel({ stories, postRunPhases, width, compact: compact2 = false, maxHeight }) {
|
|
104044
104197
|
const maxVisible = compact2 ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
|
|
104045
104198
|
const needsScrolling = stories.length > maxVisible;
|
|
104046
104199
|
const [scrollOffset, setScrollOffset] = import_react30.useState(0);
|
|
@@ -104156,15 +104309,63 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
|
|
|
104156
104309
|
" more below"
|
|
104157
104310
|
]
|
|
104158
104311
|
}, undefined, true, undefined, this)
|
|
104159
|
-
}, undefined, false, undefined, this)
|
|
104312
|
+
}, undefined, false, undefined, this),
|
|
104313
|
+
postRunPhases && (postRunPhases.acceptance || postRunPhases.regression || postRunPhases.review) && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104314
|
+
flexDirection: "column",
|
|
104315
|
+
paddingX: 1,
|
|
104316
|
+
paddingTop: 1,
|
|
104317
|
+
children: [
|
|
104318
|
+
/* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104319
|
+
borderStyle: "single",
|
|
104320
|
+
borderTop: true,
|
|
104321
|
+
borderColor: "gray"
|
|
104322
|
+
}, undefined, false, undefined, this),
|
|
104323
|
+
/* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104324
|
+
dimColor: true,
|
|
104325
|
+
children: "Post-Run"
|
|
104326
|
+
}, undefined, false, undefined, this),
|
|
104327
|
+
postRunPhases.acceptance && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
|
|
104328
|
+
label: "acceptance",
|
|
104329
|
+
phase: postRunPhases.acceptance,
|
|
104330
|
+
compact: compact2
|
|
104331
|
+
}, undefined, false, undefined, this),
|
|
104332
|
+
postRunPhases.regression && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
|
|
104333
|
+
label: "regression",
|
|
104334
|
+
phase: postRunPhases.regression,
|
|
104335
|
+
compact: compact2
|
|
104336
|
+
}, undefined, false, undefined, this),
|
|
104337
|
+
postRunPhases.review && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
|
|
104338
|
+
label: "review",
|
|
104339
|
+
phase: postRunPhases.review,
|
|
104340
|
+
compact: compact2
|
|
104341
|
+
}, undefined, false, undefined, this)
|
|
104342
|
+
]
|
|
104343
|
+
}, undefined, true, undefined, this)
|
|
104160
104344
|
]
|
|
104161
104345
|
}, undefined, true, undefined, this);
|
|
104162
104346
|
}
|
|
104347
|
+
function PostRunPhaseRow({ label, phase, compact: compact2 }) {
|
|
104348
|
+
const icon = phase.status === "running" ? ">" : phase.status === "passed" ? "[OK]" : "[X]";
|
|
104349
|
+
const color = phase.status === "running" ? "cyan" : phase.status === "passed" ? "green" : "red";
|
|
104350
|
+
return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104351
|
+
children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104352
|
+
color,
|
|
104353
|
+
children: [
|
|
104354
|
+
icon,
|
|
104355
|
+
" ",
|
|
104356
|
+
compact2 ? label.slice(0, 3) : label
|
|
104357
|
+
]
|
|
104358
|
+
}, undefined, true, undefined, this)
|
|
104359
|
+
}, undefined, false, undefined, this);
|
|
104360
|
+
}
|
|
104163
104361
|
|
|
104164
104362
|
// src/tui/hooks/useAgentStreamEvents.ts
|
|
104165
104363
|
var import_react31 = __toESM(require_react(), 1);
|
|
104166
104364
|
function useAgentStreamEvents(bus) {
|
|
104167
104365
|
const [activeCalls, setActiveCalls] = import_react31.useState(new Map);
|
|
104366
|
+
const [inputTokens, setInputTokens] = import_react31.useState(0);
|
|
104367
|
+
const [outputTokens, setOutputTokens] = import_react31.useState(0);
|
|
104368
|
+
const lastTokensRef = import_react31.useRef(new Map);
|
|
104168
104369
|
import_react31.useEffect(() => {
|
|
104169
104370
|
if (!bus)
|
|
104170
104371
|
return;
|
|
@@ -104221,6 +104422,18 @@ function useAgentStreamEvents(bus) {
|
|
|
104221
104422
|
lastActivityAt: event.timestamp
|
|
104222
104423
|
});
|
|
104223
104424
|
}
|
|
104425
|
+
{
|
|
104426
|
+
const last2 = lastTokensRef.current.get(event.callId) ?? { input: 0, output: 0 };
|
|
104427
|
+
const newInput = event.inputTokens ?? last2.input;
|
|
104428
|
+
const newOutput = event.outputTokens ?? last2.output;
|
|
104429
|
+
const deltaIn = newInput - last2.input;
|
|
104430
|
+
const deltaOut = newOutput - last2.output;
|
|
104431
|
+
lastTokensRef.current.set(event.callId, { input: newInput, output: newOutput });
|
|
104432
|
+
if (deltaIn > 0)
|
|
104433
|
+
setInputTokens((prev2) => prev2 + deltaIn);
|
|
104434
|
+
if (deltaOut > 0)
|
|
104435
|
+
setOutputTokens((prev2) => prev2 + deltaOut);
|
|
104436
|
+
}
|
|
104224
104437
|
break;
|
|
104225
104438
|
}
|
|
104226
104439
|
case "agent.tool_call_update": {
|
|
@@ -104241,6 +104454,7 @@ function useAgentStreamEvents(bus) {
|
|
|
104241
104454
|
next.set(event.callId, { ...state, status: "ended" });
|
|
104242
104455
|
next.delete(event.callId);
|
|
104243
104456
|
}
|
|
104457
|
+
lastTokensRef.current.delete(event.callId);
|
|
104244
104458
|
break;
|
|
104245
104459
|
}
|
|
104246
104460
|
default:
|
|
@@ -104251,7 +104465,7 @@ function useAgentStreamEvents(bus) {
|
|
|
104251
104465
|
});
|
|
104252
104466
|
return unsubscribe;
|
|
104253
104467
|
}, [bus]);
|
|
104254
|
-
return { activeCalls };
|
|
104468
|
+
return { activeCalls, inputTokens, outputTokens };
|
|
104255
104469
|
}
|
|
104256
104470
|
|
|
104257
104471
|
// src/tui/hooks/useKeyboard.ts
|
|
@@ -104311,20 +104525,13 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104311
104525
|
const [state, setState] = import_react32.useState(() => ({
|
|
104312
104526
|
stories: initialStories,
|
|
104313
104527
|
totalCost: 0,
|
|
104314
|
-
elapsedMs: 0,
|
|
104315
104528
|
runPaused: false,
|
|
104316
104529
|
runErrored: false,
|
|
104317
|
-
escalationLog: []
|
|
104530
|
+
escalationLog: [],
|
|
104531
|
+
storySteps: {},
|
|
104532
|
+
postRunPhases: {}
|
|
104318
104533
|
}));
|
|
104319
|
-
const startTimeRef = import_react32.useRef(Date.now());
|
|
104320
104534
|
import_react32.useEffect(() => {
|
|
104321
|
-
const startTime = startTimeRef.current;
|
|
104322
|
-
const timer = setInterval(() => {
|
|
104323
|
-
setState((prev) => ({
|
|
104324
|
-
...prev,
|
|
104325
|
-
elapsedMs: Date.now() - startTime
|
|
104326
|
-
}));
|
|
104327
|
-
}, 1000);
|
|
104328
104535
|
const unsubStarted = pipelineEventBus.on("story:started", (event) => {
|
|
104329
104536
|
setState((prev) => ({
|
|
104330
104537
|
...prev,
|
|
@@ -104347,18 +104554,19 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104347
104554
|
return s;
|
|
104348
104555
|
});
|
|
104349
104556
|
const totalCost = newStories.reduce((sum2, s) => sum2 + (s.cost ?? 0), 0);
|
|
104350
|
-
|
|
104557
|
+
const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
|
|
104558
|
+
return { ...prev, stories: newStories, totalCost, storySteps: remainingSteps };
|
|
104351
104559
|
});
|
|
104352
104560
|
});
|
|
104353
104561
|
const unsubFailed = pipelineEventBus.on("story:failed", (event) => {
|
|
104354
|
-
setState((prev) =>
|
|
104355
|
-
...prev
|
|
104356
|
-
|
|
104357
|
-
...
|
|
104358
|
-
status: "failed",
|
|
104359
|
-
|
|
104360
|
-
}
|
|
104361
|
-
})
|
|
104562
|
+
setState((prev) => {
|
|
104563
|
+
const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
|
|
104564
|
+
return {
|
|
104565
|
+
...prev,
|
|
104566
|
+
stories: prev.stories.map((s) => s.story.id === event.storyId ? { ...s, status: "failed", failureReason: event.reason } : s),
|
|
104567
|
+
storySteps: remainingSteps
|
|
104568
|
+
};
|
|
104569
|
+
});
|
|
104362
104570
|
});
|
|
104363
104571
|
const unsubSkipped = pipelineEventBus.on("story:skipped", (event) => {
|
|
104364
104572
|
setState((prev) => ({
|
|
@@ -104392,7 +104600,6 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104392
104600
|
setState((prev) => ({ ...prev, runPaused: false }));
|
|
104393
104601
|
});
|
|
104394
104602
|
const unsubCompleted2 = pipelineEventBus.on("run:completed", (event) => {
|
|
104395
|
-
clearInterval(timer);
|
|
104396
104603
|
const summary = {
|
|
104397
104604
|
totalStories: event.totalStories,
|
|
104398
104605
|
passedStories: event.passedStories,
|
|
@@ -104404,7 +104611,6 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104404
104611
|
};
|
|
104405
104612
|
setState((prev) => ({
|
|
104406
104613
|
...prev,
|
|
104407
|
-
elapsedMs: event.durationMs,
|
|
104408
104614
|
runSummary: summary,
|
|
104409
104615
|
totalCost: event.totalCost ?? prev.totalCost
|
|
104410
104616
|
}));
|
|
@@ -104412,8 +104618,28 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104412
104618
|
const unsubErrored = pipelineEventBus.on("run:errored", (_event) => {
|
|
104413
104619
|
setState((prev) => ({ ...prev, runErrored: true }));
|
|
104414
104620
|
});
|
|
104621
|
+
const unsubStep = pipelineEventBus.on("story:step", (event) => {
|
|
104622
|
+
setState((prev) => ({
|
|
104623
|
+
...prev,
|
|
104624
|
+
storySteps: { ...prev.storySteps, [event.storyId]: event.step }
|
|
104625
|
+
}));
|
|
104626
|
+
});
|
|
104627
|
+
const unsubPostRunStarted = pipelineEventBus.on("postrun:phase:started", (event) => {
|
|
104628
|
+
setState((prev) => ({
|
|
104629
|
+
...prev,
|
|
104630
|
+
postRunPhases: { ...prev.postRunPhases, [event.phase]: { status: "running" } }
|
|
104631
|
+
}));
|
|
104632
|
+
});
|
|
104633
|
+
const unsubPostRunCompleted = pipelineEventBus.on("postrun:phase:completed", (event) => {
|
|
104634
|
+
setState((prev) => ({
|
|
104635
|
+
...prev,
|
|
104636
|
+
postRunPhases: {
|
|
104637
|
+
...prev.postRunPhases,
|
|
104638
|
+
[event.phase]: { status: event.passed ? "passed" : "failed" }
|
|
104639
|
+
}
|
|
104640
|
+
}));
|
|
104641
|
+
});
|
|
104415
104642
|
return () => {
|
|
104416
|
-
clearInterval(timer);
|
|
104417
104643
|
unsubStarted();
|
|
104418
104644
|
unsubCompleted();
|
|
104419
104645
|
unsubFailed();
|
|
@@ -104424,6 +104650,9 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104424
104650
|
unsubResumed();
|
|
104425
104651
|
unsubCompleted2();
|
|
104426
104652
|
unsubErrored();
|
|
104653
|
+
unsubStep();
|
|
104654
|
+
unsubPostRunStarted();
|
|
104655
|
+
unsubPostRunCompleted();
|
|
104427
104656
|
};
|
|
104428
104657
|
}, []);
|
|
104429
104658
|
return state;
|
|
@@ -104542,6 +104771,8 @@ function usePty(options) {
|
|
|
104542
104771
|
|
|
104543
104772
|
// src/tui/App.tsx
|
|
104544
104773
|
var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
|
|
104774
|
+
var MemoStoriesPanel = import_react35.memo(StoriesPanel);
|
|
104775
|
+
var MemoLiveActivityPanel = import_react35.memo(LiveActivityPanel);
|
|
104545
104776
|
function formatElapsed(ms) {
|
|
104546
104777
|
const mins = Math.floor(ms / 60000);
|
|
104547
104778
|
const secs = Math.floor(ms % 60000 / 1000);
|
|
@@ -104550,8 +104781,16 @@ function formatElapsed(ms) {
|
|
|
104550
104781
|
function formatCost3(cost) {
|
|
104551
104782
|
return `$${cost.toFixed(4)}`;
|
|
104552
104783
|
}
|
|
104784
|
+
function formatTokens(n) {
|
|
104785
|
+
if (n >= 1e6)
|
|
104786
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
104787
|
+
if (n >= 1000)
|
|
104788
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
104789
|
+
return `${n}`;
|
|
104790
|
+
}
|
|
104553
104791
|
function App2({
|
|
104554
104792
|
feature,
|
|
104793
|
+
version: version2,
|
|
104555
104794
|
stories: initialStories,
|
|
104556
104795
|
events,
|
|
104557
104796
|
queueFilePath,
|
|
@@ -104562,13 +104801,21 @@ function App2({
|
|
|
104562
104801
|
const busState = usePipelineBusEvents(initialStories);
|
|
104563
104802
|
const { currentStage } = usePipelineEvents(events);
|
|
104564
104803
|
const { exit } = use_app_default();
|
|
104804
|
+
const startTimeRef = import_react35.useRef(Date.now());
|
|
104805
|
+
const [elapsedMs, setElapsedMs] = import_react35.useState(0);
|
|
104806
|
+
import_react35.useEffect(() => {
|
|
104807
|
+
if (busState.runSummary)
|
|
104808
|
+
return;
|
|
104809
|
+
const timer = setInterval(() => setElapsedMs(Date.now() - startTimeRef.current), 1000);
|
|
104810
|
+
return () => clearInterval(timer);
|
|
104811
|
+
}, [busState.runSummary]);
|
|
104565
104812
|
const [focus, setFocus] = import_react35.useState("stories" /* Stories */);
|
|
104566
104813
|
const [showHelp, setShowHelp] = import_react35.useState(false);
|
|
104567
104814
|
const [showCost, setShowCost] = import_react35.useState(false);
|
|
104568
104815
|
const [showQuitConfirm, setShowQuitConfirm] = import_react35.useState(false);
|
|
104569
104816
|
const [showAbortConfirm, setShowAbortConfirm] = import_react35.useState(false);
|
|
104570
104817
|
const { handle: ptyHandle } = usePty(ptyOptions ?? null);
|
|
104571
|
-
const { activeCalls } = useAgentStreamEvents(agentStreamEvents);
|
|
104818
|
+
const { activeCalls, inputTokens, outputTokens } = useAgentStreamEvents(agentStreamEvents);
|
|
104572
104819
|
const isRunComplete = !!busState.runSummary;
|
|
104573
104820
|
const runningStories = busState.stories.filter((s) => s.status === "running");
|
|
104574
104821
|
const isParallel = runningStories.length > 1;
|
|
@@ -104655,10 +104902,13 @@ function App2({
|
|
|
104655
104902
|
});
|
|
104656
104903
|
const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
|
|
104657
104904
|
const activeCount = runningStories.length;
|
|
104905
|
+
const displayElapsed = busState.runSummary ? busState.runSummary.durationMs : elapsedMs;
|
|
104906
|
+
const tokensStr = inputTokens > 0 || outputTokens > 0 ? `${formatTokens(inputTokens)} in / ${formatTokens(outputTokens)} out` : null;
|
|
104658
104907
|
const headerRight = [
|
|
104659
104908
|
activeCount > 0 ? `${activeCount} running` : null,
|
|
104660
104909
|
formatCost3(busState.totalCost),
|
|
104661
|
-
|
|
104910
|
+
tokensStr,
|
|
104911
|
+
formatElapsed(displayElapsed)
|
|
104662
104912
|
].filter(Boolean).join(" \xB7 ");
|
|
104663
104913
|
const maxHeight = layout.mode === "single" ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
|
|
104664
104914
|
return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
@@ -104677,7 +104927,15 @@ function App2({
|
|
|
104677
104927
|
color: "cyan",
|
|
104678
104928
|
children: [
|
|
104679
104929
|
"nax run \u2014 ",
|
|
104680
|
-
feature
|
|
104930
|
+
feature,
|
|
104931
|
+
version2 ? /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
104932
|
+
dimColor: true,
|
|
104933
|
+
color: "cyan",
|
|
104934
|
+
children: [
|
|
104935
|
+
" ",
|
|
104936
|
+
version2
|
|
104937
|
+
]
|
|
104938
|
+
}, undefined, true, undefined, this) : null
|
|
104681
104939
|
]
|
|
104682
104940
|
}, undefined, true, undefined, this),
|
|
104683
104941
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
@@ -104704,15 +104962,17 @@ function App2({
|
|
|
104704
104962
|
flexDirection: layout.mode === "single" ? "column" : "row",
|
|
104705
104963
|
flexGrow: 1,
|
|
104706
104964
|
children: [
|
|
104707
|
-
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(
|
|
104965
|
+
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoStoriesPanel, {
|
|
104708
104966
|
stories: busState.stories,
|
|
104967
|
+
postRunPhases: busState.postRunPhases,
|
|
104709
104968
|
width: layout.mode === "single" ? layout.width : layout.storiesPanelWidth,
|
|
104710
104969
|
compact: layout.mode === "single",
|
|
104711
104970
|
maxHeight
|
|
104712
104971
|
}, undefined, false, undefined, this),
|
|
104713
|
-
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(
|
|
104972
|
+
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoLiveActivityPanel, {
|
|
104714
104973
|
focused: focus === "agent" /* Agent */,
|
|
104715
104974
|
activeCalls,
|
|
104975
|
+
storySteps: busState.storySteps,
|
|
104716
104976
|
runSummary: busState.runSummary,
|
|
104717
104977
|
runErrored: runErroredForPanel,
|
|
104718
104978
|
escalationLog: busState.escalationLog
|
|
@@ -104881,7 +105141,7 @@ Next: nax generate --package ${options.package}`));
|
|
|
104881
105141
|
}
|
|
104882
105142
|
return;
|
|
104883
105143
|
}
|
|
104884
|
-
const naxDir =
|
|
105144
|
+
const naxDir = join84(workdir, ".nax");
|
|
104885
105145
|
if (existsSync36(naxDir) && !options.force) {
|
|
104886
105146
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
104887
105147
|
return;
|
|
@@ -104910,11 +105170,11 @@ Next: nax generate --package ${options.package}`));
|
|
|
104910
105170
|
}
|
|
104911
105171
|
}
|
|
104912
105172
|
}
|
|
104913
|
-
mkdirSync7(
|
|
104914
|
-
mkdirSync7(
|
|
105173
|
+
mkdirSync7(join84(naxDir, "features"), { recursive: true });
|
|
105174
|
+
mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
|
|
104915
105175
|
const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
|
|
104916
|
-
await Bun.write(
|
|
104917
|
-
await Bun.write(
|
|
105176
|
+
await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
|
|
105177
|
+
await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
|
|
104918
105178
|
hooks: {
|
|
104919
105179
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
104920
105180
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -104922,12 +105182,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
104922
105182
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
104923
105183
|
}
|
|
104924
105184
|
}, null, 2));
|
|
104925
|
-
await Bun.write(
|
|
105185
|
+
await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
|
|
104926
105186
|
*.tmp
|
|
104927
105187
|
.paused.json
|
|
104928
105188
|
.nax-verifier-verdict.json
|
|
104929
105189
|
`);
|
|
104930
|
-
await Bun.write(
|
|
105190
|
+
await Bun.write(join84(naxDir, "context.md"), `# Project Context
|
|
104931
105191
|
|
|
104932
105192
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
104933
105193
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -105057,8 +105317,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105057
105317
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
105058
105318
|
process.exit(1);
|
|
105059
105319
|
}
|
|
105060
|
-
const featureDir =
|
|
105061
|
-
const prdPath =
|
|
105320
|
+
const featureDir = join84(naxDir, "features", options.feature);
|
|
105321
|
+
const prdPath = join84(featureDir, "prd.json");
|
|
105062
105322
|
if (options.plan && options.from) {
|
|
105063
105323
|
if (existsSync36(prdPath) && !options.force) {
|
|
105064
105324
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -105080,10 +105340,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105080
105340
|
}
|
|
105081
105341
|
}
|
|
105082
105342
|
try {
|
|
105083
|
-
const planLogDir =
|
|
105343
|
+
const planLogDir = join84(featureDir, "plan");
|
|
105084
105344
|
mkdirSync7(planLogDir, { recursive: true });
|
|
105085
105345
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105086
|
-
const planLogPath =
|
|
105346
|
+
const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
|
|
105087
105347
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
105088
105348
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
105089
105349
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -105129,10 +105389,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105129
105389
|
resetLogger();
|
|
105130
105390
|
const projectKey = config2.name?.trim() || basename16(workdir);
|
|
105131
105391
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
105132
|
-
const runsDir =
|
|
105392
|
+
const runsDir = join84(outputDir, "features", options.feature, "runs");
|
|
105133
105393
|
mkdirSync7(runsDir, { recursive: true });
|
|
105134
105394
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105135
|
-
const logFilePath =
|
|
105395
|
+
const logFilePath = join84(runsDir, `${runId}.jsonl`);
|
|
105136
105396
|
const isTTY = process.stdout.isTTY ?? false;
|
|
105137
105397
|
const headlessFlag = options.headless ?? false;
|
|
105138
105398
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -105150,7 +105410,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105150
105410
|
config2.agent.default = options.agent;
|
|
105151
105411
|
}
|
|
105152
105412
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
105153
|
-
const globalNaxDir =
|
|
105413
|
+
const globalNaxDir = join84(homedir3(), ".nax");
|
|
105154
105414
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
105155
105415
|
const eventEmitter = new PipelineEventEmitter;
|
|
105156
105416
|
const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
|
|
@@ -105165,16 +105425,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105165
105425
|
}));
|
|
105166
105426
|
tuiInstance = renderTui({
|
|
105167
105427
|
feature: options.feature,
|
|
105428
|
+
version: NAX_BUILD_INFO,
|
|
105168
105429
|
stories: initialStories,
|
|
105169
105430
|
events: eventEmitter,
|
|
105170
105431
|
ptyOptions: null,
|
|
105171
105432
|
agentStreamEvents,
|
|
105172
|
-
queueFilePath:
|
|
105433
|
+
queueFilePath: join84(workdir, ".queue.txt")
|
|
105173
105434
|
});
|
|
105174
105435
|
} else {
|
|
105175
105436
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
105176
105437
|
}
|
|
105177
|
-
const statusFilePath =
|
|
105438
|
+
const statusFilePath = join84(outputDir, "status.json");
|
|
105178
105439
|
let parallel;
|
|
105179
105440
|
if (options.parallel !== undefined) {
|
|
105180
105441
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -105201,7 +105462,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105201
105462
|
skipPrecheck: options.skipPrecheck ?? false,
|
|
105202
105463
|
agentStreamEvents
|
|
105203
105464
|
});
|
|
105204
|
-
const latestSymlink =
|
|
105465
|
+
const latestSymlink = join84(runsDir, "latest.jsonl");
|
|
105205
105466
|
try {
|
|
105206
105467
|
if (existsSync36(latestSymlink)) {
|
|
105207
105468
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -105262,9 +105523,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
105262
105523
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
105263
105524
|
process.exit(1);
|
|
105264
105525
|
}
|
|
105265
|
-
const featureDir =
|
|
105526
|
+
const featureDir = join84(naxDir, "features", name);
|
|
105266
105527
|
mkdirSync7(featureDir, { recursive: true });
|
|
105267
|
-
await Bun.write(
|
|
105528
|
+
await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
|
|
105268
105529
|
|
|
105269
105530
|
## Overview
|
|
105270
105531
|
|
|
@@ -105297,7 +105558,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
105297
105558
|
|
|
105298
105559
|
<!-- What this feature explicitly does NOT cover. -->
|
|
105299
105560
|
`);
|
|
105300
|
-
await Bun.write(
|
|
105561
|
+
await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
|
|
105301
105562
|
|
|
105302
105563
|
Created: ${new Date().toISOString()}
|
|
105303
105564
|
|
|
@@ -105323,7 +105584,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
105323
105584
|
console.error(source_default.red("nax not initialized."));
|
|
105324
105585
|
process.exit(1);
|
|
105325
105586
|
}
|
|
105326
|
-
const featuresDir =
|
|
105587
|
+
const featuresDir = join84(naxDir, "features");
|
|
105327
105588
|
if (!existsSync36(featuresDir)) {
|
|
105328
105589
|
console.log(source_default.dim("No features yet."));
|
|
105329
105590
|
return;
|
|
@@ -105338,7 +105599,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
105338
105599
|
Features:
|
|
105339
105600
|
`));
|
|
105340
105601
|
for (const name of entries) {
|
|
105341
|
-
const prdPath =
|
|
105602
|
+
const prdPath = join84(featuresDir, name, "prd.json");
|
|
105342
105603
|
if (existsSync36(prdPath)) {
|
|
105343
105604
|
const prd = await loadPRD(prdPath);
|
|
105344
105605
|
const c = countStories(prd);
|
|
@@ -105373,10 +105634,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
105373
105634
|
cliOverrides.profile = options.profile;
|
|
105374
105635
|
}
|
|
105375
105636
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
105376
|
-
const featureLogDir =
|
|
105637
|
+
const featureLogDir = join84(naxDir, "features", options.feature, "plan");
|
|
105377
105638
|
mkdirSync7(featureLogDir, { recursive: true });
|
|
105378
105639
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105379
|
-
const planLogPath =
|
|
105640
|
+
const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
|
|
105380
105641
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
105381
105642
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
105382
105643
|
try {
|