@nathapp/nax 0.54.5 → 0.54.7
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 +160 -50
- package/package.json +2 -2
package/dist/nax.js
CHANGED
|
@@ -19826,7 +19826,7 @@ async function saveAcpSession(workdir, featureName, storyId, sessionName, agentN
|
|
|
19826
19826
|
getSafeLogger()?.warn("acp-adapter", "Failed to save session to sidecar", { error: String(err) });
|
|
19827
19827
|
}
|
|
19828
19828
|
}
|
|
19829
|
-
async function clearAcpSession(workdir, featureName, storyId) {
|
|
19829
|
+
async function clearAcpSession(workdir, featureName, storyId, sessionRole) {
|
|
19830
19830
|
try {
|
|
19831
19831
|
const path = acpSessionsPath(workdir, featureName);
|
|
19832
19832
|
let data = {};
|
|
@@ -19836,7 +19836,8 @@ async function clearAcpSession(workdir, featureName, storyId) {
|
|
|
19836
19836
|
} catch {
|
|
19837
19837
|
return;
|
|
19838
19838
|
}
|
|
19839
|
-
|
|
19839
|
+
const sidecarKey = sessionRole ? `${storyId}:${sessionRole}` : storyId;
|
|
19840
|
+
delete data[sidecarKey];
|
|
19840
19841
|
await Bun.write(path, JSON.stringify(data, null, 2));
|
|
19841
19842
|
} catch (err) {
|
|
19842
19843
|
getSafeLogger()?.warn("acp-adapter", "Failed to clear session from sidecar", { error: String(err) });
|
|
@@ -20024,7 +20025,8 @@ class AcpAgentAdapter {
|
|
|
20024
20025
|
await client.start();
|
|
20025
20026
|
let sessionName = options.acpSessionName;
|
|
20026
20027
|
if (!sessionName && options.featureName && options.storyId) {
|
|
20027
|
-
|
|
20028
|
+
const sidecarKey = options.sessionRole ? `${options.storyId}:${options.sessionRole}` : options.storyId;
|
|
20029
|
+
sessionName = await readAcpSession(options.workdir, options.featureName, sidecarKey) ?? undefined;
|
|
20028
20030
|
}
|
|
20029
20031
|
sessionName ??= buildSessionName(options.workdir, options.featureName, options.storyId, options.sessionRole);
|
|
20030
20032
|
const resolvedPerm = resolvePermissions(options.config, options.pipelineStage ?? "run");
|
|
@@ -20035,7 +20037,8 @@ class AcpAgentAdapter {
|
|
|
20035
20037
|
});
|
|
20036
20038
|
const session = await ensureAcpSession(client, sessionName, this.name, permissionMode);
|
|
20037
20039
|
if (options.featureName && options.storyId) {
|
|
20038
|
-
|
|
20040
|
+
const sidecarKey = options.sessionRole ? `${options.storyId}:${options.sessionRole}` : options.storyId;
|
|
20041
|
+
await saveAcpSession(options.workdir, options.featureName, sidecarKey, sessionName, this.name);
|
|
20039
20042
|
}
|
|
20040
20043
|
let lastResponse = null;
|
|
20041
20044
|
let timedOut = false;
|
|
@@ -20072,7 +20075,8 @@ class AcpAgentAdapter {
|
|
|
20072
20075
|
totalExactCostUsd = (totalExactCostUsd ?? 0) + lastResponse.exactCostUsd;
|
|
20073
20076
|
}
|
|
20074
20077
|
const outputText = extractOutput(lastResponse);
|
|
20075
|
-
const
|
|
20078
|
+
const isEndTurn = lastResponse.stopReason === "end_turn";
|
|
20079
|
+
const question = isEndTurn ? extractQuestion(outputText) : null;
|
|
20076
20080
|
if (!question || !options.interactionBridge)
|
|
20077
20081
|
break;
|
|
20078
20082
|
getSafeLogger()?.debug("acp-adapter", "Agent asked question, routing to interactionBridge", { question });
|
|
@@ -22348,7 +22352,7 @@ var package_default;
|
|
|
22348
22352
|
var init_package = __esm(() => {
|
|
22349
22353
|
package_default = {
|
|
22350
22354
|
name: "@nathapp/nax",
|
|
22351
|
-
version: "0.54.
|
|
22355
|
+
version: "0.54.7",
|
|
22352
22356
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22353
22357
|
type: "module",
|
|
22354
22358
|
bin: {
|
|
@@ -22361,7 +22365,7 @@ var init_package = __esm(() => {
|
|
|
22361
22365
|
typecheck: "bun x tsc --noEmit",
|
|
22362
22366
|
lint: "bun x biome check src/ bin/",
|
|
22363
22367
|
release: "bun scripts/release.ts",
|
|
22364
|
-
test: "bun test test/ --timeout=60000",
|
|
22368
|
+
test: "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
|
|
22365
22369
|
"test:watch": "bun test --watch",
|
|
22366
22370
|
"test:unit": "bun test ./test/unit/ --timeout=60000",
|
|
22367
22371
|
"test:integration": "bun test ./test/integration/ --timeout=60000",
|
|
@@ -22425,8 +22429,8 @@ var init_version = __esm(() => {
|
|
|
22425
22429
|
NAX_VERSION = package_default.version;
|
|
22426
22430
|
NAX_COMMIT = (() => {
|
|
22427
22431
|
try {
|
|
22428
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22429
|
-
return "
|
|
22432
|
+
if (/^[0-9a-f]{6,10}$/.test("ccfd70c"))
|
|
22433
|
+
return "ccfd70c";
|
|
22430
22434
|
} catch {}
|
|
22431
22435
|
try {
|
|
22432
22436
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -24866,14 +24870,34 @@ async function collectDiff(workdir, storyGitRef) {
|
|
|
24866
24870
|
}
|
|
24867
24871
|
return stdout;
|
|
24868
24872
|
}
|
|
24869
|
-
function
|
|
24873
|
+
async function collectDiffStat(workdir, storyGitRef) {
|
|
24874
|
+
const proc = _semanticDeps.spawn({
|
|
24875
|
+
cmd: ["git", "diff", "--stat", `${storyGitRef}..HEAD`],
|
|
24876
|
+
cwd: workdir,
|
|
24877
|
+
stdout: "pipe",
|
|
24878
|
+
stderr: "pipe"
|
|
24879
|
+
});
|
|
24880
|
+
const [exitCode, stdout] = await Promise.all([
|
|
24881
|
+
proc.exited,
|
|
24882
|
+
new Response(proc.stdout).text(),
|
|
24883
|
+
new Response(proc.stderr).text()
|
|
24884
|
+
]);
|
|
24885
|
+
return exitCode === 0 ? stdout.trim() : "";
|
|
24886
|
+
}
|
|
24887
|
+
function truncateDiff(diff, stat) {
|
|
24870
24888
|
if (diff.length <= DIFF_CAP_BYTES) {
|
|
24871
24889
|
return diff;
|
|
24872
24890
|
}
|
|
24873
24891
|
const truncated = diff.slice(0, DIFF_CAP_BYTES);
|
|
24874
|
-
const
|
|
24875
|
-
|
|
24876
|
-
|
|
24892
|
+
const visibleFiles = (truncated.match(/^diff --git/gm) ?? []).length;
|
|
24893
|
+
const totalFiles = (diff.match(/^diff --git/gm) ?? []).length;
|
|
24894
|
+
const statPreamble = stat ? `## File Summary (all changed files)
|
|
24895
|
+
${stat}
|
|
24896
|
+
|
|
24897
|
+
## Diff (truncated \u2014 ${visibleFiles}/${totalFiles} files shown)
|
|
24898
|
+
` : "";
|
|
24899
|
+
return `${statPreamble}${truncated}
|
|
24900
|
+
... (truncated at ${DIFF_CAP_BYTES} bytes, showing ${visibleFiles}/${totalFiles} files)`;
|
|
24877
24901
|
}
|
|
24878
24902
|
function buildPrompt(story, semanticConfig, diff) {
|
|
24879
24903
|
const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
@@ -24980,7 +25004,9 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
24980
25004
|
}
|
|
24981
25005
|
logger?.info("review", "Running semantic check", { storyId: story.id, modelTier: semanticConfig.modelTier });
|
|
24982
25006
|
const rawDiff = await collectDiff(workdir, storyGitRef);
|
|
24983
|
-
const
|
|
25007
|
+
const needsTruncation = rawDiff.length > DIFF_CAP_BYTES;
|
|
25008
|
+
const stat = needsTruncation ? await collectDiffStat(workdir, storyGitRef) : undefined;
|
|
25009
|
+
const diff = truncateDiff(rawDiff, stat);
|
|
24984
25010
|
const agent = modelResolver(semanticConfig.modelTier);
|
|
24985
25011
|
if (!agent) {
|
|
24986
25012
|
logger?.warn("semantic", "No agent available for semantic review \u2014 skipping", {
|
|
@@ -25016,6 +25042,20 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
25016
25042
|
}
|
|
25017
25043
|
const parsed = parseLLMResponse(rawResponse);
|
|
25018
25044
|
if (!parsed) {
|
|
25045
|
+
const looksLikeFail = /"passed"\s*:\s*false/.test(rawResponse);
|
|
25046
|
+
if (looksLikeFail) {
|
|
25047
|
+
logger?.warn("semantic", "LLM returned truncated JSON with passed:false \u2014 treating as failure", {
|
|
25048
|
+
rawResponse: rawResponse.slice(0, 200)
|
|
25049
|
+
});
|
|
25050
|
+
return {
|
|
25051
|
+
check: "semantic",
|
|
25052
|
+
success: false,
|
|
25053
|
+
command: "",
|
|
25054
|
+
exitCode: 1,
|
|
25055
|
+
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
25056
|
+
durationMs: Date.now() - startTime
|
|
25057
|
+
};
|
|
25058
|
+
}
|
|
25019
25059
|
logger?.warn("semantic", "LLM returned invalid JSON \u2014 fail-open", { rawResponse: rawResponse.slice(0, 200) });
|
|
25020
25060
|
return {
|
|
25021
25061
|
check: "semantic",
|
|
@@ -25068,7 +25108,7 @@ ${formatFindings(parsed.findings)}`;
|
|
|
25068
25108
|
durationMs
|
|
25069
25109
|
};
|
|
25070
25110
|
}
|
|
25071
|
-
var _semanticDeps, DIFF_CAP_BYTES =
|
|
25111
|
+
var _semanticDeps, DIFF_CAP_BYTES = 102400, DEFAULT_RULES;
|
|
25072
25112
|
var init_semantic = __esm(() => {
|
|
25073
25113
|
init_logger2();
|
|
25074
25114
|
_semanticDeps = {
|
|
@@ -27464,6 +27504,9 @@ function shouldRetryRectification(state, config2) {
|
|
|
27464
27504
|
if (state.attempt >= config2.maxRetries) {
|
|
27465
27505
|
return false;
|
|
27466
27506
|
}
|
|
27507
|
+
if (state.lastExitCode !== undefined && state.lastExitCode !== 0 && state.currentFailures === 0) {
|
|
27508
|
+
return true;
|
|
27509
|
+
}
|
|
27467
27510
|
if (state.currentFailures === 0) {
|
|
27468
27511
|
return false;
|
|
27469
27512
|
}
|
|
@@ -29754,7 +29797,8 @@ async function runRectificationLoop2(opts) {
|
|
|
29754
29797
|
const rectificationState = {
|
|
29755
29798
|
attempt: 0,
|
|
29756
29799
|
initialFailures: testSummary.failed,
|
|
29757
|
-
currentFailures: testSummary.failed
|
|
29800
|
+
currentFailures: testSummary.failed,
|
|
29801
|
+
lastExitCode: 1
|
|
29758
29802
|
};
|
|
29759
29803
|
logger?.info("rectification", `Starting ${label} loop`, {
|
|
29760
29804
|
storyId: story.id,
|
|
@@ -29832,6 +29876,7 @@ ${rectificationPrompt}`;
|
|
|
29832
29876
|
if (retryVerification.output) {
|
|
29833
29877
|
const newTestSummary = parseBunTestOutput(retryVerification.output);
|
|
29834
29878
|
rectificationState.currentFailures = newTestSummary.failed;
|
|
29879
|
+
rectificationState.lastExitCode = retryVerification.status === "SUCCESS" ? 0 : 1;
|
|
29835
29880
|
testSummary.failures = newTestSummary.failures;
|
|
29836
29881
|
testSummary.failed = newTestSummary.failed;
|
|
29837
29882
|
testSummary.passed = newTestSummary.passed;
|
|
@@ -30040,7 +30085,8 @@ function makeFailResult(storyId, strategy, status, opts = {}) {
|
|
|
30040
30085
|
failures: opts.failures ?? [],
|
|
30041
30086
|
rawOutput: opts.rawOutput,
|
|
30042
30087
|
durationMs: opts.durationMs ?? 0,
|
|
30043
|
-
countsTowardEscalation: opts.countsTowardEscalation ?? true
|
|
30088
|
+
countsTowardEscalation: opts.countsTowardEscalation ?? true,
|
|
30089
|
+
exitCode: opts.exitCode
|
|
30044
30090
|
};
|
|
30045
30091
|
}
|
|
30046
30092
|
function makePassResult(storyId, strategy, opts = {}) {
|
|
@@ -30449,7 +30495,8 @@ class ScopedStrategy {
|
|
|
30449
30495
|
passCount: parsed.passed,
|
|
30450
30496
|
failCount: parsed.failed,
|
|
30451
30497
|
failures: parsed.failures,
|
|
30452
|
-
durationMs
|
|
30498
|
+
durationMs,
|
|
30499
|
+
exitCode: result.status === "TEST_FAILURE" ? 1 : undefined
|
|
30453
30500
|
});
|
|
30454
30501
|
}
|
|
30455
30502
|
}
|
|
@@ -34298,7 +34345,7 @@ async function executeStoryInWorktree(story, worktreePath, context, routing, eve
|
|
|
34298
34345
|
};
|
|
34299
34346
|
}
|
|
34300
34347
|
}
|
|
34301
|
-
async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter) {
|
|
34348
|
+
async function executeParallelBatch(stories, projectRoot, config2, context, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs) {
|
|
34302
34349
|
const logger = getSafeLogger();
|
|
34303
34350
|
const results = {
|
|
34304
34351
|
pipelinePassed: [],
|
|
@@ -34319,7 +34366,9 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
|
|
|
34319
34366
|
continue;
|
|
34320
34367
|
}
|
|
34321
34368
|
const routing = routeTask(story.title, story.description, story.acceptanceCriteria, story.tags, config2);
|
|
34322
|
-
const
|
|
34369
|
+
const storyConfig = storyEffectiveConfigs?.get(story.id);
|
|
34370
|
+
const storyContext = storyConfig ? { ...context, effectiveConfig: storyConfig } : context;
|
|
34371
|
+
const executePromise = executeStoryInWorktree(story, worktreePath, storyContext, routing, eventEmitter).then((result) => {
|
|
34323
34372
|
results.totalCost += result.cost;
|
|
34324
34373
|
results.storyCosts.set(story.id, result.cost);
|
|
34325
34374
|
if (result.success) {
|
|
@@ -34354,6 +34403,7 @@ var init_parallel_worker = __esm(() => {
|
|
|
34354
34403
|
});
|
|
34355
34404
|
|
|
34356
34405
|
// src/execution/parallel-coordinator.ts
|
|
34406
|
+
import { existsSync as existsSync33, symlinkSync as symlinkSync2 } from "fs";
|
|
34357
34407
|
import os3 from "os";
|
|
34358
34408
|
import { join as join48 } from "path";
|
|
34359
34409
|
function groupStoriesByDependencies(stories) {
|
|
@@ -34398,7 +34448,7 @@ function resolveMaxConcurrency(parallel) {
|
|
|
34398
34448
|
}
|
|
34399
34449
|
return Math.max(1, parallel);
|
|
34400
34450
|
}
|
|
34401
|
-
async function executeParallel(stories, prdPath, projectRoot, config2, hooks, plugins, prd, featureDir, parallel, eventEmitter, agentGetFn) {
|
|
34451
|
+
async function executeParallel(stories, prdPath, projectRoot, config2, hooks, plugins, prd, featureDir, parallel, eventEmitter, agentGetFn, pidRegistry, interactionChain) {
|
|
34402
34452
|
const logger = getSafeLogger();
|
|
34403
34453
|
const maxConcurrency = resolveMaxConcurrency(parallel);
|
|
34404
34454
|
const worktreeManager = new WorktreeManager;
|
|
@@ -34430,9 +34480,12 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34430
34480
|
hooks,
|
|
34431
34481
|
plugins,
|
|
34432
34482
|
storyStartTime: new Date().toISOString(),
|
|
34433
|
-
agentGetFn
|
|
34483
|
+
agentGetFn,
|
|
34484
|
+
pidRegistry,
|
|
34485
|
+
interaction: interactionChain ?? undefined
|
|
34434
34486
|
};
|
|
34435
34487
|
const worktreePaths = new Map;
|
|
34488
|
+
const storyEffectiveConfigs = new Map;
|
|
34436
34489
|
for (const story of batch) {
|
|
34437
34490
|
const worktreePath = join48(projectRoot, ".nax-wt", story.id);
|
|
34438
34491
|
try {
|
|
@@ -34442,6 +34495,27 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34442
34495
|
storyId: story.id,
|
|
34443
34496
|
worktreePath
|
|
34444
34497
|
});
|
|
34498
|
+
if (story.workdir) {
|
|
34499
|
+
const pkgNodeModulesSrc = join48(projectRoot, story.workdir, "node_modules");
|
|
34500
|
+
const pkgNodeModulesDst = join48(worktreePath, story.workdir, "node_modules");
|
|
34501
|
+
if (existsSync33(pkgNodeModulesSrc) && !existsSync33(pkgNodeModulesDst)) {
|
|
34502
|
+
try {
|
|
34503
|
+
symlinkSync2(pkgNodeModulesSrc, pkgNodeModulesDst, "dir");
|
|
34504
|
+
logger?.debug("parallel", "Symlinked package node_modules", {
|
|
34505
|
+
storyId: story.id,
|
|
34506
|
+
src: pkgNodeModulesSrc
|
|
34507
|
+
});
|
|
34508
|
+
} catch (symlinkError) {
|
|
34509
|
+
logger?.warn("parallel", "Failed to symlink package node_modules \u2014 test runner may not find deps", {
|
|
34510
|
+
storyId: story.id,
|
|
34511
|
+
error: errorMessage(symlinkError)
|
|
34512
|
+
});
|
|
34513
|
+
}
|
|
34514
|
+
}
|
|
34515
|
+
}
|
|
34516
|
+
const rootConfigPath = join48(projectRoot, ".nax", "config.json");
|
|
34517
|
+
const effectiveConfig = story.workdir ? await loadConfigForWorkdir(rootConfigPath, story.workdir) : config2;
|
|
34518
|
+
storyEffectiveConfigs.set(story.id, effectiveConfig);
|
|
34445
34519
|
} catch (error48) {
|
|
34446
34520
|
markStoryFailed(currentPrd, story.id, undefined, undefined);
|
|
34447
34521
|
logger?.error("parallel", "Failed to create worktree", {
|
|
@@ -34450,7 +34524,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34450
34524
|
});
|
|
34451
34525
|
}
|
|
34452
34526
|
}
|
|
34453
|
-
const batchResult = await executeParallelBatch(batch, projectRoot, config2, baseContext, worktreePaths, maxConcurrency, eventEmitter);
|
|
34527
|
+
const batchResult = await executeParallelBatch(batch, projectRoot, config2, baseContext, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs);
|
|
34454
34528
|
totalCost += batchResult.totalCost;
|
|
34455
34529
|
if (batchResult.pipelinePassed.length > 0) {
|
|
34456
34530
|
const successfulIds = batchResult.pipelinePassed.map((s) => s.id);
|
|
@@ -34520,6 +34594,7 @@ async function executeParallel(stories, prdPath, projectRoot, config2, hooks, pl
|
|
|
34520
34594
|
return { storiesCompleted, totalCost, updatedPrd: currentPrd, mergeConflicts: allMergeConflicts };
|
|
34521
34595
|
}
|
|
34522
34596
|
var init_parallel_coordinator = __esm(() => {
|
|
34597
|
+
init_loader();
|
|
34523
34598
|
init_logger2();
|
|
34524
34599
|
init_prd();
|
|
34525
34600
|
init_manager();
|
|
@@ -34807,7 +34882,7 @@ async function runParallelExecution(options, initialPrd) {
|
|
|
34807
34882
|
const batchStoryMetrics = [];
|
|
34808
34883
|
let conflictedStories = [];
|
|
34809
34884
|
try {
|
|
34810
|
-
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter, options.agentGetFn);
|
|
34885
|
+
const parallelResult = await _parallelExecutorDeps.executeParallel(readyStories, prdPath, workdir, config2, hooks, pluginRegistry, prd, featureDir, parallelCount, eventEmitter, options.agentGetFn, options.pidRegistry, options.interactionChain);
|
|
34811
34886
|
const batchDurationMs = Date.now() - batchStartMs;
|
|
34812
34887
|
const batchCompletedAt = new Date().toISOString();
|
|
34813
34888
|
prd = parallelResult.updatedPrd;
|
|
@@ -67812,7 +67887,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
67812
67887
|
|
|
67813
67888
|
// bin/nax.ts
|
|
67814
67889
|
init_source();
|
|
67815
|
-
import { existsSync as
|
|
67890
|
+
import { existsSync as existsSync35, mkdirSync as mkdirSync6 } from "fs";
|
|
67816
67891
|
import { homedir as homedir8 } from "os";
|
|
67817
67892
|
import { join as join56 } from "path";
|
|
67818
67893
|
|
|
@@ -69067,9 +69142,10 @@ async function planCommand(workdir, config2, options) {
|
|
|
69067
69142
|
const timeoutSeconds = config2?.execution?.sessionTimeoutSeconds ?? 600;
|
|
69068
69143
|
let rawResponse;
|
|
69069
69144
|
if (options.auto) {
|
|
69070
|
-
const
|
|
69071
|
-
const
|
|
69072
|
-
|
|
69145
|
+
const isAcp = config2?.agent?.protocol === "acp";
|
|
69146
|
+
const prompt = buildPlanningPrompt(specContent, codebaseContext, isAcp ? outputPath : undefined, relativePackages, packageDetails, config2?.project);
|
|
69147
|
+
const adapter = _planDeps.getAgent(agentName, config2);
|
|
69148
|
+
if (!adapter)
|
|
69073
69149
|
throw new Error(`[plan] No agent adapter found for '${agentName}'`);
|
|
69074
69150
|
let autoModel;
|
|
69075
69151
|
try {
|
|
@@ -69080,20 +69156,52 @@ async function planCommand(workdir, config2, options) {
|
|
|
69080
69156
|
if (entry)
|
|
69081
69157
|
autoModel = resolveModel2(entry).model;
|
|
69082
69158
|
} catch {}
|
|
69083
|
-
|
|
69084
|
-
|
|
69085
|
-
|
|
69086
|
-
|
|
69087
|
-
|
|
69088
|
-
|
|
69089
|
-
|
|
69090
|
-
|
|
69091
|
-
|
|
69092
|
-
|
|
69093
|
-
|
|
69094
|
-
|
|
69159
|
+
if (isAcp) {
|
|
69160
|
+
logger?.info("plan", "Starting ACP auto planning session", {
|
|
69161
|
+
agent: agentName,
|
|
69162
|
+
model: autoModel ?? config2?.plan?.model ?? "balanced",
|
|
69163
|
+
workdir,
|
|
69164
|
+
feature: options.feature,
|
|
69165
|
+
timeoutSeconds
|
|
69166
|
+
});
|
|
69167
|
+
const pidRegistry = new PidRegistry(workdir);
|
|
69168
|
+
try {
|
|
69169
|
+
await adapter.plan({
|
|
69170
|
+
prompt,
|
|
69171
|
+
workdir,
|
|
69172
|
+
interactive: false,
|
|
69173
|
+
timeoutSeconds,
|
|
69174
|
+
config: config2,
|
|
69175
|
+
modelTier: config2?.plan?.model ?? "balanced",
|
|
69176
|
+
dangerouslySkipPermissions: resolvePermissions(config2, "plan").skipPermissions,
|
|
69177
|
+
maxInteractionTurns: config2?.agent?.maxInteractionTurns,
|
|
69178
|
+
featureName: options.feature,
|
|
69179
|
+
pidRegistry,
|
|
69180
|
+
sessionRole: "plan"
|
|
69181
|
+
});
|
|
69182
|
+
} finally {
|
|
69183
|
+
await pidRegistry.killAll().catch(() => {});
|
|
69095
69184
|
}
|
|
69096
|
-
|
|
69185
|
+
if (!_planDeps.existsSync(outputPath)) {
|
|
69186
|
+
throw new Error(`[plan] ACP agent did not write PRD to ${outputPath}. Check agent logs for errors.`);
|
|
69187
|
+
}
|
|
69188
|
+
rawResponse = await _planDeps.readFile(outputPath);
|
|
69189
|
+
} else {
|
|
69190
|
+
rawResponse = await adapter.complete(prompt, {
|
|
69191
|
+
model: autoModel,
|
|
69192
|
+
jsonMode: true,
|
|
69193
|
+
workdir,
|
|
69194
|
+
config: config2,
|
|
69195
|
+
featureName: options.feature,
|
|
69196
|
+
sessionRole: "plan"
|
|
69197
|
+
});
|
|
69198
|
+
try {
|
|
69199
|
+
const envelope = JSON.parse(rawResponse);
|
|
69200
|
+
if (envelope?.type === "result" && typeof envelope?.result === "string") {
|
|
69201
|
+
rawResponse = envelope.result;
|
|
69202
|
+
}
|
|
69203
|
+
} catch {}
|
|
69204
|
+
}
|
|
69097
69205
|
} else {
|
|
69098
69206
|
const prompt = buildPlanningPrompt(specContent, codebaseContext, outputPath, relativePackages, packageDetails, config2?.project);
|
|
69099
69207
|
const adapter = _planDeps.getAgent(agentName, config2);
|
|
@@ -72116,7 +72224,9 @@ async function runExecutionPhase(options, prd, pluginRegistry) {
|
|
|
72116
72224
|
pluginRegistry,
|
|
72117
72225
|
formatterMode: options.formatterMode,
|
|
72118
72226
|
headless: options.headless,
|
|
72119
|
-
agentGetFn: options.agentGetFn
|
|
72227
|
+
agentGetFn: options.agentGetFn,
|
|
72228
|
+
pidRegistry: options.pidRegistry,
|
|
72229
|
+
interactionChain: options.interactionChain
|
|
72120
72230
|
}, prd);
|
|
72121
72231
|
prd = parallelResult.prd;
|
|
72122
72232
|
totalCost = parallelResult.totalCost;
|
|
@@ -79712,7 +79822,7 @@ Next: nax generate --package ${options.package}`));
|
|
|
79712
79822
|
return;
|
|
79713
79823
|
}
|
|
79714
79824
|
const naxDir = join56(workdir, ".nax");
|
|
79715
|
-
if (
|
|
79825
|
+
if (existsSync35(naxDir) && !options.force) {
|
|
79716
79826
|
console.log(source_default.yellow("nax already initialized. Use --force to overwrite."));
|
|
79717
79827
|
return;
|
|
79718
79828
|
}
|
|
@@ -79829,7 +79939,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79829
79939
|
console.error(source_default.red("Error: --plan requires --from <spec-path>"));
|
|
79830
79940
|
process.exit(1);
|
|
79831
79941
|
}
|
|
79832
|
-
if (options.from && !
|
|
79942
|
+
if (options.from && !existsSync35(options.from)) {
|
|
79833
79943
|
console.error(source_default.red(`Error: File not found: ${options.from} (required with --plan)`));
|
|
79834
79944
|
process.exit(1);
|
|
79835
79945
|
}
|
|
@@ -79861,7 +79971,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79861
79971
|
const featureDir = join56(naxDir, "features", options.feature);
|
|
79862
79972
|
const prdPath = join56(featureDir, "prd.json");
|
|
79863
79973
|
if (options.plan && options.from) {
|
|
79864
|
-
if (
|
|
79974
|
+
if (existsSync35(prdPath) && !options.force) {
|
|
79865
79975
|
console.error(source_default.red(`Error: prd.json already exists for feature "${options.feature}".`));
|
|
79866
79976
|
console.error(source_default.dim(" Use --force to overwrite, or run without --plan to use the existing PRD."));
|
|
79867
79977
|
process.exit(1);
|
|
@@ -79917,7 +80027,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79917
80027
|
process.exit(1);
|
|
79918
80028
|
}
|
|
79919
80029
|
}
|
|
79920
|
-
if (!
|
|
80030
|
+
if (!existsSync35(prdPath)) {
|
|
79921
80031
|
console.error(source_default.red(`Feature "${options.feature}" not found or missing prd.json`));
|
|
79922
80032
|
process.exit(1);
|
|
79923
80033
|
}
|
|
@@ -79992,7 +80102,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79992
80102
|
});
|
|
79993
80103
|
const latestSymlink = join56(runsDir, "latest.jsonl");
|
|
79994
80104
|
try {
|
|
79995
|
-
if (
|
|
80105
|
+
if (existsSync35(latestSymlink)) {
|
|
79996
80106
|
Bun.spawnSync(["rm", latestSymlink]);
|
|
79997
80107
|
}
|
|
79998
80108
|
Bun.spawnSync(["ln", "-s", `${runId}.jsonl`, latestSymlink], {
|
|
@@ -80090,7 +80200,7 @@ features.command("list").description("List all features").option("-d, --dir <pat
|
|
|
80090
80200
|
process.exit(1);
|
|
80091
80201
|
}
|
|
80092
80202
|
const featuresDir = join56(naxDir, "features");
|
|
80093
|
-
if (!
|
|
80203
|
+
if (!existsSync35(featuresDir)) {
|
|
80094
80204
|
console.log(source_default.dim("No features yet."));
|
|
80095
80205
|
return;
|
|
80096
80206
|
}
|
|
@@ -80105,7 +80215,7 @@ Features:
|
|
|
80105
80215
|
`));
|
|
80106
80216
|
for (const name of entries) {
|
|
80107
80217
|
const prdPath = join56(featuresDir, name, "prd.json");
|
|
80108
|
-
if (
|
|
80218
|
+
if (existsSync35(prdPath)) {
|
|
80109
80219
|
const prd = await loadPRD(prdPath);
|
|
80110
80220
|
const c = countStories(prd);
|
|
80111
80221
|
console.log(` ${name} \u2014 ${c.passed}/${c.total} stories done`);
|
|
@@ -80176,7 +80286,7 @@ program2.command("analyze").description("(deprecated) Parse spec.md into prd.jso
|
|
|
80176
80286
|
process.exit(1);
|
|
80177
80287
|
}
|
|
80178
80288
|
const featureDir = join56(naxDir, "features", options.feature);
|
|
80179
|
-
if (!
|
|
80289
|
+
if (!existsSync35(featureDir)) {
|
|
80180
80290
|
console.error(source_default.red(`Feature "${options.feature}" not found.`));
|
|
80181
80291
|
process.exit(1);
|
|
80182
80292
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nathapp/nax",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.7",
|
|
4
4
|
"description": "AI Coding Agent Orchestrator — loops until done",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"typecheck": "bun x tsc --noEmit",
|
|
14
14
|
"lint": "bun x biome check src/ bin/",
|
|
15
15
|
"release": "bun scripts/release.ts",
|
|
16
|
-
"test": "bun test test/ --timeout=60000",
|
|
16
|
+
"test": "bun test test/unit/ --timeout=60000 && bun test test/integration/ --timeout=60000 && bun test test/ui/ --timeout=60000",
|
|
17
17
|
"test:watch": "bun test --watch",
|
|
18
18
|
"test:unit": "bun test ./test/unit/ --timeout=60000",
|
|
19
19
|
"test:integration": "bun test ./test/integration/ --timeout=60000",
|