@nathapp/nax 0.58.0 → 0.58.1
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 +251 -147
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -3596,75 +3596,106 @@ var init_env = __esm(() => {
|
|
|
3596
3596
|
});
|
|
3597
3597
|
|
|
3598
3598
|
// src/agents/acp/parser.ts
|
|
3599
|
-
function
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
if (
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
input_tokens: u.inputTokens ?? u.input_tokens ?? 0,
|
|
3630
|
-
output_tokens: u.outputTokens ?? u.output_tokens ?? 0,
|
|
3631
|
-
cache_read_input_tokens: u.cachedReadTokens ?? u.cache_read_input_tokens ?? 0,
|
|
3632
|
-
cache_creation_input_tokens: u.cachedWriteTokens ?? u.cache_creation_input_tokens ?? 0
|
|
3633
|
-
};
|
|
3634
|
-
}
|
|
3599
|
+
function createParseState() {
|
|
3600
|
+
return { text: "", tokenUsage: undefined, exactCostUsd: undefined, stopReason: undefined, error: undefined };
|
|
3601
|
+
}
|
|
3602
|
+
function parseAcpxJsonLine(line, state) {
|
|
3603
|
+
try {
|
|
3604
|
+
const event = JSON.parse(line);
|
|
3605
|
+
if (event.jsonrpc === "2.0") {
|
|
3606
|
+
if (event.method === "session/update" && event.params?.update) {
|
|
3607
|
+
const update = event.params.update;
|
|
3608
|
+
if (update.sessionUpdate === "agent_message_chunk" && update.content?.type === "text" && update.content.text) {
|
|
3609
|
+
state.text += update.content.text;
|
|
3610
|
+
}
|
|
3611
|
+
if (update.sessionUpdate === "usage_update" && typeof update.cost?.amount === "number") {
|
|
3612
|
+
state.exactCostUsd = update.cost.amount;
|
|
3613
|
+
}
|
|
3614
|
+
}
|
|
3615
|
+
if (event.id !== undefined && event.result && typeof event.result === "object") {
|
|
3616
|
+
const result = event.result;
|
|
3617
|
+
if (result.stopReason)
|
|
3618
|
+
state.stopReason = result.stopReason;
|
|
3619
|
+
if (result.stop_reason)
|
|
3620
|
+
state.stopReason = result.stop_reason;
|
|
3621
|
+
if (result.usage && typeof result.usage === "object") {
|
|
3622
|
+
const u = result.usage;
|
|
3623
|
+
state.tokenUsage = {
|
|
3624
|
+
input_tokens: u.inputTokens ?? u.input_tokens ?? 0,
|
|
3625
|
+
output_tokens: u.outputTokens ?? u.output_tokens ?? 0,
|
|
3626
|
+
cache_read_input_tokens: u.cachedReadTokens ?? u.cache_read_input_tokens ?? 0,
|
|
3627
|
+
cache_creation_input_tokens: u.cachedWriteTokens ?? u.cache_creation_input_tokens ?? 0
|
|
3628
|
+
};
|
|
3635
3629
|
}
|
|
3636
|
-
continue;
|
|
3637
|
-
}
|
|
3638
|
-
if (event.content && typeof event.content === "string")
|
|
3639
|
-
text += event.content;
|
|
3640
|
-
if (event.text && typeof event.text === "string")
|
|
3641
|
-
text += event.text;
|
|
3642
|
-
if (event.result && typeof event.result === "string")
|
|
3643
|
-
text = event.result;
|
|
3644
|
-
if (event.cumulative_token_usage)
|
|
3645
|
-
tokenUsage = event.cumulative_token_usage;
|
|
3646
|
-
if (event.usage) {
|
|
3647
|
-
tokenUsage = {
|
|
3648
|
-
input_tokens: event.usage.input_tokens ?? event.usage.prompt_tokens ?? 0,
|
|
3649
|
-
output_tokens: event.usage.output_tokens ?? event.usage.completion_tokens ?? 0
|
|
3650
|
-
};
|
|
3651
|
-
}
|
|
3652
|
-
if (event.stopReason)
|
|
3653
|
-
stopReason = event.stopReason;
|
|
3654
|
-
if (event.stop_reason)
|
|
3655
|
-
stopReason = event.stop_reason;
|
|
3656
|
-
if (event.error) {
|
|
3657
|
-
error = typeof event.error === "string" ? event.error : event.error.message ?? JSON.stringify(event.error);
|
|
3658
3630
|
}
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3631
|
+
return;
|
|
3632
|
+
}
|
|
3633
|
+
if (event.content && typeof event.content === "string")
|
|
3634
|
+
state.text += event.content;
|
|
3635
|
+
if (event.text && typeof event.text === "string")
|
|
3636
|
+
state.text += event.text;
|
|
3637
|
+
if (event.result && typeof event.result === "string")
|
|
3638
|
+
state.text = event.result;
|
|
3639
|
+
if (event.cumulative_token_usage)
|
|
3640
|
+
state.tokenUsage = event.cumulative_token_usage;
|
|
3641
|
+
if (event.usage) {
|
|
3642
|
+
state.tokenUsage = {
|
|
3643
|
+
input_tokens: event.usage.input_tokens ?? event.usage.prompt_tokens ?? 0,
|
|
3644
|
+
output_tokens: event.usage.output_tokens ?? event.usage.completion_tokens ?? 0
|
|
3645
|
+
};
|
|
3662
3646
|
}
|
|
3647
|
+
if (event.stopReason)
|
|
3648
|
+
state.stopReason = event.stopReason;
|
|
3649
|
+
if (event.stop_reason)
|
|
3650
|
+
state.stopReason = event.stop_reason;
|
|
3651
|
+
if (event.error) {
|
|
3652
|
+
state.error = typeof event.error === "string" ? event.error : event.error.message ?? JSON.stringify(event.error);
|
|
3653
|
+
}
|
|
3654
|
+
} catch {
|
|
3655
|
+
if (!state.text)
|
|
3656
|
+
state.text = line;
|
|
3663
3657
|
}
|
|
3664
|
-
|
|
3658
|
+
}
|
|
3659
|
+
function finalizeParseState(state) {
|
|
3660
|
+
return {
|
|
3661
|
+
text: state.text.trim(),
|
|
3662
|
+
tokenUsage: state.tokenUsage,
|
|
3663
|
+
exactCostUsd: state.exactCostUsd,
|
|
3664
|
+
stopReason: state.stopReason,
|
|
3665
|
+
error: state.error
|
|
3666
|
+
};
|
|
3665
3667
|
}
|
|
3666
3668
|
|
|
3667
3669
|
// src/agents/acp/spawn-client.ts
|
|
3670
|
+
async function readAndParseLines(stream, state) {
|
|
3671
|
+
const decoder = new TextDecoder;
|
|
3672
|
+
let remainder = "";
|
|
3673
|
+
const reader = stream.getReader();
|
|
3674
|
+
try {
|
|
3675
|
+
while (true) {
|
|
3676
|
+
const { done, value } = await reader.read();
|
|
3677
|
+
if (done)
|
|
3678
|
+
break;
|
|
3679
|
+
remainder += decoder.decode(value, { stream: true });
|
|
3680
|
+
for (;; ) {
|
|
3681
|
+
const nl = remainder.indexOf(`
|
|
3682
|
+
`);
|
|
3683
|
+
if (nl < 0)
|
|
3684
|
+
break;
|
|
3685
|
+
const line = remainder.slice(0, nl);
|
|
3686
|
+
remainder = remainder.slice(nl + 1);
|
|
3687
|
+
if (line.trim())
|
|
3688
|
+
parseAcpxJsonLine(line, state);
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
remainder += decoder.decode();
|
|
3692
|
+
if (remainder.trim())
|
|
3693
|
+
parseAcpxJsonLine(remainder.trim(), state);
|
|
3694
|
+
} finally {
|
|
3695
|
+
reader.releaseLock();
|
|
3696
|
+
}
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3668
3699
|
class SpawnAcpSession {
|
|
3669
3700
|
agentName;
|
|
3670
3701
|
sessionName;
|
|
@@ -3729,13 +3760,22 @@ class SpawnAcpSession {
|
|
|
3729
3760
|
session: this.sessionName
|
|
3730
3761
|
});
|
|
3731
3762
|
}
|
|
3732
|
-
const
|
|
3763
|
+
const parseState = createParseState();
|
|
3764
|
+
const parsePromise = readAndParseLines(proc.stdout, parseState).catch(() => {});
|
|
3733
3765
|
const stderrPromise = new Response(proc.stderr).text().catch(() => "");
|
|
3734
3766
|
const exitCode = await proc.exited;
|
|
3735
|
-
const
|
|
3736
|
-
|
|
3737
|
-
Promise
|
|
3738
|
-
|
|
3767
|
+
const makeDrain = (ms) => {
|
|
3768
|
+
let id;
|
|
3769
|
+
const promise = new Promise((resolve) => {
|
|
3770
|
+
id = setTimeout(() => resolve(""), ms);
|
|
3771
|
+
});
|
|
3772
|
+
return { promise, cancel: () => clearTimeout(id) };
|
|
3773
|
+
};
|
|
3774
|
+
const drainA = makeDrain(_spawnClientDeps.streamDrainTimeoutMs);
|
|
3775
|
+
const drainB = makeDrain(_spawnClientDeps.streamDrainTimeoutMs);
|
|
3776
|
+
const [, stderr] = await Promise.all([
|
|
3777
|
+
Promise.race([parsePromise, drainA.promise]).finally(() => drainA.cancel()),
|
|
3778
|
+
Promise.race([stderrPromise, drainB.promise]).finally(() => drainB.cancel())
|
|
3739
3779
|
]);
|
|
3740
3780
|
if (exitCode !== 0) {
|
|
3741
3781
|
getSafeLogger()?.warn("acp-adapter", `Session prompt exited with code ${exitCode}`, {
|
|
@@ -3748,7 +3788,7 @@ class SpawnAcpSession {
|
|
|
3748
3788
|
};
|
|
3749
3789
|
}
|
|
3750
3790
|
try {
|
|
3751
|
-
const parsed =
|
|
3791
|
+
const parsed = finalizeParseState(parseState);
|
|
3752
3792
|
return {
|
|
3753
3793
|
messages: [{ role: "assistant", content: parsed.text || "" }],
|
|
3754
3794
|
stopReason: parsed.stopReason ?? "end_turn",
|
|
@@ -18209,7 +18249,9 @@ var init_schemas3 = __esm(() => {
|
|
|
18209
18249
|
typecheck: exports_external.string().optional(),
|
|
18210
18250
|
lint: exports_external.string().optional(),
|
|
18211
18251
|
test: exports_external.string().optional(),
|
|
18212
|
-
build: exports_external.string().optional()
|
|
18252
|
+
build: exports_external.string().optional(),
|
|
18253
|
+
lintFix: exports_external.string().optional(),
|
|
18254
|
+
formatFix: exports_external.string().optional()
|
|
18213
18255
|
}),
|
|
18214
18256
|
pluginMode: exports_external.enum(["per-story", "deferred"]).default("per-story"),
|
|
18215
18257
|
semantic: SemanticReviewConfigSchema.optional(),
|
|
@@ -20862,7 +20904,7 @@ function isPlainObject2(value) {
|
|
|
20862
20904
|
|
|
20863
20905
|
// src/config/path-security.ts
|
|
20864
20906
|
import { existsSync as existsSync3, lstatSync, realpathSync } from "fs";
|
|
20865
|
-
import { isAbsolute as isAbsolute3, normalize, resolve } from "path";
|
|
20907
|
+
import { basename, isAbsolute as isAbsolute3, normalize, resolve } from "path";
|
|
20866
20908
|
function validateDirectory(dirPath, baseDir) {
|
|
20867
20909
|
const resolved = resolve(dirPath);
|
|
20868
20910
|
if (!existsSync3(resolved)) {
|
|
@@ -20909,7 +20951,7 @@ function validateFilePath(filePath, baseDir) {
|
|
|
20909
20951
|
const parent = resolve(resolved, "..");
|
|
20910
20952
|
if (existsSync3(parent)) {
|
|
20911
20953
|
const realParent = realpathSync(parent);
|
|
20912
|
-
realPath = resolve(realParent,
|
|
20954
|
+
realPath = resolve(realParent, basename(resolved));
|
|
20913
20955
|
} else {
|
|
20914
20956
|
realPath = resolved;
|
|
20915
20957
|
}
|
|
@@ -21093,7 +21135,7 @@ var init_profile = __esm(() => {
|
|
|
21093
21135
|
|
|
21094
21136
|
// src/config/loader.ts
|
|
21095
21137
|
import { existsSync as existsSync4 } from "fs";
|
|
21096
|
-
import { basename, dirname, join as join6, resolve as resolve3 } from "path";
|
|
21138
|
+
import { basename as basename2, dirname, join as join6, resolve as resolve3 } from "path";
|
|
21097
21139
|
function globalConfigPath() {
|
|
21098
21140
|
return join6(globalConfigDir(), "config.json");
|
|
21099
21141
|
}
|
|
@@ -21148,8 +21190,8 @@ function applyBatchModeCompat(conf) {
|
|
|
21148
21190
|
}
|
|
21149
21191
|
async function loadConfig(startDir, cliOverrides) {
|
|
21150
21192
|
let rawConfig = structuredClone(DEFAULT_CONFIG);
|
|
21151
|
-
const projDir = startDir ?
|
|
21152
|
-
const projectRoot = startDir ?
|
|
21193
|
+
const projDir = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? startDir : findProjectDir(startDir) : findProjectDir();
|
|
21194
|
+
const projectRoot = startDir ? basename2(startDir) === PROJECT_NAX_DIR ? dirname(startDir) : startDir : process.cwd();
|
|
21153
21195
|
const profileName = await resolveProfileName(cliOverrides ?? {}, process.env, projectRoot);
|
|
21154
21196
|
const globalConfRaw = await loadJsonFile(globalConfigPath(), "config");
|
|
21155
21197
|
if (globalConfRaw) {
|
|
@@ -21202,7 +21244,7 @@ async function loadConfigForWorkdir(rootConfigPath, packageDir) {
|
|
|
21202
21244
|
const packageConfigPath = join6(repoRoot, PROJECT_NAX_DIR, "mono", packageDir, "config.json");
|
|
21203
21245
|
const packageOverride = await loadJsonFile(packageConfigPath, "config");
|
|
21204
21246
|
if (!packageOverride) {
|
|
21205
|
-
logger.
|
|
21247
|
+
logger.info("config", "Per-package config not found \u2014 falling back to root config", {
|
|
21206
21248
|
packageConfigPath,
|
|
21207
21249
|
packageDir
|
|
21208
21250
|
});
|
|
@@ -22761,7 +22803,7 @@ var package_default;
|
|
|
22761
22803
|
var init_package = __esm(() => {
|
|
22762
22804
|
package_default = {
|
|
22763
22805
|
name: "@nathapp/nax",
|
|
22764
|
-
version: "0.58.
|
|
22806
|
+
version: "0.58.1",
|
|
22765
22807
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22766
22808
|
type: "module",
|
|
22767
22809
|
bin: {
|
|
@@ -22841,8 +22883,8 @@ var init_version = __esm(() => {
|
|
|
22841
22883
|
NAX_VERSION = package_default.version;
|
|
22842
22884
|
NAX_COMMIT = (() => {
|
|
22843
22885
|
try {
|
|
22844
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22845
|
-
return "
|
|
22886
|
+
if (/^[0-9a-f]{6,10}$/.test("2975c129"))
|
|
22887
|
+
return "2975c129";
|
|
22846
22888
|
} catch {}
|
|
22847
22889
|
try {
|
|
22848
22890
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -29092,8 +29134,8 @@ var init_autofix = __esm(() => {
|
|
|
29092
29134
|
return { action: "continue" };
|
|
29093
29135
|
}
|
|
29094
29136
|
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
29095
|
-
const lintFixCmd = effectiveConfig.quality.commands.lintFix;
|
|
29096
|
-
const formatFixCmd = effectiveConfig.quality.commands.formatFix;
|
|
29137
|
+
const lintFixCmd = effectiveConfig.quality.commands.lintFix ?? effectiveConfig.review.commands.lintFix;
|
|
29138
|
+
const formatFixCmd = effectiveConfig.quality.commands.formatFix ?? effectiveConfig.review.commands.formatFix;
|
|
29097
29139
|
const effectiveWorkdir = ctx.story.workdir ? join20(ctx.workdir, ctx.story.workdir) : ctx.workdir;
|
|
29098
29140
|
const failedCheckNames = new Set((reviewResult.checks ?? []).filter((c) => !c.success).map((c) => c.check));
|
|
29099
29141
|
const hasLintFailure = failedCheckNames.has("lint");
|
|
@@ -29757,8 +29799,8 @@ function extractTestStructure(source) {
|
|
|
29757
29799
|
function deriveTestPatterns(contextFiles) {
|
|
29758
29800
|
const patterns = new Set;
|
|
29759
29801
|
for (const filePath of contextFiles) {
|
|
29760
|
-
const
|
|
29761
|
-
const basenameNoExt =
|
|
29802
|
+
const basename3 = path7.basename(filePath);
|
|
29803
|
+
const basenameNoExt = basename3.replace(/\.(ts|js|tsx|jsx)$/, "");
|
|
29762
29804
|
patterns.add(`${basenameNoExt}.test.ts`);
|
|
29763
29805
|
patterns.add(`${basenameNoExt}.test.js`);
|
|
29764
29806
|
patterns.add(`${basenameNoExt}.test.tsx`);
|
|
@@ -29811,8 +29853,8 @@ async function scanTestFiles(options) {
|
|
|
29811
29853
|
const files = [];
|
|
29812
29854
|
for await (const filePath of glob.scan({ cwd: scanDir, absolute: false })) {
|
|
29813
29855
|
if (allowedBasenames !== null) {
|
|
29814
|
-
const
|
|
29815
|
-
if (!allowedBasenames.has(
|
|
29856
|
+
const basename3 = path7.basename(filePath);
|
|
29857
|
+
if (!allowedBasenames.has(basename3)) {
|
|
29816
29858
|
continue;
|
|
29817
29859
|
}
|
|
29818
29860
|
}
|
|
@@ -34044,8 +34086,8 @@ function extractSearchTerms(sourceFile) {
|
|
|
34044
34086
|
const withoutSrc = sourceFile.replace(/^src\//, "");
|
|
34045
34087
|
const withoutExt = withoutSrc.replace(/\.ts$/, "");
|
|
34046
34088
|
const parts = withoutExt.split("/");
|
|
34047
|
-
const
|
|
34048
|
-
return [`/${
|
|
34089
|
+
const basename3 = parts[parts.length - 1];
|
|
34090
|
+
return [`/${basename3}`, withoutExt];
|
|
34049
34091
|
}
|
|
34050
34092
|
async function importGrepFallback(sourceFiles, workdir, testFilePatterns) {
|
|
34051
34093
|
if (sourceFiles.length === 0 || testFilePatterns.length === 0)
|
|
@@ -34403,7 +34445,6 @@ var init_regression2 = __esm(() => {
|
|
|
34403
34445
|
import { join as join28 } from "path";
|
|
34404
34446
|
var routingStage, _routingDeps;
|
|
34405
34447
|
var init_routing2 = __esm(() => {
|
|
34406
|
-
init_registry();
|
|
34407
34448
|
init_greenfield();
|
|
34408
34449
|
init_logger2();
|
|
34409
34450
|
init_prd();
|
|
@@ -34417,6 +34458,9 @@ var init_routing2 = __esm(() => {
|
|
|
34417
34458
|
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
34418
34459
|
const agentName = effectiveConfig.execution?.agent ?? "claude";
|
|
34419
34460
|
const adapter = ctx.agentGetFn ? ctx.agentGetFn(agentName) : undefined;
|
|
34461
|
+
if (ctx.story.id === ctx.stories[0]?.id) {
|
|
34462
|
+
_routingDeps.clearCache();
|
|
34463
|
+
}
|
|
34420
34464
|
const decision = await _routingDeps.resolveRouting(ctx.story, effectiveConfig, ctx.plugins, adapter);
|
|
34421
34465
|
const TIER_RANK = { fast: 0, balanced: 1, powerful: 2 };
|
|
34422
34466
|
const derivedTier = decision.modelTier;
|
|
@@ -34467,8 +34511,7 @@ var init_routing2 = __esm(() => {
|
|
|
34467
34511
|
complexityToModelTier,
|
|
34468
34512
|
isGreenfieldStory,
|
|
34469
34513
|
clearCache,
|
|
34470
|
-
savePRD
|
|
34471
|
-
getAgent
|
|
34514
|
+
savePRD
|
|
34472
34515
|
};
|
|
34473
34516
|
});
|
|
34474
34517
|
|
|
@@ -34769,7 +34812,7 @@ __export(exports_init_context, {
|
|
|
34769
34812
|
});
|
|
34770
34813
|
import { existsSync as existsSync24 } from "fs";
|
|
34771
34814
|
import { mkdir as mkdir2 } from "fs/promises";
|
|
34772
|
-
import { basename as
|
|
34815
|
+
import { basename as basename4, join as join33 } from "path";
|
|
34773
34816
|
async function findFiles(dir, maxFiles = 200) {
|
|
34774
34817
|
try {
|
|
34775
34818
|
const proc = Bun.spawnSync([
|
|
@@ -34857,7 +34900,7 @@ async function scanProject(projectRoot) {
|
|
|
34857
34900
|
const readmeSnippet = await readReadmeSnippet(projectRoot);
|
|
34858
34901
|
const entryPoints = await detectEntryPoints(projectRoot);
|
|
34859
34902
|
const configFiles = await detectConfigFiles(projectRoot);
|
|
34860
|
-
const projectName = packageManifest?.name ||
|
|
34903
|
+
const projectName = packageManifest?.name || basename4(projectRoot);
|
|
34861
34904
|
return {
|
|
34862
34905
|
projectName,
|
|
34863
34906
|
fileTree,
|
|
@@ -35439,11 +35482,11 @@ function getSafeLogger6() {
|
|
|
35439
35482
|
return getSafeLogger();
|
|
35440
35483
|
}
|
|
35441
35484
|
function extractPluginName(pluginPath) {
|
|
35442
|
-
const
|
|
35443
|
-
if (
|
|
35485
|
+
const basename6 = path13.basename(pluginPath);
|
|
35486
|
+
if (basename6 === "index.ts" || basename6 === "index.js" || basename6 === "index.mjs") {
|
|
35444
35487
|
return path13.basename(path13.dirname(pluginPath));
|
|
35445
35488
|
}
|
|
35446
|
-
return
|
|
35489
|
+
return basename6.replace(/\.(ts|js|mjs)$/, "");
|
|
35447
35490
|
}
|
|
35448
35491
|
async function loadPlugins(globalDir, projectDir, configPlugins, projectRoot, disabledPlugins) {
|
|
35449
35492
|
const loadedPlugins = [];
|
|
@@ -35685,8 +35728,50 @@ function validateHookCommand(command) {
|
|
|
35685
35728
|
}
|
|
35686
35729
|
}
|
|
35687
35730
|
function parseCommandToArgv(command) {
|
|
35688
|
-
const
|
|
35689
|
-
|
|
35731
|
+
const safeEnv = buildAllowedEnv();
|
|
35732
|
+
const home = safeEnv.HOME ?? "";
|
|
35733
|
+
const args = [];
|
|
35734
|
+
let current = "";
|
|
35735
|
+
let i = 0;
|
|
35736
|
+
const s = command.trim();
|
|
35737
|
+
while (i < s.length) {
|
|
35738
|
+
const ch = s[i];
|
|
35739
|
+
if (ch === " " || ch === "\t") {
|
|
35740
|
+
if (current.length > 0) {
|
|
35741
|
+
args.push(current);
|
|
35742
|
+
current = "";
|
|
35743
|
+
}
|
|
35744
|
+
i++;
|
|
35745
|
+
continue;
|
|
35746
|
+
}
|
|
35747
|
+
if (ch === "'") {
|
|
35748
|
+
i++;
|
|
35749
|
+
while (i < s.length && s[i] !== "'") {
|
|
35750
|
+
current += s[i++];
|
|
35751
|
+
}
|
|
35752
|
+
i++;
|
|
35753
|
+
continue;
|
|
35754
|
+
}
|
|
35755
|
+
if (ch === '"') {
|
|
35756
|
+
i++;
|
|
35757
|
+
while (i < s.length && s[i] !== '"') {
|
|
35758
|
+
if (s[i] === "\\" && i + 1 < s.length && (s[i + 1] === '"' || s[i + 1] === "\\")) {
|
|
35759
|
+
current += s[i + 1];
|
|
35760
|
+
i += 2;
|
|
35761
|
+
} else {
|
|
35762
|
+
current += s[i++];
|
|
35763
|
+
}
|
|
35764
|
+
}
|
|
35765
|
+
i++;
|
|
35766
|
+
continue;
|
|
35767
|
+
}
|
|
35768
|
+
current += ch;
|
|
35769
|
+
i++;
|
|
35770
|
+
}
|
|
35771
|
+
if (current.length > 0) {
|
|
35772
|
+
args.push(current);
|
|
35773
|
+
}
|
|
35774
|
+
return args.map((token) => token.startsWith("~/") && home ? home + token.slice(1) : token);
|
|
35690
35775
|
}
|
|
35691
35776
|
async function executeHook(hookDef, ctx, workdir) {
|
|
35692
35777
|
if (hookDef.enabled === false) {
|
|
@@ -35719,7 +35804,7 @@ async function executeHook(hookDef, ctx, workdir) {
|
|
|
35719
35804
|
stdin: new Response(contextJson),
|
|
35720
35805
|
stdout: "pipe",
|
|
35721
35806
|
stderr: "pipe",
|
|
35722
|
-
env: {
|
|
35807
|
+
env: buildAllowedEnv({ env: env2 })
|
|
35723
35808
|
});
|
|
35724
35809
|
const timeoutId = setTimeout(() => {
|
|
35725
35810
|
killProcessGroup(proc.pid, "SIGTERM");
|
|
@@ -35769,6 +35854,7 @@ async function fireHook(config2, event, ctx, workdir) {
|
|
|
35769
35854
|
}
|
|
35770
35855
|
var DEFAULT_TIMEOUT = 5000;
|
|
35771
35856
|
var init_runner4 = __esm(() => {
|
|
35857
|
+
init_env();
|
|
35772
35858
|
init_logger2();
|
|
35773
35859
|
init_json_file();
|
|
35774
35860
|
});
|
|
@@ -35780,46 +35866,51 @@ var init_hooks = __esm(() => {
|
|
|
35780
35866
|
|
|
35781
35867
|
// src/execution/crash-heartbeat.ts
|
|
35782
35868
|
import { appendFileSync as appendFileSync2 } from "fs";
|
|
35783
|
-
function
|
|
35869
|
+
async function heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
35784
35870
|
const logger = getSafeLogger();
|
|
35785
|
-
|
|
35786
|
-
|
|
35787
|
-
|
|
35788
|
-
|
|
35789
|
-
|
|
35790
|
-
|
|
35791
|
-
|
|
35792
|
-
|
|
35793
|
-
|
|
35794
|
-
|
|
35795
|
-
|
|
35796
|
-
|
|
35797
|
-
|
|
35798
|
-
|
|
35799
|
-
|
|
35800
|
-
}
|
|
35801
|
-
|
|
35871
|
+
while (heartbeatActive) {
|
|
35872
|
+
await Bun.sleep(60000);
|
|
35873
|
+
if (!heartbeatActive)
|
|
35874
|
+
break;
|
|
35875
|
+
try {
|
|
35876
|
+
logger?.debug("crash-recovery", "Heartbeat");
|
|
35877
|
+
if (jsonlFilePath) {
|
|
35878
|
+
const heartbeatEntry = {
|
|
35879
|
+
timestamp: new Date().toISOString(),
|
|
35880
|
+
level: "debug",
|
|
35881
|
+
stage: "heartbeat",
|
|
35882
|
+
message: "Process alive",
|
|
35883
|
+
data: {
|
|
35884
|
+
pid: process.pid,
|
|
35885
|
+
memoryUsageMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024)
|
|
35886
|
+
}
|
|
35887
|
+
};
|
|
35888
|
+
const line = `${JSON.stringify(heartbeatEntry)}
|
|
35802
35889
|
`;
|
|
35803
|
-
|
|
35804
|
-
}
|
|
35805
|
-
await statusWriter.update(getTotalCost(), getIterations(), {
|
|
35806
|
-
lastHeartbeat: new Date().toISOString()
|
|
35807
|
-
});
|
|
35808
|
-
} catch (err) {
|
|
35809
|
-
logger?.warn("crash-recovery", "Failed during heartbeat", { error: err.message });
|
|
35890
|
+
appendFileSync2(jsonlFilePath, line);
|
|
35810
35891
|
}
|
|
35811
|
-
|
|
35812
|
-
|
|
35892
|
+
await statusWriter.update(getTotalCost(), getIterations(), {
|
|
35893
|
+
lastHeartbeat: new Date().toISOString()
|
|
35894
|
+
});
|
|
35895
|
+
} catch (err) {
|
|
35896
|
+
logger?.warn("crash-recovery", "Failed during heartbeat", { error: err.message });
|
|
35897
|
+
}
|
|
35898
|
+
}
|
|
35899
|
+
}
|
|
35900
|
+
function startHeartbeat(statusWriter, getTotalCost, getIterations, jsonlFilePath) {
|
|
35901
|
+
const logger = getSafeLogger();
|
|
35902
|
+
stopHeartbeat();
|
|
35903
|
+
heartbeatActive = true;
|
|
35904
|
+
heartbeatLoop(statusWriter, getTotalCost, getIterations, jsonlFilePath).catch(() => {});
|
|
35813
35905
|
logger?.debug("crash-recovery", "Heartbeat started (60s interval)");
|
|
35814
35906
|
}
|
|
35815
35907
|
function stopHeartbeat() {
|
|
35816
|
-
if (
|
|
35817
|
-
|
|
35818
|
-
heartbeatTimer = null;
|
|
35908
|
+
if (heartbeatActive) {
|
|
35909
|
+
heartbeatActive = false;
|
|
35819
35910
|
getSafeLogger()?.debug("crash-recovery", "Heartbeat stopped");
|
|
35820
35911
|
}
|
|
35821
35912
|
}
|
|
35822
|
-
var
|
|
35913
|
+
var heartbeatActive = false;
|
|
35823
35914
|
var init_crash_heartbeat = __esm(() => {
|
|
35824
35915
|
init_logger2();
|
|
35825
35916
|
});
|
|
@@ -35847,7 +35938,8 @@ async function writeFatalLog(jsonlFilePath, signal, error48) {
|
|
|
35847
35938
|
`;
|
|
35848
35939
|
appendFileSync3(jsonlFilePath, line);
|
|
35849
35940
|
} catch (err) {
|
|
35850
|
-
|
|
35941
|
+
process.stderr.write(`[crash-recovery] Failed to write fatal log: ${String(err)}
|
|
35942
|
+
`);
|
|
35851
35943
|
}
|
|
35852
35944
|
}
|
|
35853
35945
|
async function writeRunComplete(ctx, exitReason) {
|
|
@@ -35883,7 +35975,8 @@ async function writeRunComplete(ctx, exitReason) {
|
|
|
35883
35975
|
appendFileSync3(ctx.jsonlFilePath, line);
|
|
35884
35976
|
logger?.debug("crash-recovery", "run.complete event written", { exitReason });
|
|
35885
35977
|
} catch (err) {
|
|
35886
|
-
|
|
35978
|
+
process.stderr.write(`[crash-recovery] Failed to write run.complete event: ${String(err)}
|
|
35979
|
+
`);
|
|
35887
35980
|
}
|
|
35888
35981
|
}
|
|
35889
35982
|
async function updateStatusToCrashed(statusWriter, totalCost, iterations, signal, featureDir) {
|
|
@@ -35900,7 +35993,8 @@ async function updateStatusToCrashed(statusWriter, totalCost, iterations, signal
|
|
|
35900
35993
|
});
|
|
35901
35994
|
}
|
|
35902
35995
|
} catch (err) {
|
|
35903
|
-
|
|
35996
|
+
process.stderr.write(`[crash-recovery] Failed to update status.json: ${String(err)}
|
|
35997
|
+
`);
|
|
35904
35998
|
}
|
|
35905
35999
|
}
|
|
35906
36000
|
async function writeExitSummary(jsonlFilePath, totalCost, iterations, storiesCompleted, durationMs) {
|
|
@@ -37366,10 +37460,10 @@ var init_headless_formatter = __esm(() => {
|
|
|
37366
37460
|
// src/pipeline/subscribers/events-writer.ts
|
|
37367
37461
|
import { appendFile as appendFile3, mkdir as mkdir3 } from "fs/promises";
|
|
37368
37462
|
import { homedir as homedir5 } from "os";
|
|
37369
|
-
import { basename as
|
|
37463
|
+
import { basename as basename7, join as join51 } from "path";
|
|
37370
37464
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
37371
37465
|
const logger = getSafeLogger();
|
|
37372
|
-
const project =
|
|
37466
|
+
const project = basename7(workdir);
|
|
37373
37467
|
const eventsDir = join51(homedir5(), ".nax", "events", project);
|
|
37374
37468
|
const eventsFile = join51(eventsDir, "events.jsonl");
|
|
37375
37469
|
let dirReady = false;
|
|
@@ -37552,10 +37646,10 @@ var init_interaction2 = __esm(() => {
|
|
|
37552
37646
|
// src/pipeline/subscribers/registry.ts
|
|
37553
37647
|
import { mkdir as mkdir4, writeFile } from "fs/promises";
|
|
37554
37648
|
import { homedir as homedir6 } from "os";
|
|
37555
|
-
import { basename as
|
|
37649
|
+
import { basename as basename8, join as join52 } from "path";
|
|
37556
37650
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
37557
37651
|
const logger = getSafeLogger();
|
|
37558
|
-
const project =
|
|
37652
|
+
const project = basename8(workdir);
|
|
37559
37653
|
const runDir = join52(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
37560
37654
|
const metaFile = join52(runDir, "meta.json");
|
|
37561
37655
|
const unsub = bus.on("run:started", (_ev) => {
|
|
@@ -38499,7 +38593,7 @@ async function executeParallelBatch(stories, projectRoot, config2, context, work
|
|
|
38499
38593
|
executing.delete(executePromise);
|
|
38500
38594
|
});
|
|
38501
38595
|
executing.add(executePromise);
|
|
38502
|
-
|
|
38596
|
+
while (executing.size >= maxConcurrency) {
|
|
38503
38597
|
await Promise.race(executing);
|
|
38504
38598
|
}
|
|
38505
38599
|
}
|
|
@@ -39051,12 +39145,10 @@ async function runParallelBatch(options) {
|
|
|
39051
39145
|
}
|
|
39052
39146
|
const rootConfigPath = path17.join(workdir, ".nax", "config.json");
|
|
39053
39147
|
const storyEffectiveConfigs = new Map;
|
|
39054
|
-
|
|
39055
|
-
|
|
39056
|
-
|
|
39057
|
-
|
|
39058
|
-
}
|
|
39059
|
-
}
|
|
39148
|
+
await Promise.all(stories.filter((story) => story.workdir).map(async (story) => {
|
|
39149
|
+
const effectiveConfig = await loadConfigForWorkdir(rootConfigPath, story.workdir);
|
|
39150
|
+
storyEffectiveConfigs.set(story.id, effectiveConfig);
|
|
39151
|
+
}));
|
|
39060
39152
|
const workerResult = await _parallelBatchDeps.executeParallelBatch(stories, workdir, config2, pipelineContext, worktreePaths, maxConcurrency, eventEmitter, storyEffectiveConfigs.size > 0 ? storyEffectiveConfigs : undefined);
|
|
39061
39153
|
const batchEndMs = Date.now();
|
|
39062
39154
|
const completed = [];
|
|
@@ -72318,7 +72410,7 @@ init_logger2();
|
|
|
72318
72410
|
|
|
72319
72411
|
// src/prd/decompose-mapper.ts
|
|
72320
72412
|
init_errors();
|
|
72321
|
-
function mapDecomposedStoriesToUserStories(stories, parentStoryId) {
|
|
72413
|
+
function mapDecomposedStoriesToUserStories(stories, parentStoryId, parentWorkdir) {
|
|
72322
72414
|
return stories.map((story, entryIndex) => {
|
|
72323
72415
|
if (!story.id) {
|
|
72324
72416
|
throw new NaxError(`Entry at index ${entryIndex} is missing required field: id`, "DECOMPOSE_VALIDATION_FAILED", {
|
|
@@ -72348,6 +72440,7 @@ function mapDecomposedStoriesToUserStories(stories, parentStoryId) {
|
|
|
72348
72440
|
escalations: [],
|
|
72349
72441
|
attempts: 0,
|
|
72350
72442
|
parentStoryId,
|
|
72443
|
+
...parentWorkdir !== undefined && { workdir: parentWorkdir },
|
|
72351
72444
|
routing: {
|
|
72352
72445
|
complexity: story.complexity,
|
|
72353
72446
|
testStrategy: story.testStrategy ?? "test-after",
|
|
@@ -73032,6 +73125,15 @@ async function planDecomposeCommand(workdir, config2, options) {
|
|
|
73032
73125
|
const timeoutSeconds = config2?.plan?.timeoutSeconds ?? DEFAULT_TIMEOUT_SECONDS2;
|
|
73033
73126
|
const maxAcCount = config2?.precheck?.storySizeGate?.maxAcCount ?? Number.POSITIVE_INFINITY;
|
|
73034
73127
|
const maxReplanAttempts = config2?.precheck?.storySizeGate?.maxReplanAttempts ?? 3;
|
|
73128
|
+
let decomposeModelDef;
|
|
73129
|
+
try {
|
|
73130
|
+
const decomposeTier = config2?.plan?.model ?? "balanced";
|
|
73131
|
+
const { resolveModelForAgent: resolveModelForAgent2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
73132
|
+
if (config2?.models) {
|
|
73133
|
+
const defaultAgent = config2.autoMode?.defaultAgent ?? "claude";
|
|
73134
|
+
decomposeModelDef = resolveModelForAgent2(config2.models, agentName, decomposeTier, defaultAgent);
|
|
73135
|
+
}
|
|
73136
|
+
} catch {}
|
|
73035
73137
|
if (typeof adapter.decompose !== "function") {
|
|
73036
73138
|
throw new NaxError(`Agent "${agentName}" does not support decompose() required by plan --decompose`, "DECOMPOSE_NOT_SUPPORTED", { stage: "decompose", agent: agentName, storyId: options.storyId });
|
|
73037
73139
|
}
|
|
@@ -73078,7 +73180,8 @@ ${repairHint}` : codebaseContext;
|
|
|
73078
73180
|
siblings,
|
|
73079
73181
|
featureName: options.feature,
|
|
73080
73182
|
storyId: options.storyId,
|
|
73081
|
-
config: config2
|
|
73183
|
+
config: config2,
|
|
73184
|
+
modelDef: decomposeModelDef
|
|
73082
73185
|
});
|
|
73083
73186
|
decompStories = result.stories;
|
|
73084
73187
|
}
|
|
@@ -73100,7 +73203,7 @@ ${repairHint}` : codebaseContext;
|
|
|
73100
73203
|
repairHint = `REPAIR REQUIRED (attempt ${attempt + 1}/${maxReplanAttempts}): The following sub-stories exceeded maxAcCount of ${maxAcCount}: ${violationSummary}. Split each offending story further so every sub-story has at most ${maxAcCount} acceptance criteria.`;
|
|
73101
73204
|
decompStories = undefined;
|
|
73102
73205
|
}
|
|
73103
|
-
const subStoriesWithParent = mapDecomposedStoriesToUserStories(decompStories, options.storyId);
|
|
73206
|
+
const subStoriesWithParent = mapDecomposedStoriesToUserStories(decompStories, options.storyId, targetStory.workdir);
|
|
73104
73207
|
const updatedStories = prd.userStories.map((s) => s.id === options.storyId ? { ...s, status: "decomposed" } : s);
|
|
73105
73208
|
const originalIndex = updatedStories.findIndex((s) => s.id === options.storyId);
|
|
73106
73209
|
const finalStories = [
|
|
@@ -83184,8 +83287,9 @@ function usePipelineEvents(events, initialStories) {
|
|
|
83184
83287
|
totalCost: 0,
|
|
83185
83288
|
elapsedMs: 0
|
|
83186
83289
|
}));
|
|
83187
|
-
const
|
|
83290
|
+
const startTimeRef = import_react32.useRef(Date.now());
|
|
83188
83291
|
import_react32.useEffect(() => {
|
|
83292
|
+
const startTime = startTimeRef.current;
|
|
83189
83293
|
let timer = null;
|
|
83190
83294
|
const startTimer = () => {
|
|
83191
83295
|
if (!timer) {
|
|
@@ -83272,7 +83376,7 @@ function usePipelineEvents(events, initialStories) {
|
|
|
83272
83376
|
events.off("stage:enter", onStageEnter);
|
|
83273
83377
|
events.off("run:complete", onRunComplete);
|
|
83274
83378
|
};
|
|
83275
|
-
}, [events
|
|
83379
|
+
}, [events]);
|
|
83276
83380
|
return state;
|
|
83277
83381
|
}
|
|
83278
83382
|
|