@nathapp/nax 0.68.7 → 0.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +802 -414
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -20944,12 +20944,21 @@ function parseAcpxJsonLine(line, state) {
|
|
|
20944
20944
|
}
|
|
20945
20945
|
if (update.sessionUpdate === "usage_update") {
|
|
20946
20946
|
const activity = { kind: "usage_update" };
|
|
20947
|
-
|
|
20947
|
+
const metaUsage = update._meta != null && typeof update._meta === "object" ? update._meta.usage : undefined;
|
|
20948
|
+
if (metaUsage != null && typeof metaUsage === "object") {
|
|
20949
|
+
const inp = metaUsage.inputTokens ?? metaUsage.input_tokens;
|
|
20950
|
+
if (typeof inp === "number")
|
|
20951
|
+
activity.inputTokens = inp;
|
|
20952
|
+
const out = metaUsage.outputTokens ?? metaUsage.output_tokens;
|
|
20953
|
+
if (typeof out === "number")
|
|
20954
|
+
activity.outputTokens = out;
|
|
20955
|
+
}
|
|
20956
|
+
if (activity.outputTokens == null && typeof update.used === "number") {
|
|
20948
20957
|
activity.outputTokens = update.used;
|
|
20949
20958
|
}
|
|
20950
20959
|
if (typeof update.cost?.amount === "number") {
|
|
20951
20960
|
activity.costUsd = update.cost.amount;
|
|
20952
|
-
state.exactCostUsd =
|
|
20961
|
+
state.exactCostUsd = activity.costUsd;
|
|
20953
20962
|
}
|
|
20954
20963
|
return activity;
|
|
20955
20964
|
}
|
|
@@ -27497,6 +27506,9 @@ function getContextFiles(story) {
|
|
|
27497
27506
|
const files = story.contextFiles ?? story.relevantFiles ?? [];
|
|
27498
27507
|
return files.map((f) => typeof f === "string" ? f : f.path);
|
|
27499
27508
|
}
|
|
27509
|
+
function getExpectedFiles(story) {
|
|
27510
|
+
return story.expectedFiles ?? [];
|
|
27511
|
+
}
|
|
27500
27512
|
function isStalled(prd) {
|
|
27501
27513
|
const remaining = prd.userStories.filter((s) => s.status !== "passed" && s.status !== "skipped");
|
|
27502
27514
|
if (remaining.length === 0)
|
|
@@ -27772,6 +27784,24 @@ function validateStory(raw, index, allIds) {
|
|
|
27772
27784
|
}
|
|
27773
27785
|
}
|
|
27774
27786
|
}
|
|
27787
|
+
const rawExpectedFiles = s.expectedFiles;
|
|
27788
|
+
const expectedFiles = [];
|
|
27789
|
+
if (Array.isArray(rawExpectedFiles)) {
|
|
27790
|
+
for (const f of rawExpectedFiles) {
|
|
27791
|
+
if (typeof f !== "string")
|
|
27792
|
+
continue;
|
|
27793
|
+
const trimmed = f.trim();
|
|
27794
|
+
if (trimmed === "")
|
|
27795
|
+
continue;
|
|
27796
|
+
if (trimmed.startsWith("/")) {
|
|
27797
|
+
throw new Error(`[schema] story[${index}].expectedFiles entry must be relative (no absolute paths): "${trimmed}"`);
|
|
27798
|
+
}
|
|
27799
|
+
if (trimmed.includes("..")) {
|
|
27800
|
+
throw new Error(`[schema] story[${index}].expectedFiles entry must not contain '..': "${trimmed}"`);
|
|
27801
|
+
}
|
|
27802
|
+
expectedFiles.push(trimmed);
|
|
27803
|
+
}
|
|
27804
|
+
}
|
|
27775
27805
|
const VALID_VERIFIED_BY_KINDS = ["test", "symbol", "file"];
|
|
27776
27806
|
let verifiedBy;
|
|
27777
27807
|
if (s.verifiedBy !== undefined && s.verifiedBy !== null) {
|
|
@@ -27805,6 +27835,7 @@ function validateStory(raw, index, allIds) {
|
|
|
27805
27835
|
},
|
|
27806
27836
|
...workdir !== undefined ? { workdir } : {},
|
|
27807
27837
|
...contextFiles.length > 0 ? { contextFiles } : {},
|
|
27838
|
+
...expectedFiles.length > 0 ? { expectedFiles } : {},
|
|
27808
27839
|
...suggestedCriteria !== undefined ? { suggestedCriteria } : {},
|
|
27809
27840
|
...verifiedBy !== undefined ? { verifiedBy } : {},
|
|
27810
27841
|
...intent !== undefined ? { intent } : {}
|
|
@@ -29366,6 +29397,12 @@ function renderErrorSection(sections, byType) {
|
|
|
29366
29397
|
|
|
29367
29398
|
// src/context/builder.ts
|
|
29368
29399
|
import path2 from "path";
|
|
29400
|
+
function readContextMessage(relativeFilePath) {
|
|
29401
|
+
return `_Path: \`${relativeFilePath}\` \u2014 read this file before implementing._`;
|
|
29402
|
+
}
|
|
29403
|
+
function createIntentMessage(relativeFilePath) {
|
|
29404
|
+
return `_Path: \`${relativeFilePath}\` \u2014 this file does not exist yet; you will CREATE it as part of this story._`;
|
|
29405
|
+
}
|
|
29369
29406
|
function sortContextElements(elements) {
|
|
29370
29407
|
return [...elements].sort((a, b) => {
|
|
29371
29408
|
if (a.priority !== b.priority)
|
|
@@ -29493,7 +29530,6 @@ async function addTestCoverageElement(elements, storyContext, story) {
|
|
|
29493
29530
|
}
|
|
29494
29531
|
}
|
|
29495
29532
|
async function addFileElements(elements, storyContext, story) {
|
|
29496
|
-
const MAX_FILES3 = 5;
|
|
29497
29533
|
const fileInjection = storyContext.config?.context?.fileInjection;
|
|
29498
29534
|
let contextFiles = getContextFiles(story);
|
|
29499
29535
|
const parentFiles = getParentOutputFiles(story, storyContext.prd?.userStories ?? []);
|
|
@@ -29530,26 +29566,51 @@ async function addFileElements(elements, storyContext, story) {
|
|
|
29530
29566
|
});
|
|
29531
29567
|
}
|
|
29532
29568
|
}
|
|
29533
|
-
|
|
29569
|
+
const expectedFiles = getExpectedFiles(story);
|
|
29570
|
+
if (contextFiles.length === 0 && expectedFiles.length === 0)
|
|
29534
29571
|
return;
|
|
29535
|
-
const filesToLoad = contextFiles.slice(0, MAX_FILES3);
|
|
29536
29572
|
const { workdir } = storyContext;
|
|
29537
29573
|
if (!workdir) {
|
|
29538
29574
|
getLogger().warn("context", "workdir not set \u2014 cannot load context files", { storyId: story.id });
|
|
29539
29575
|
return;
|
|
29540
29576
|
}
|
|
29577
|
+
const expectedSet = new Set(expectedFiles);
|
|
29578
|
+
const surfaced = new Set;
|
|
29579
|
+
const filesToLoad = contextFiles.slice(0, FILE_INJECTION_MAX_FILES);
|
|
29541
29580
|
for (let i = 0;i < filesToLoad.length; i++) {
|
|
29542
29581
|
const relativeFilePath = filesToLoad[i];
|
|
29582
|
+
surfaced.add(relativeFilePath);
|
|
29543
29583
|
const absolutePath = path2.resolve(workdir, relativeFilePath);
|
|
29544
|
-
|
|
29545
|
-
|
|
29546
|
-
getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
|
|
29584
|
+
if (await Bun.file(absolutePath).exists()) {
|
|
29585
|
+
elements.push(createFileContext(relativeFilePath, readContextMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
|
|
29547
29586
|
continue;
|
|
29548
29587
|
}
|
|
29549
|
-
|
|
29588
|
+
if (expectedSet.has(relativeFilePath)) {
|
|
29589
|
+
elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - i));
|
|
29590
|
+
getLogger().debug("context", "Context file does not exist yet \u2014 treated as to-be-created", {
|
|
29591
|
+
storyId: story.id,
|
|
29592
|
+
filePath: relativeFilePath
|
|
29593
|
+
});
|
|
29594
|
+
} else {
|
|
29595
|
+
getLogger().warn("context", "Relevant file not found", { filePath: relativeFilePath, storyId: story.id });
|
|
29596
|
+
}
|
|
29550
29597
|
}
|
|
29598
|
+
await addCreateIntentElements(elements, workdir, expectedFiles, surfaced);
|
|
29551
29599
|
}
|
|
29552
|
-
|
|
29600
|
+
async function addCreateIntentElements(elements, workdir, expectedFiles, surfaced) {
|
|
29601
|
+
let idx = 0;
|
|
29602
|
+
for (const relativeFilePath of expectedFiles.slice(0, FILE_INJECTION_MAX_FILES)) {
|
|
29603
|
+
if (surfaced.has(relativeFilePath))
|
|
29604
|
+
continue;
|
|
29605
|
+
const absolutePath = path2.resolve(workdir, relativeFilePath);
|
|
29606
|
+
if (await Bun.file(absolutePath).exists())
|
|
29607
|
+
continue;
|
|
29608
|
+
elements.push(createFileContext(relativeFilePath, createIntentMessage(relativeFilePath), FILE_CONTEXT_PRIORITY_BASE - FILE_INJECTION_MAX_FILES - idx));
|
|
29609
|
+
surfaced.add(relativeFilePath);
|
|
29610
|
+
idx++;
|
|
29611
|
+
}
|
|
29612
|
+
}
|
|
29613
|
+
var _contextBuilderDeps, FILE_INJECTION_MAX_FILES = 5, FILE_CONTEXT_PRIORITY_BASE = 60;
|
|
29553
29614
|
var init_builder = __esm(() => {
|
|
29554
29615
|
init_logger2();
|
|
29555
29616
|
init_prd();
|
|
@@ -34222,6 +34283,7 @@ ${outputFormat}`, overridable: false }
|
|
|
34222
34283
|
});
|
|
34223
34284
|
|
|
34224
34285
|
// src/operations/plan-refine.ts
|
|
34286
|
+
import { join as join20 } from "path";
|
|
34225
34287
|
function hasToken(text, tokens) {
|
|
34226
34288
|
const lower = text.toLowerCase();
|
|
34227
34289
|
return tokens.some((token) => lower.includes(token));
|
|
@@ -34284,6 +34346,80 @@ async function readSpecDriftViolations(input) {
|
|
|
34284
34346
|
return [];
|
|
34285
34347
|
}
|
|
34286
34348
|
}
|
|
34349
|
+
function collectUpstreamProducedFiles(story, byId) {
|
|
34350
|
+
const produced = new Set;
|
|
34351
|
+
const seen = new Set;
|
|
34352
|
+
const stack = [...story.dependencies ?? []];
|
|
34353
|
+
while (stack.length > 0) {
|
|
34354
|
+
const depId = stack.pop();
|
|
34355
|
+
if (!depId || seen.has(depId))
|
|
34356
|
+
continue;
|
|
34357
|
+
seen.add(depId);
|
|
34358
|
+
const dep = byId.get(depId);
|
|
34359
|
+
if (!dep)
|
|
34360
|
+
continue;
|
|
34361
|
+
for (const filePath of getExpectedFiles(dep))
|
|
34362
|
+
produced.add(filePath);
|
|
34363
|
+
stack.push(...dep.dependencies ?? []);
|
|
34364
|
+
}
|
|
34365
|
+
return produced;
|
|
34366
|
+
}
|
|
34367
|
+
async function normalizeStoryFiles(story, workdir, fileExists, upstreamProduced) {
|
|
34368
|
+
const contextFiles = story.contextFiles ?? [];
|
|
34369
|
+
if (contextFiles.length === 0)
|
|
34370
|
+
return { story, changed: false };
|
|
34371
|
+
const logger = getSafeLogger();
|
|
34372
|
+
const expected = new Set(getExpectedFiles(story));
|
|
34373
|
+
const kept = [];
|
|
34374
|
+
const moved = [];
|
|
34375
|
+
for (const entry of contextFiles) {
|
|
34376
|
+
const filePath = typeof entry === "string" ? entry : entry.path;
|
|
34377
|
+
const factId = typeof entry === "string" ? undefined : entry.factId;
|
|
34378
|
+
if (expected.has(filePath) || await fileExists(join20(workdir, filePath))) {
|
|
34379
|
+
kept.push(entry);
|
|
34380
|
+
continue;
|
|
34381
|
+
}
|
|
34382
|
+
if (upstreamProduced.has(filePath)) {
|
|
34383
|
+
kept.push(entry);
|
|
34384
|
+
logger?.debug("plan", "Kept cross-story produced file in contextFiles (upstream dependency creates it)", {
|
|
34385
|
+
storyId: story.id,
|
|
34386
|
+
filePath
|
|
34387
|
+
});
|
|
34388
|
+
continue;
|
|
34389
|
+
}
|
|
34390
|
+
if (factId) {
|
|
34391
|
+
logger?.warn("plan", "Context file cites a manifest fact but is absent on disk", {
|
|
34392
|
+
storyId: story.id,
|
|
34393
|
+
filePath,
|
|
34394
|
+
factId
|
|
34395
|
+
});
|
|
34396
|
+
kept.push(entry);
|
|
34397
|
+
continue;
|
|
34398
|
+
}
|
|
34399
|
+
moved.push(filePath);
|
|
34400
|
+
}
|
|
34401
|
+
if (moved.length === 0)
|
|
34402
|
+
return { story, changed: false };
|
|
34403
|
+
const newExpected = [...getExpectedFiles(story)];
|
|
34404
|
+
for (const filePath of moved) {
|
|
34405
|
+
if (!newExpected.includes(filePath))
|
|
34406
|
+
newExpected.push(filePath);
|
|
34407
|
+
}
|
|
34408
|
+
logger?.info("plan", "Moved absent contextFiles entries to expectedFiles (story creates them)", {
|
|
34409
|
+
storyId: story.id,
|
|
34410
|
+
moved
|
|
34411
|
+
});
|
|
34412
|
+
return { story: { ...story, contextFiles: kept, expectedFiles: newExpected }, changed: true };
|
|
34413
|
+
}
|
|
34414
|
+
async function normalizeCreatedContextFiles(prd, workdir, fileExists) {
|
|
34415
|
+
if (!workdir)
|
|
34416
|
+
return prd;
|
|
34417
|
+
const byId = new Map(prd.userStories.map((story) => [story.id, story]));
|
|
34418
|
+
const results = await Promise.all(prd.userStories.map((story) => normalizeStoryFiles(story, workdir, fileExists, collectUpstreamProducedFiles(story, byId))));
|
|
34419
|
+
if (!results.some((r) => r.changed))
|
|
34420
|
+
return prd;
|
|
34421
|
+
return { ...prd, userStories: results.map((r) => r.story) };
|
|
34422
|
+
}
|
|
34287
34423
|
var _planRefineDeps, NEGATIVE_PATH_TOKENS, planRefineOp;
|
|
34288
34424
|
var init_plan_refine = __esm(() => {
|
|
34289
34425
|
init_retry();
|
|
@@ -34405,7 +34541,7 @@ ${outputFormat}`,
|
|
|
34405
34541
|
if (ctx.config.plan.specGuard) {
|
|
34406
34542
|
warnOnSpecDrift(validated, input.featureName);
|
|
34407
34543
|
}
|
|
34408
|
-
return validated;
|
|
34544
|
+
return await normalizeCreatedContextFiles(validated, input.workdir, ctx.fileExists);
|
|
34409
34545
|
},
|
|
34410
34546
|
recover: async (input, ctx) => {
|
|
34411
34547
|
const content = await ctx.readFile(input.outputPath);
|
|
@@ -35391,11 +35527,11 @@ function extractTestCode(output) {
|
|
|
35391
35527
|
|
|
35392
35528
|
// src/acceptance/generator.ts
|
|
35393
35529
|
import { existsSync as existsSync5 } from "fs";
|
|
35394
|
-
import { join as
|
|
35530
|
+
import { join as join21 } from "path";
|
|
35395
35531
|
function resolvePytestBin(packageDir) {
|
|
35396
35532
|
if (packageDir) {
|
|
35397
35533
|
for (const venvDir of [".venv", "venv", "env"]) {
|
|
35398
|
-
const candidate =
|
|
35534
|
+
const candidate = join21(packageDir, venvDir, "bin", "pytest");
|
|
35399
35535
|
if (existsSync5(candidate))
|
|
35400
35536
|
return candidate;
|
|
35401
35537
|
}
|
|
@@ -38004,14 +38140,14 @@ var init_plan_critic_llm = __esm(() => {
|
|
|
38004
38140
|
|
|
38005
38141
|
// src/context/greenfield.ts
|
|
38006
38142
|
import { readdir as readdir2 } from "fs/promises";
|
|
38007
|
-
import { join as
|
|
38143
|
+
import { join as join22 } from "path";
|
|
38008
38144
|
async function scanForTestFiles(dir, testPatterns, isRootCall = true) {
|
|
38009
38145
|
const results = [];
|
|
38010
38146
|
const ignoreDirs = new Set(["node_modules", "dist", "build", ".next", ".git"]);
|
|
38011
38147
|
try {
|
|
38012
38148
|
const entries = await readdir2(dir, { withFileTypes: true });
|
|
38013
38149
|
for (const entry of entries) {
|
|
38014
|
-
const fullPath =
|
|
38150
|
+
const fullPath = join22(dir, entry.name);
|
|
38015
38151
|
if (entry.isDirectory()) {
|
|
38016
38152
|
if (ignoreDirs.has(entry.name))
|
|
38017
38153
|
continue;
|
|
@@ -38216,13 +38352,13 @@ __export(exports_runners, {
|
|
|
38216
38352
|
_regressionRunnerDeps: () => _regressionRunnerDeps
|
|
38217
38353
|
});
|
|
38218
38354
|
import { existsSync as existsSync6 } from "fs";
|
|
38219
|
-
import { join as
|
|
38355
|
+
import { join as join23 } from "path";
|
|
38220
38356
|
async function verifyAssets(workingDirectory, expectedFiles) {
|
|
38221
38357
|
if (!expectedFiles || expectedFiles.length === 0)
|
|
38222
38358
|
return { success: true, missingFiles: [] };
|
|
38223
38359
|
const missingFiles = [];
|
|
38224
38360
|
for (const file3 of expectedFiles) {
|
|
38225
|
-
if (!existsSync6(
|
|
38361
|
+
if (!existsSync6(join23(workingDirectory, file3)))
|
|
38226
38362
|
missingFiles.push(file3);
|
|
38227
38363
|
}
|
|
38228
38364
|
if (missingFiles.length > 0) {
|
|
@@ -38668,7 +38804,7 @@ var init_apply_test_edit_declarations = __esm(() => {
|
|
|
38668
38804
|
});
|
|
38669
38805
|
|
|
38670
38806
|
// src/operations/validate-mock-structure-files.ts
|
|
38671
|
-
import { join as
|
|
38807
|
+
import { join as join24 } from "path";
|
|
38672
38808
|
async function validateMockStructureFiles(declarations, resolvedTestPatterns, packageDir, deps) {
|
|
38673
38809
|
const fileExists = deps?.fileExists ?? defaultFileExists;
|
|
38674
38810
|
const valid = [];
|
|
@@ -38681,7 +38817,7 @@ async function validateMockStructureFiles(declarations, resolvedTestPatterns, pa
|
|
|
38681
38817
|
const files = d.files ?? [d.file];
|
|
38682
38818
|
let allValid = true;
|
|
38683
38819
|
for (const file3 of files) {
|
|
38684
|
-
const absolutePath =
|
|
38820
|
+
const absolutePath = join24(packageDir, file3);
|
|
38685
38821
|
const exists = await fileExists(absolutePath);
|
|
38686
38822
|
if (!exists) {
|
|
38687
38823
|
allValid = false;
|
|
@@ -39975,7 +40111,7 @@ var init_lint_parsing = __esm(() => {
|
|
|
39975
40111
|
});
|
|
39976
40112
|
|
|
39977
40113
|
// src/review/scoped-lint.ts
|
|
39978
|
-
import { join as
|
|
40114
|
+
import { join as join25, relative as relative10 } from "path";
|
|
39979
40115
|
function shellQuotePath4(path5) {
|
|
39980
40116
|
return `'${path5.replaceAll("'", "'\\''")}'`;
|
|
39981
40117
|
}
|
|
@@ -40023,7 +40159,7 @@ function uniqueFiles(files) {
|
|
|
40023
40159
|
async function filterFilesToScope(files, workdir, projectDir, activePackageDir) {
|
|
40024
40160
|
const inScope = [];
|
|
40025
40161
|
for (const relPath of files) {
|
|
40026
|
-
const absPath =
|
|
40162
|
+
const absPath = join25(workdir, relPath);
|
|
40027
40163
|
const exists = await _scopedLintDeps.fileExists(absPath);
|
|
40028
40164
|
if (!exists)
|
|
40029
40165
|
continue;
|
|
@@ -42177,7 +42313,7 @@ Output ONLY the JSON object. Do not include markdown fences or explanation.`;
|
|
|
42177
42313
|
buildRefineContinuation(outputFilePath, specGuard = false) {
|
|
42178
42314
|
const specGuardItems = specGuard ? `
|
|
42179
42315
|
#### 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.
|
|
42316
|
+
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
42317
|
|
|
42182
42318
|
#### no-behavior-degradation
|
|
42183
42319
|
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 +42444,9 @@ Based on your Step 2 analysis, create stories that produce CODE CHANGES.
|
|
|
42308
42444
|
|
|
42309
42445
|
${buildSharedQualityRules(specContent, projectProfile)}
|
|
42310
42446
|
|
|
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.
|
|
42447
|
+
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
42448
|
|
|
42313
|
-
|
|
42449
|
+
${CONTEXT_VS_EXPECTED_FILES_RULE}`;
|
|
42314
42450
|
const suggestedCriteriaField = specContent.trim() ? `
|
|
42315
42451
|
"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
42452
|
const outputDirective = outputFilePath ? `Write the PRD JSON directly to this file path: ${outputFilePath}
|
|
@@ -42332,7 +42468,8 @@ Generate a JSON object with this exact structure (no markdown, no explanation \u
|
|
|
42332
42468
|
"title": "string \u2014 concise story title",
|
|
42333
42469
|
"description": "string \u2014 detailed description of the story",
|
|
42334
42470
|
"acceptanceCriteria": ["string \u2014 behavioral, testable criteria. Format: 'When [X], then [Y]'. One assertion per AC. Never include quality gates."],${suggestedCriteriaField}
|
|
42335
|
-
"contextFiles": ["string \u2014
|
|
42471
|
+
"contextFiles": ["string \u2014 EXISTING source files the agent should read (max 5, relative paths)"],
|
|
42472
|
+
${EXPECTED_FILES_SCHEMA_FIELD}
|
|
42336
42473
|
"tags": ["string \u2014 routing tags, e.g. feature, security, api"],
|
|
42337
42474
|
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
42338
42475
|
"status": "pending",
|
|
@@ -42405,9 +42542,9 @@ Every concrete claim referencing existing code must cite [F-NNN] or [S-NNN] from
|
|
|
42405
42542
|
|
|
42406
42543
|
${buildSharedQualityRules(input.specContent, input.projectProfile)}
|
|
42407
42544
|
|
|
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.
|
|
42545
|
+
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
42546
|
|
|
42410
|
-
|
|
42547
|
+
${CONTEXT_VS_EXPECTED_FILES_RULE}
|
|
42411
42548
|
|
|
42412
42549
|
## Output Schema
|
|
42413
42550
|
|
|
@@ -42423,7 +42560,8 @@ Produce a JSON object with this exact structure. Field names are mandatory \u201
|
|
|
42423
42560
|
"title": "string \u2014 concise story title",
|
|
42424
42561
|
"description": "string \u2014 detailed description of what to implement",
|
|
42425
42562
|
"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)"],
|
|
42563
|
+
"contextFiles": ["string \u2014 EXISTING relative paths the implementer should read (max 5)"],
|
|
42564
|
+
${EXPECTED_FILES_SCHEMA_FIELD}
|
|
42427
42565
|
"tags": ["string"],
|
|
42428
42566
|
"dependencies": ["string \u2014 story IDs this story depends on"],${workdirField}
|
|
42429
42567
|
"routing": {
|
|
@@ -42464,6 +42602,9 @@ ${rows.join(`
|
|
|
42464
42602
|
`)}
|
|
42465
42603
|
`;
|
|
42466
42604
|
}
|
|
42605
|
+
var CONTEXT_VS_EXPECTED_FILES_RULE = `**\`contextFiles\` rule \u2014 files readable when this story runs.** List paths that already exist in the repo today, PLUS any file an UPSTREAM dependency story creates (it does not exist now but will exist by the time this story runs, because dependencies execute first). The pipeline verifies every \`contextFiles\` entry against the filesystem; a path that exists neither on disk nor in an upstream dependency's outputs is treated as a missing-context warning.
|
|
42606
|
+
|
|
42607
|
+
**\`expectedFiles\` rule \u2014 files THIS story CREATES.** List every NEW file this story authors (relative paths). A file this story creates belongs here, NEVER in \`contextFiles\` \u2014 these are the story's outputs, not files to read first. A file created by an upstream dependency and only read/modified here belongs in \`contextFiles\`, NOT here (this story does not author it). 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
42608
|
var init_plan_builder = __esm(() => {
|
|
42468
42609
|
init_config();
|
|
42469
42610
|
});
|
|
@@ -43392,7 +43533,7 @@ var init_call = __esm(() => {
|
|
|
43392
43533
|
|
|
43393
43534
|
// src/runtime/cost-aggregator.ts
|
|
43394
43535
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
43395
|
-
import { join as
|
|
43536
|
+
import { join as join26 } from "path";
|
|
43396
43537
|
function makeCorrelationId() {
|
|
43397
43538
|
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
43398
43539
|
}
|
|
@@ -43583,7 +43724,7 @@ class CostAggregator {
|
|
|
43583
43724
|
if (events.length === 0 && errors3.length === 0)
|
|
43584
43725
|
return;
|
|
43585
43726
|
mkdirSync2(this._drainDir, { recursive: true });
|
|
43586
|
-
const path5 =
|
|
43727
|
+
const path5 = join26(this._drainDir, `${this._runId}.jsonl`);
|
|
43587
43728
|
const sorted = [...events, ...errors3].sort((a, b) => a.ts - b.ts);
|
|
43588
43729
|
await _costAggDeps.write(path5, `${sorted.map((e) => JSON.stringify(e)).join(`
|
|
43589
43730
|
`)}
|
|
@@ -43623,7 +43764,7 @@ var init_cost_aggregator = __esm(() => {
|
|
|
43623
43764
|
// src/runtime/prompt-auditor.ts
|
|
43624
43765
|
import { appendFileSync } from "fs";
|
|
43625
43766
|
import { mkdir as mkdir4 } from "fs/promises";
|
|
43626
|
-
import { join as
|
|
43767
|
+
import { join as join27 } from "path";
|
|
43627
43768
|
function createNoOpPromptAuditor() {
|
|
43628
43769
|
return {
|
|
43629
43770
|
record() {},
|
|
@@ -43689,8 +43830,8 @@ class PromptAuditor {
|
|
|
43689
43830
|
_jsonlPath;
|
|
43690
43831
|
_featureDir;
|
|
43691
43832
|
constructor(runId, flushDir, featureName) {
|
|
43692
|
-
this._featureDir =
|
|
43693
|
-
this._jsonlPath =
|
|
43833
|
+
this._featureDir = join27(flushDir, featureName);
|
|
43834
|
+
this._jsonlPath = join27(this._featureDir, `${runId}.jsonl`);
|
|
43694
43835
|
}
|
|
43695
43836
|
record(entry) {
|
|
43696
43837
|
this._enqueue(entry);
|
|
@@ -43739,7 +43880,7 @@ class PromptAuditor {
|
|
|
43739
43880
|
const auditEntry = entry;
|
|
43740
43881
|
const filename = deriveTxtFilename(auditEntry);
|
|
43741
43882
|
try {
|
|
43742
|
-
await _promptAuditorDeps.write(
|
|
43883
|
+
await _promptAuditorDeps.write(join27(this._featureDir, filename), buildTxtContent(auditEntry));
|
|
43743
43884
|
} catch (err) {
|
|
43744
43885
|
throw tagAuditError(err, "txt");
|
|
43745
43886
|
}
|
|
@@ -44851,7 +44992,7 @@ var init_pid_registry = __esm(() => {
|
|
|
44851
44992
|
// src/session/manager-deps.ts
|
|
44852
44993
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
44853
44994
|
import { mkdir as mkdir5 } from "fs/promises";
|
|
44854
|
-
import { isAbsolute as isAbsolute9, join as
|
|
44995
|
+
import { isAbsolute as isAbsolute9, join as join28, relative as relative11, sep as sep2 } from "path";
|
|
44855
44996
|
function resolveProjectDirFromScratchDir(scratchDir) {
|
|
44856
44997
|
const marker = `${sep2}.nax${sep2}features${sep2}`;
|
|
44857
44998
|
const markerIdx = scratchDir.lastIndexOf(marker);
|
|
@@ -44872,7 +45013,7 @@ var init_manager_deps = __esm(() => {
|
|
|
44872
45013
|
now: () => new Date().toISOString(),
|
|
44873
45014
|
nowMs: () => Date.now(),
|
|
44874
45015
|
uuid: () => randomUUID3(),
|
|
44875
|
-
sessionScratchDir: (projectDir, featureName, sessionId) =>
|
|
45016
|
+
sessionScratchDir: (projectDir, featureName, sessionId) => join28(projectDir, ".nax", "features", featureName, "sessions", sessionId),
|
|
44876
45017
|
writeDescriptor: async (scratchDir, descriptor, projectDir) => {
|
|
44877
45018
|
await mkdir5(scratchDir, { recursive: true });
|
|
44878
45019
|
const { handle: _handle, ...persistable } = descriptor;
|
|
@@ -44883,7 +45024,7 @@ var init_manager_deps = __esm(() => {
|
|
|
44883
45024
|
persistable.scratchDir = toProjectRelativePath(derivedProjectDir, persistable.scratchDir);
|
|
44884
45025
|
}
|
|
44885
45026
|
}
|
|
44886
|
-
await Bun.write(
|
|
45027
|
+
await Bun.write(join28(scratchDir, "descriptor.json"), JSON.stringify(persistable, null, 2));
|
|
44887
45028
|
}
|
|
44888
45029
|
};
|
|
44889
45030
|
});
|
|
@@ -45634,7 +45775,7 @@ __export(exports_runtime, {
|
|
|
45634
45775
|
CostAggregator: () => CostAggregator,
|
|
45635
45776
|
AgentStreamEventBus: () => AgentStreamEventBus
|
|
45636
45777
|
});
|
|
45637
|
-
import { basename as basename5, join as
|
|
45778
|
+
import { basename as basename5, join as join29 } from "path";
|
|
45638
45779
|
function createRuntime(config2, workdir, opts) {
|
|
45639
45780
|
const runId = crypto.randomUUID();
|
|
45640
45781
|
const controller = new AbortController;
|
|
@@ -45650,10 +45791,10 @@ function createRuntime(config2, workdir, opts) {
|
|
|
45650
45791
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
45651
45792
|
const globalDir = globalOutputDir();
|
|
45652
45793
|
const curatorRollupPathValue = curatorRollupPath(globalDir, config2.curator?.rollupPath);
|
|
45653
|
-
const costDir =
|
|
45794
|
+
const costDir = join29(outputDir, "cost");
|
|
45654
45795
|
const costAggregator = opts?.costAggregator ?? new CostAggregator(runId, costDir);
|
|
45655
45796
|
const auditEnabled = config2.agent?.promptAudit?.enabled ?? false;
|
|
45656
|
-
const auditDir = config2.agent?.promptAudit?.dir ??
|
|
45797
|
+
const auditDir = config2.agent?.promptAudit?.dir ?? join29(outputDir, "prompt-audit");
|
|
45657
45798
|
let promptAuditor;
|
|
45658
45799
|
if (opts?.promptAuditor) {
|
|
45659
45800
|
promptAuditor = opts.promptAuditor;
|
|
@@ -45804,9 +45945,9 @@ async function allSettledBounded(tasks, limit) {
|
|
|
45804
45945
|
|
|
45805
45946
|
// src/context/injector.ts
|
|
45806
45947
|
import { existsSync as existsSync8 } from "fs";
|
|
45807
|
-
import { join as
|
|
45948
|
+
import { join as join30 } from "path";
|
|
45808
45949
|
async function detectNode(workdir) {
|
|
45809
|
-
const pkgPath =
|
|
45950
|
+
const pkgPath = join30(workdir, "package.json");
|
|
45810
45951
|
if (!existsSync8(pkgPath))
|
|
45811
45952
|
return null;
|
|
45812
45953
|
try {
|
|
@@ -45823,7 +45964,7 @@ async function detectNode(workdir) {
|
|
|
45823
45964
|
}
|
|
45824
45965
|
}
|
|
45825
45966
|
async function detectGo(workdir) {
|
|
45826
|
-
const goMod =
|
|
45967
|
+
const goMod = join30(workdir, "go.mod");
|
|
45827
45968
|
if (!existsSync8(goMod))
|
|
45828
45969
|
return null;
|
|
45829
45970
|
try {
|
|
@@ -45847,7 +45988,7 @@ async function detectGo(workdir) {
|
|
|
45847
45988
|
}
|
|
45848
45989
|
}
|
|
45849
45990
|
async function detectRust(workdir) {
|
|
45850
|
-
const cargoPath =
|
|
45991
|
+
const cargoPath = join30(workdir, "Cargo.toml");
|
|
45851
45992
|
if (!existsSync8(cargoPath))
|
|
45852
45993
|
return null;
|
|
45853
45994
|
try {
|
|
@@ -45863,8 +46004,8 @@ async function detectRust(workdir) {
|
|
|
45863
46004
|
}
|
|
45864
46005
|
}
|
|
45865
46006
|
async function detectPython(workdir) {
|
|
45866
|
-
const pyproject =
|
|
45867
|
-
const requirements =
|
|
46007
|
+
const pyproject = join30(workdir, "pyproject.toml");
|
|
46008
|
+
const requirements = join30(workdir, "requirements.txt");
|
|
45868
46009
|
if (!existsSync8(pyproject) && !existsSync8(requirements))
|
|
45869
46010
|
return null;
|
|
45870
46011
|
try {
|
|
@@ -45883,7 +46024,7 @@ async function detectPython(workdir) {
|
|
|
45883
46024
|
}
|
|
45884
46025
|
}
|
|
45885
46026
|
async function detectPhp(workdir) {
|
|
45886
|
-
const composerPath =
|
|
46027
|
+
const composerPath = join30(workdir, "composer.json");
|
|
45887
46028
|
if (!existsSync8(composerPath))
|
|
45888
46029
|
return null;
|
|
45889
46030
|
try {
|
|
@@ -45896,7 +46037,7 @@ async function detectPhp(workdir) {
|
|
|
45896
46037
|
}
|
|
45897
46038
|
}
|
|
45898
46039
|
async function detectRuby(workdir) {
|
|
45899
|
-
const gemfile =
|
|
46040
|
+
const gemfile = join30(workdir, "Gemfile");
|
|
45900
46041
|
if (!existsSync8(gemfile))
|
|
45901
46042
|
return null;
|
|
45902
46043
|
try {
|
|
@@ -45908,9 +46049,9 @@ async function detectRuby(workdir) {
|
|
|
45908
46049
|
}
|
|
45909
46050
|
}
|
|
45910
46051
|
async function detectJvm(workdir) {
|
|
45911
|
-
const pom =
|
|
45912
|
-
const gradle =
|
|
45913
|
-
const gradleKts =
|
|
46052
|
+
const pom = join30(workdir, "pom.xml");
|
|
46053
|
+
const gradle = join30(workdir, "build.gradle");
|
|
46054
|
+
const gradleKts = join30(workdir, "build.gradle.kts");
|
|
45914
46055
|
if (!existsSync8(pom) && !existsSync8(gradle) && !existsSync8(gradleKts))
|
|
45915
46056
|
return null;
|
|
45916
46057
|
try {
|
|
@@ -45918,7 +46059,7 @@ async function detectJvm(workdir) {
|
|
|
45918
46059
|
const content2 = await Bun.file(pom).text();
|
|
45919
46060
|
const nameMatch = content2.match(/<artifactId>([^<]+)<\/artifactId>/);
|
|
45920
46061
|
const deps2 = [...content2.matchAll(/<artifactId>([^<]+)<\/artifactId>/g)].map((m) => m[1]).filter((d) => d !== nameMatch?.[1]).slice(0, 10);
|
|
45921
|
-
const lang2 = existsSync8(
|
|
46062
|
+
const lang2 = existsSync8(join30(workdir, "src/main/kotlin")) ? "Kotlin" : "Java";
|
|
45922
46063
|
return { name: nameMatch?.[1], lang: lang2, dependencies: deps2 };
|
|
45923
46064
|
}
|
|
45924
46065
|
const gradleFile = existsSync8(gradleKts) ? gradleKts : gradle;
|
|
@@ -46172,7 +46313,7 @@ var init_windsurf = __esm(() => {
|
|
|
46172
46313
|
|
|
46173
46314
|
// src/context/generator.ts
|
|
46174
46315
|
import { existsSync as existsSync9 } from "fs";
|
|
46175
|
-
import { join as
|
|
46316
|
+
import { join as join31, relative as relative12 } from "path";
|
|
46176
46317
|
async function loadContextContent(options, config2) {
|
|
46177
46318
|
if (!_generatorDeps.existsSync(options.contextPath)) {
|
|
46178
46319
|
throw new Error(`Context file not found: ${options.contextPath}`);
|
|
@@ -46190,7 +46331,7 @@ async function generateFor(agent, options, config2) {
|
|
|
46190
46331
|
try {
|
|
46191
46332
|
const context = await loadContextContent(options, config2);
|
|
46192
46333
|
const content = generator.generate(context);
|
|
46193
|
-
const outputPath =
|
|
46334
|
+
const outputPath = join31(options.outputDir, generator.outputFile);
|
|
46194
46335
|
validateFilePath(outputPath, options.outputDir);
|
|
46195
46336
|
if (!options.dryRun) {
|
|
46196
46337
|
await _generatorDeps.writeFile(outputPath, content);
|
|
@@ -46208,7 +46349,7 @@ async function generateAll(options, config2, agentFilter) {
|
|
|
46208
46349
|
for (const [agentKey, generator] of entries) {
|
|
46209
46350
|
try {
|
|
46210
46351
|
const content = generator.generate(context);
|
|
46211
|
-
const outputPath =
|
|
46352
|
+
const outputPath = join31(options.outputDir, generator.outputFile);
|
|
46212
46353
|
validateFilePath(outputPath, options.outputDir);
|
|
46213
46354
|
if (!options.dryRun) {
|
|
46214
46355
|
await _generatorDeps.writeFile(outputPath, content);
|
|
@@ -46228,7 +46369,7 @@ async function discoverPackages(repoRoot) {
|
|
|
46228
46369
|
const glob = new Bun.Glob(pattern);
|
|
46229
46370
|
for await (const match of glob.scan({ cwd: repoRoot, dot: true })) {
|
|
46230
46371
|
const pkgRelative = match.replace(/^\.nax\/mono\//, "").replace(/\/context\.md$/, "");
|
|
46231
|
-
const pkgAbsolute =
|
|
46372
|
+
const pkgAbsolute = join31(repoRoot, pkgRelative);
|
|
46232
46373
|
if (!seen.has(pkgAbsolute)) {
|
|
46233
46374
|
seen.add(pkgAbsolute);
|
|
46234
46375
|
packages.push(pkgAbsolute);
|
|
@@ -46260,14 +46401,14 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
46260
46401
|
}
|
|
46261
46402
|
}
|
|
46262
46403
|
}
|
|
46263
|
-
const turboPath =
|
|
46404
|
+
const turboPath = join31(repoRoot, "turbo.json");
|
|
46264
46405
|
try {
|
|
46265
46406
|
const turbo = JSON.parse(await _generatorDeps.readTextFile(turboPath));
|
|
46266
46407
|
if (Array.isArray(turbo.packages)) {
|
|
46267
46408
|
await resolveGlobs(turbo.packages);
|
|
46268
46409
|
}
|
|
46269
46410
|
} catch {}
|
|
46270
|
-
const pkgPath =
|
|
46411
|
+
const pkgPath = join31(repoRoot, "package.json");
|
|
46271
46412
|
try {
|
|
46272
46413
|
const pkg = JSON.parse(await _generatorDeps.readTextFile(pkgPath));
|
|
46273
46414
|
const ws = pkg.workspaces;
|
|
@@ -46275,7 +46416,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
46275
46416
|
if (patterns.length > 0)
|
|
46276
46417
|
await resolveGlobs(patterns);
|
|
46277
46418
|
} catch {}
|
|
46278
|
-
const pnpmPath =
|
|
46419
|
+
const pnpmPath = join31(repoRoot, "pnpm-workspace.yaml");
|
|
46279
46420
|
try {
|
|
46280
46421
|
const raw = await _generatorDeps.readTextFile(pnpmPath);
|
|
46281
46422
|
const lines = raw.split(`
|
|
@@ -46301,7 +46442,7 @@ async function discoverWorkspacePackages2(repoRoot) {
|
|
|
46301
46442
|
async function generateForPackage(packageDir, config2, dryRun = false, repoRoot) {
|
|
46302
46443
|
const resolvedRepoRoot = repoRoot ?? packageDir;
|
|
46303
46444
|
const relativePkgPath = relative12(resolvedRepoRoot, packageDir);
|
|
46304
|
-
const contextPath =
|
|
46445
|
+
const contextPath = join31(resolvedRepoRoot, ".nax", "mono", relativePkgPath, "context.md");
|
|
46305
46446
|
if (!_generatorDeps.existsSync(contextPath)) {
|
|
46306
46447
|
return [
|
|
46307
46448
|
{
|
|
@@ -46369,7 +46510,7 @@ var init_generator2 = __esm(() => {
|
|
|
46369
46510
|
});
|
|
46370
46511
|
|
|
46371
46512
|
// src/analyze/scanner.ts
|
|
46372
|
-
import { join as
|
|
46513
|
+
import { join as join32 } from "path";
|
|
46373
46514
|
function resolveFrameworkAndRunner(language, pkg) {
|
|
46374
46515
|
if (language === "go")
|
|
46375
46516
|
return { framework: "", testRunner: "go-test" };
|
|
@@ -46391,7 +46532,7 @@ async function scanSourceRoots(workdir) {
|
|
|
46391
46532
|
});
|
|
46392
46533
|
try {
|
|
46393
46534
|
const language = await deps.detectLanguage(workdir);
|
|
46394
|
-
const pkg = await deps.readPackageJson(
|
|
46535
|
+
const pkg = await deps.readPackageJson(join32(workdir, "package.json"));
|
|
46395
46536
|
const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
|
|
46396
46537
|
return [{ path: ".", language, framework, testRunner }];
|
|
46397
46538
|
} catch {
|
|
@@ -46409,9 +46550,9 @@ async function scanSourceRoots(workdir) {
|
|
|
46409
46550
|
packages = packages.slice(0, MAX_SOURCE_ROOTS);
|
|
46410
46551
|
}
|
|
46411
46552
|
return Promise.all(packages.map(async (pkgPath) => {
|
|
46412
|
-
const pkgDir = pkgPath === "." ? workdir :
|
|
46553
|
+
const pkgDir = pkgPath === "." ? workdir : join32(workdir, pkgPath);
|
|
46413
46554
|
const language = await deps.detectLanguage(pkgDir);
|
|
46414
|
-
const pkg = await deps.readPackageJson(
|
|
46555
|
+
const pkg = await deps.readPackageJson(join32(pkgDir, "package.json"));
|
|
46415
46556
|
const { framework, testRunner } = resolveFrameworkAndRunner(language, pkg);
|
|
46416
46557
|
return { path: pkgPath, language, framework, testRunner };
|
|
46417
46558
|
}));
|
|
@@ -46444,7 +46585,7 @@ var init_analyze = __esm(() => {
|
|
|
46444
46585
|
});
|
|
46445
46586
|
|
|
46446
46587
|
// src/debate/pre-phase/grounder.ts
|
|
46447
|
-
import { join as
|
|
46588
|
+
import { join as join33 } from "path";
|
|
46448
46589
|
async function buildCodebaseContext(workdir) {
|
|
46449
46590
|
const roots = await _grounderDeps.scanSourceRoots(workdir);
|
|
46450
46591
|
return buildSourceRootsSection(normalizeRoots(workdir, roots));
|
|
@@ -46456,7 +46597,7 @@ function normalizeRoots(workdir, roots) {
|
|
|
46456
46597
|
}));
|
|
46457
46598
|
}
|
|
46458
46599
|
async function writeManifestArtifact(ctx, manifest) {
|
|
46459
|
-
const manifestPath =
|
|
46600
|
+
const manifestPath = join33(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
|
|
46460
46601
|
await _grounderDeps.write(manifestPath, JSON.stringify(manifest, null, 2));
|
|
46461
46602
|
}
|
|
46462
46603
|
var _grounderDeps, grounderStrategy = async (ctx) => {
|
|
@@ -46799,7 +46940,7 @@ function formatSpecDeltas(blockers, manifest) {
|
|
|
46799
46940
|
|
|
46800
46941
|
// src/debate/verifiers/checks.ts
|
|
46801
46942
|
import { existsSync as defaultExistsSync } from "fs";
|
|
46802
|
-
import { join as
|
|
46943
|
+
import { join as join34 } from "path";
|
|
46803
46944
|
function checkFilesExist(prd, workdir, deps) {
|
|
46804
46945
|
const existsSync10 = deps?.existsSync ?? defaultExistsSync;
|
|
46805
46946
|
const findings = [];
|
|
@@ -46809,7 +46950,7 @@ function checkFilesExist(prd, workdir, deps) {
|
|
|
46809
46950
|
for (const entry of story.contextFiles) {
|
|
46810
46951
|
const filePath = typeof entry === "string" ? entry : entry.path;
|
|
46811
46952
|
const factId = typeof entry === "string" ? undefined : entry.factId;
|
|
46812
|
-
const absPath =
|
|
46953
|
+
const absPath = join34(workdir, filePath);
|
|
46813
46954
|
if (existsSync10(absPath))
|
|
46814
46955
|
continue;
|
|
46815
46956
|
if (factId) {
|
|
@@ -46920,7 +47061,7 @@ var init_checks3 = () => {};
|
|
|
46920
47061
|
|
|
46921
47062
|
// src/debate/verifiers/plan-checklist.ts
|
|
46922
47063
|
import { existsSync as existsSync10 } from "fs";
|
|
46923
|
-
import { join as
|
|
47064
|
+
import { join as join35 } from "path";
|
|
46924
47065
|
function parsePrd(output) {
|
|
46925
47066
|
if (!output)
|
|
46926
47067
|
return null;
|
|
@@ -46931,7 +47072,7 @@ function parsePrd(output) {
|
|
|
46931
47072
|
}
|
|
46932
47073
|
}
|
|
46933
47074
|
async function loadManifest(ctx) {
|
|
46934
|
-
const manifestPath =
|
|
47075
|
+
const manifestPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "facts-manifest.json");
|
|
46935
47076
|
const raw = await _planChecklistDeps.readFile(manifestPath);
|
|
46936
47077
|
if (!raw)
|
|
46937
47078
|
return null;
|
|
@@ -46944,7 +47085,7 @@ async function loadManifest(ctx) {
|
|
|
46944
47085
|
}
|
|
46945
47086
|
}
|
|
46946
47087
|
async function emitSpecDeltas(ctx, blockers, manifest) {
|
|
46947
|
-
const artifactPath =
|
|
47088
|
+
const artifactPath = join35(ctx.workdir, ".nax", "runs", ctx.ctx.runtime.runId, "plan", ctx.storyId, "spec-deltas.md");
|
|
46948
47089
|
const content = formatSpecDeltas(blockers, manifest ?? { repoFacts: [], specClaims: [], gaps: [] });
|
|
46949
47090
|
await _planChecklistDeps.write(artifactPath, content);
|
|
46950
47091
|
return artifactPath;
|
|
@@ -47290,7 +47431,7 @@ var init_runner_plan_helpers = __esm(() => {
|
|
|
47290
47431
|
});
|
|
47291
47432
|
|
|
47292
47433
|
// src/debate/runner-plan.ts
|
|
47293
|
-
import { join as
|
|
47434
|
+
import { join as join36 } from "path";
|
|
47294
47435
|
async function runPlan(ctx, taskContext, outputFormat, opts) {
|
|
47295
47436
|
const logger = _debateSessionDeps.getSafeLogger();
|
|
47296
47437
|
const config2 = ctx.stageConfig;
|
|
@@ -47349,7 +47490,7 @@ async function runPlan(ctx, taskContext, outputFormat, opts) {
|
|
|
47349
47490
|
sessionMode: ctx.stageConfig.sessionMode ?? "one-shot",
|
|
47350
47491
|
proposers: ctx.stageConfig.proposers
|
|
47351
47492
|
});
|
|
47352
|
-
const outputPaths = resolved.map((_, i) =>
|
|
47493
|
+
const outputPaths = resolved.map((_, i) => join36(opts.outputDir, `prd-debate-${i}.json`));
|
|
47353
47494
|
const successful = [];
|
|
47354
47495
|
let rebuttalList;
|
|
47355
47496
|
if (selectorKind === "verifier-pick") {
|
|
@@ -49592,9 +49733,9 @@ function validateFeatureName(feature) {
|
|
|
49592
49733
|
|
|
49593
49734
|
// src/plan/critic.ts
|
|
49594
49735
|
import { mkdir as mkdir6 } from "fs/promises";
|
|
49595
|
-
import { dirname as dirname7, join as
|
|
49736
|
+
import { dirname as dirname7, join as join39 } from "path";
|
|
49596
49737
|
async function writeSpecDeltas(findings, workdir, runId, storyId, manifest) {
|
|
49597
|
-
const path7 =
|
|
49738
|
+
const path7 = join39(workdir, ".nax", "runs", runId, "plan", storyId, "spec-deltas.md");
|
|
49598
49739
|
await mkdir6(dirname7(path7), { recursive: true });
|
|
49599
49740
|
await Bun.write(path7, formatSpecDeltas(findings, manifest));
|
|
49600
49741
|
return path7;
|
|
@@ -50807,9 +50948,9 @@ __export(exports_plan_decompose, {
|
|
|
50807
50948
|
runReplanLoop: () => runReplanLoop,
|
|
50808
50949
|
planDecomposeCommand: () => planDecomposeCommand
|
|
50809
50950
|
});
|
|
50810
|
-
import { join as
|
|
50951
|
+
import { join as join40 } from "path";
|
|
50811
50952
|
async function planDecomposeCommand(workdir, config2, options) {
|
|
50812
|
-
const prdPath =
|
|
50953
|
+
const prdPath = join40(workdir, ".nax", "features", options.feature, "prd.json");
|
|
50813
50954
|
if (!_planDeps.existsSync(prdPath)) {
|
|
50814
50955
|
throw new NaxError(`PRD not found: ${prdPath}`, "PRD_NOT_FOUND", {
|
|
50815
50956
|
stage: "decompose",
|
|
@@ -50983,7 +51124,7 @@ var init_plan_decompose = __esm(() => {
|
|
|
50983
51124
|
|
|
50984
51125
|
// src/cli/plan-runtime.ts
|
|
50985
51126
|
import { existsSync as existsSync15 } from "fs";
|
|
50986
|
-
import { join as
|
|
51127
|
+
import { join as join41 } from "path";
|
|
50987
51128
|
function isRuntimeWithAgentManager(value) {
|
|
50988
51129
|
return typeof value === "object" && value !== null && "agentManager" in value;
|
|
50989
51130
|
}
|
|
@@ -51035,7 +51176,7 @@ var init_plan_runtime = __esm(() => {
|
|
|
51035
51176
|
writeFile: (path7, content) => Bun.write(path7, content).then(() => {}),
|
|
51036
51177
|
scanSourceRoots: (workdir) => scanSourceRoots(workdir),
|
|
51037
51178
|
createRuntime: (cfg, wd, featureName) => createRuntime(cfg, wd, { featureName }),
|
|
51038
|
-
readPackageJson: (workdir) => Bun.file(
|
|
51179
|
+
readPackageJson: (workdir) => Bun.file(join41(workdir, "package.json")).json().catch(() => null),
|
|
51039
51180
|
spawnSync: (cmd, opts) => {
|
|
51040
51181
|
const result = Bun.spawnSync(cmd, opts ? { cwd: opts.cwd } : {});
|
|
51041
51182
|
return { stdout: result.stdout, exitCode: result.exitCode };
|
|
@@ -51420,7 +51561,7 @@ var init_metrics = __esm(() => {
|
|
|
51420
51561
|
|
|
51421
51562
|
// src/commands/common.ts
|
|
51422
51563
|
import { existsSync as existsSync16, readdirSync as readdirSync2, realpathSync as realpathSync3 } from "fs";
|
|
51423
|
-
import { join as
|
|
51564
|
+
import { join as join42, resolve as resolve13 } from "path";
|
|
51424
51565
|
function resolveProject(options = {}) {
|
|
51425
51566
|
const { dir, feature } = options;
|
|
51426
51567
|
let projectRoot;
|
|
@@ -51428,12 +51569,12 @@ function resolveProject(options = {}) {
|
|
|
51428
51569
|
let configPath;
|
|
51429
51570
|
if (dir) {
|
|
51430
51571
|
projectRoot = realpathSync3(resolve13(dir));
|
|
51431
|
-
naxDir =
|
|
51572
|
+
naxDir = join42(projectRoot, ".nax");
|
|
51432
51573
|
if (!existsSync16(naxDir)) {
|
|
51433
51574
|
throw new NaxError(`Directory does not contain a nax project: ${projectRoot}
|
|
51434
51575
|
Expected to find: ${naxDir}`, "NAX_DIR_NOT_FOUND", { projectRoot, naxDir });
|
|
51435
51576
|
}
|
|
51436
|
-
configPath =
|
|
51577
|
+
configPath = join42(naxDir, "config.json");
|
|
51437
51578
|
if (!existsSync16(configPath)) {
|
|
51438
51579
|
throw new NaxError(`.nax directory found but config.json is missing: ${naxDir}
|
|
51439
51580
|
Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
@@ -51441,17 +51582,17 @@ Expected to find: ${configPath}`, "CONFIG_NOT_FOUND", { naxDir, configPath });
|
|
|
51441
51582
|
} else {
|
|
51442
51583
|
const found = findProjectRoot(process.cwd());
|
|
51443
51584
|
if (!found) {
|
|
51444
|
-
const cwdNaxDir =
|
|
51585
|
+
const cwdNaxDir = join42(process.cwd(), ".nax");
|
|
51445
51586
|
if (existsSync16(cwdNaxDir)) {
|
|
51446
|
-
const cwdConfigPath =
|
|
51587
|
+
const cwdConfigPath = join42(cwdNaxDir, "config.json");
|
|
51447
51588
|
throw new NaxError(`.nax directory found but config.json is missing: ${cwdNaxDir}
|
|
51448
51589
|
Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, configPath: cwdConfigPath });
|
|
51449
51590
|
}
|
|
51450
51591
|
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
51592
|
}
|
|
51452
51593
|
projectRoot = found;
|
|
51453
|
-
naxDir =
|
|
51454
|
-
configPath =
|
|
51594
|
+
naxDir = join42(projectRoot, ".nax");
|
|
51595
|
+
configPath = join42(naxDir, "config.json");
|
|
51455
51596
|
}
|
|
51456
51597
|
let featureDir;
|
|
51457
51598
|
if (feature) {
|
|
@@ -51460,8 +51601,8 @@ Expected to find: ${cwdConfigPath}`, "CONFIG_NOT_FOUND", { naxDir: cwdNaxDir, co
|
|
|
51460
51601
|
} catch (error48) {
|
|
51461
51602
|
throw new NaxError(error48.message, "FEATURE_INVALID", { feature });
|
|
51462
51603
|
}
|
|
51463
|
-
const featuresDir =
|
|
51464
|
-
featureDir =
|
|
51604
|
+
const featuresDir = join42(naxDir, "features");
|
|
51605
|
+
featureDir = join42(featuresDir, feature);
|
|
51465
51606
|
if (!existsSync16(featureDir)) {
|
|
51466
51607
|
const availableFeatures = existsSync16(featuresDir) ? readdirSync2(featuresDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name) : [];
|
|
51467
51608
|
const availableMsg = availableFeatures.length > 0 ? `
|
|
@@ -51494,7 +51635,7 @@ async function resolveProjectAsync(options = {}) {
|
|
|
51494
51635
|
}
|
|
51495
51636
|
const isPlainName = !dir.includes("/") && !dir.includes("\\");
|
|
51496
51637
|
if (isPlainName) {
|
|
51497
|
-
const registryIdentityPath =
|
|
51638
|
+
const registryIdentityPath = join42(globalConfigDir(), dir, ".identity");
|
|
51498
51639
|
const identityFile = Bun.file(registryIdentityPath);
|
|
51499
51640
|
if (await identityFile.exists()) {
|
|
51500
51641
|
try {
|
|
@@ -51526,12 +51667,12 @@ function findProjectRoot(startDir) {
|
|
|
51526
51667
|
let current = resolve13(startDir);
|
|
51527
51668
|
let depth = 0;
|
|
51528
51669
|
while (depth < MAX_DIRECTORY_DEPTH) {
|
|
51529
|
-
const naxDir =
|
|
51530
|
-
const configPath =
|
|
51670
|
+
const naxDir = join42(current, ".nax");
|
|
51671
|
+
const configPath = join42(naxDir, "config.json");
|
|
51531
51672
|
if (existsSync16(configPath)) {
|
|
51532
51673
|
return realpathSync3(current);
|
|
51533
51674
|
}
|
|
51534
|
-
const parent =
|
|
51675
|
+
const parent = join42(current, "..");
|
|
51535
51676
|
if (parent === current) {
|
|
51536
51677
|
break;
|
|
51537
51678
|
}
|
|
@@ -52690,10 +52831,10 @@ var init_effectiveness = __esm(() => {
|
|
|
52690
52831
|
|
|
52691
52832
|
// src/execution/progress.ts
|
|
52692
52833
|
import { appendFile as appendFile2, mkdir as mkdir7 } from "fs/promises";
|
|
52693
|
-
import { join as
|
|
52834
|
+
import { join as join45 } from "path";
|
|
52694
52835
|
async function appendProgress(featureDir, storyId, status, message) {
|
|
52695
52836
|
await mkdir7(featureDir, { recursive: true });
|
|
52696
|
-
const progressPath =
|
|
52837
|
+
const progressPath = join45(featureDir, "progress.txt");
|
|
52697
52838
|
const timestamp = new Date().toISOString();
|
|
52698
52839
|
const entry = `[${timestamp}] ${storyId} \u2014 ${status.toUpperCase()} \u2014 ${message}
|
|
52699
52840
|
`;
|
|
@@ -52887,7 +53028,7 @@ var init_completion = __esm(() => {
|
|
|
52887
53028
|
|
|
52888
53029
|
// src/constitution/loader.ts
|
|
52889
53030
|
import { existsSync as existsSync19 } from "fs";
|
|
52890
|
-
import { join as
|
|
53031
|
+
import { join as join46 } from "path";
|
|
52891
53032
|
function truncateToTokens(text, maxTokens) {
|
|
52892
53033
|
const maxChars = maxTokens * 3;
|
|
52893
53034
|
if (text.length <= maxChars) {
|
|
@@ -52909,7 +53050,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
52909
53050
|
}
|
|
52910
53051
|
let combinedContent = "";
|
|
52911
53052
|
if (!config2.skipGlobal) {
|
|
52912
|
-
const globalPath =
|
|
53053
|
+
const globalPath = join46(globalConfigDir(), config2.path);
|
|
52913
53054
|
if (existsSync19(globalPath)) {
|
|
52914
53055
|
const validatedPath = validateFilePath(globalPath, globalConfigDir());
|
|
52915
53056
|
const globalFile = Bun.file(validatedPath);
|
|
@@ -52919,7 +53060,7 @@ async function loadConstitution(projectDir, config2) {
|
|
|
52919
53060
|
}
|
|
52920
53061
|
}
|
|
52921
53062
|
}
|
|
52922
|
-
const projectPath =
|
|
53063
|
+
const projectPath = join46(projectDir, config2.path);
|
|
52923
53064
|
if (existsSync19(projectPath)) {
|
|
52924
53065
|
const validatedPath = validateFilePath(projectPath, projectDir);
|
|
52925
53066
|
const projectFile = Bun.file(validatedPath);
|
|
@@ -53762,6 +53903,9 @@ async function runPhase(ctx, slot, phaseCosts, phaseOutputs, isThreeSession = fa
|
|
|
53762
53903
|
});
|
|
53763
53904
|
}
|
|
53764
53905
|
logUnifiedReviewPhaseStart(ctx.storyId, opName);
|
|
53906
|
+
if (ctx.storyId) {
|
|
53907
|
+
pipelineEventBus.emit({ type: "story:step", storyId: ctx.storyId, step: opName });
|
|
53908
|
+
}
|
|
53765
53909
|
const phaseStartedAt = Date.now();
|
|
53766
53910
|
const scope = ctx.runtime.costAggregator.openScope();
|
|
53767
53911
|
try {
|
|
@@ -54116,6 +54260,7 @@ var init_story_orchestrator = __esm(() => {
|
|
|
54116
54260
|
init_logger2();
|
|
54117
54261
|
init_operations();
|
|
54118
54262
|
init_call();
|
|
54263
|
+
init_event_bus();
|
|
54119
54264
|
init_prepare_inputs();
|
|
54120
54265
|
init_git();
|
|
54121
54266
|
_storyOrchestratorDeps = {
|
|
@@ -54182,7 +54327,7 @@ var init_story_orchestrator = __esm(() => {
|
|
|
54182
54327
|
});
|
|
54183
54328
|
|
|
54184
54329
|
// src/execution/build-plan-for-strategy.ts
|
|
54185
|
-
import { join as
|
|
54330
|
+
import { join as join47 } from "path";
|
|
54186
54331
|
function requiresInitialRefCapture(strategy) {
|
|
54187
54332
|
return isThreeSessionStrategy(strategy);
|
|
54188
54333
|
}
|
|
@@ -54228,7 +54373,7 @@ async function buildPlanForStrategy(ctx, story, config2, testStrategy, inputs) {
|
|
|
54228
54373
|
}
|
|
54229
54374
|
if (shouldRunRectification(config2) && inputs.rectification) {
|
|
54230
54375
|
const sink = makeDeclarationSink();
|
|
54231
|
-
const packageDir =
|
|
54376
|
+
const packageDir = join47(ctx.packageDir, story.workdir ?? "");
|
|
54232
54377
|
const resolvedTestPatterns = await resolveTestFilePatterns(config2, ctx.packageDir, story.workdir);
|
|
54233
54378
|
const strategies = [];
|
|
54234
54379
|
if (config2.quality.commands.lintFix || config2.quality.commands.lintFixScoped) {
|
|
@@ -55780,7 +55925,7 @@ function buildFrontmatter(story, ctx, role) {
|
|
|
55780
55925
|
}
|
|
55781
55926
|
|
|
55782
55927
|
// src/cli/prompts-tdd.ts
|
|
55783
|
-
import { join as
|
|
55928
|
+
import { join as join48 } from "path";
|
|
55784
55929
|
async function handleThreeSessionTddPrompts(story, ctx, outputDir, logger) {
|
|
55785
55930
|
const [testWriterPrompt, implementerPrompt, verifierPrompt] = await Promise.all([
|
|
55786
55931
|
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 +55944,7 @@ ${frontmatter}---
|
|
|
55799
55944
|
|
|
55800
55945
|
${session.prompt}`;
|
|
55801
55946
|
if (outputDir) {
|
|
55802
|
-
const promptFile =
|
|
55947
|
+
const promptFile = join48(outputDir, `${story.id}.${session.role}.md`);
|
|
55803
55948
|
await Bun.write(promptFile, fullOutput);
|
|
55804
55949
|
logger.info("cli", "Written TDD prompt file", {
|
|
55805
55950
|
storyId: story.id,
|
|
@@ -55815,7 +55960,7 @@ ${"=".repeat(80)}`);
|
|
|
55815
55960
|
}
|
|
55816
55961
|
}
|
|
55817
55962
|
if (outputDir && ctx.contextMarkdown) {
|
|
55818
|
-
const contextFile =
|
|
55963
|
+
const contextFile = join48(outputDir, `${story.id}.context.md`);
|
|
55819
55964
|
const frontmatter = buildFrontmatter(story, ctx);
|
|
55820
55965
|
const contextOutput = `---
|
|
55821
55966
|
${frontmatter}---
|
|
@@ -55830,16 +55975,16 @@ var init_prompts_tdd = __esm(() => {
|
|
|
55830
55975
|
|
|
55831
55976
|
// src/cli/prompts-main.ts
|
|
55832
55977
|
import { existsSync as existsSync20, mkdirSync as mkdirSync3 } from "fs";
|
|
55833
|
-
import { join as
|
|
55978
|
+
import { join as join49 } from "path";
|
|
55834
55979
|
async function promptsCommand(options) {
|
|
55835
55980
|
const logger = getLogger();
|
|
55836
55981
|
const { feature, workdir, config: config2, storyId, outputDir } = options;
|
|
55837
|
-
const naxDir =
|
|
55982
|
+
const naxDir = join49(workdir, ".nax");
|
|
55838
55983
|
if (!existsSync20(naxDir)) {
|
|
55839
55984
|
throw new Error(`.nax directory not found. Run 'nax init' first in ${workdir}`);
|
|
55840
55985
|
}
|
|
55841
|
-
const featureDir =
|
|
55842
|
-
const prdPath =
|
|
55986
|
+
const featureDir = join49(naxDir, "features", feature);
|
|
55987
|
+
const prdPath = join49(featureDir, "prd.json");
|
|
55843
55988
|
if (!existsSync20(prdPath)) {
|
|
55844
55989
|
throw new Error(`Feature "${feature}" not found or missing prd.json`);
|
|
55845
55990
|
}
|
|
@@ -55906,10 +56051,10 @@ ${frontmatter}---
|
|
|
55906
56051
|
|
|
55907
56052
|
${ctx.prompt}`;
|
|
55908
56053
|
if (outputDir) {
|
|
55909
|
-
const promptFile =
|
|
56054
|
+
const promptFile = join49(outputDir, `${story.id}.prompt.md`);
|
|
55910
56055
|
await Bun.write(promptFile, fullOutput);
|
|
55911
56056
|
if (ctx.contextMarkdown) {
|
|
55912
|
-
const contextFile =
|
|
56057
|
+
const contextFile = join49(outputDir, `${story.id}.context.md`);
|
|
55913
56058
|
const contextOutput = `---
|
|
55914
56059
|
${frontmatter}---
|
|
55915
56060
|
|
|
@@ -55945,12 +56090,12 @@ var init_prompts_main = __esm(() => {
|
|
|
55945
56090
|
|
|
55946
56091
|
// src/cli/prompts-init.ts
|
|
55947
56092
|
import { existsSync as existsSync21, mkdirSync as mkdirSync4 } from "fs";
|
|
55948
|
-
import { join as
|
|
56093
|
+
import { join as join50 } from "path";
|
|
55949
56094
|
async function promptsInitCommand(options) {
|
|
55950
56095
|
const { workdir, force = false, autoWireConfig = true } = options;
|
|
55951
|
-
const templatesDir =
|
|
56096
|
+
const templatesDir = join50(workdir, ".nax", "templates");
|
|
55952
56097
|
mkdirSync4(templatesDir, { recursive: true });
|
|
55953
|
-
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(
|
|
56098
|
+
const existingFiles = TEMPLATE_ROLES.map((t) => t.file).filter((f) => existsSync21(join50(templatesDir, f)));
|
|
55954
56099
|
if (existingFiles.length > 0 && !force) {
|
|
55955
56100
|
_promptsInitDeps.warn(`[WARN] nax/templates/ already contains files: ${existingFiles.join(", ")}. No files overwritten.
|
|
55956
56101
|
Pass --force to overwrite existing templates.`);
|
|
@@ -55958,7 +56103,7 @@ async function promptsInitCommand(options) {
|
|
|
55958
56103
|
}
|
|
55959
56104
|
const written = [];
|
|
55960
56105
|
for (const template of TEMPLATE_ROLES) {
|
|
55961
|
-
const filePath =
|
|
56106
|
+
const filePath = join50(templatesDir, template.file);
|
|
55962
56107
|
const roleBody = template.role === "implementer" ? buildRoleTaskSection(template.role, template.variant) : buildRoleTaskSection(template.role);
|
|
55963
56108
|
const content = TEMPLATE_HEADER + roleBody;
|
|
55964
56109
|
await Bun.write(filePath, content);
|
|
@@ -55974,7 +56119,7 @@ async function promptsInitCommand(options) {
|
|
|
55974
56119
|
return written;
|
|
55975
56120
|
}
|
|
55976
56121
|
async function autoWirePromptsConfig(workdir) {
|
|
55977
|
-
const configPath =
|
|
56122
|
+
const configPath = join50(workdir, "nax.config.json");
|
|
55978
56123
|
if (!existsSync21(configPath)) {
|
|
55979
56124
|
const exampleConfig = JSON.stringify({
|
|
55980
56125
|
prompts: {
|
|
@@ -56144,7 +56289,7 @@ __export(exports_init_context, {
|
|
|
56144
56289
|
});
|
|
56145
56290
|
import { existsSync as existsSync22 } from "fs";
|
|
56146
56291
|
import { mkdir as mkdir8 } from "fs/promises";
|
|
56147
|
-
import { basename as basename9, join as
|
|
56292
|
+
import { basename as basename9, join as join51 } from "path";
|
|
56148
56293
|
async function findFiles(dir, maxFiles = 200) {
|
|
56149
56294
|
try {
|
|
56150
56295
|
const proc = Bun.spawnSync([
|
|
@@ -56172,7 +56317,7 @@ async function findFiles(dir, maxFiles = 200) {
|
|
|
56172
56317
|
return [];
|
|
56173
56318
|
}
|
|
56174
56319
|
async function readPackageManifest(projectRoot) {
|
|
56175
|
-
const packageJsonPath =
|
|
56320
|
+
const packageJsonPath = join51(projectRoot, "package.json");
|
|
56176
56321
|
if (!existsSync22(packageJsonPath)) {
|
|
56177
56322
|
return null;
|
|
56178
56323
|
}
|
|
@@ -56190,7 +56335,7 @@ async function readPackageManifest(projectRoot) {
|
|
|
56190
56335
|
}
|
|
56191
56336
|
}
|
|
56192
56337
|
async function readReadmeSnippet(projectRoot) {
|
|
56193
|
-
const readmePath =
|
|
56338
|
+
const readmePath = join51(projectRoot, "README.md");
|
|
56194
56339
|
if (!existsSync22(readmePath)) {
|
|
56195
56340
|
return null;
|
|
56196
56341
|
}
|
|
@@ -56208,7 +56353,7 @@ async function detectEntryPoints(projectRoot) {
|
|
|
56208
56353
|
const candidates = ["src/index.ts", "src/main.ts", "main.go", "src/lib.rs"];
|
|
56209
56354
|
const found = [];
|
|
56210
56355
|
for (const candidate of candidates) {
|
|
56211
|
-
const path13 =
|
|
56356
|
+
const path13 = join51(projectRoot, candidate);
|
|
56212
56357
|
if (existsSync22(path13)) {
|
|
56213
56358
|
found.push(candidate);
|
|
56214
56359
|
}
|
|
@@ -56219,7 +56364,7 @@ async function detectConfigFiles(projectRoot) {
|
|
|
56219
56364
|
const candidates = ["tsconfig.json", "biome.json", "turbo.json", ".env.example"];
|
|
56220
56365
|
const found = [];
|
|
56221
56366
|
for (const candidate of candidates) {
|
|
56222
|
-
const path13 =
|
|
56367
|
+
const path13 = join51(projectRoot, candidate);
|
|
56223
56368
|
if (existsSync22(path13)) {
|
|
56224
56369
|
found.push(candidate);
|
|
56225
56370
|
}
|
|
@@ -56380,8 +56525,8 @@ function generatePackageContextTemplate(packagePath) {
|
|
|
56380
56525
|
}
|
|
56381
56526
|
async function initPackage(repoRoot, packagePath, force = false) {
|
|
56382
56527
|
const logger = getLogger();
|
|
56383
|
-
const naxDir =
|
|
56384
|
-
const contextPath =
|
|
56528
|
+
const naxDir = join51(repoRoot, ".nax", "mono", packagePath);
|
|
56529
|
+
const contextPath = join51(naxDir, "context.md");
|
|
56385
56530
|
if (existsSync22(contextPath) && !force) {
|
|
56386
56531
|
logger.info("init", "Package context.md already exists (use --force to overwrite)", { path: contextPath });
|
|
56387
56532
|
return;
|
|
@@ -56395,8 +56540,8 @@ async function initPackage(repoRoot, packagePath, force = false) {
|
|
|
56395
56540
|
}
|
|
56396
56541
|
async function initContext(projectRoot, options = {}) {
|
|
56397
56542
|
const logger = getLogger();
|
|
56398
|
-
const naxDir =
|
|
56399
|
-
const contextPath =
|
|
56543
|
+
const naxDir = join51(projectRoot, ".nax");
|
|
56544
|
+
const contextPath = join51(naxDir, "context.md");
|
|
56400
56545
|
if (existsSync22(contextPath) && !options.force) {
|
|
56401
56546
|
logger.info("init", "context.md already exists, skipping (use --force to overwrite)", { path: contextPath });
|
|
56402
56547
|
return;
|
|
@@ -56426,9 +56571,9 @@ var init_init_context = __esm(() => {
|
|
|
56426
56571
|
|
|
56427
56572
|
// src/cli/init-detect.ts
|
|
56428
56573
|
import { existsSync as existsSync23, readFileSync } from "fs";
|
|
56429
|
-
import { join as
|
|
56574
|
+
import { join as join52 } from "path";
|
|
56430
56575
|
function readPackageJson(projectRoot) {
|
|
56431
|
-
const pkgPath =
|
|
56576
|
+
const pkgPath = join52(projectRoot, "package.json");
|
|
56432
56577
|
if (!existsSync23(pkgPath))
|
|
56433
56578
|
return;
|
|
56434
56579
|
try {
|
|
@@ -56471,41 +56616,41 @@ function detectStack(projectRoot) {
|
|
|
56471
56616
|
};
|
|
56472
56617
|
}
|
|
56473
56618
|
function detectRuntime(projectRoot) {
|
|
56474
|
-
if (existsSync23(
|
|
56619
|
+
if (existsSync23(join52(projectRoot, "bun.lockb")) || existsSync23(join52(projectRoot, "bunfig.toml"))) {
|
|
56475
56620
|
return "bun";
|
|
56476
56621
|
}
|
|
56477
|
-
if (existsSync23(
|
|
56622
|
+
if (existsSync23(join52(projectRoot, "package-lock.json")) || existsSync23(join52(projectRoot, "yarn.lock")) || existsSync23(join52(projectRoot, "pnpm-lock.yaml"))) {
|
|
56478
56623
|
return "node";
|
|
56479
56624
|
}
|
|
56480
56625
|
return "unknown";
|
|
56481
56626
|
}
|
|
56482
56627
|
function detectLanguage2(projectRoot) {
|
|
56483
|
-
if (existsSync23(
|
|
56628
|
+
if (existsSync23(join52(projectRoot, "tsconfig.json")))
|
|
56484
56629
|
return "typescript";
|
|
56485
|
-
if (existsSync23(
|
|
56630
|
+
if (existsSync23(join52(projectRoot, "pyproject.toml")) || existsSync23(join52(projectRoot, "setup.py"))) {
|
|
56486
56631
|
return "python";
|
|
56487
56632
|
}
|
|
56488
|
-
if (existsSync23(
|
|
56633
|
+
if (existsSync23(join52(projectRoot, "Cargo.toml")))
|
|
56489
56634
|
return "rust";
|
|
56490
|
-
if (existsSync23(
|
|
56635
|
+
if (existsSync23(join52(projectRoot, "go.mod")))
|
|
56491
56636
|
return "go";
|
|
56492
56637
|
return "unknown";
|
|
56493
56638
|
}
|
|
56494
56639
|
function detectLinter(projectRoot) {
|
|
56495
|
-
if (existsSync23(
|
|
56640
|
+
if (existsSync23(join52(projectRoot, "biome.json")) || existsSync23(join52(projectRoot, "biome.jsonc"))) {
|
|
56496
56641
|
return "biome";
|
|
56497
56642
|
}
|
|
56498
|
-
if (existsSync23(
|
|
56643
|
+
if (existsSync23(join52(projectRoot, ".eslintrc.json")) || existsSync23(join52(projectRoot, ".eslintrc.js")) || existsSync23(join52(projectRoot, "eslint.config.js"))) {
|
|
56499
56644
|
return "eslint";
|
|
56500
56645
|
}
|
|
56501
56646
|
return "unknown";
|
|
56502
56647
|
}
|
|
56503
56648
|
function detectMonorepo(projectRoot) {
|
|
56504
|
-
if (existsSync23(
|
|
56649
|
+
if (existsSync23(join52(projectRoot, "turbo.json")))
|
|
56505
56650
|
return "turborepo";
|
|
56506
|
-
if (existsSync23(
|
|
56651
|
+
if (existsSync23(join52(projectRoot, "nx.json")))
|
|
56507
56652
|
return "nx";
|
|
56508
|
-
if (existsSync23(
|
|
56653
|
+
if (existsSync23(join52(projectRoot, "pnpm-workspace.yaml")))
|
|
56509
56654
|
return "pnpm-workspaces";
|
|
56510
56655
|
const pkg = readPackageJson(projectRoot);
|
|
56511
56656
|
if (pkg?.workspaces)
|
|
@@ -56649,7 +56794,7 @@ __export(exports_init, {
|
|
|
56649
56794
|
});
|
|
56650
56795
|
import { existsSync as existsSync24 } from "fs";
|
|
56651
56796
|
import { mkdir as mkdir9 } from "fs/promises";
|
|
56652
|
-
import { join as
|
|
56797
|
+
import { join as join53 } from "path";
|
|
56653
56798
|
function validateProjectName(name) {
|
|
56654
56799
|
if (!name)
|
|
56655
56800
|
return { valid: false, error: "name must be non-empty" };
|
|
@@ -56685,7 +56830,7 @@ async function checkInitCollision(name, currentWorkdir, currentRemote) {
|
|
|
56685
56830
|
}
|
|
56686
56831
|
async function updateGitignore(projectRoot) {
|
|
56687
56832
|
const logger = getLogger();
|
|
56688
|
-
const gitignorePath =
|
|
56833
|
+
const gitignorePath = join53(projectRoot, ".gitignore");
|
|
56689
56834
|
let existing = "";
|
|
56690
56835
|
if (existsSync24(gitignorePath)) {
|
|
56691
56836
|
existing = await Bun.file(gitignorePath).text();
|
|
@@ -56771,7 +56916,7 @@ async function initGlobal() {
|
|
|
56771
56916
|
await mkdir9(globalDir, { recursive: true });
|
|
56772
56917
|
logger.info("init", "Created global config directory", { path: globalDir });
|
|
56773
56918
|
}
|
|
56774
|
-
const configPath =
|
|
56919
|
+
const configPath = join53(globalDir, "config.json");
|
|
56775
56920
|
if (!existsSync24(configPath)) {
|
|
56776
56921
|
await Bun.write(configPath, `${JSON.stringify(MINIMAL_GLOBAL_CONFIG, null, 2)}
|
|
56777
56922
|
`);
|
|
@@ -56779,14 +56924,14 @@ async function initGlobal() {
|
|
|
56779
56924
|
} else {
|
|
56780
56925
|
logger.info("init", "Global config already exists", { path: configPath });
|
|
56781
56926
|
}
|
|
56782
|
-
const constitutionPath =
|
|
56927
|
+
const constitutionPath = join53(globalDir, "constitution.md");
|
|
56783
56928
|
if (!existsSync24(constitutionPath)) {
|
|
56784
56929
|
await Bun.write(constitutionPath, buildConstitution({ runtime: "unknown", language: "unknown", linter: "unknown", monorepo: "none" }));
|
|
56785
56930
|
logger.info("init", "Created global constitution", { path: constitutionPath });
|
|
56786
56931
|
} else {
|
|
56787
56932
|
logger.info("init", "Global constitution already exists", { path: constitutionPath });
|
|
56788
56933
|
}
|
|
56789
|
-
const hooksDir =
|
|
56934
|
+
const hooksDir = join53(globalDir, "hooks");
|
|
56790
56935
|
if (!existsSync24(hooksDir)) {
|
|
56791
56936
|
await mkdir9(hooksDir, { recursive: true });
|
|
56792
56937
|
logger.info("init", "Created global hooks directory", { path: hooksDir });
|
|
@@ -56819,7 +56964,7 @@ async function initProject(projectRoot, options) {
|
|
|
56819
56964
|
if (detectedName && !options?.force) {
|
|
56820
56965
|
const collision = await checkInitCollision(detectedName, projectRoot, currentRemote);
|
|
56821
56966
|
if (collision.collision && collision.existing) {
|
|
56822
|
-
const configPath2 =
|
|
56967
|
+
const configPath2 = join53(projectDir, "config.json");
|
|
56823
56968
|
throw new NaxError([
|
|
56824
56969
|
`Project name collision: "${detectedName}"`,
|
|
56825
56970
|
` This project: ${projectRoot}`,
|
|
@@ -56847,7 +56992,7 @@ async function initProject(projectRoot, options) {
|
|
|
56847
56992
|
linter: stack.linter,
|
|
56848
56993
|
monorepo: stack.monorepo
|
|
56849
56994
|
});
|
|
56850
|
-
const configPath =
|
|
56995
|
+
const configPath = join53(projectDir, "config.json");
|
|
56851
56996
|
if (!existsSync24(configPath)) {
|
|
56852
56997
|
await Bun.write(configPath, `${JSON.stringify(projectConfig, null, 2)}
|
|
56853
56998
|
`);
|
|
@@ -56856,14 +57001,14 @@ async function initProject(projectRoot, options) {
|
|
|
56856
57001
|
logger.info("init", "Project config already exists", { path: configPath });
|
|
56857
57002
|
}
|
|
56858
57003
|
await initContext(projectRoot, { ai: options?.ai, force: options?.force });
|
|
56859
|
-
const constitutionPath =
|
|
57004
|
+
const constitutionPath = join53(projectDir, "constitution.md");
|
|
56860
57005
|
if (!existsSync24(constitutionPath) || options?.force) {
|
|
56861
57006
|
await Bun.write(constitutionPath, buildConstitution(stack));
|
|
56862
57007
|
logger.info("init", "Created project constitution", { path: constitutionPath });
|
|
56863
57008
|
} else {
|
|
56864
57009
|
logger.info("init", "Project constitution already exists", { path: constitutionPath });
|
|
56865
57010
|
}
|
|
56866
|
-
const hooksDir =
|
|
57011
|
+
const hooksDir = join53(projectDir, "hooks");
|
|
56867
57012
|
if (!existsSync24(hooksDir)) {
|
|
56868
57013
|
await mkdir9(hooksDir, { recursive: true });
|
|
56869
57014
|
logger.info("init", "Created project hooks directory", { path: hooksDir });
|
|
@@ -58298,12 +58443,12 @@ var init_loader4 = __esm(() => {
|
|
|
58298
58443
|
});
|
|
58299
58444
|
|
|
58300
58445
|
// src/utils/paths.ts
|
|
58301
|
-
import { join as
|
|
58446
|
+
import { join as join64 } from "path";
|
|
58302
58447
|
function getRunsDir() {
|
|
58303
|
-
return process.env.NAX_RUNS_DIR ??
|
|
58448
|
+
return process.env.NAX_RUNS_DIR ?? join64(globalConfigDir(), "runs");
|
|
58304
58449
|
}
|
|
58305
58450
|
function getEventsRootDir() {
|
|
58306
|
-
return
|
|
58451
|
+
return join64(globalConfigDir(), "events");
|
|
58307
58452
|
}
|
|
58308
58453
|
var init_paths3 = __esm(() => {
|
|
58309
58454
|
init_paths();
|
|
@@ -58363,7 +58508,7 @@ var init_command_argv = __esm(() => {
|
|
|
58363
58508
|
});
|
|
58364
58509
|
|
|
58365
58510
|
// src/hooks/runner.ts
|
|
58366
|
-
import { join as
|
|
58511
|
+
import { join as join71 } from "path";
|
|
58367
58512
|
function createDrainDeadline2(deadlineMs) {
|
|
58368
58513
|
let timeoutId;
|
|
58369
58514
|
const promise2 = new Promise((resolve16) => {
|
|
@@ -58382,14 +58527,14 @@ async function loadHooksConfig(projectDir, globalDir) {
|
|
|
58382
58527
|
let globalHooks = { hooks: {} };
|
|
58383
58528
|
let projectHooks = { hooks: {} };
|
|
58384
58529
|
let skipGlobal = false;
|
|
58385
|
-
const projectPath =
|
|
58530
|
+
const projectPath = join71(projectDir, "hooks.json");
|
|
58386
58531
|
const projectData = await loadJsonFile(projectPath, "hooks");
|
|
58387
58532
|
if (projectData) {
|
|
58388
58533
|
projectHooks = projectData;
|
|
58389
58534
|
skipGlobal = projectData.skipGlobal ?? false;
|
|
58390
58535
|
}
|
|
58391
58536
|
if (!skipGlobal && globalDir) {
|
|
58392
|
-
const globalPath =
|
|
58537
|
+
const globalPath = join71(globalDir, "hooks.json");
|
|
58393
58538
|
const globalData = await loadJsonFile(globalPath, "hooks");
|
|
58394
58539
|
if (globalData) {
|
|
58395
58540
|
globalHooks = globalData;
|
|
@@ -58559,7 +58704,7 @@ var package_default;
|
|
|
58559
58704
|
var init_package = __esm(() => {
|
|
58560
58705
|
package_default = {
|
|
58561
58706
|
name: "@nathapp/nax",
|
|
58562
|
-
version: "0.
|
|
58707
|
+
version: "0.69.0",
|
|
58563
58708
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
58564
58709
|
type: "module",
|
|
58565
58710
|
bin: {
|
|
@@ -58654,8 +58799,8 @@ var init_version = __esm(() => {
|
|
|
58654
58799
|
NAX_VERSION = package_default.version;
|
|
58655
58800
|
NAX_COMMIT = (() => {
|
|
58656
58801
|
try {
|
|
58657
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
58658
|
-
return "
|
|
58802
|
+
if (/^[0-9a-f]{6,10}$/.test("ce4d8f0e"))
|
|
58803
|
+
return "ce4d8f0e";
|
|
58659
58804
|
} catch {}
|
|
58660
58805
|
try {
|
|
58661
58806
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -59532,15 +59677,15 @@ var init_acceptance_loop = __esm(() => {
|
|
|
59532
59677
|
|
|
59533
59678
|
// src/session/scratch-purge.ts
|
|
59534
59679
|
import { mkdir as mkdir13, rename, rm } from "fs/promises";
|
|
59535
|
-
import { dirname as dirname12, join as
|
|
59680
|
+
import { dirname as dirname12, join as join72 } from "path";
|
|
59536
59681
|
async function purgeStaleScratch(projectDir, featureName, retentionDays, archiveInsteadOfDelete = false) {
|
|
59537
|
-
const sessionsDir =
|
|
59682
|
+
const sessionsDir = join72(projectDir, ".nax", "features", featureName, "sessions");
|
|
59538
59683
|
const sessionIds = await _scratchPurgeDeps.listSessionDirs(sessionsDir);
|
|
59539
59684
|
const cutoffMs = _scratchPurgeDeps.now() - retentionDays * 86400000;
|
|
59540
59685
|
let purged = 0;
|
|
59541
59686
|
for (const sessionId of sessionIds) {
|
|
59542
|
-
const sessionDir =
|
|
59543
|
-
const descriptorPath =
|
|
59687
|
+
const sessionDir = join72(sessionsDir, sessionId);
|
|
59688
|
+
const descriptorPath = join72(sessionDir, "descriptor.json");
|
|
59544
59689
|
if (!await _scratchPurgeDeps.fileExists(descriptorPath))
|
|
59545
59690
|
continue;
|
|
59546
59691
|
let lastActivityAt;
|
|
@@ -59556,7 +59701,7 @@ async function purgeStaleScratch(projectDir, featureName, retentionDays, archive
|
|
|
59556
59701
|
if (new Date(lastActivityAt).getTime() >= cutoffMs)
|
|
59557
59702
|
continue;
|
|
59558
59703
|
if (archiveInsteadOfDelete) {
|
|
59559
|
-
const archiveDest =
|
|
59704
|
+
const archiveDest = join72(projectDir, ".nax", "features", featureName, "_archive", "sessions", sessionId);
|
|
59560
59705
|
await _scratchPurgeDeps.move(sessionDir, archiveDest);
|
|
59561
59706
|
} else {
|
|
59562
59707
|
await _scratchPurgeDeps.remove(sessionDir);
|
|
@@ -59936,6 +60081,7 @@ async function handleRunCompletion(options) {
|
|
|
59936
60081
|
const regressionMode = config2.execution.regressionGate?.mode;
|
|
59937
60082
|
if (options.skipRegression) {} else if (regressionMode === "deferred" && config2.quality.commands.test) {
|
|
59938
60083
|
statusWriter.setPostRunPhase("regression", { status: "running" });
|
|
60084
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "regression" });
|
|
59939
60085
|
const regressionResult = await _runCompletionDeps.runDeferredRegression({
|
|
59940
60086
|
config: config2,
|
|
59941
60087
|
prd,
|
|
@@ -59950,6 +60096,7 @@ async function handleRunCompletion(options) {
|
|
|
59950
60096
|
});
|
|
59951
60097
|
if (regressionResult.success) {
|
|
59952
60098
|
statusWriter.setPostRunPhase("regression", { status: "passed", lastRunAt });
|
|
60099
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: true });
|
|
59953
60100
|
} else {
|
|
59954
60101
|
statusWriter.setPostRunPhase("regression", {
|
|
59955
60102
|
status: "failed",
|
|
@@ -59957,6 +60104,7 @@ async function handleRunCompletion(options) {
|
|
|
59957
60104
|
affectedStories: regressionResult.affectedStories,
|
|
59958
60105
|
lastRunAt
|
|
59959
60106
|
});
|
|
60107
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "regression", passed: false });
|
|
59960
60108
|
for (const storyId of regressionResult.affectedStories) {
|
|
59961
60109
|
const story = prd.userStories.find((s) => s.id === storyId);
|
|
59962
60110
|
if (story) {
|
|
@@ -60021,6 +60169,9 @@ async function handleRunCompletion(options) {
|
|
|
60021
60169
|
}
|
|
60022
60170
|
let pluginGateFailed = false;
|
|
60023
60171
|
const deferredReview = options.deferredReview;
|
|
60172
|
+
if (deferredReview !== undefined) {
|
|
60173
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "review", passed: !deferredReview.anyFailed });
|
|
60174
|
+
}
|
|
60024
60175
|
if (deferredReview?.anyFailed) {
|
|
60025
60176
|
const failedReviewers = deferredReview.reviewerResults.filter((r) => !r.passed).map((r) => r.name);
|
|
60026
60177
|
pluginGateFailed = config2.review.pluginMode === "gating";
|
|
@@ -60288,12 +60439,12 @@ var DEFAULT_MAX_BATCH_SIZE = 4;
|
|
|
60288
60439
|
|
|
60289
60440
|
// src/pipeline/subscribers/events-writer.ts
|
|
60290
60441
|
import { appendFile as appendFile4, mkdir as mkdir14 } from "fs/promises";
|
|
60291
|
-
import { basename as basename13, join as
|
|
60442
|
+
import { basename as basename13, join as join73 } from "path";
|
|
60292
60443
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
60293
60444
|
const logger = getSafeLogger();
|
|
60294
60445
|
const project = basename13(workdir);
|
|
60295
|
-
const eventsDir =
|
|
60296
|
-
const eventsFile =
|
|
60446
|
+
const eventsDir = join73(getEventsRootDir(), project);
|
|
60447
|
+
const eventsFile = join73(eventsDir, "events.jsonl");
|
|
60297
60448
|
let dirReady = false;
|
|
60298
60449
|
const write = (line) => {
|
|
60299
60450
|
return (async () => {
|
|
@@ -60474,12 +60625,12 @@ var init_interaction2 = __esm(() => {
|
|
|
60474
60625
|
|
|
60475
60626
|
// src/pipeline/subscribers/registry.ts
|
|
60476
60627
|
import { mkdir as mkdir15, writeFile as writeFile2 } from "fs/promises";
|
|
60477
|
-
import { basename as basename14, join as
|
|
60628
|
+
import { basename as basename14, join as join74 } from "path";
|
|
60478
60629
|
function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
60479
60630
|
const logger = getSafeLogger();
|
|
60480
60631
|
const project = basename14(workdir);
|
|
60481
|
-
const runDir =
|
|
60482
|
-
const metaFile =
|
|
60632
|
+
const runDir = join74(getRunsDir(), `${project}-${feature}-${runId}`);
|
|
60633
|
+
const metaFile = join74(runDir, "meta.json");
|
|
60483
60634
|
const unsub = bus.on("run:started", (_ev) => {
|
|
60484
60635
|
return (async () => {
|
|
60485
60636
|
try {
|
|
@@ -60489,8 +60640,8 @@ function wireRegistry(bus, feature, runId, workdir, outputDir) {
|
|
|
60489
60640
|
project,
|
|
60490
60641
|
feature,
|
|
60491
60642
|
workdir,
|
|
60492
|
-
statusPath:
|
|
60493
|
-
eventsDir:
|
|
60643
|
+
statusPath: join74(outputDir, "features", feature, "status.json"),
|
|
60644
|
+
eventsDir: join74(outputDir, "features", feature, "runs"),
|
|
60494
60645
|
registeredAt: new Date().toISOString()
|
|
60495
60646
|
};
|
|
60496
60647
|
await writeFile2(metaFile, JSON.stringify(meta3, null, 2));
|
|
@@ -60736,7 +60887,7 @@ var init_types9 = __esm(() => {
|
|
|
60736
60887
|
|
|
60737
60888
|
// src/worktree/dependencies.ts
|
|
60738
60889
|
import { existsSync as existsSync31 } from "fs";
|
|
60739
|
-
import { join as
|
|
60890
|
+
import { join as join75 } from "path";
|
|
60740
60891
|
async function prepareWorktreeDependencies(options) {
|
|
60741
60892
|
const mode = options.config.execution.worktreeDependencies.mode;
|
|
60742
60893
|
const resolvedCwd = resolveDependencyCwd(options);
|
|
@@ -60750,7 +60901,7 @@ async function prepareWorktreeDependencies(options) {
|
|
|
60750
60901
|
}
|
|
60751
60902
|
}
|
|
60752
60903
|
function resolveDependencyCwd(options) {
|
|
60753
|
-
return options.storyWorkdir ?
|
|
60904
|
+
return options.storyWorkdir ? join75(options.worktreeRoot, options.storyWorkdir) : options.worktreeRoot;
|
|
60754
60905
|
}
|
|
60755
60906
|
function resolveInheritedDependencies(options, resolvedCwd) {
|
|
60756
60907
|
if (hasDependencyManifests(options.worktreeRoot, resolvedCwd)) {
|
|
@@ -60760,7 +60911,7 @@ function resolveInheritedDependencies(options, resolvedCwd) {
|
|
|
60760
60911
|
}
|
|
60761
60912
|
function hasDependencyManifests(worktreeRoot, resolvedCwd) {
|
|
60762
60913
|
const directories = resolvedCwd === worktreeRoot ? [worktreeRoot] : [worktreeRoot, resolvedCwd];
|
|
60763
|
-
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(
|
|
60914
|
+
return directories.some((directory) => PHASE_ONE_INHERIT_UNSUPPORTED_FILES.some((filename) => _worktreeDependencyDeps.existsSync(join75(directory, filename))));
|
|
60764
60915
|
}
|
|
60765
60916
|
async function provisionDependencies(config2, worktreeRoot, resolvedCwd) {
|
|
60766
60917
|
const setupCommand = config2.execution.worktreeDependencies.setupCommand;
|
|
@@ -60824,13 +60975,13 @@ __export(exports_manager, {
|
|
|
60824
60975
|
});
|
|
60825
60976
|
import { existsSync as existsSync32, symlinkSync } from "fs";
|
|
60826
60977
|
import { mkdir as mkdir16 } from "fs/promises";
|
|
60827
|
-
import { join as
|
|
60978
|
+
import { join as join76 } from "path";
|
|
60828
60979
|
|
|
60829
60980
|
class WorktreeManager {
|
|
60830
60981
|
async ensureGitExcludes(projectRoot) {
|
|
60831
60982
|
const logger = getSafeLogger();
|
|
60832
|
-
const infoDir =
|
|
60833
|
-
const excludePath =
|
|
60983
|
+
const infoDir = join76(projectRoot, ".git", "info");
|
|
60984
|
+
const excludePath = join76(infoDir, "exclude");
|
|
60834
60985
|
try {
|
|
60835
60986
|
await mkdir16(infoDir, { recursive: true });
|
|
60836
60987
|
let existing = "";
|
|
@@ -60857,7 +61008,7 @@ ${missing.join(`
|
|
|
60857
61008
|
}
|
|
60858
61009
|
async create(projectRoot, storyId) {
|
|
60859
61010
|
validateStoryId(storyId);
|
|
60860
|
-
const worktreePath =
|
|
61011
|
+
const worktreePath = join76(projectRoot, ".nax-wt", storyId);
|
|
60861
61012
|
const branchName = `nax/${storyId}`;
|
|
60862
61013
|
try {
|
|
60863
61014
|
const pruneProc = _managerDeps.spawn(["git", "worktree", "prune"], {
|
|
@@ -60898,9 +61049,9 @@ ${missing.join(`
|
|
|
60898
61049
|
}
|
|
60899
61050
|
throw new Error(`Failed to create worktree: ${String(error48)}`);
|
|
60900
61051
|
}
|
|
60901
|
-
const envSource =
|
|
61052
|
+
const envSource = join76(projectRoot, ".env");
|
|
60902
61053
|
if (existsSync32(envSource)) {
|
|
60903
|
-
const envTarget =
|
|
61054
|
+
const envTarget = join76(worktreePath, ".env");
|
|
60904
61055
|
try {
|
|
60905
61056
|
symlinkSync(envSource, envTarget, "file");
|
|
60906
61057
|
} catch (error48) {
|
|
@@ -60911,7 +61062,7 @@ ${missing.join(`
|
|
|
60911
61062
|
}
|
|
60912
61063
|
async remove(projectRoot, storyId) {
|
|
60913
61064
|
validateStoryId(storyId);
|
|
60914
|
-
const worktreePath =
|
|
61065
|
+
const worktreePath = join76(projectRoot, ".nax-wt", storyId);
|
|
60915
61066
|
const branchName = `nax/${storyId}`;
|
|
60916
61067
|
try {
|
|
60917
61068
|
const proc = _managerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
@@ -61723,10 +61874,10 @@ var init_merge_conflict_rectify = __esm(() => {
|
|
|
61723
61874
|
});
|
|
61724
61875
|
|
|
61725
61876
|
// src/execution/pipeline-result-handler.ts
|
|
61726
|
-
import { join as
|
|
61877
|
+
import { join as join77 } from "path";
|
|
61727
61878
|
async function removeWorktreeDirectory(projectRoot, storyId) {
|
|
61728
61879
|
const logger = getSafeLogger();
|
|
61729
|
-
const worktreePath =
|
|
61880
|
+
const worktreePath = join77(projectRoot, ".nax-wt", storyId);
|
|
61730
61881
|
try {
|
|
61731
61882
|
const proc = _resultHandlerDeps.spawn(["git", "worktree", "remove", worktreePath, "--force"], {
|
|
61732
61883
|
cwd: projectRoot,
|
|
@@ -61943,7 +62094,7 @@ var init_pipeline_result_handler = __esm(() => {
|
|
|
61943
62094
|
|
|
61944
62095
|
// src/execution/iteration-runner.ts
|
|
61945
62096
|
import { existsSync as existsSync33 } from "fs";
|
|
61946
|
-
import { join as
|
|
62097
|
+
import { join as join78 } from "path";
|
|
61947
62098
|
async function runIteration(ctx, prd, selection, iterations, totalCost, allStoryMetrics) {
|
|
61948
62099
|
const { story, storiesToExecute, routing, isBatchExecution } = selection;
|
|
61949
62100
|
if (ctx.dryRun) {
|
|
@@ -61968,7 +62119,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
61968
62119
|
const storyStartTime = Date.now();
|
|
61969
62120
|
let effectiveWorkdir = ctx.workdir;
|
|
61970
62121
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
61971
|
-
const worktreePath =
|
|
62122
|
+
const worktreePath = join78(ctx.workdir, ".nax-wt", story.id);
|
|
61972
62123
|
const worktreeExists = _iterationRunnerDeps.existsSync(worktreePath);
|
|
61973
62124
|
if (!worktreeExists) {
|
|
61974
62125
|
await _iterationRunnerDeps.worktreeManager.ensureGitExcludes(ctx.workdir);
|
|
@@ -61988,7 +62139,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
61988
62139
|
}
|
|
61989
62140
|
const accumulatedAttemptCost = (story.priorFailures || []).reduce((sum, f) => sum + (f.cost || 0), 0);
|
|
61990
62141
|
const profileOverride = ctx.config.profile && ctx.config.profile !== "default" ? { profile: ctx.config.profile } : undefined;
|
|
61991
|
-
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(
|
|
62142
|
+
const effectiveConfig = story.workdir ? await _iterationRunnerDeps.loadConfigForWorkdir(join78(ctx.workdir, ".nax", "config.json"), story.workdir, profileOverride) : ctx.config;
|
|
61992
62143
|
let dependencyContext;
|
|
61993
62144
|
if (ctx.config.execution.storyIsolation === "worktree") {
|
|
61994
62145
|
try {
|
|
@@ -62015,7 +62166,7 @@ async function runIteration(ctx, prd, selection, iterations, totalCost, allStory
|
|
|
62015
62166
|
};
|
|
62016
62167
|
}
|
|
62017
62168
|
}
|
|
62018
|
-
const resolvedWorkdir = dependencyContext?.cwd ? dependencyContext.cwd : ctx.config.execution.storyIsolation === "worktree" ? story.workdir ?
|
|
62169
|
+
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
62170
|
const pipelineContext = {
|
|
62020
62171
|
config: effectiveConfig,
|
|
62021
62172
|
rootConfig: ctx.config,
|
|
@@ -62217,7 +62368,7 @@ __export(exports_parallel_worker, {
|
|
|
62217
62368
|
buildWorktreePipelineContext: () => buildWorktreePipelineContext,
|
|
62218
62369
|
_parallelWorkerDeps: () => _parallelWorkerDeps
|
|
62219
62370
|
});
|
|
62220
|
-
import { join as
|
|
62371
|
+
import { join as join79 } from "path";
|
|
62221
62372
|
function buildWorktreePipelineContext(base, _story) {
|
|
62222
62373
|
return { ...base, prd: structuredClone(base.prd) };
|
|
62223
62374
|
}
|
|
@@ -62240,7 +62391,7 @@ async function executeStoryInWorktree(story, worktreePath, dependencyContext, co
|
|
|
62240
62391
|
story,
|
|
62241
62392
|
stories: [story],
|
|
62242
62393
|
projectDir: context.projectDir,
|
|
62243
|
-
workdir: dependencyContext.cwd ?? (story.workdir ?
|
|
62394
|
+
workdir: dependencyContext.cwd ?? (story.workdir ? join79(worktreePath, story.workdir) : worktreePath),
|
|
62244
62395
|
worktreeDependencyContext: dependencyContext,
|
|
62245
62396
|
routing,
|
|
62246
62397
|
storyGitRef: storyGitRef ?? undefined
|
|
@@ -62617,6 +62768,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
62617
62768
|
if (isComplete(prd)) {
|
|
62618
62769
|
logger?.info("execution", "All stories already complete \u2014 skipping pre-run pipeline");
|
|
62619
62770
|
const naxIgnoreIndex = await getRunNaxIgnoreIndex(prd);
|
|
62771
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
|
|
62620
62772
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
|
|
62621
62773
|
return buildResult2("completed");
|
|
62622
62774
|
}
|
|
@@ -62670,6 +62822,7 @@ async function executeUnified(ctx, initialPrd) {
|
|
|
62670
62822
|
return buildResult2("pre-merge-aborted");
|
|
62671
62823
|
}
|
|
62672
62824
|
logger?.debug("execution", "Running deferred review");
|
|
62825
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "review" });
|
|
62673
62826
|
deferredReview = await runDeferredReview(ctx.workdir, ctx.config.review, ctx.pluginRegistry, runStartRef, naxIgnoreIndex);
|
|
62674
62827
|
logger?.debug("execution", "Deferred review done \u2014 returning completed");
|
|
62675
62828
|
return buildResult2("completed");
|
|
@@ -63125,7 +63278,7 @@ async function writeStatusFile(filePath, status) {
|
|
|
63125
63278
|
var init_status_file = () => {};
|
|
63126
63279
|
|
|
63127
63280
|
// src/execution/status-writer.ts
|
|
63128
|
-
import { join as
|
|
63281
|
+
import { join as join80 } from "path";
|
|
63129
63282
|
|
|
63130
63283
|
class StatusWriter {
|
|
63131
63284
|
statusFile;
|
|
@@ -63244,7 +63397,7 @@ class StatusWriter {
|
|
|
63244
63397
|
if (!this._prd)
|
|
63245
63398
|
return;
|
|
63246
63399
|
const safeLogger = getSafeLogger();
|
|
63247
|
-
const featureStatusPath =
|
|
63400
|
+
const featureStatusPath = join80(featureDir, "status.json");
|
|
63248
63401
|
const write = async () => {
|
|
63249
63402
|
try {
|
|
63250
63403
|
const base = this.getSnapshot(totalCost, iterations);
|
|
@@ -63678,7 +63831,7 @@ __export(exports_run_initialization, {
|
|
|
63678
63831
|
initializeRun: () => initializeRun,
|
|
63679
63832
|
_reconcileDeps: () => _reconcileDeps
|
|
63680
63833
|
});
|
|
63681
|
-
import { join as
|
|
63834
|
+
import { join as join81 } from "path";
|
|
63682
63835
|
async function reconcileState(prd, prdPath, workdir, config2) {
|
|
63683
63836
|
const logger = getSafeLogger();
|
|
63684
63837
|
let reconciledCount = 0;
|
|
@@ -63695,7 +63848,7 @@ async function reconcileState(prd, prdPath, workdir, config2) {
|
|
|
63695
63848
|
});
|
|
63696
63849
|
continue;
|
|
63697
63850
|
}
|
|
63698
|
-
const effectiveWorkdir = story.workdir ?
|
|
63851
|
+
const effectiveWorkdir = story.workdir ? join81(workdir, story.workdir) : workdir;
|
|
63699
63852
|
try {
|
|
63700
63853
|
const reviewResult = await _reconcileDeps.runReview(config2.review, effectiveWorkdir, config2.execution);
|
|
63701
63854
|
if (!reviewResult.success) {
|
|
@@ -93488,7 +93641,7 @@ __export(exports_curator, {
|
|
|
93488
93641
|
});
|
|
93489
93642
|
import { readdirSync as readdirSync8 } from "fs";
|
|
93490
93643
|
import { unlink as unlink4 } from "fs/promises";
|
|
93491
|
-
import { basename as basename15, join as
|
|
93644
|
+
import { basename as basename15, join as join83 } from "path";
|
|
93492
93645
|
function getProjectKey(config2, projectDir) {
|
|
93493
93646
|
return config2.name?.trim() || basename15(projectDir);
|
|
93494
93647
|
}
|
|
@@ -93571,7 +93724,7 @@ async function curatorStatus(options) {
|
|
|
93571
93724
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93572
93725
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93573
93726
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93574
|
-
const runsDir =
|
|
93727
|
+
const runsDir = join83(outputDir, "runs");
|
|
93575
93728
|
const runIds = listRunIds(runsDir);
|
|
93576
93729
|
let runId;
|
|
93577
93730
|
if (options.run) {
|
|
@@ -93588,8 +93741,8 @@ async function curatorStatus(options) {
|
|
|
93588
93741
|
runId = runIds[runIds.length - 1];
|
|
93589
93742
|
}
|
|
93590
93743
|
console.log(`Run: ${runId}`);
|
|
93591
|
-
const runDir =
|
|
93592
|
-
const observationsPath =
|
|
93744
|
+
const runDir = join83(runsDir, runId);
|
|
93745
|
+
const observationsPath = join83(runDir, "observations.jsonl");
|
|
93593
93746
|
const observations = await parseObservations(observationsPath);
|
|
93594
93747
|
const counts = new Map;
|
|
93595
93748
|
for (const obs of observations) {
|
|
@@ -93599,7 +93752,7 @@ async function curatorStatus(options) {
|
|
|
93599
93752
|
for (const [kind, count] of counts.entries()) {
|
|
93600
93753
|
console.log(` ${kind}: ${count}`);
|
|
93601
93754
|
}
|
|
93602
|
-
const proposalsPath =
|
|
93755
|
+
const proposalsPath = join83(runDir, "curator-proposals.md");
|
|
93603
93756
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
93604
93757
|
if (proposalText !== null) {
|
|
93605
93758
|
console.log("");
|
|
@@ -93613,8 +93766,8 @@ async function curatorCommit(options) {
|
|
|
93613
93766
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93614
93767
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93615
93768
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93616
|
-
const runDir =
|
|
93617
|
-
const proposalsPath =
|
|
93769
|
+
const runDir = join83(outputDir, "runs", options.runId);
|
|
93770
|
+
const proposalsPath = join83(runDir, "curator-proposals.md");
|
|
93618
93771
|
const proposalText = await _curatorCmdDeps.readFile(proposalsPath).catch(() => null);
|
|
93619
93772
|
if (proposalText === null) {
|
|
93620
93773
|
console.log(`curator-proposals.md not found for run ${options.runId}.`);
|
|
@@ -93630,7 +93783,7 @@ async function curatorCommit(options) {
|
|
|
93630
93783
|
const dropFileState = new Map;
|
|
93631
93784
|
const skippedDrops = new Set;
|
|
93632
93785
|
for (const drop2 of drops) {
|
|
93633
|
-
const targetPath =
|
|
93786
|
+
const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
|
|
93634
93787
|
if (!dropFileState.has(targetPath)) {
|
|
93635
93788
|
const fileExists2 = await Bun.file(targetPath).exists();
|
|
93636
93789
|
const existing = fileExists2 ? await _curatorCmdDeps.readFile(targetPath).catch(() => "") : "";
|
|
@@ -93664,7 +93817,7 @@ async function curatorCommit(options) {
|
|
|
93664
93817
|
if (skippedDrops.has(drop2)) {
|
|
93665
93818
|
continue;
|
|
93666
93819
|
}
|
|
93667
|
-
const targetPath =
|
|
93820
|
+
const targetPath = join83(resolved.projectDir, drop2.canonicalFile);
|
|
93668
93821
|
const existing = await _curatorCmdDeps.readFile(targetPath).catch(() => "");
|
|
93669
93822
|
const filtered = filterDropContent(existing, drop2.description);
|
|
93670
93823
|
await _curatorCmdDeps.writeFile(targetPath, filtered);
|
|
@@ -93673,7 +93826,7 @@ async function curatorCommit(options) {
|
|
|
93673
93826
|
}
|
|
93674
93827
|
const adds = proposals.filter((p) => p.action === "add" || p.action === "advisory");
|
|
93675
93828
|
for (const add2 of adds) {
|
|
93676
|
-
const targetPath =
|
|
93829
|
+
const targetPath = join83(resolved.projectDir, add2.canonicalFile);
|
|
93677
93830
|
const content = buildAddContent(add2);
|
|
93678
93831
|
await _curatorCmdDeps.appendFile(targetPath, content);
|
|
93679
93832
|
modifiedFiles.add(targetPath);
|
|
@@ -93710,7 +93863,7 @@ async function curatorDryrun(options) {
|
|
|
93710
93863
|
const config2 = await _curatorCmdDeps.loadConfig(resolved.projectDir);
|
|
93711
93864
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93712
93865
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93713
|
-
const runsDir =
|
|
93866
|
+
const runsDir = join83(outputDir, "runs");
|
|
93714
93867
|
const runIds = listRunIds(runsDir);
|
|
93715
93868
|
if (runIds.length === 0) {
|
|
93716
93869
|
console.log("No runs found.");
|
|
@@ -93721,7 +93874,7 @@ async function curatorDryrun(options) {
|
|
|
93721
93874
|
console.log(`Run ${options.run} not found in ${runsDir}.`);
|
|
93722
93875
|
return;
|
|
93723
93876
|
}
|
|
93724
|
-
const observationsPath =
|
|
93877
|
+
const observationsPath = join83(runsDir, runId, "observations.jsonl");
|
|
93725
93878
|
const observations = await parseObservations(observationsPath);
|
|
93726
93879
|
const thresholds = getThresholds(config2);
|
|
93727
93880
|
const proposals = runHeuristics(observations, thresholds);
|
|
@@ -93762,12 +93915,12 @@ async function curatorGc(options) {
|
|
|
93762
93915
|
await _curatorCmdDeps.writeFile(rollupPath, newContent);
|
|
93763
93916
|
const projectKey = getProjectKey(config2, resolved.projectDir);
|
|
93764
93917
|
const outputDir = _curatorCmdDeps.projectOutputDir(projectKey, config2.outputDir);
|
|
93765
|
-
const perRunsDir =
|
|
93918
|
+
const perRunsDir = join83(outputDir, "runs");
|
|
93766
93919
|
for (const runId of uniqueRunIds) {
|
|
93767
93920
|
if (!keepSet.has(runId)) {
|
|
93768
|
-
const runDir =
|
|
93769
|
-
await _curatorCmdDeps.removeFile(
|
|
93770
|
-
await _curatorCmdDeps.removeFile(
|
|
93921
|
+
const runDir = join83(perRunsDir, runId);
|
|
93922
|
+
await _curatorCmdDeps.removeFile(join83(runDir, "observations.jsonl"));
|
|
93923
|
+
await _curatorCmdDeps.removeFile(join83(runDir, "curator-proposals.md"));
|
|
93771
93924
|
}
|
|
93772
93925
|
}
|
|
93773
93926
|
console.log(`[gc] Pruned rollup to ${keep} most recent runs (was ${uniqueRunIds.length}).`);
|
|
@@ -93812,7 +93965,7 @@ var init_curator2 = __esm(() => {
|
|
|
93812
93965
|
init_source();
|
|
93813
93966
|
import { existsSync as existsSync36, mkdirSync as mkdirSync7 } from "fs";
|
|
93814
93967
|
import { homedir as homedir3 } from "os";
|
|
93815
|
-
import { basename as basename16, join as
|
|
93968
|
+
import { basename as basename16, join as join84 } from "path";
|
|
93816
93969
|
|
|
93817
93970
|
// node_modules/commander/esm.mjs
|
|
93818
93971
|
var import__ = __toESM(require_commander(), 1);
|
|
@@ -93836,12 +93989,12 @@ init_errors();
|
|
|
93836
93989
|
init_operations();
|
|
93837
93990
|
|
|
93838
93991
|
// src/plan/strategies/context-builder.ts
|
|
93839
|
-
import { join as
|
|
93992
|
+
import { join as join38 } from "path";
|
|
93840
93993
|
init_config();
|
|
93841
93994
|
init_errors();
|
|
93842
93995
|
init_interaction();
|
|
93843
93996
|
async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
93844
|
-
const naxDir =
|
|
93997
|
+
const naxDir = join38(workdir, ".nax");
|
|
93845
93998
|
if (!deps.existsSync(naxDir)) {
|
|
93846
93999
|
throw new NaxError(`.nax directory not found. Run 'nax init' first in ${workdir}`, "PLAN_CONTEXT_NO_NAX_DIR", {
|
|
93847
94000
|
stage: "plan",
|
|
@@ -93849,8 +94002,8 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
93849
94002
|
});
|
|
93850
94003
|
}
|
|
93851
94004
|
validateFeatureName(options.feature);
|
|
93852
|
-
const outputDir =
|
|
93853
|
-
const outputPath =
|
|
94005
|
+
const outputDir = join38(naxDir, "features", options.feature);
|
|
94006
|
+
const outputPath = join38(outputDir, "prd.json");
|
|
93854
94007
|
const [specContent, sourceRoots, pkg] = await Promise.all([
|
|
93855
94008
|
deps.readFile(options.from),
|
|
93856
94009
|
deps.scanSourceRoots(workdir),
|
|
@@ -93865,7 +94018,7 @@ async function buildPlanModeContext(workdir, fullConfig, options, deps) {
|
|
|
93865
94018
|
...new Set(sourceRoots.map((root) => root.path).filter((path7) => path7 !== ".").map((path7) => path7.startsWith("/") ? path7.replace(`${workdir}/`, "") : path7))
|
|
93866
94019
|
];
|
|
93867
94020
|
const packageDetails = relativePackages.length === 0 ? [] : await Promise.all(relativePackages.map(async (relativePath) => {
|
|
93868
|
-
const packageJson = await deps.readPackageJsonAt(
|
|
94021
|
+
const packageJson = await deps.readPackageJsonAt(join38(workdir, relativePath, "package.json"));
|
|
93869
94022
|
return buildPackageSummary(relativePath, packageJson);
|
|
93870
94023
|
}));
|
|
93871
94024
|
const projectName = detectProjectName(workdir, pkg);
|
|
@@ -94182,7 +94335,8 @@ class RefinePlanStrategy {
|
|
|
94182
94335
|
packages: ctx.relativePackages,
|
|
94183
94336
|
packageDetails: ctx.packageDetails,
|
|
94184
94337
|
projectProfile: ctx.config.project,
|
|
94185
|
-
specGuard: ctx.config.plan.specGuard ?? false
|
|
94338
|
+
specGuard: ctx.config.plan.specGuard ?? false,
|
|
94339
|
+
workdir: ctx.workdir
|
|
94186
94340
|
});
|
|
94187
94341
|
return writeOrRecoverPrd(ctx, prd);
|
|
94188
94342
|
} catch (err) {
|
|
@@ -94467,7 +94621,7 @@ init_interaction();
|
|
|
94467
94621
|
init_prd();
|
|
94468
94622
|
init_runtime();
|
|
94469
94623
|
import { existsSync as existsSync17, readdirSync as readdirSync3 } from "fs";
|
|
94470
|
-
import { basename as basename7, join as
|
|
94624
|
+
import { basename as basename7, join as join43, resolve as resolve14 } from "path";
|
|
94471
94625
|
var _statusFeaturesDeps = {
|
|
94472
94626
|
projectOutputDir,
|
|
94473
94627
|
loadConfig
|
|
@@ -94481,7 +94635,7 @@ function isPidAlive(pid) {
|
|
|
94481
94635
|
}
|
|
94482
94636
|
}
|
|
94483
94637
|
async function loadStatusFile(featureDir) {
|
|
94484
|
-
const statusPath =
|
|
94638
|
+
const statusPath = join43(featureDir, "status.json");
|
|
94485
94639
|
if (!existsSync17(statusPath)) {
|
|
94486
94640
|
return null;
|
|
94487
94641
|
}
|
|
@@ -94496,7 +94650,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
94496
94650
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94497
94651
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94498
94652
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94499
|
-
const statusPath =
|
|
94653
|
+
const statusPath = join43(outputDir, "status.json");
|
|
94500
94654
|
if (!existsSync17(statusPath)) {
|
|
94501
94655
|
return null;
|
|
94502
94656
|
}
|
|
@@ -94508,7 +94662,7 @@ async function loadProjectStatusFile(projectDir) {
|
|
|
94508
94662
|
}
|
|
94509
94663
|
}
|
|
94510
94664
|
async function getFeatureSummary(featureName, featureDir) {
|
|
94511
|
-
const prdPath =
|
|
94665
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
94512
94666
|
if (!existsSync17(prdPath)) {
|
|
94513
94667
|
return {
|
|
94514
94668
|
name: featureName,
|
|
@@ -94551,7 +94705,7 @@ async function getFeatureSummary(featureName, featureDir) {
|
|
|
94551
94705
|
};
|
|
94552
94706
|
}
|
|
94553
94707
|
}
|
|
94554
|
-
const runsDir =
|
|
94708
|
+
const runsDir = join43(featureDir, "runs");
|
|
94555
94709
|
if (existsSync17(runsDir)) {
|
|
94556
94710
|
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
94711
|
if (runs.length > 0) {
|
|
@@ -94565,7 +94719,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94565
94719
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94566
94720
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94567
94721
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94568
|
-
const featuresDir =
|
|
94722
|
+
const featuresDir = join43(outputDir, "features");
|
|
94569
94723
|
if (!existsSync17(featuresDir)) {
|
|
94570
94724
|
console.log(source_default.dim("No features found."));
|
|
94571
94725
|
return;
|
|
@@ -94606,7 +94760,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94606
94760
|
console.log();
|
|
94607
94761
|
}
|
|
94608
94762
|
}
|
|
94609
|
-
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name,
|
|
94763
|
+
const summaries = await Promise.all(features.map((name) => getFeatureSummary(name, join43(featuresDir, name))));
|
|
94610
94764
|
console.log(source_default.bold(`\uD83D\uDCCA Features
|
|
94611
94765
|
`));
|
|
94612
94766
|
const header = ` ${"Feature".padEnd(25)} ${"Done".padEnd(6)} ${"Failed".padEnd(8)} ${"Pending".padEnd(9)} ${"Last Run".padEnd(22)} ${"Cost".padEnd(10)} Status`;
|
|
@@ -94632,7 +94786,7 @@ async function displayAllFeatures(projectDir) {
|
|
|
94632
94786
|
console.log();
|
|
94633
94787
|
}
|
|
94634
94788
|
async function displayFeatureDetails(featureName, featureDir) {
|
|
94635
|
-
const prdPath =
|
|
94789
|
+
const prdPath = join43(featureDir, "prd.json");
|
|
94636
94790
|
if (!existsSync17(prdPath)) {
|
|
94637
94791
|
console.log(source_default.bold(`
|
|
94638
94792
|
\uD83D\uDCCA ${featureName}
|
|
@@ -94778,7 +94932,7 @@ async function displayFeatureStatus(options = {}) {
|
|
|
94778
94932
|
const config2 = await _statusFeaturesDeps.loadConfig(projectDir).catch(() => null);
|
|
94779
94933
|
const projectKey = config2?.name?.trim() || basename7(projectDir);
|
|
94780
94934
|
const outputDir = _statusFeaturesDeps.projectOutputDir(projectKey, config2?.outputDir);
|
|
94781
|
-
featureDir =
|
|
94935
|
+
featureDir = join43(outputDir, "features", options.feature);
|
|
94782
94936
|
} else {
|
|
94783
94937
|
const resolved = resolveProject({ feature: options.feature });
|
|
94784
94938
|
if (!resolved.featureDir) {
|
|
@@ -94798,7 +94952,7 @@ init_errors();
|
|
|
94798
94952
|
init_logger2();
|
|
94799
94953
|
init_runtime();
|
|
94800
94954
|
import { existsSync as existsSync18, readdirSync as readdirSync4 } from "fs";
|
|
94801
|
-
import { basename as basename8, join as
|
|
94955
|
+
import { basename as basename8, join as join44 } from "path";
|
|
94802
94956
|
async function resolveOutputDir2(workdir, override) {
|
|
94803
94957
|
if (override)
|
|
94804
94958
|
return override;
|
|
@@ -94822,7 +94976,7 @@ async function runsListCommand(options) {
|
|
|
94822
94976
|
const logger = getLogger();
|
|
94823
94977
|
const { feature, workdir } = options;
|
|
94824
94978
|
const outputDir = await resolveOutputDir2(workdir, options.outputDir);
|
|
94825
|
-
const runsDir =
|
|
94979
|
+
const runsDir = join44(outputDir, "features", feature, "runs");
|
|
94826
94980
|
if (!existsSync18(runsDir)) {
|
|
94827
94981
|
logger.info("cli", "No runs found for feature", { feature, hint: `Directory not found: ${runsDir}` });
|
|
94828
94982
|
return;
|
|
@@ -94834,7 +94988,7 @@ async function runsListCommand(options) {
|
|
|
94834
94988
|
}
|
|
94835
94989
|
logger.info("cli", `Runs for ${feature}`, { count: files.length });
|
|
94836
94990
|
for (const file3 of files.sort().reverse()) {
|
|
94837
|
-
const logPath =
|
|
94991
|
+
const logPath = join44(runsDir, file3);
|
|
94838
94992
|
const entries = await parseRunLog(logPath);
|
|
94839
94993
|
const startEvent = entries.find((e) => e.message === "run.start");
|
|
94840
94994
|
const completeEvent = entries.find((e) => e.message === "run.complete");
|
|
@@ -94861,7 +95015,7 @@ async function runsShowCommand(options) {
|
|
|
94861
95015
|
const logger = getLogger();
|
|
94862
95016
|
const { runId, feature, workdir } = options;
|
|
94863
95017
|
const outputDir = await resolveOutputDir2(workdir, options.outputDir);
|
|
94864
|
-
const logPath =
|
|
95018
|
+
const logPath = join44(outputDir, "features", feature, "runs", `${runId}.jsonl`);
|
|
94865
95019
|
if (!existsSync18(logPath)) {
|
|
94866
95020
|
logger.error("cli", "Run not found", { runId, feature, logPath });
|
|
94867
95021
|
throw new NaxError("Run not found", "RUN_NOT_FOUND", { runId, feature, logPath });
|
|
@@ -94976,7 +95130,7 @@ init_source();
|
|
|
94976
95130
|
init_loader();
|
|
94977
95131
|
init_generator2();
|
|
94978
95132
|
import { existsSync as existsSync25 } from "fs";
|
|
94979
|
-
import { join as
|
|
95133
|
+
import { join as join58 } from "path";
|
|
94980
95134
|
var VALID_AGENTS = ["claude", "codex", "opencode", "cursor", "windsurf", "aider", "gemini"];
|
|
94981
95135
|
async function generateCommand(options) {
|
|
94982
95136
|
const workdir = options.dir ?? process.cwd();
|
|
@@ -95019,7 +95173,7 @@ async function generateCommand(options) {
|
|
|
95019
95173
|
return;
|
|
95020
95174
|
}
|
|
95021
95175
|
if (options.package) {
|
|
95022
|
-
const packageDir =
|
|
95176
|
+
const packageDir = join58(workdir, options.package);
|
|
95023
95177
|
if (dryRun) {
|
|
95024
95178
|
console.log(source_default.yellow("\u26A0 Dry run \u2014 no files will be written"));
|
|
95025
95179
|
}
|
|
@@ -95039,8 +95193,8 @@ async function generateCommand(options) {
|
|
|
95039
95193
|
process.exit(1);
|
|
95040
95194
|
return;
|
|
95041
95195
|
}
|
|
95042
|
-
const contextPath = options.context ?
|
|
95043
|
-
const outputDir = options.output ?
|
|
95196
|
+
const contextPath = options.context ? join58(workdir, options.context) : join58(workdir, ".nax/context.md");
|
|
95197
|
+
const outputDir = options.output ? join58(workdir, options.output) : workdir;
|
|
95044
95198
|
const autoInject = !options.noAutoInject;
|
|
95045
95199
|
if (!existsSync25(contextPath)) {
|
|
95046
95200
|
console.error(source_default.red(`\u2717 Context file not found: ${contextPath}`));
|
|
@@ -95146,7 +95300,7 @@ async function generateCommand(options) {
|
|
|
95146
95300
|
// src/cli/config-display.ts
|
|
95147
95301
|
init_loader();
|
|
95148
95302
|
import { existsSync as existsSync27 } from "fs";
|
|
95149
|
-
import { join as
|
|
95303
|
+
import { join as join60 } from "path";
|
|
95150
95304
|
|
|
95151
95305
|
// src/cli/config-descriptions.ts
|
|
95152
95306
|
var FIELD_DESCRIPTIONS = {
|
|
@@ -95398,7 +95552,7 @@ function deepEqual(a, b) {
|
|
|
95398
95552
|
init_defaults();
|
|
95399
95553
|
init_loader();
|
|
95400
95554
|
import { existsSync as existsSync26 } from "fs";
|
|
95401
|
-
import { join as
|
|
95555
|
+
import { join as join59 } from "path";
|
|
95402
95556
|
async function loadConfigFile(path18) {
|
|
95403
95557
|
if (!existsSync26(path18))
|
|
95404
95558
|
return null;
|
|
@@ -95420,7 +95574,7 @@ async function loadProjectConfig() {
|
|
|
95420
95574
|
const projectDir = findProjectDir();
|
|
95421
95575
|
if (!projectDir)
|
|
95422
95576
|
return null;
|
|
95423
|
-
const projectPath =
|
|
95577
|
+
const projectPath = join59(projectDir, "config.json");
|
|
95424
95578
|
return await loadConfigFile(projectPath);
|
|
95425
95579
|
}
|
|
95426
95580
|
|
|
@@ -95480,7 +95634,7 @@ async function configCommand(config2, options = {}) {
|
|
|
95480
95634
|
function determineConfigSources() {
|
|
95481
95635
|
const globalPath = globalConfigPath();
|
|
95482
95636
|
const projectDir = findProjectDir();
|
|
95483
|
-
const projectPath = projectDir ?
|
|
95637
|
+
const projectPath = projectDir ? join60(projectDir, "config.json") : null;
|
|
95484
95638
|
return {
|
|
95485
95639
|
global: fileExists(globalPath) ? globalPath : null,
|
|
95486
95640
|
project: projectPath && fileExists(projectPath) ? projectPath : null
|
|
@@ -95629,15 +95783,15 @@ init_paths();
|
|
|
95629
95783
|
init_profile();
|
|
95630
95784
|
import { mkdirSync as mkdirSync5 } from "fs";
|
|
95631
95785
|
import { readdirSync as readdirSync5 } from "fs";
|
|
95632
|
-
import { join as
|
|
95786
|
+
import { join as join61 } from "path";
|
|
95633
95787
|
var _profileCLIDeps = {
|
|
95634
95788
|
env: process.env
|
|
95635
95789
|
};
|
|
95636
95790
|
var SENSITIVE_KEY_PATTERN = /key|token|secret|password|credential/i;
|
|
95637
95791
|
var VAR_PATTERN = /\$[A-Za-z_][A-Za-z0-9_]*/;
|
|
95638
95792
|
async function profileListCommand(startDir) {
|
|
95639
|
-
const globalProfilesDir =
|
|
95640
|
-
const projectProfilesDir =
|
|
95793
|
+
const globalProfilesDir = join61(globalConfigDir(), "profiles");
|
|
95794
|
+
const projectProfilesDir = join61(projectConfigDir(startDir), "profiles");
|
|
95641
95795
|
const globalProfiles = scanProfileDir(globalProfilesDir);
|
|
95642
95796
|
const projectProfiles = scanProfileDir(projectProfilesDir);
|
|
95643
95797
|
const activeProfile = await resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
@@ -95696,7 +95850,7 @@ function maskProfileValues(obj) {
|
|
|
95696
95850
|
return result;
|
|
95697
95851
|
}
|
|
95698
95852
|
async function profileUseCommand(profileName, startDir) {
|
|
95699
|
-
const configPath =
|
|
95853
|
+
const configPath = join61(projectConfigDir(startDir), "config.json");
|
|
95700
95854
|
const configFile = Bun.file(configPath);
|
|
95701
95855
|
let existing = {};
|
|
95702
95856
|
if (await configFile.exists()) {
|
|
@@ -95715,8 +95869,8 @@ async function profileCurrentCommand(startDir) {
|
|
|
95715
95869
|
return resolveProfileName({}, _profileCLIDeps.env, startDir);
|
|
95716
95870
|
}
|
|
95717
95871
|
async function profileCreateCommand(profileName, startDir) {
|
|
95718
|
-
const profilesDir =
|
|
95719
|
-
const profilePath =
|
|
95872
|
+
const profilesDir = join61(projectConfigDir(startDir), "profiles");
|
|
95873
|
+
const profilePath = join61(profilesDir, `${profileName}.json`);
|
|
95720
95874
|
const profileFile = Bun.file(profilePath);
|
|
95721
95875
|
if (await profileFile.exists()) {
|
|
95722
95876
|
throw new Error(`Profile "${profileName}" already exists at ${profilePath}`);
|
|
@@ -95838,7 +95992,7 @@ async function contextInspectCommand(options) {
|
|
|
95838
95992
|
init_canonical_loader();
|
|
95839
95993
|
init_errors();
|
|
95840
95994
|
import { mkdir as mkdir12 } from "fs/promises";
|
|
95841
|
-
import { basename as basename12, join as
|
|
95995
|
+
import { basename as basename12, join as join62 } from "path";
|
|
95842
95996
|
var _rulesCLIDeps = {
|
|
95843
95997
|
readFile: async (path18) => Bun.file(path18).text(),
|
|
95844
95998
|
writeFile: async (path18, content) => {
|
|
@@ -95847,7 +96001,7 @@ var _rulesCLIDeps = {
|
|
|
95847
96001
|
fileExists: async (path18) => Bun.file(path18).exists(),
|
|
95848
96002
|
globInDir: (dir) => {
|
|
95849
96003
|
try {
|
|
95850
|
-
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) =>
|
|
96004
|
+
return [...new Bun.Glob("*.md").scanSync({ cwd: dir })].sort().map((f) => join62(dir, f));
|
|
95851
96005
|
} catch {
|
|
95852
96006
|
return [];
|
|
95853
96007
|
}
|
|
@@ -95896,7 +96050,7 @@ ${r.content}`).join(`
|
|
|
95896
96050
|
`);
|
|
95897
96051
|
const shimContent = `${header + body}
|
|
95898
96052
|
`;
|
|
95899
|
-
const shimPath =
|
|
96053
|
+
const shimPath = join62(workdir, shimFileName);
|
|
95900
96054
|
if (options.dryRun) {
|
|
95901
96055
|
console.log(`[dry-run] Would write ${shimPath} (${shimContent.length} bytes)`);
|
|
95902
96056
|
return;
|
|
@@ -95925,14 +96079,14 @@ function neutralizeContent(content) {
|
|
|
95925
96079
|
}
|
|
95926
96080
|
async function collectMigrationSources(workdir) {
|
|
95927
96081
|
const sources = [];
|
|
95928
|
-
const claudeMdPath =
|
|
96082
|
+
const claudeMdPath = join62(workdir, "CLAUDE.md");
|
|
95929
96083
|
if (await _rulesCLIDeps.fileExists(claudeMdPath)) {
|
|
95930
96084
|
const content = await _rulesCLIDeps.readFile(claudeMdPath);
|
|
95931
96085
|
if (content.trim()) {
|
|
95932
96086
|
sources.push({ sourcePath: claudeMdPath, targetFileName: "project-conventions.md", content });
|
|
95933
96087
|
}
|
|
95934
96088
|
}
|
|
95935
|
-
const rulesDir =
|
|
96089
|
+
const rulesDir = join62(workdir, ".claude", "rules");
|
|
95936
96090
|
const ruleFiles = _rulesCLIDeps.globInDir(rulesDir);
|
|
95937
96091
|
for (const filePath of ruleFiles) {
|
|
95938
96092
|
try {
|
|
@@ -95952,7 +96106,7 @@ async function rulesMigrateCommand(options) {
|
|
|
95952
96106
|
console.log("[WARN] No source files found (checked CLAUDE.md and .claude/rules/*.md). Nothing to migrate.");
|
|
95953
96107
|
return;
|
|
95954
96108
|
}
|
|
95955
|
-
const targetDir =
|
|
96109
|
+
const targetDir = join62(workdir, CANONICAL_RULES_DIR);
|
|
95956
96110
|
if (!options.dryRun) {
|
|
95957
96111
|
try {
|
|
95958
96112
|
await _rulesCLIDeps.mkdir(targetDir);
|
|
@@ -95963,7 +96117,7 @@ async function rulesMigrateCommand(options) {
|
|
|
95963
96117
|
let written = 0;
|
|
95964
96118
|
let skipped = 0;
|
|
95965
96119
|
for (const { sourcePath, targetFileName, content } of sources) {
|
|
95966
|
-
const targetPath =
|
|
96120
|
+
const targetPath = join62(targetDir, targetFileName);
|
|
95967
96121
|
if (!force && !options.dryRun && await _rulesCLIDeps.fileExists(targetPath)) {
|
|
95968
96122
|
console.log(`[skip] ${targetFileName} already exists (use --force to overwrite)`);
|
|
95969
96123
|
skipped++;
|
|
@@ -96002,7 +96156,7 @@ function collectCanonicalRuleRoots(workdir) {
|
|
|
96002
96156
|
const packageRel = normalized.slice(0, idx);
|
|
96003
96157
|
if (!packageRel)
|
|
96004
96158
|
continue;
|
|
96005
|
-
roots.add(
|
|
96159
|
+
roots.add(join62(workdir, packageRel));
|
|
96006
96160
|
}
|
|
96007
96161
|
return [...roots].sort();
|
|
96008
96162
|
}
|
|
@@ -96024,7 +96178,7 @@ init_logger2();
|
|
|
96024
96178
|
init_detect2();
|
|
96025
96179
|
init_workspace();
|
|
96026
96180
|
init_common();
|
|
96027
|
-
import { join as
|
|
96181
|
+
import { join as join63 } from "path";
|
|
96028
96182
|
function resolveEffective(detected, configPatterns) {
|
|
96029
96183
|
if (configPatterns !== undefined)
|
|
96030
96184
|
return "config";
|
|
@@ -96109,7 +96263,7 @@ async function detectCommand(options) {
|
|
|
96109
96263
|
const rootDetected = detectionMap[""] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
96110
96264
|
const pkgEntries = await Promise.all(packageDirs.map(async (dir) => {
|
|
96111
96265
|
const det = detectionMap[dir] ?? { patterns: [], confidence: "empty", sources: [] };
|
|
96112
|
-
const pkgConfigPath =
|
|
96266
|
+
const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
|
|
96113
96267
|
const pkgRaw = await loadRawConfig(pkgConfigPath);
|
|
96114
96268
|
const pkgPatterns = deepGet(pkgRaw, TEST_PATTERNS_KEY);
|
|
96115
96269
|
const effective = Array.isArray(pkgPatterns) ? pkgPatterns : undefined;
|
|
@@ -96163,13 +96317,13 @@ async function detectCommand(options) {
|
|
|
96163
96317
|
if (rootDetected.confidence === "empty") {
|
|
96164
96318
|
console.log(source_default.yellow(" root: skipped (empty detection)"));
|
|
96165
96319
|
} else {
|
|
96166
|
-
const rootConfigPath =
|
|
96320
|
+
const rootConfigPath = join63(workdir, ".nax", "config.json");
|
|
96167
96321
|
try {
|
|
96168
96322
|
const status = await applyToConfig(rootConfigPath, rootDetected.patterns, options.force ?? false);
|
|
96169
96323
|
if (status === "skipped") {
|
|
96170
96324
|
console.log(source_default.dim(" root: skipped (testFilePatterns already set; use --force to overwrite)"));
|
|
96171
96325
|
} else {
|
|
96172
|
-
console.log(source_default.green(` root: ${status} \u2192 ${
|
|
96326
|
+
console.log(source_default.green(` root: ${status} \u2192 ${join63(".nax", "config.json")}`));
|
|
96173
96327
|
}
|
|
96174
96328
|
} catch (err) {
|
|
96175
96329
|
console.error(source_default.red(` root: write failed \u2014 ${err.message}`));
|
|
@@ -96182,13 +96336,13 @@ async function detectCommand(options) {
|
|
|
96182
96336
|
console.log(source_default.dim(` ${dir}: skipped (empty detection)`));
|
|
96183
96337
|
continue;
|
|
96184
96338
|
}
|
|
96185
|
-
const pkgConfigPath =
|
|
96339
|
+
const pkgConfigPath = join63(workdir, ".nax", "mono", dir, "config.json");
|
|
96186
96340
|
try {
|
|
96187
96341
|
const status = await applyToConfig(pkgConfigPath, det.patterns, options.force ?? false);
|
|
96188
96342
|
if (status === "skipped") {
|
|
96189
96343
|
console.log(source_default.dim(` ${dir}: skipped (already set)`));
|
|
96190
96344
|
} else {
|
|
96191
|
-
console.log(source_default.green(` ${dir}: ${status} \u2192 ${
|
|
96345
|
+
console.log(source_default.green(` ${dir}: ${status} \u2192 ${join63(".nax", "mono", dir, "config.json")}`));
|
|
96192
96346
|
}
|
|
96193
96347
|
} catch (err) {
|
|
96194
96348
|
console.error(source_default.red(` ${dir}: write failed \u2014 ${err.message}`));
|
|
@@ -96206,19 +96360,19 @@ async function detectCommand(options) {
|
|
|
96206
96360
|
// src/commands/logs.ts
|
|
96207
96361
|
init_common();
|
|
96208
96362
|
import { existsSync as existsSync29 } from "fs";
|
|
96209
|
-
import { join as
|
|
96363
|
+
import { join as join67 } from "path";
|
|
96210
96364
|
|
|
96211
96365
|
// src/commands/logs-formatter.ts
|
|
96212
96366
|
init_source();
|
|
96213
96367
|
init_formatter();
|
|
96214
96368
|
import { readdirSync as readdirSync7 } from "fs";
|
|
96215
|
-
import { join as
|
|
96369
|
+
import { join as join66 } from "path";
|
|
96216
96370
|
|
|
96217
96371
|
// src/commands/logs-reader.ts
|
|
96218
96372
|
init_paths3();
|
|
96219
96373
|
import { existsSync as existsSync28, readdirSync as readdirSync6 } from "fs";
|
|
96220
96374
|
import { readdir as readdir4 } from "fs/promises";
|
|
96221
|
-
import { join as
|
|
96375
|
+
import { join as join65 } from "path";
|
|
96222
96376
|
var _logsReaderDeps = {
|
|
96223
96377
|
getRunsDir
|
|
96224
96378
|
};
|
|
@@ -96232,7 +96386,7 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
96232
96386
|
}
|
|
96233
96387
|
let matched = null;
|
|
96234
96388
|
for (const entry of entries) {
|
|
96235
|
-
const metaPath =
|
|
96389
|
+
const metaPath = join65(runsDir, entry, "meta.json");
|
|
96236
96390
|
try {
|
|
96237
96391
|
const meta3 = await Bun.file(metaPath).json();
|
|
96238
96392
|
if (meta3.runId === runId || meta3.runId.startsWith(runId)) {
|
|
@@ -96254,14 +96408,14 @@ async function resolveRunFileFromRegistry(runId) {
|
|
|
96254
96408
|
return null;
|
|
96255
96409
|
}
|
|
96256
96410
|
const specificFile = files.find((f) => f === `${matched.runId}.jsonl`);
|
|
96257
|
-
return
|
|
96411
|
+
return join65(matched.eventsDir, specificFile ?? files[0]);
|
|
96258
96412
|
}
|
|
96259
96413
|
async function selectRunFile(runsDir) {
|
|
96260
96414
|
const files = readdirSync6(runsDir).filter((f) => f.endsWith(".jsonl") && f !== "latest.jsonl").sort().reverse();
|
|
96261
96415
|
if (files.length === 0) {
|
|
96262
96416
|
return null;
|
|
96263
96417
|
}
|
|
96264
|
-
return
|
|
96418
|
+
return join65(runsDir, files[0]);
|
|
96265
96419
|
}
|
|
96266
96420
|
async function extractRunSummary(filePath) {
|
|
96267
96421
|
const file3 = Bun.file(filePath);
|
|
@@ -96347,7 +96501,7 @@ Runs:
|
|
|
96347
96501
|
console.log(source_default.gray(" Timestamp Stories Duration Cost Status"));
|
|
96348
96502
|
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
96503
|
for (const file3 of files) {
|
|
96350
|
-
const filePath =
|
|
96504
|
+
const filePath = join66(runsDir, file3);
|
|
96351
96505
|
const summary = await extractRunSummary(filePath);
|
|
96352
96506
|
const timestamp = file3.replace(".jsonl", "");
|
|
96353
96507
|
const stories = summary ? `${summary.passed}/${summary.total}` : "?/?";
|
|
@@ -96461,7 +96615,7 @@ async function logsCommand(options) {
|
|
|
96461
96615
|
return;
|
|
96462
96616
|
}
|
|
96463
96617
|
const resolved = resolveProject({ dir: options.dir });
|
|
96464
|
-
const naxDir =
|
|
96618
|
+
const naxDir = join67(resolved.projectDir, ".nax");
|
|
96465
96619
|
const configPath = resolved.configPath;
|
|
96466
96620
|
const configFile = Bun.file(configPath);
|
|
96467
96621
|
const config2 = await configFile.json();
|
|
@@ -96469,8 +96623,8 @@ async function logsCommand(options) {
|
|
|
96469
96623
|
if (!featureName) {
|
|
96470
96624
|
throw new Error("No feature specified in config.json");
|
|
96471
96625
|
}
|
|
96472
|
-
const featureDir =
|
|
96473
|
-
const runsDir =
|
|
96626
|
+
const featureDir = join67(naxDir, "features", featureName);
|
|
96627
|
+
const runsDir = join67(featureDir, "runs");
|
|
96474
96628
|
if (!existsSync29(runsDir)) {
|
|
96475
96629
|
throw new Error(`No runs directory found for feature: ${featureName}`);
|
|
96476
96630
|
}
|
|
@@ -96496,7 +96650,7 @@ init_prd();
|
|
|
96496
96650
|
init_precheck();
|
|
96497
96651
|
init_common();
|
|
96498
96652
|
import { existsSync as existsSync30 } from "fs";
|
|
96499
|
-
import { join as
|
|
96653
|
+
import { join as join68 } from "path";
|
|
96500
96654
|
async function precheckCommand(options) {
|
|
96501
96655
|
const resolved = resolveProject({
|
|
96502
96656
|
dir: options.dir,
|
|
@@ -96518,9 +96672,9 @@ async function precheckCommand(options) {
|
|
|
96518
96672
|
process.exit(1);
|
|
96519
96673
|
}
|
|
96520
96674
|
}
|
|
96521
|
-
const naxDir =
|
|
96522
|
-
const featureDir =
|
|
96523
|
-
const prdPath =
|
|
96675
|
+
const naxDir = join68(resolved.projectDir, ".nax");
|
|
96676
|
+
const featureDir = join68(naxDir, "features", featureName);
|
|
96677
|
+
const prdPath = join68(featureDir, "prd.json");
|
|
96524
96678
|
if (!existsSync30(featureDir)) {
|
|
96525
96679
|
console.error(source_default.red(`Feature not found: ${featureName}`));
|
|
96526
96680
|
process.exit(1);
|
|
@@ -96543,7 +96697,7 @@ async function precheckCommand(options) {
|
|
|
96543
96697
|
init_source();
|
|
96544
96698
|
init_paths3();
|
|
96545
96699
|
import { readdir as readdir5 } from "fs/promises";
|
|
96546
|
-
import { join as
|
|
96700
|
+
import { join as join69 } from "path";
|
|
96547
96701
|
var DEFAULT_LIMIT = 20;
|
|
96548
96702
|
var _runsCmdDeps = {
|
|
96549
96703
|
getRunsDir
|
|
@@ -96598,7 +96752,7 @@ async function runsCommand(options = {}) {
|
|
|
96598
96752
|
}
|
|
96599
96753
|
const rows = [];
|
|
96600
96754
|
for (const entry of entries) {
|
|
96601
|
-
const metaPath =
|
|
96755
|
+
const metaPath = join69(runsDir, entry, "meta.json");
|
|
96602
96756
|
let meta3;
|
|
96603
96757
|
try {
|
|
96604
96758
|
meta3 = await Bun.file(metaPath).json();
|
|
@@ -96675,7 +96829,7 @@ async function runsCommand(options = {}) {
|
|
|
96675
96829
|
|
|
96676
96830
|
// src/commands/unlock.ts
|
|
96677
96831
|
init_source();
|
|
96678
|
-
import { join as
|
|
96832
|
+
import { join as join70 } from "path";
|
|
96679
96833
|
function isProcessAlive2(pid) {
|
|
96680
96834
|
try {
|
|
96681
96835
|
process.kill(pid, 0);
|
|
@@ -96690,7 +96844,7 @@ function formatLockAge(ageMs) {
|
|
|
96690
96844
|
}
|
|
96691
96845
|
async function unlockCommand(options) {
|
|
96692
96846
|
const workdir = options.dir ?? process.cwd();
|
|
96693
|
-
const lockPath =
|
|
96847
|
+
const lockPath = join70(workdir, "nax.lock");
|
|
96694
96848
|
const lockFile = Bun.file(lockPath);
|
|
96695
96849
|
const exists = await lockFile.exists();
|
|
96696
96850
|
if (!exists) {
|
|
@@ -96742,6 +96896,7 @@ init_acceptance2();
|
|
|
96742
96896
|
init_config();
|
|
96743
96897
|
init_hooks();
|
|
96744
96898
|
init_logger2();
|
|
96899
|
+
init_pipeline();
|
|
96745
96900
|
init_prd();
|
|
96746
96901
|
init_git();
|
|
96747
96902
|
init_crash_recovery();
|
|
@@ -96775,6 +96930,7 @@ async function runCompletionPhase(options) {
|
|
|
96775
96930
|
logger?.info("execution", "Acceptance already passed \u2014 skipping acceptance phase");
|
|
96776
96931
|
} else if (options.config.acceptance.enabled && isComplete(options.prd)) {
|
|
96777
96932
|
options.statusWriter.setPostRunPhase("acceptance", { status: "running" });
|
|
96933
|
+
pipelineEventBus.emit({ type: "postrun:phase:started", phase: "acceptance" });
|
|
96778
96934
|
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
96935
|
const relativeWorkdir = path19.relative(options.workdir, g.packageDir);
|
|
96780
96936
|
let groupConfig = options.config;
|
|
@@ -96821,6 +96977,7 @@ async function runCompletionPhase(options) {
|
|
|
96821
96977
|
const lastRunAt = new Date().toISOString();
|
|
96822
96978
|
if (acceptanceResult.success) {
|
|
96823
96979
|
options.statusWriter.setPostRunPhase("acceptance", { status: "passed", lastRunAt });
|
|
96980
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: true });
|
|
96824
96981
|
} else {
|
|
96825
96982
|
acceptancePassed = false;
|
|
96826
96983
|
options.statusWriter.setPostRunPhase("acceptance", {
|
|
@@ -96829,6 +96986,7 @@ async function runCompletionPhase(options) {
|
|
|
96829
96986
|
retries: acceptanceResult.retries ?? 0,
|
|
96830
96987
|
lastRunAt
|
|
96831
96988
|
});
|
|
96989
|
+
pipelineEventBus.emit({ type: "postrun:phase:completed", phase: "acceptance", passed: false });
|
|
96832
96990
|
}
|
|
96833
96991
|
Object.assign(options, {
|
|
96834
96992
|
prd: acceptanceResult.prd,
|
|
@@ -103725,9 +103883,11 @@ var MAX_ESCALATION_DISPLAY = 5;
|
|
|
103725
103883
|
function LiveActivityPanel({
|
|
103726
103884
|
focused = false,
|
|
103727
103885
|
activeCalls,
|
|
103886
|
+
storySteps,
|
|
103728
103887
|
runSummary,
|
|
103729
103888
|
runErrored,
|
|
103730
|
-
escalationLog = []
|
|
103889
|
+
escalationLog = [],
|
|
103890
|
+
currentStage
|
|
103731
103891
|
}) {
|
|
103732
103892
|
const borderColor = focused ? "cyan" : "gray";
|
|
103733
103893
|
const activeCallList = activeCalls ? Array.from(activeCalls.values()) : [];
|
|
@@ -103783,7 +103943,9 @@ function LiveActivityPanel({
|
|
|
103783
103943
|
paddingX: 1,
|
|
103784
103944
|
paddingY: 1,
|
|
103785
103945
|
children: activeCallList.map((call) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(ActiveCallRow, {
|
|
103786
|
-
call
|
|
103946
|
+
call,
|
|
103947
|
+
step: call.storyId ? storySteps?.[call.storyId] : undefined,
|
|
103948
|
+
currentStage
|
|
103787
103949
|
}, call.callId, false, undefined, this))
|
|
103788
103950
|
}, undefined, false, undefined, this),
|
|
103789
103951
|
hasEscalations && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
@@ -103799,10 +103961,49 @@ function LiveActivityPanel({
|
|
|
103799
103961
|
}, `${entry.storyId}-${entry.at}`, false, undefined, this))
|
|
103800
103962
|
]
|
|
103801
103963
|
}, undefined, true, undefined, this),
|
|
103802
|
-
!hasActiveCalls &&
|
|
103964
|
+
!hasActiveCalls && storySteps && Object.keys(storySteps).length > 0 && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103965
|
+
flexDirection: "column",
|
|
103803
103966
|
paddingX: 1,
|
|
103804
103967
|
paddingY: 1,
|
|
103805
|
-
children: /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(
|
|
103968
|
+
children: Object.entries(storySteps).map(([sid, step]) => /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103969
|
+
flexDirection: "row",
|
|
103970
|
+
gap: 1,
|
|
103971
|
+
children: [
|
|
103972
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103973
|
+
children: sid
|
|
103974
|
+
}, undefined, false, undefined, this),
|
|
103975
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103976
|
+
color: "yellow",
|
|
103977
|
+
children: [
|
|
103978
|
+
"[",
|
|
103979
|
+
step,
|
|
103980
|
+
"]"
|
|
103981
|
+
]
|
|
103982
|
+
}, undefined, true, undefined, this)
|
|
103983
|
+
]
|
|
103984
|
+
}, sid, true, undefined, this))
|
|
103985
|
+
}, undefined, false, undefined, this),
|
|
103986
|
+
!hasActiveCalls && !hasSummary && !hasError && (!storySteps || Object.keys(storySteps).length === 0) && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103987
|
+
paddingX: 1,
|
|
103988
|
+
paddingY: 1,
|
|
103989
|
+
children: currentStage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103990
|
+
flexDirection: "row",
|
|
103991
|
+
gap: 1,
|
|
103992
|
+
children: [
|
|
103993
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103994
|
+
color: "yellow",
|
|
103995
|
+
children: [
|
|
103996
|
+
"[",
|
|
103997
|
+
currentStage,
|
|
103998
|
+
"]"
|
|
103999
|
+
]
|
|
104000
|
+
}, undefined, true, undefined, this),
|
|
104001
|
+
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
104002
|
+
dimColor: true,
|
|
104003
|
+
children: "preparing..."
|
|
104004
|
+
}, undefined, false, undefined, this)
|
|
104005
|
+
]
|
|
104006
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103806
104007
|
dimColor: true,
|
|
103807
104008
|
children: "Waiting for agent..."
|
|
103808
104009
|
}, undefined, false, undefined, this)
|
|
@@ -103810,7 +104011,29 @@ function LiveActivityPanel({
|
|
|
103810
104011
|
]
|
|
103811
104012
|
}, undefined, true, undefined, this);
|
|
103812
104013
|
}
|
|
103813
|
-
function ActiveCallRow({ call }) {
|
|
104014
|
+
function ActiveCallRow({ call, step, currentStage }) {
|
|
104015
|
+
const stageLabel = step ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
104016
|
+
color: "yellow",
|
|
104017
|
+
children: [
|
|
104018
|
+
"[",
|
|
104019
|
+
step,
|
|
104020
|
+
"]"
|
|
104021
|
+
]
|
|
104022
|
+
}, undefined, true, undefined, this) : call.stage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
104023
|
+
dimColor: true,
|
|
104024
|
+
children: [
|
|
104025
|
+
"[",
|
|
104026
|
+
call.stage,
|
|
104027
|
+
"]"
|
|
104028
|
+
]
|
|
104029
|
+
}, undefined, true, undefined, this) : currentStage ? /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
104030
|
+
dimColor: true,
|
|
104031
|
+
children: [
|
|
104032
|
+
"[",
|
|
104033
|
+
currentStage,
|
|
104034
|
+
"]"
|
|
104035
|
+
]
|
|
104036
|
+
}, undefined, true, undefined, this) : null;
|
|
103814
104037
|
return /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
103815
104038
|
flexDirection: "column",
|
|
103816
104039
|
marginBottom: 1,
|
|
@@ -103826,14 +104049,7 @@ function ActiveCallRow({ call }) {
|
|
|
103826
104049
|
call.storyId && /* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Text, {
|
|
103827
104050
|
children: call.storyId
|
|
103828
104051
|
}, undefined, false, undefined, this),
|
|
103829
|
-
|
|
103830
|
-
dimColor: true,
|
|
103831
|
-
children: [
|
|
103832
|
-
"[",
|
|
103833
|
-
call.stage,
|
|
103834
|
-
"]"
|
|
103835
|
-
]
|
|
103836
|
-
}, undefined, true, undefined, this)
|
|
104052
|
+
stageLabel
|
|
103837
104053
|
]
|
|
103838
104054
|
}, undefined, true, undefined, this),
|
|
103839
104055
|
/* @__PURE__ */ jsx_dev_runtime3.jsxDEV(Box_default, {
|
|
@@ -104040,7 +104256,14 @@ function getStatusIcon(status) {
|
|
|
104040
104256
|
return "\u23F8\uFE0F";
|
|
104041
104257
|
}
|
|
104042
104258
|
}
|
|
104043
|
-
function StoriesPanel({
|
|
104259
|
+
function StoriesPanel({
|
|
104260
|
+
stories,
|
|
104261
|
+
preRunPhases,
|
|
104262
|
+
postRunPhases,
|
|
104263
|
+
width,
|
|
104264
|
+
compact: compact2 = false,
|
|
104265
|
+
maxHeight
|
|
104266
|
+
}) {
|
|
104044
104267
|
const maxVisible = compact2 ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
|
|
104045
104268
|
const needsScrolling = stories.length > maxVisible;
|
|
104046
104269
|
const [scrollOffset, setScrollOffset] = import_react30.useState(0);
|
|
@@ -104072,7 +104295,7 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
|
|
|
104072
104295
|
children: [
|
|
104073
104296
|
/* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104074
104297
|
bold: true,
|
|
104075
|
-
children: "
|
|
104298
|
+
children: "Progress"
|
|
104076
104299
|
}, undefined, false, undefined, this),
|
|
104077
104300
|
needsScrolling && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104078
104301
|
dimColor: true,
|
|
@@ -104084,6 +104307,16 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
|
|
|
104084
104307
|
}, undefined, true, undefined, this)
|
|
104085
104308
|
]
|
|
104086
104309
|
}, undefined, true, undefined, this),
|
|
104310
|
+
preRunPhases && Object.keys(preRunPhases).length > 0 && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104311
|
+
flexDirection: "column",
|
|
104312
|
+
paddingX: 1,
|
|
104313
|
+
paddingTop: 1,
|
|
104314
|
+
children: Object.entries(preRunPhases).map(([name, phase]) => /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PreRunPhaseRow, {
|
|
104315
|
+
label: name,
|
|
104316
|
+
phase,
|
|
104317
|
+
compact: compact2
|
|
104318
|
+
}, name, false, undefined, this))
|
|
104319
|
+
}, undefined, false, undefined, this),
|
|
104087
104320
|
needsScrolling && canScrollUp && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104088
104321
|
paddingX: 1,
|
|
104089
104322
|
children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
@@ -104156,102 +104389,183 @@ function StoriesPanel({ stories, width, compact: compact2 = false, maxHeight })
|
|
|
104156
104389
|
" more below"
|
|
104157
104390
|
]
|
|
104158
104391
|
}, undefined, true, undefined, this)
|
|
104159
|
-
}, undefined, false, undefined, this)
|
|
104392
|
+
}, undefined, false, undefined, this),
|
|
104393
|
+
postRunPhases && (postRunPhases.acceptance || postRunPhases.regression || postRunPhases.review) && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104394
|
+
flexDirection: "column",
|
|
104395
|
+
paddingX: 1,
|
|
104396
|
+
paddingTop: 1,
|
|
104397
|
+
children: [
|
|
104398
|
+
/* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104399
|
+
dimColor: true,
|
|
104400
|
+
children: "Post-Run"
|
|
104401
|
+
}, undefined, false, undefined, this),
|
|
104402
|
+
postRunPhases.acceptance && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
|
|
104403
|
+
label: "acceptance",
|
|
104404
|
+
phase: postRunPhases.acceptance,
|
|
104405
|
+
compact: compact2
|
|
104406
|
+
}, undefined, false, undefined, this),
|
|
104407
|
+
postRunPhases.regression && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
|
|
104408
|
+
label: "regression",
|
|
104409
|
+
phase: postRunPhases.regression,
|
|
104410
|
+
compact: compact2
|
|
104411
|
+
}, undefined, false, undefined, this),
|
|
104412
|
+
postRunPhases.review && /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(PostRunPhaseRow, {
|
|
104413
|
+
label: "review",
|
|
104414
|
+
phase: postRunPhases.review,
|
|
104415
|
+
compact: compact2
|
|
104416
|
+
}, undefined, false, undefined, this)
|
|
104417
|
+
]
|
|
104418
|
+
}, undefined, true, undefined, this)
|
|
104160
104419
|
]
|
|
104161
104420
|
}, undefined, true, undefined, this);
|
|
104162
104421
|
}
|
|
104422
|
+
function PreRunPhaseRow({ label, phase, compact: compact2 }) {
|
|
104423
|
+
const icon = phase.status === "running" ? "\u25CF" : phase.status === "passed" ? "\u2713" : "\u2717";
|
|
104424
|
+
const color = phase.status === "running" ? "yellow" : phase.status === "passed" ? "green" : "red";
|
|
104425
|
+
const displayLabel = compact2 ? label.slice(0, 6) : label;
|
|
104426
|
+
return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104427
|
+
children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104428
|
+
color,
|
|
104429
|
+
children: [
|
|
104430
|
+
icon,
|
|
104431
|
+
" ",
|
|
104432
|
+
displayLabel
|
|
104433
|
+
]
|
|
104434
|
+
}, undefined, true, undefined, this)
|
|
104435
|
+
}, undefined, false, undefined, this);
|
|
104436
|
+
}
|
|
104437
|
+
function PostRunPhaseRow({ label, phase, compact: compact2 }) {
|
|
104438
|
+
const icon = phase.status === "running" ? ">" : phase.status === "passed" ? "[OK]" : "[X]";
|
|
104439
|
+
const color = phase.status === "running" ? "cyan" : phase.status === "passed" ? "green" : "red";
|
|
104440
|
+
return /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Box_default, {
|
|
104441
|
+
children: /* @__PURE__ */ jsx_dev_runtime5.jsxDEV(Text, {
|
|
104442
|
+
color,
|
|
104443
|
+
children: [
|
|
104444
|
+
icon,
|
|
104445
|
+
" ",
|
|
104446
|
+
compact2 ? label.slice(0, 3) : label
|
|
104447
|
+
]
|
|
104448
|
+
}, undefined, true, undefined, this)
|
|
104449
|
+
}, undefined, false, undefined, this);
|
|
104450
|
+
}
|
|
104163
104451
|
|
|
104164
104452
|
// src/tui/hooks/useAgentStreamEvents.ts
|
|
104165
104453
|
var import_react31 = __toESM(require_react(), 1);
|
|
104454
|
+
var RENDER_INTERVAL_MS = 150;
|
|
104166
104455
|
function useAgentStreamEvents(bus) {
|
|
104456
|
+
const activeCallsRef = import_react31.useRef(new Map);
|
|
104457
|
+
const inputTokensRef = import_react31.useRef(0);
|
|
104458
|
+
const outputTokensRef = import_react31.useRef(0);
|
|
104459
|
+
const lastTokensRef = import_react31.useRef(new Map);
|
|
104460
|
+
const dirtyRef = import_react31.useRef(false);
|
|
104167
104461
|
const [activeCalls, setActiveCalls] = import_react31.useState(new Map);
|
|
104462
|
+
const [inputTokens, setInputTokens] = import_react31.useState(0);
|
|
104463
|
+
const [outputTokens, setOutputTokens] = import_react31.useState(0);
|
|
104168
104464
|
import_react31.useEffect(() => {
|
|
104169
104465
|
if (!bus)
|
|
104170
104466
|
return;
|
|
104171
104467
|
const unsubscribe = bus.onAgentStream((event) => {
|
|
104172
|
-
|
|
104173
|
-
|
|
104174
|
-
|
|
104175
|
-
|
|
104176
|
-
|
|
104468
|
+
const next = new Map(activeCallsRef.current);
|
|
104469
|
+
switch (event.kind) {
|
|
104470
|
+
case "agent.call_started": {
|
|
104471
|
+
next.set(event.callId, {
|
|
104472
|
+
callId: event.callId,
|
|
104473
|
+
agentName: event.agentName,
|
|
104474
|
+
storyId: event.storyId,
|
|
104475
|
+
stage: event.stage,
|
|
104476
|
+
startedAt: event.timestamp,
|
|
104477
|
+
lastActivityAt: event.timestamp,
|
|
104478
|
+
messageUpdates: 0,
|
|
104479
|
+
thinkingUpdates: 0,
|
|
104480
|
+
usageUpdates: 0,
|
|
104481
|
+
toolCallUpdates: 0,
|
|
104482
|
+
status: "active",
|
|
104483
|
+
model: event.model
|
|
104484
|
+
});
|
|
104485
|
+
break;
|
|
104486
|
+
}
|
|
104487
|
+
case "agent.message_update": {
|
|
104488
|
+
const state = next.get(event.callId);
|
|
104489
|
+
if (state) {
|
|
104177
104490
|
next.set(event.callId, {
|
|
104178
|
-
|
|
104179
|
-
|
|
104180
|
-
|
|
104181
|
-
stage: event.stage,
|
|
104182
|
-
startedAt: now3,
|
|
104183
|
-
lastActivityAt: now3,
|
|
104184
|
-
messageUpdates: 0,
|
|
104185
|
-
thinkingUpdates: 0,
|
|
104186
|
-
usageUpdates: 0,
|
|
104187
|
-
toolCallUpdates: 0,
|
|
104188
|
-
status: "active",
|
|
104189
|
-
model: event.model
|
|
104491
|
+
...state,
|
|
104492
|
+
messageUpdates: state.messageUpdates + 1,
|
|
104493
|
+
lastActivityAt: event.timestamp
|
|
104190
104494
|
});
|
|
104191
|
-
break;
|
|
104192
|
-
}
|
|
104193
|
-
case "agent.message_update": {
|
|
104194
|
-
const state = next.get(event.callId);
|
|
104195
|
-
if (state) {
|
|
104196
|
-
next.set(event.callId, {
|
|
104197
|
-
...state,
|
|
104198
|
-
messageUpdates: state.messageUpdates + 1,
|
|
104199
|
-
lastActivityAt: event.timestamp
|
|
104200
|
-
});
|
|
104201
|
-
}
|
|
104202
|
-
break;
|
|
104203
104495
|
}
|
|
104204
|
-
|
|
104205
|
-
|
|
104206
|
-
|
|
104207
|
-
|
|
104208
|
-
|
|
104209
|
-
|
|
104210
|
-
|
|
104211
|
-
|
|
104212
|
-
|
|
104213
|
-
|
|
104496
|
+
break;
|
|
104497
|
+
}
|
|
104498
|
+
case "agent.thinking_update": {
|
|
104499
|
+
const state = next.get(event.callId);
|
|
104500
|
+
if (state) {
|
|
104501
|
+
next.set(event.callId, {
|
|
104502
|
+
...state,
|
|
104503
|
+
thinkingUpdates: state.thinkingUpdates + 1,
|
|
104504
|
+
lastActivityAt: event.timestamp
|
|
104505
|
+
});
|
|
104214
104506
|
}
|
|
104215
|
-
|
|
104216
|
-
|
|
104217
|
-
|
|
104218
|
-
|
|
104219
|
-
|
|
104220
|
-
|
|
104221
|
-
|
|
104222
|
-
|
|
104223
|
-
|
|
104224
|
-
|
|
104507
|
+
break;
|
|
104508
|
+
}
|
|
104509
|
+
case "agent.usage_update": {
|
|
104510
|
+
const state = next.get(event.callId);
|
|
104511
|
+
if (state) {
|
|
104512
|
+
next.set(event.callId, {
|
|
104513
|
+
...state,
|
|
104514
|
+
usageUpdates: state.usageUpdates + 1,
|
|
104515
|
+
lastActivityAt: event.timestamp
|
|
104516
|
+
});
|
|
104225
104517
|
}
|
|
104226
|
-
|
|
104227
|
-
const
|
|
104228
|
-
|
|
104229
|
-
|
|
104230
|
-
|
|
104231
|
-
|
|
104232
|
-
|
|
104233
|
-
|
|
104234
|
-
|
|
104235
|
-
|
|
104236
|
-
|
|
104518
|
+
{
|
|
104519
|
+
const last2 = lastTokensRef.current.get(event.callId) ?? { input: 0, output: 0 };
|
|
104520
|
+
const newInput = event.inputTokens ?? last2.input;
|
|
104521
|
+
const newOutput = event.outputTokens ?? last2.output;
|
|
104522
|
+
const deltaIn = newInput - last2.input;
|
|
104523
|
+
const deltaOut = newOutput - last2.output;
|
|
104524
|
+
lastTokensRef.current.set(event.callId, { input: newInput, output: newOutput });
|
|
104525
|
+
if (deltaIn > 0)
|
|
104526
|
+
inputTokensRef.current += deltaIn;
|
|
104527
|
+
if (deltaOut > 0)
|
|
104528
|
+
outputTokensRef.current += deltaOut;
|
|
104237
104529
|
}
|
|
104238
|
-
|
|
104239
|
-
|
|
104240
|
-
|
|
104241
|
-
|
|
104242
|
-
|
|
104243
|
-
|
|
104244
|
-
|
|
104530
|
+
break;
|
|
104531
|
+
}
|
|
104532
|
+
case "agent.tool_call_update": {
|
|
104533
|
+
const state = next.get(event.callId);
|
|
104534
|
+
if (state) {
|
|
104535
|
+
next.set(event.callId, {
|
|
104536
|
+
...state,
|
|
104537
|
+
toolCallUpdates: state.toolCallUpdates + 1,
|
|
104538
|
+
lastActivityAt: event.timestamp,
|
|
104539
|
+
lastToolName: event.toolName
|
|
104540
|
+
});
|
|
104245
104541
|
}
|
|
104246
|
-
|
|
104247
|
-
break;
|
|
104542
|
+
break;
|
|
104248
104543
|
}
|
|
104249
|
-
|
|
104250
|
-
|
|
104544
|
+
case "agent.call_ended": {
|
|
104545
|
+
next.delete(event.callId);
|
|
104546
|
+
lastTokensRef.current.delete(event.callId);
|
|
104547
|
+
break;
|
|
104548
|
+
}
|
|
104549
|
+
default:
|
|
104550
|
+
break;
|
|
104551
|
+
}
|
|
104552
|
+
activeCallsRef.current = next;
|
|
104553
|
+
dirtyRef.current = true;
|
|
104251
104554
|
});
|
|
104252
104555
|
return unsubscribe;
|
|
104253
104556
|
}, [bus]);
|
|
104254
|
-
|
|
104557
|
+
import_react31.useEffect(() => {
|
|
104558
|
+
const interval = setInterval(() => {
|
|
104559
|
+
if (!dirtyRef.current)
|
|
104560
|
+
return;
|
|
104561
|
+
dirtyRef.current = false;
|
|
104562
|
+
setActiveCalls(new Map(activeCallsRef.current));
|
|
104563
|
+
setInputTokens(inputTokensRef.current);
|
|
104564
|
+
setOutputTokens(outputTokensRef.current);
|
|
104565
|
+
}, RENDER_INTERVAL_MS);
|
|
104566
|
+
return () => clearInterval(interval);
|
|
104567
|
+
}, []);
|
|
104568
|
+
return { activeCalls, inputTokens, outputTokens };
|
|
104255
104569
|
}
|
|
104256
104570
|
|
|
104257
104571
|
// src/tui/hooks/useKeyboard.ts
|
|
@@ -104311,20 +104625,13 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104311
104625
|
const [state, setState] = import_react32.useState(() => ({
|
|
104312
104626
|
stories: initialStories,
|
|
104313
104627
|
totalCost: 0,
|
|
104314
|
-
elapsedMs: 0,
|
|
104315
104628
|
runPaused: false,
|
|
104316
104629
|
runErrored: false,
|
|
104317
|
-
escalationLog: []
|
|
104630
|
+
escalationLog: [],
|
|
104631
|
+
storySteps: {},
|
|
104632
|
+
postRunPhases: {}
|
|
104318
104633
|
}));
|
|
104319
|
-
const startTimeRef = import_react32.useRef(Date.now());
|
|
104320
104634
|
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
104635
|
const unsubStarted = pipelineEventBus.on("story:started", (event) => {
|
|
104329
104636
|
setState((prev) => ({
|
|
104330
104637
|
...prev,
|
|
@@ -104347,18 +104654,19 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104347
104654
|
return s;
|
|
104348
104655
|
});
|
|
104349
104656
|
const totalCost = newStories.reduce((sum2, s) => sum2 + (s.cost ?? 0), 0);
|
|
104350
|
-
|
|
104657
|
+
const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
|
|
104658
|
+
return { ...prev, stories: newStories, totalCost, storySteps: remainingSteps };
|
|
104351
104659
|
});
|
|
104352
104660
|
});
|
|
104353
104661
|
const unsubFailed = pipelineEventBus.on("story:failed", (event) => {
|
|
104354
|
-
setState((prev) =>
|
|
104355
|
-
...prev
|
|
104356
|
-
|
|
104357
|
-
...
|
|
104358
|
-
status: "failed",
|
|
104359
|
-
|
|
104360
|
-
}
|
|
104361
|
-
})
|
|
104662
|
+
setState((prev) => {
|
|
104663
|
+
const { [event.storyId]: _removed, ...remainingSteps } = prev.storySteps;
|
|
104664
|
+
return {
|
|
104665
|
+
...prev,
|
|
104666
|
+
stories: prev.stories.map((s) => s.story.id === event.storyId ? { ...s, status: "failed", failureReason: event.reason } : s),
|
|
104667
|
+
storySteps: remainingSteps
|
|
104668
|
+
};
|
|
104669
|
+
});
|
|
104362
104670
|
});
|
|
104363
104671
|
const unsubSkipped = pipelineEventBus.on("story:skipped", (event) => {
|
|
104364
104672
|
setState((prev) => ({
|
|
@@ -104392,7 +104700,6 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104392
104700
|
setState((prev) => ({ ...prev, runPaused: false }));
|
|
104393
104701
|
});
|
|
104394
104702
|
const unsubCompleted2 = pipelineEventBus.on("run:completed", (event) => {
|
|
104395
|
-
clearInterval(timer);
|
|
104396
104703
|
const summary = {
|
|
104397
104704
|
totalStories: event.totalStories,
|
|
104398
104705
|
passedStories: event.passedStories,
|
|
@@ -104404,7 +104711,6 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104404
104711
|
};
|
|
104405
104712
|
setState((prev) => ({
|
|
104406
104713
|
...prev,
|
|
104407
|
-
elapsedMs: event.durationMs,
|
|
104408
104714
|
runSummary: summary,
|
|
104409
104715
|
totalCost: event.totalCost ?? prev.totalCost
|
|
104410
104716
|
}));
|
|
@@ -104412,8 +104718,28 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104412
104718
|
const unsubErrored = pipelineEventBus.on("run:errored", (_event) => {
|
|
104413
104719
|
setState((prev) => ({ ...prev, runErrored: true }));
|
|
104414
104720
|
});
|
|
104721
|
+
const unsubStep = pipelineEventBus.on("story:step", (event) => {
|
|
104722
|
+
setState((prev) => ({
|
|
104723
|
+
...prev,
|
|
104724
|
+
storySteps: { ...prev.storySteps, [event.storyId]: event.step }
|
|
104725
|
+
}));
|
|
104726
|
+
});
|
|
104727
|
+
const unsubPostRunStarted = pipelineEventBus.on("postrun:phase:started", (event) => {
|
|
104728
|
+
setState((prev) => ({
|
|
104729
|
+
...prev,
|
|
104730
|
+
postRunPhases: { ...prev.postRunPhases, [event.phase]: { status: "running" } }
|
|
104731
|
+
}));
|
|
104732
|
+
});
|
|
104733
|
+
const unsubPostRunCompleted = pipelineEventBus.on("postrun:phase:completed", (event) => {
|
|
104734
|
+
setState((prev) => ({
|
|
104735
|
+
...prev,
|
|
104736
|
+
postRunPhases: {
|
|
104737
|
+
...prev.postRunPhases,
|
|
104738
|
+
[event.phase]: { status: event.passed ? "passed" : "failed" }
|
|
104739
|
+
}
|
|
104740
|
+
}));
|
|
104741
|
+
});
|
|
104415
104742
|
return () => {
|
|
104416
|
-
clearInterval(timer);
|
|
104417
104743
|
unsubStarted();
|
|
104418
104744
|
unsubCompleted();
|
|
104419
104745
|
unsubFailed();
|
|
@@ -104424,6 +104750,9 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104424
104750
|
unsubResumed();
|
|
104425
104751
|
unsubCompleted2();
|
|
104426
104752
|
unsubErrored();
|
|
104753
|
+
unsubStep();
|
|
104754
|
+
unsubPostRunStarted();
|
|
104755
|
+
unsubPostRunCompleted();
|
|
104427
104756
|
};
|
|
104428
104757
|
}, []);
|
|
104429
104758
|
return state;
|
|
@@ -104431,14 +104760,33 @@ function usePipelineBusEvents(initialStories) {
|
|
|
104431
104760
|
|
|
104432
104761
|
// src/tui/hooks/usePipelineEvents.ts
|
|
104433
104762
|
var import_react33 = __toESM(require_react(), 1);
|
|
104763
|
+
var PRE_RUN_STAGES = new Set(["acceptance-setup"]);
|
|
104434
104764
|
function usePipelineEvents(events) {
|
|
104435
104765
|
const [currentStage, setCurrentStage] = import_react33.useState(undefined);
|
|
104766
|
+
const [preRunPhases, setPreRunPhases] = import_react33.useState({});
|
|
104436
104767
|
import_react33.useEffect(() => {
|
|
104437
|
-
const onStageEnter = (stage) =>
|
|
104768
|
+
const onStageEnter = (stage) => {
|
|
104769
|
+
setCurrentStage(stage);
|
|
104770
|
+
if (PRE_RUN_STAGES.has(stage)) {
|
|
104771
|
+
setPreRunPhases((prev) => ({ ...prev, [stage]: { status: "running" } }));
|
|
104772
|
+
}
|
|
104773
|
+
};
|
|
104774
|
+
const onStageExit = (stage, result2) => {
|
|
104775
|
+
if (PRE_RUN_STAGES.has(stage)) {
|
|
104776
|
+
setPreRunPhases((prev) => ({
|
|
104777
|
+
...prev,
|
|
104778
|
+
[stage]: { status: result2.action === "fail" ? "failed" : "passed" }
|
|
104779
|
+
}));
|
|
104780
|
+
}
|
|
104781
|
+
};
|
|
104438
104782
|
events.on("stage:enter", onStageEnter);
|
|
104439
|
-
|
|
104783
|
+
events.on("stage:exit", onStageExit);
|
|
104784
|
+
return () => {
|
|
104785
|
+
events.off("stage:enter", onStageEnter);
|
|
104786
|
+
events.off("stage:exit", onStageExit);
|
|
104787
|
+
};
|
|
104440
104788
|
}, [events]);
|
|
104441
|
-
return { currentStage };
|
|
104789
|
+
return { currentStage, preRunPhases };
|
|
104442
104790
|
}
|
|
104443
104791
|
|
|
104444
104792
|
// src/tui/hooks/usePty.ts
|
|
@@ -104542,6 +104890,8 @@ function usePty(options) {
|
|
|
104542
104890
|
|
|
104543
104891
|
// src/tui/App.tsx
|
|
104544
104892
|
var jsx_dev_runtime6 = __toESM(require_jsx_dev_runtime(), 1);
|
|
104893
|
+
var MemoStoriesPanel = import_react35.memo(StoriesPanel);
|
|
104894
|
+
var MemoLiveActivityPanel = import_react35.memo(LiveActivityPanel);
|
|
104545
104895
|
function formatElapsed(ms) {
|
|
104546
104896
|
const mins = Math.floor(ms / 60000);
|
|
104547
104897
|
const secs = Math.floor(ms % 60000 / 1000);
|
|
@@ -104550,8 +104900,16 @@ function formatElapsed(ms) {
|
|
|
104550
104900
|
function formatCost3(cost) {
|
|
104551
104901
|
return `$${cost.toFixed(4)}`;
|
|
104552
104902
|
}
|
|
104903
|
+
function formatTokens(n) {
|
|
104904
|
+
if (n >= 1e6)
|
|
104905
|
+
return `${(n / 1e6).toFixed(1)}M`;
|
|
104906
|
+
if (n >= 1000)
|
|
104907
|
+
return `${(n / 1000).toFixed(1)}k`;
|
|
104908
|
+
return `${n}`;
|
|
104909
|
+
}
|
|
104553
104910
|
function App2({
|
|
104554
104911
|
feature,
|
|
104912
|
+
version: version2,
|
|
104555
104913
|
stories: initialStories,
|
|
104556
104914
|
events,
|
|
104557
104915
|
queueFilePath,
|
|
@@ -104560,19 +104918,29 @@ function App2({
|
|
|
104560
104918
|
}) {
|
|
104561
104919
|
const layout = useLayout();
|
|
104562
104920
|
const busState = usePipelineBusEvents(initialStories);
|
|
104563
|
-
const { currentStage } = usePipelineEvents(events);
|
|
104921
|
+
const { currentStage, preRunPhases } = usePipelineEvents(events);
|
|
104564
104922
|
const { exit } = use_app_default();
|
|
104923
|
+
const startTimeRef = import_react35.useRef(Date.now());
|
|
104924
|
+
const [elapsedMs, setElapsedMs] = import_react35.useState(0);
|
|
104925
|
+
import_react35.useEffect(() => {
|
|
104926
|
+
if (busState.runSummary)
|
|
104927
|
+
return;
|
|
104928
|
+
const timer = setInterval(() => setElapsedMs(Date.now() - startTimeRef.current), 1000);
|
|
104929
|
+
return () => clearInterval(timer);
|
|
104930
|
+
}, [busState.runSummary]);
|
|
104565
104931
|
const [focus, setFocus] = import_react35.useState("stories" /* Stories */);
|
|
104566
104932
|
const [showHelp, setShowHelp] = import_react35.useState(false);
|
|
104567
104933
|
const [showCost, setShowCost] = import_react35.useState(false);
|
|
104568
104934
|
const [showQuitConfirm, setShowQuitConfirm] = import_react35.useState(false);
|
|
104569
104935
|
const [showAbortConfirm, setShowAbortConfirm] = import_react35.useState(false);
|
|
104570
104936
|
const { handle: ptyHandle } = usePty(ptyOptions ?? null);
|
|
104571
|
-
const { activeCalls } = useAgentStreamEvents(agentStreamEvents);
|
|
104937
|
+
const { activeCalls, inputTokens, outputTokens } = useAgentStreamEvents(agentStreamEvents);
|
|
104572
104938
|
const isRunComplete = !!busState.runSummary;
|
|
104573
104939
|
const runningStories = busState.stories.filter((s) => s.status === "running");
|
|
104574
104940
|
const isParallel = runningStories.length > 1;
|
|
104575
104941
|
const currentRunningStory = runningStories[0];
|
|
104942
|
+
const runningPostRunPhase = busState.postRunPhases.acceptance?.status === "running" ? "post-run: acceptance" : busState.postRunPhases.regression?.status === "running" ? "post-run: regression" : busState.postRunPhases.review?.status === "running" ? "post-run: review" : undefined;
|
|
104943
|
+
const currentPhaseLabel = runningPostRunPhase ?? currentStage;
|
|
104576
104944
|
const runErroredForPanel = busState.runErrored ? "Run encountered an error" : undefined;
|
|
104577
104945
|
const handleKeyboardAction = async (action) => {
|
|
104578
104946
|
switch (action.type) {
|
|
@@ -104655,10 +105023,17 @@ function App2({
|
|
|
104655
105023
|
});
|
|
104656
105024
|
const isTooSmall = layout.width < MIN_TERMINAL_WIDTH;
|
|
104657
105025
|
const activeCount = runningStories.length;
|
|
105026
|
+
const displayElapsed = busState.runSummary ? busState.runSummary.durationMs : elapsedMs;
|
|
105027
|
+
const tokenParts = [
|
|
105028
|
+
inputTokens > 0 ? `${formatTokens(inputTokens)} in` : null,
|
|
105029
|
+
outputTokens > 0 ? `${formatTokens(outputTokens)} out` : null
|
|
105030
|
+
].filter(Boolean);
|
|
105031
|
+
const tokensStr = tokenParts.length > 0 ? tokenParts.join(" / ") : null;
|
|
104658
105032
|
const headerRight = [
|
|
104659
105033
|
activeCount > 0 ? `${activeCount} running` : null,
|
|
104660
105034
|
formatCost3(busState.totalCost),
|
|
104661
|
-
|
|
105035
|
+
tokensStr,
|
|
105036
|
+
formatElapsed(displayElapsed)
|
|
104662
105037
|
].filter(Boolean).join(" \xB7 ");
|
|
104663
105038
|
const maxHeight = layout.mode === "single" ? COMPACT_MAX_VISIBLE_STORIES : MAX_VISIBLE_STORIES;
|
|
104664
105039
|
return /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Box_default, {
|
|
@@ -104677,7 +105052,15 @@ function App2({
|
|
|
104677
105052
|
color: "cyan",
|
|
104678
105053
|
children: [
|
|
104679
105054
|
"nax run \u2014 ",
|
|
104680
|
-
feature
|
|
105055
|
+
feature,
|
|
105056
|
+
version2 ? /* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
105057
|
+
dimColor: true,
|
|
105058
|
+
color: "cyan",
|
|
105059
|
+
children: [
|
|
105060
|
+
" ",
|
|
105061
|
+
version2
|
|
105062
|
+
]
|
|
105063
|
+
}, undefined, true, undefined, this) : null
|
|
104681
105064
|
]
|
|
104682
105065
|
}, undefined, true, undefined, this),
|
|
104683
105066
|
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(Text, {
|
|
@@ -104704,18 +105087,22 @@ function App2({
|
|
|
104704
105087
|
flexDirection: layout.mode === "single" ? "column" : "row",
|
|
104705
105088
|
flexGrow: 1,
|
|
104706
105089
|
children: [
|
|
104707
|
-
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(
|
|
105090
|
+
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoStoriesPanel, {
|
|
104708
105091
|
stories: busState.stories,
|
|
105092
|
+
preRunPhases,
|
|
105093
|
+
postRunPhases: busState.postRunPhases,
|
|
104709
105094
|
width: layout.mode === "single" ? layout.width : layout.storiesPanelWidth,
|
|
104710
105095
|
compact: layout.mode === "single",
|
|
104711
105096
|
maxHeight
|
|
104712
105097
|
}, undefined, false, undefined, this),
|
|
104713
|
-
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(
|
|
105098
|
+
/* @__PURE__ */ jsx_dev_runtime6.jsxDEV(MemoLiveActivityPanel, {
|
|
104714
105099
|
focused: focus === "agent" /* Agent */,
|
|
104715
105100
|
activeCalls,
|
|
105101
|
+
storySteps: busState.storySteps,
|
|
104716
105102
|
runSummary: busState.runSummary,
|
|
104717
105103
|
runErrored: runErroredForPanel,
|
|
104718
|
-
escalationLog: busState.escalationLog
|
|
105104
|
+
escalationLog: busState.escalationLog,
|
|
105105
|
+
currentStage: currentPhaseLabel
|
|
104719
105106
|
}, undefined, false, undefined, this)
|
|
104720
105107
|
]
|
|
104721
105108
|
}, undefined, true, undefined, this),
|
|
@@ -104881,7 +105268,7 @@ Next: nax generate --package ${options.package}`));
|
|
|
104881
105268
|
}
|
|
104882
105269
|
return;
|
|
104883
105270
|
}
|
|
104884
|
-
const naxDir =
|
|
105271
|
+
const naxDir = join84(workdir, ".nax");
|
|
104885
105272
|
if (existsSync36(naxDir) && !options.force) {
|
|
104886
105273
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
104887
105274
|
return;
|
|
@@ -104910,11 +105297,11 @@ Next: nax generate --package ${options.package}`));
|
|
|
104910
105297
|
}
|
|
104911
105298
|
}
|
|
104912
105299
|
}
|
|
104913
|
-
mkdirSync7(
|
|
104914
|
-
mkdirSync7(
|
|
105300
|
+
mkdirSync7(join84(naxDir, "features"), { recursive: true });
|
|
105301
|
+
mkdirSync7(join84(naxDir, "hooks"), { recursive: true });
|
|
104915
105302
|
const initConfig = options.name ? { ...DEFAULT_CONFIG, name: options.name } : DEFAULT_CONFIG;
|
|
104916
|
-
await Bun.write(
|
|
104917
|
-
await Bun.write(
|
|
105303
|
+
await Bun.write(join84(naxDir, "config.json"), JSON.stringify(initConfig, null, 2));
|
|
105304
|
+
await Bun.write(join84(naxDir, "hooks.json"), JSON.stringify({
|
|
104918
105305
|
hooks: {
|
|
104919
105306
|
"on-start": { command: 'echo "nax started: $NAX_FEATURE"', enabled: false },
|
|
104920
105307
|
"on-complete": { command: 'echo "nax complete: $NAX_FEATURE"', enabled: false },
|
|
@@ -104922,12 +105309,12 @@ Next: nax generate --package ${options.package}`));
|
|
|
104922
105309
|
"on-error": { command: 'echo "nax error: $NAX_REASON"', enabled: false }
|
|
104923
105310
|
}
|
|
104924
105311
|
}, null, 2));
|
|
104925
|
-
await Bun.write(
|
|
105312
|
+
await Bun.write(join84(naxDir, ".gitignore"), `# nax temp files
|
|
104926
105313
|
*.tmp
|
|
104927
105314
|
.paused.json
|
|
104928
105315
|
.nax-verifier-verdict.json
|
|
104929
105316
|
`);
|
|
104930
|
-
await Bun.write(
|
|
105317
|
+
await Bun.write(join84(naxDir, "context.md"), `# Project Context
|
|
104931
105318
|
|
|
104932
105319
|
This document defines coding standards, architectural decisions, and forbidden patterns for this project.
|
|
104933
105320
|
Run \`nax generate\` to regenerate agent config files (CLAUDE.md, AGENTS.md, .cursorrules, etc.) from this file.
|
|
@@ -105057,8 +105444,8 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105057
105444
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
105058
105445
|
process.exit(1);
|
|
105059
105446
|
}
|
|
105060
|
-
const featureDir =
|
|
105061
|
-
const prdPath =
|
|
105447
|
+
const featureDir = join84(naxDir, "features", options.feature);
|
|
105448
|
+
const prdPath = join84(featureDir, "prd.json");
|
|
105062
105449
|
if (options.plan && options.from) {
|
|
105063
105450
|
if (existsSync36(prdPath) && !options.force) {
|
|
105064
105451
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
@@ -105080,10 +105467,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105080
105467
|
}
|
|
105081
105468
|
}
|
|
105082
105469
|
try {
|
|
105083
|
-
const planLogDir =
|
|
105470
|
+
const planLogDir = join84(featureDir, "plan");
|
|
105084
105471
|
mkdirSync7(planLogDir, { recursive: true });
|
|
105085
105472
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105086
|
-
const planLogPath =
|
|
105473
|
+
const planLogPath = join84(planLogDir, `${planLogId}.jsonl`);
|
|
105087
105474
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
105088
105475
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
105089
105476
|
console.log(source_default.dim(" [Planning phase: generating PRD from spec]"));
|
|
@@ -105129,10 +105516,10 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105129
105516
|
resetLogger();
|
|
105130
105517
|
const projectKey = config2.name?.trim() || basename16(workdir);
|
|
105131
105518
|
const outputDir = projectOutputDir(projectKey, config2.outputDir);
|
|
105132
|
-
const runsDir =
|
|
105519
|
+
const runsDir = join84(outputDir, "features", options.feature, "runs");
|
|
105133
105520
|
mkdirSync7(runsDir, { recursive: true });
|
|
105134
105521
|
const runId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105135
|
-
const logFilePath =
|
|
105522
|
+
const logFilePath = join84(runsDir, `${runId}.jsonl`);
|
|
105136
105523
|
const isTTY = process.stdout.isTTY ?? false;
|
|
105137
105524
|
const headlessFlag = options.headless ?? false;
|
|
105138
105525
|
const headlessEnv = process.env.NAX_HEADLESS === "1";
|
|
@@ -105150,7 +105537,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105150
105537
|
config2.agent.default = options.agent;
|
|
105151
105538
|
}
|
|
105152
105539
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
105153
|
-
const globalNaxDir =
|
|
105540
|
+
const globalNaxDir = join84(homedir3(), ".nax");
|
|
105154
105541
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
105155
105542
|
const eventEmitter = new PipelineEventEmitter;
|
|
105156
105543
|
const agentStreamEvents = useHeadless ? undefined : new AgentStreamEventBus;
|
|
@@ -105165,16 +105552,17 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105165
105552
|
}));
|
|
105166
105553
|
tuiInstance = renderTui({
|
|
105167
105554
|
feature: options.feature,
|
|
105555
|
+
version: NAX_BUILD_INFO,
|
|
105168
105556
|
stories: initialStories,
|
|
105169
105557
|
events: eventEmitter,
|
|
105170
105558
|
ptyOptions: null,
|
|
105171
105559
|
agentStreamEvents,
|
|
105172
|
-
queueFilePath:
|
|
105560
|
+
queueFilePath: join84(workdir, ".queue.txt")
|
|
105173
105561
|
});
|
|
105174
105562
|
} else {
|
|
105175
105563
|
console.log(source_default.dim(" [Headless mode \u2014 pipe output]"));
|
|
105176
105564
|
}
|
|
105177
|
-
const statusFilePath =
|
|
105565
|
+
const statusFilePath = join84(outputDir, "status.json");
|
|
105178
105566
|
let parallel;
|
|
105179
105567
|
if (options.parallel !== undefined) {
|
|
105180
105568
|
parallel = Number.parseInt(options.parallel, 10);
|
|
@@ -105201,7 +105589,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
105201
105589
|
skipPrecheck: options.skipPrecheck ?? false,
|
|
105202
105590
|
agentStreamEvents
|
|
105203
105591
|
});
|
|
105204
|
-
const latestSymlink =
|
|
105592
|
+
const latestSymlink = join84(runsDir, "latest.jsonl");
|
|
105205
105593
|
try {
|
|
105206
105594
|
if (existsSync36(latestSymlink)) {
|
|
105207
105595
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
@@ -105262,9 +105650,9 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
105262
105650
|
console.error(source_default.red("nax not initialized. Run: nax init"));
|
|
105263
105651
|
process.exit(1);
|
|
105264
105652
|
}
|
|
105265
|
-
const featureDir =
|
|
105653
|
+
const featureDir = join84(naxDir, "features", name);
|
|
105266
105654
|
mkdirSync7(featureDir, { recursive: true });
|
|
105267
|
-
await Bun.write(
|
|
105655
|
+
await Bun.write(join84(featureDir, "spec.md"), `# Feature: ${name}
|
|
105268
105656
|
|
|
105269
105657
|
## Overview
|
|
105270
105658
|
|
|
@@ -105297,7 +105685,7 @@ features.command("create <name>").description("Create a new feature").option("-d
|
|
|
105297
105685
|
|
|
105298
105686
|
<!-- What this feature explicitly does NOT cover. -->
|
|
105299
105687
|
`);
|
|
105300
|
-
await Bun.write(
|
|
105688
|
+
await Bun.write(join84(featureDir, "progress.txt"), `# Progress: ${name}
|
|
105301
105689
|
|
|
105302
105690
|
Created: ${new Date().toISOString()}
|
|
105303
105691
|
|
|
@@ -105323,7 +105711,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
105323
105711
|
console.error(source_default.red("nax not initialized."));
|
|
105324
105712
|
process.exit(1);
|
|
105325
105713
|
}
|
|
105326
|
-
const featuresDir =
|
|
105714
|
+
const featuresDir = join84(naxDir, "features");
|
|
105327
105715
|
if (!existsSync36(featuresDir)) {
|
|
105328
105716
|
console.log(source_default.dim("No features yet."));
|
|
105329
105717
|
return;
|
|
@@ -105338,7 +105726,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
105338
105726
|
Features:
|
|
105339
105727
|
`));
|
|
105340
105728
|
for (const name of entries) {
|
|
105341
|
-
const prdPath =
|
|
105729
|
+
const prdPath = join84(featuresDir, name, "prd.json");
|
|
105342
105730
|
if (existsSync36(prdPath)) {
|
|
105343
105731
|
const prd = await loadPRD(prdPath);
|
|
105344
105732
|
const c = countStories(prd);
|
|
@@ -105373,10 +105761,10 @@ Use: nax plan -f <feature> --from <spec>`));
|
|
|
105373
105761
|
cliOverrides.profile = options.profile;
|
|
105374
105762
|
}
|
|
105375
105763
|
const config2 = await loadConfig(workdir, cliOverrides);
|
|
105376
|
-
const featureLogDir =
|
|
105764
|
+
const featureLogDir = join84(naxDir, "features", options.feature, "plan");
|
|
105377
105765
|
mkdirSync7(featureLogDir, { recursive: true });
|
|
105378
105766
|
const planLogId = new Date().toISOString().replace(/:/g, "-").replace(/\..+/, "");
|
|
105379
|
-
const planLogPath =
|
|
105767
|
+
const planLogPath = join84(featureLogDir, `${planLogId}.jsonl`);
|
|
105380
105768
|
initLogger({ level: "info", filePath: planLogPath, useChalk: false, headless: true });
|
|
105381
105769
|
console.log(source_default.dim(` [Plan log: ${planLogPath}]`));
|
|
105382
105770
|
try {
|