@nathapp/nax 0.54.1 → 0.54.2
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 +309 -199
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -3580,6 +3580,44 @@ var init_complete = __esm(() => {
|
|
|
3580
3580
|
};
|
|
3581
3581
|
});
|
|
3582
3582
|
|
|
3583
|
+
// src/agents/shared/env.ts
|
|
3584
|
+
import { homedir } from "os";
|
|
3585
|
+
import { isAbsolute } from "path";
|
|
3586
|
+
function buildAllowedEnv(options) {
|
|
3587
|
+
const allowed = {};
|
|
3588
|
+
for (const varName of ESSENTIAL_VARS) {
|
|
3589
|
+
if (process.env[varName])
|
|
3590
|
+
allowed[varName] = process.env[varName];
|
|
3591
|
+
}
|
|
3592
|
+
const rawHome = process.env.HOME ?? "";
|
|
3593
|
+
const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
|
|
3594
|
+
if (rawHome !== safeHome) {
|
|
3595
|
+
getSafeLogger()?.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
|
|
3596
|
+
}
|
|
3597
|
+
allowed.HOME = safeHome;
|
|
3598
|
+
for (const varName of API_KEY_VARS) {
|
|
3599
|
+
if (process.env[varName])
|
|
3600
|
+
allowed[varName] = process.env[varName];
|
|
3601
|
+
}
|
|
3602
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
3603
|
+
if (ALLOWED_PREFIXES.some((prefix) => key.startsWith(prefix))) {
|
|
3604
|
+
allowed[key] = value;
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
if (options?.modelEnv)
|
|
3608
|
+
Object.assign(allowed, options.modelEnv);
|
|
3609
|
+
if (options?.env)
|
|
3610
|
+
Object.assign(allowed, options.env);
|
|
3611
|
+
return allowed;
|
|
3612
|
+
}
|
|
3613
|
+
var ESSENTIAL_VARS, API_KEY_VARS, ALLOWED_PREFIXES;
|
|
3614
|
+
var init_env = __esm(() => {
|
|
3615
|
+
init_logger2();
|
|
3616
|
+
ESSENTIAL_VARS = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
3617
|
+
API_KEY_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
|
|
3618
|
+
ALLOWED_PREFIXES = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_", "ANTHROPIC_"];
|
|
3619
|
+
});
|
|
3620
|
+
|
|
3583
3621
|
// src/agents/cost/pricing.ts
|
|
3584
3622
|
var COST_RATES, MODEL_PRICING;
|
|
3585
3623
|
var init_pricing = __esm(() => {
|
|
@@ -3743,49 +3781,12 @@ var init_cost2 = __esm(() => {
|
|
|
3743
3781
|
});
|
|
3744
3782
|
|
|
3745
3783
|
// src/agents/claude/execution.ts
|
|
3746
|
-
import { homedir } from "os";
|
|
3747
|
-
import { isAbsolute } from "path";
|
|
3748
3784
|
function buildCommand(binary, options) {
|
|
3749
3785
|
const model = options.modelDef.model;
|
|
3750
3786
|
const { skipPermissions } = resolvePermissions(options.config, options.pipelineStage ?? "run");
|
|
3751
3787
|
const permArgs = skipPermissions ? ["--dangerously-skip-permissions"] : [];
|
|
3752
3788
|
return [binary, "--model", model, ...permArgs, "-p", options.prompt];
|
|
3753
3789
|
}
|
|
3754
|
-
function buildAllowedEnv(options) {
|
|
3755
|
-
const allowed = {};
|
|
3756
|
-
const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
3757
|
-
for (const varName of essentialVars) {
|
|
3758
|
-
if (process.env[varName]) {
|
|
3759
|
-
allowed[varName] = process.env[varName];
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
const rawHome = process.env.HOME ?? "";
|
|
3763
|
-
const safeHome = rawHome && isAbsolute(rawHome) ? rawHome : homedir();
|
|
3764
|
-
if (rawHome !== safeHome) {
|
|
3765
|
-
const logger = getLogger();
|
|
3766
|
-
logger.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
|
|
3767
|
-
}
|
|
3768
|
-
allowed.HOME = safeHome;
|
|
3769
|
-
const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY"];
|
|
3770
|
-
for (const varName of apiKeyVars) {
|
|
3771
|
-
if (process.env[varName]) {
|
|
3772
|
-
allowed[varName] = process.env[varName];
|
|
3773
|
-
}
|
|
3774
|
-
}
|
|
3775
|
-
const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_"];
|
|
3776
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
3777
|
-
if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
3778
|
-
allowed[key] = value;
|
|
3779
|
-
}
|
|
3780
|
-
}
|
|
3781
|
-
if (options.modelDef.env) {
|
|
3782
|
-
Object.assign(allowed, options.modelDef.env);
|
|
3783
|
-
}
|
|
3784
|
-
if (options.env) {
|
|
3785
|
-
Object.assign(allowed, options.env);
|
|
3786
|
-
}
|
|
3787
|
-
return allowed;
|
|
3788
|
-
}
|
|
3789
3790
|
async function executeOnce(binary, options, pidRegistry) {
|
|
3790
3791
|
const cmd = _runOnceDeps.buildCmd(binary, options);
|
|
3791
3792
|
const startTime = Date.now();
|
|
@@ -3803,7 +3804,7 @@ async function executeOnce(binary, options, pidRegistry) {
|
|
|
3803
3804
|
cwd: options.workdir,
|
|
3804
3805
|
stdout: "pipe",
|
|
3805
3806
|
stderr: "inherit",
|
|
3806
|
-
env: buildAllowedEnv(options)
|
|
3807
|
+
env: buildAllowedEnv({ env: options.env, modelEnv: options.modelDef.env })
|
|
3807
3808
|
});
|
|
3808
3809
|
const processPid = proc.pid;
|
|
3809
3810
|
await pidRegistry.register(processPid);
|
|
@@ -3866,7 +3867,9 @@ var MAX_AGENT_OUTPUT_CHARS = 5000, MAX_AGENT_STDERR_CHARS = 1000, SIGKILL_GRACE_
|
|
|
3866
3867
|
var init_execution = __esm(() => {
|
|
3867
3868
|
init_logger2();
|
|
3868
3869
|
init_bun_deps();
|
|
3870
|
+
init_env();
|
|
3869
3871
|
init_cost2();
|
|
3872
|
+
init_env();
|
|
3870
3873
|
_runOnceDeps = {
|
|
3871
3874
|
killProc(proc, signal) {
|
|
3872
3875
|
proc.kill(signal);
|
|
@@ -17978,7 +17981,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17978
17981
|
});
|
|
17979
17982
|
SemanticReviewConfigSchema = exports_external.object({
|
|
17980
17983
|
modelTier: ModelTierSchema.default("balanced"),
|
|
17981
|
-
rules: exports_external.array(exports_external.string()).default([])
|
|
17984
|
+
rules: exports_external.array(exports_external.string()).default([]),
|
|
17985
|
+
timeoutMs: exports_external.number().int().positive().default(600000)
|
|
17982
17986
|
});
|
|
17983
17987
|
ReviewConfigSchema = exports_external.object({
|
|
17984
17988
|
enabled: exports_external.boolean(),
|
|
@@ -18273,7 +18277,8 @@ var init_defaults = __esm(() => {
|
|
|
18273
18277
|
pluginMode: "per-story",
|
|
18274
18278
|
semantic: {
|
|
18275
18279
|
modelTier: "balanced",
|
|
18276
|
-
rules: []
|
|
18280
|
+
rules: [],
|
|
18281
|
+
timeoutMs: 600000
|
|
18277
18282
|
}
|
|
18278
18283
|
},
|
|
18279
18284
|
plan: {
|
|
@@ -18859,13 +18864,13 @@ function skeletonImportLine(testFramework) {
|
|
|
18859
18864
|
function acceptanceTestFilename(language) {
|
|
18860
18865
|
switch (language?.toLowerCase()) {
|
|
18861
18866
|
case "go":
|
|
18862
|
-
return "acceptance_test.go";
|
|
18867
|
+
return ".nax-acceptance_test.go";
|
|
18863
18868
|
case "python":
|
|
18864
|
-
return "
|
|
18869
|
+
return ".nax-acceptance.test.py";
|
|
18865
18870
|
case "rust":
|
|
18866
|
-
return "
|
|
18871
|
+
return ".nax-acceptance.rs";
|
|
18867
18872
|
default:
|
|
18868
|
-
return "acceptance.test.ts";
|
|
18873
|
+
return ".nax-acceptance.test.ts";
|
|
18869
18874
|
}
|
|
18870
18875
|
}
|
|
18871
18876
|
function buildAcceptanceRunCommand(testPath, testFramework, commandOverride) {
|
|
@@ -18936,7 +18941,7 @@ Rules:
|
|
|
18936
18941
|
- Every test MUST have real assertions that PASS when the feature is correctly implemented and FAIL when it is broken
|
|
18937
18942
|
- **Prefer behavioral tests** \u2014 import functions and call them rather than reading source files. For example, to verify "getPostRunActions() returns empty array", import PluginRegistry and call getPostRunActions(), don't grep the source file for the method name.
|
|
18938
18943
|
- Output raw code only \u2014 no markdown fences, start directly with the language's import or package declaration
|
|
18939
|
-
- **Path anchor (CRITICAL)**: This test file
|
|
18944
|
+
- **Path anchor (CRITICAL)**: This test file lives at \`<package-root>/${acceptanceTestFilename(options.language)}\` and runs from the package root. Import from package sources using relative paths like \`./src/...\`. No deep \`../../../../\` traversal needed.`;
|
|
18940
18945
|
const prompt = basePrompt;
|
|
18941
18946
|
logger.info("acceptance", "Generating tests from PRD refined criteria", { count: refinedCriteria.length });
|
|
18942
18947
|
const rawOutput = await (options.adapter ?? _generatorPRDDeps.adapter).complete(prompt, {
|
|
@@ -18947,7 +18952,7 @@ Rules:
|
|
|
18947
18952
|
});
|
|
18948
18953
|
let testCode = extractTestCode(rawOutput);
|
|
18949
18954
|
if (!testCode) {
|
|
18950
|
-
const targetPath = join2(options.featureDir,
|
|
18955
|
+
const targetPath = join2(options.featureDir, acceptanceTestFilename(options.language));
|
|
18951
18956
|
try {
|
|
18952
18957
|
const existing = await Bun.file(targetPath).text();
|
|
18953
18958
|
const recovered = extractTestCode(existing);
|
|
@@ -19471,37 +19476,6 @@ function parseAcpxJsonOutput(rawOutput) {
|
|
|
19471
19476
|
}
|
|
19472
19477
|
|
|
19473
19478
|
// src/agents/acp/spawn-client.ts
|
|
19474
|
-
import { homedir as homedir2 } from "os";
|
|
19475
|
-
import { isAbsolute as isAbsolute2 } from "path";
|
|
19476
|
-
function buildAllowedEnv2(extraEnv) {
|
|
19477
|
-
const allowed = {};
|
|
19478
|
-
const essentialVars = ["PATH", "TMPDIR", "NODE_ENV", "USER", "LOGNAME"];
|
|
19479
|
-
for (const varName of essentialVars) {
|
|
19480
|
-
if (process.env[varName])
|
|
19481
|
-
allowed[varName] = process.env[varName];
|
|
19482
|
-
}
|
|
19483
|
-
const rawHome = process.env.HOME ?? "";
|
|
19484
|
-
const safeHome = rawHome && isAbsolute2(rawHome) ? rawHome : homedir2();
|
|
19485
|
-
if (rawHome !== safeHome) {
|
|
19486
|
-
getSafeLogger()?.warn("env", `HOME env is not absolute ("${rawHome}"), falling back to os.homedir(): ${safeHome}`);
|
|
19487
|
-
}
|
|
19488
|
-
allowed.HOME = safeHome;
|
|
19489
|
-
const apiKeyVars = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY", "CLAUDE_API_KEY"];
|
|
19490
|
-
for (const varName of apiKeyVars) {
|
|
19491
|
-
if (process.env[varName])
|
|
19492
|
-
allowed[varName] = process.env[varName];
|
|
19493
|
-
}
|
|
19494
|
-
const allowedPrefixes = ["CLAUDE_", "NAX_", "CLAW_", "TURBO_", "ACPX_", "CODEX_", "GEMINI_"];
|
|
19495
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
19496
|
-
if (allowedPrefixes.some((prefix) => key.startsWith(prefix))) {
|
|
19497
|
-
allowed[key] = value;
|
|
19498
|
-
}
|
|
19499
|
-
}
|
|
19500
|
-
if (extraEnv)
|
|
19501
|
-
Object.assign(allowed, extraEnv);
|
|
19502
|
-
return allowed;
|
|
19503
|
-
}
|
|
19504
|
-
|
|
19505
19479
|
class SpawnAcpSession {
|
|
19506
19480
|
agentName;
|
|
19507
19481
|
sessionName;
|
|
@@ -19591,7 +19565,7 @@ class SpawnAcpSession {
|
|
|
19591
19565
|
await this.pidRegistry?.unregister(processPid);
|
|
19592
19566
|
}
|
|
19593
19567
|
}
|
|
19594
|
-
async close() {
|
|
19568
|
+
async close(options) {
|
|
19595
19569
|
if (this.activeProc) {
|
|
19596
19570
|
try {
|
|
19597
19571
|
this.activeProc.kill(15);
|
|
@@ -19610,6 +19584,14 @@ class SpawnAcpSession {
|
|
|
19610
19584
|
stderr: stderr.slice(0, 200)
|
|
19611
19585
|
});
|
|
19612
19586
|
}
|
|
19587
|
+
if (options?.forceTerminate) {
|
|
19588
|
+
try {
|
|
19589
|
+
const stopProc = _spawnClientDeps.spawn(["acpx", this.agentName, "stop"], { stdout: "pipe", stderr: "pipe" });
|
|
19590
|
+
await stopProc.exited;
|
|
19591
|
+
} catch (err) {
|
|
19592
|
+
getSafeLogger()?.debug("acp-adapter", "acpx stop failed (swallowed)", { cause: String(err) });
|
|
19593
|
+
}
|
|
19594
|
+
}
|
|
19613
19595
|
}
|
|
19614
19596
|
async cancelActivePrompt() {
|
|
19615
19597
|
if (this.activeProc) {
|
|
@@ -19643,7 +19625,7 @@ class SpawnAcpClient {
|
|
|
19643
19625
|
this.agentName = lastToken;
|
|
19644
19626
|
this.cwd = cwd || process.cwd();
|
|
19645
19627
|
this.timeoutSeconds = timeoutSeconds || 1800;
|
|
19646
|
-
this.env =
|
|
19628
|
+
this.env = buildAllowedEnv();
|
|
19647
19629
|
this.pidRegistry = pidRegistry;
|
|
19648
19630
|
}
|
|
19649
19631
|
async start() {}
|
|
@@ -19695,6 +19677,7 @@ var _spawnClientDeps;
|
|
|
19695
19677
|
var init_spawn_client = __esm(() => {
|
|
19696
19678
|
init_logger2();
|
|
19697
19679
|
init_bun_deps();
|
|
19680
|
+
init_env();
|
|
19698
19681
|
_spawnClientDeps = {
|
|
19699
19682
|
spawn: typedSpawn
|
|
19700
19683
|
};
|
|
@@ -20116,6 +20099,7 @@ class AcpAgentAdapter {
|
|
|
20116
20099
|
const client = _acpAdapterDeps.createClient(cmdStr, workdir);
|
|
20117
20100
|
await client.start();
|
|
20118
20101
|
let session = null;
|
|
20102
|
+
let hadError = false;
|
|
20119
20103
|
try {
|
|
20120
20104
|
session = await client.createSession({
|
|
20121
20105
|
agentName: this.name,
|
|
@@ -20157,6 +20141,7 @@ class AcpAgentAdapter {
|
|
|
20157
20141
|
}
|
|
20158
20142
|
return unwrapped;
|
|
20159
20143
|
} catch (err) {
|
|
20144
|
+
hadError = true;
|
|
20160
20145
|
const error48 = err instanceof Error ? err : new Error(String(err));
|
|
20161
20146
|
lastError = error48;
|
|
20162
20147
|
const shouldRetry = isRateLimitError(error48) && attempt < MAX_RATE_LIMIT_RETRIES - 1;
|
|
@@ -20170,7 +20155,7 @@ class AcpAgentAdapter {
|
|
|
20170
20155
|
await _acpAdapterDeps.sleep(backoffMs);
|
|
20171
20156
|
} finally {
|
|
20172
20157
|
if (session) {
|
|
20173
|
-
await session.close().catch(() => {});
|
|
20158
|
+
await session.close({ forceTerminate: hadError }).catch(() => {});
|
|
20174
20159
|
}
|
|
20175
20160
|
await client.close().catch(() => {});
|
|
20176
20161
|
}
|
|
@@ -20978,7 +20963,7 @@ function isPlainObject2(value) {
|
|
|
20978
20963
|
|
|
20979
20964
|
// src/config/path-security.ts
|
|
20980
20965
|
import { existsSync as existsSync4, lstatSync, realpathSync } from "fs";
|
|
20981
|
-
import { isAbsolute as
|
|
20966
|
+
import { isAbsolute as isAbsolute2, normalize, resolve } from "path";
|
|
20982
20967
|
function validateDirectory(dirPath, baseDir) {
|
|
20983
20968
|
const resolved = resolve(dirPath);
|
|
20984
20969
|
if (!existsSync4(resolved)) {
|
|
@@ -21010,7 +20995,7 @@ function validateDirectory(dirPath, baseDir) {
|
|
|
21010
20995
|
function isWithinDirectory(targetPath, basePath) {
|
|
21011
20996
|
const normalizedTarget = normalize(targetPath);
|
|
21012
20997
|
const normalizedBase = normalize(basePath);
|
|
21013
|
-
if (!
|
|
20998
|
+
if (!isAbsolute2(normalizedTarget) || !isAbsolute2(normalizedBase)) {
|
|
21014
20999
|
return false;
|
|
21015
21000
|
}
|
|
21016
21001
|
const baseWithSlash = normalizedBase.endsWith("/") ? normalizedBase : `${normalizedBase}/`;
|
|
@@ -21046,10 +21031,10 @@ var MAX_DIRECTORY_DEPTH = 10;
|
|
|
21046
21031
|
var init_path_security = () => {};
|
|
21047
21032
|
|
|
21048
21033
|
// src/config/paths.ts
|
|
21049
|
-
import { homedir as
|
|
21034
|
+
import { homedir as homedir2 } from "os";
|
|
21050
21035
|
import { join as join5, resolve as resolve2 } from "path";
|
|
21051
21036
|
function globalConfigDir() {
|
|
21052
|
-
return join5(
|
|
21037
|
+
return join5(homedir2(), ".nax");
|
|
21053
21038
|
}
|
|
21054
21039
|
var PROJECT_NAX_DIR = ".nax";
|
|
21055
21040
|
var init_paths = () => {};
|
|
@@ -22320,7 +22305,7 @@ var package_default;
|
|
|
22320
22305
|
var init_package = __esm(() => {
|
|
22321
22306
|
package_default = {
|
|
22322
22307
|
name: "@nathapp/nax",
|
|
22323
|
-
version: "0.54.
|
|
22308
|
+
version: "0.54.2",
|
|
22324
22309
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22325
22310
|
type: "module",
|
|
22326
22311
|
bin: {
|
|
@@ -22397,8 +22382,8 @@ var init_version = __esm(() => {
|
|
|
22397
22382
|
NAX_VERSION = package_default.version;
|
|
22398
22383
|
NAX_COMMIT = (() => {
|
|
22399
22384
|
try {
|
|
22400
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22401
|
-
return "
|
|
22385
|
+
if (/^[0-9a-f]{6,10}$/.test("18dd8fc"))
|
|
22386
|
+
return "18dd8fc";
|
|
22402
22387
|
} catch {}
|
|
22403
22388
|
try {
|
|
22404
22389
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -24085,53 +24070,44 @@ var init_acceptance2 = __esm(() => {
|
|
|
24085
24070
|
logger.warn("acceptance", "No feature directory \u2014 skipping acceptance tests");
|
|
24086
24071
|
return { action: "continue" };
|
|
24087
24072
|
}
|
|
24088
|
-
const
|
|
24089
|
-
|
|
24090
|
-
|
|
24091
|
-
|
|
24092
|
-
|
|
24093
|
-
|
|
24073
|
+
const testGroups = ctx.acceptanceTestPaths ?? [
|
|
24074
|
+
{
|
|
24075
|
+
testPath: path4.join(ctx.featureDir, effectiveConfig.acceptance.testPath),
|
|
24076
|
+
packageDir: ctx.workdir
|
|
24077
|
+
}
|
|
24078
|
+
];
|
|
24079
|
+
const allFailedACs = [];
|
|
24080
|
+
const allOutputParts = [];
|
|
24081
|
+
let anyError = false;
|
|
24082
|
+
let errorExitCode = 0;
|
|
24083
|
+
for (const { testPath, packageDir } of testGroups) {
|
|
24084
|
+
const testFile = Bun.file(testPath);
|
|
24085
|
+
const exists = await testFile.exists();
|
|
24086
|
+
if (!exists) {
|
|
24087
|
+
logger.warn("acceptance", "Acceptance test file not found \u2014 skipping", { testPath });
|
|
24088
|
+
continue;
|
|
24089
|
+
}
|
|
24090
|
+
const testCmdParts = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
|
|
24091
|
+
logger.info("acceptance", "Running acceptance command", {
|
|
24092
|
+
cmd: testCmdParts.join(" "),
|
|
24093
|
+
packageDir
|
|
24094
24094
|
});
|
|
24095
|
-
|
|
24096
|
-
|
|
24097
|
-
|
|
24098
|
-
|
|
24099
|
-
|
|
24100
|
-
|
|
24101
|
-
|
|
24102
|
-
|
|
24103
|
-
|
|
24104
|
-
|
|
24105
|
-
|
|
24106
|
-
new Response(proc.stdout).text(),
|
|
24107
|
-
new Response(proc.stderr).text()
|
|
24108
|
-
]);
|
|
24109
|
-
const output = `${stdout}
|
|
24095
|
+
const proc = Bun.spawn(testCmdParts, {
|
|
24096
|
+
cwd: packageDir,
|
|
24097
|
+
stdout: "pipe",
|
|
24098
|
+
stderr: "pipe"
|
|
24099
|
+
});
|
|
24100
|
+
const [exitCode, stdout, stderr] = await Promise.all([
|
|
24101
|
+
proc.exited,
|
|
24102
|
+
new Response(proc.stdout).text(),
|
|
24103
|
+
new Response(proc.stderr).text()
|
|
24104
|
+
]);
|
|
24105
|
+
const output = `${stdout}
|
|
24110
24106
|
${stderr}`;
|
|
24111
|
-
|
|
24112
|
-
|
|
24113
|
-
|
|
24114
|
-
|
|
24115
|
-
logger.info("acceptance", "All acceptance tests passed");
|
|
24116
|
-
return { action: "continue" };
|
|
24117
|
-
}
|
|
24118
|
-
if (failedACs.length > 0 && actualFailures.length === 0) {
|
|
24119
|
-
logger.info("acceptance", "All failed ACs are overridden \u2014 treating as pass");
|
|
24120
|
-
return { action: "continue" };
|
|
24121
|
-
}
|
|
24122
|
-
if (failedACs.length === 0 && exitCode !== 0) {
|
|
24123
|
-
logger.error("acceptance", "Tests errored with no AC failures parsed", { exitCode });
|
|
24124
|
-
logTestOutput(logger, "acceptance", output);
|
|
24125
|
-
ctx.acceptanceFailures = {
|
|
24126
|
-
failedACs: ["AC-ERROR"],
|
|
24127
|
-
testOutput: output
|
|
24128
|
-
};
|
|
24129
|
-
return {
|
|
24130
|
-
action: "fail",
|
|
24131
|
-
reason: `Acceptance tests errored (exit code ${exitCode}): syntax error, import failure, or unhandled exception`
|
|
24132
|
-
};
|
|
24133
|
-
}
|
|
24134
|
-
if (actualFailures.length > 0) {
|
|
24107
|
+
allOutputParts.push(output);
|
|
24108
|
+
const failedACs = parseTestFailures(output);
|
|
24109
|
+
const overrides = ctx.prd.acceptanceOverrides ?? {};
|
|
24110
|
+
const actualFailures = failedACs.filter((acId) => !overrides[acId]);
|
|
24135
24111
|
const overriddenFailures = failedACs.filter((acId) => overrides[acId]);
|
|
24136
24112
|
if (overriddenFailures.length > 0) {
|
|
24137
24113
|
logger.warn("acceptance", "Skipped failures (overridden)", {
|
|
@@ -24139,19 +24115,52 @@ ${stderr}`;
|
|
|
24139
24115
|
overrides: overriddenFailures.map((acId) => ({ acId, reason: overrides[acId] }))
|
|
24140
24116
|
});
|
|
24141
24117
|
}
|
|
24142
|
-
|
|
24143
|
-
|
|
24144
|
-
|
|
24145
|
-
|
|
24146
|
-
|
|
24147
|
-
|
|
24118
|
+
if (failedACs.length === 0 && exitCode !== 0) {
|
|
24119
|
+
logger.error("acceptance", "Tests errored with no AC failures parsed", {
|
|
24120
|
+
exitCode,
|
|
24121
|
+
packageDir
|
|
24122
|
+
});
|
|
24123
|
+
logTestOutput(logger, "acceptance", output);
|
|
24124
|
+
anyError = true;
|
|
24125
|
+
errorExitCode = exitCode;
|
|
24126
|
+
allFailedACs.push("AC-ERROR");
|
|
24127
|
+
continue;
|
|
24128
|
+
}
|
|
24129
|
+
for (const acId of actualFailures) {
|
|
24130
|
+
if (!allFailedACs.includes(acId)) {
|
|
24131
|
+
allFailedACs.push(acId);
|
|
24132
|
+
}
|
|
24133
|
+
}
|
|
24134
|
+
if (actualFailures.length > 0) {
|
|
24135
|
+
logger.error("acceptance", "Acceptance tests failed", {
|
|
24136
|
+
failedACs: actualFailures,
|
|
24137
|
+
packageDir
|
|
24138
|
+
});
|
|
24139
|
+
logTestOutput(logger, "acceptance", output);
|
|
24140
|
+
} else if (exitCode === 0) {
|
|
24141
|
+
logger.info("acceptance", "Package acceptance tests passed", { packageDir });
|
|
24142
|
+
}
|
|
24143
|
+
}
|
|
24144
|
+
const combinedOutput = allOutputParts.join(`
|
|
24145
|
+
`);
|
|
24146
|
+
if (allFailedACs.length === 0) {
|
|
24147
|
+
logger.info("acceptance", "All acceptance tests passed");
|
|
24148
|
+
return { action: "continue" };
|
|
24149
|
+
}
|
|
24150
|
+
ctx.acceptanceFailures = {
|
|
24151
|
+
failedACs: allFailedACs,
|
|
24152
|
+
testOutput: combinedOutput
|
|
24153
|
+
};
|
|
24154
|
+
if (anyError) {
|
|
24148
24155
|
return {
|
|
24149
24156
|
action: "fail",
|
|
24150
|
-
reason: `Acceptance tests
|
|
24157
|
+
reason: `Acceptance tests errored (exit code ${errorExitCode}): syntax error, import failure, or unhandled exception`
|
|
24151
24158
|
};
|
|
24152
24159
|
}
|
|
24153
|
-
|
|
24154
|
-
|
|
24160
|
+
return {
|
|
24161
|
+
action: "fail",
|
|
24162
|
+
reason: `Acceptance tests failed: ${allFailedACs.join(", ")}`
|
|
24163
|
+
};
|
|
24155
24164
|
}
|
|
24156
24165
|
};
|
|
24157
24166
|
});
|
|
@@ -24333,57 +24342,103 @@ ${stderr}` };
|
|
|
24333
24342
|
return { action: "fail", reason: "[acceptance-setup] featureDir is not set" };
|
|
24334
24343
|
}
|
|
24335
24344
|
const language = (ctx.effectiveConfig ?? ctx.config).project?.language;
|
|
24336
|
-
const testPath = path5.join(ctx.featureDir, acceptanceTestFilename(language));
|
|
24337
24345
|
const metaPath = path5.join(ctx.featureDir, "acceptance-meta.json");
|
|
24338
24346
|
const allCriteria = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-")).flatMap((s) => s.acceptanceCriteria);
|
|
24347
|
+
const nonFixStories = ctx.prd.userStories.filter((s) => !s.id.startsWith("US-FIX-"));
|
|
24348
|
+
const workdirGroups = new Map;
|
|
24349
|
+
for (const story of nonFixStories) {
|
|
24350
|
+
const wd = story.workdir ?? "";
|
|
24351
|
+
if (!workdirGroups.has(wd)) {
|
|
24352
|
+
workdirGroups.set(wd, { stories: [], criteria: [] });
|
|
24353
|
+
}
|
|
24354
|
+
const group = workdirGroups.get(wd);
|
|
24355
|
+
if (group) {
|
|
24356
|
+
group.stories.push(story);
|
|
24357
|
+
group.criteria.push(...story.acceptanceCriteria);
|
|
24358
|
+
}
|
|
24359
|
+
}
|
|
24360
|
+
if (workdirGroups.size === 0) {
|
|
24361
|
+
workdirGroups.set("", { stories: [], criteria: [] });
|
|
24362
|
+
}
|
|
24363
|
+
const testPaths = [];
|
|
24364
|
+
for (const [workdir] of workdirGroups) {
|
|
24365
|
+
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
24366
|
+
const testPath = path5.join(packageDir, acceptanceTestFilename(language));
|
|
24367
|
+
testPaths.push({ testPath, packageDir });
|
|
24368
|
+
}
|
|
24339
24369
|
let totalCriteria = 0;
|
|
24340
24370
|
let testableCount = 0;
|
|
24341
|
-
const
|
|
24342
|
-
|
|
24343
|
-
|
|
24371
|
+
const existsResults = await Promise.all(testPaths.map(({ testPath }) => _acceptanceSetupDeps.fileExists(testPath)));
|
|
24372
|
+
const anyFileMissing = existsResults.some((exists) => !exists);
|
|
24373
|
+
let shouldGenerate = anyFileMissing;
|
|
24374
|
+
if (!anyFileMissing) {
|
|
24344
24375
|
const fingerprint = computeACFingerprint(allCriteria);
|
|
24345
24376
|
const meta3 = await _acceptanceSetupDeps.readMeta(metaPath);
|
|
24377
|
+
getSafeLogger()?.debug("acceptance-setup", "Fingerprint check", {
|
|
24378
|
+
currentFingerprint: fingerprint,
|
|
24379
|
+
storedFingerprint: meta3?.acFingerprint ?? "none",
|
|
24380
|
+
match: meta3?.acFingerprint === fingerprint
|
|
24381
|
+
});
|
|
24346
24382
|
if (!meta3 || meta3.acFingerprint !== fingerprint) {
|
|
24347
|
-
|
|
24348
|
-
|
|
24383
|
+
getSafeLogger()?.info("acceptance-setup", "ACs changed \u2014 regenerating acceptance tests", {
|
|
24384
|
+
reason: !meta3 ? "no meta file" : "fingerprint mismatch"
|
|
24385
|
+
});
|
|
24386
|
+
for (const { testPath } of testPaths) {
|
|
24387
|
+
if (await _acceptanceSetupDeps.fileExists(testPath)) {
|
|
24388
|
+
await _acceptanceSetupDeps.copyFile(testPath, `${testPath}.bak`);
|
|
24389
|
+
await _acceptanceSetupDeps.deleteFile(testPath);
|
|
24390
|
+
}
|
|
24391
|
+
}
|
|
24349
24392
|
shouldGenerate = true;
|
|
24393
|
+
} else {
|
|
24394
|
+
getSafeLogger()?.info("acceptance-setup", "Reusing existing acceptance tests (fingerprint match)");
|
|
24350
24395
|
}
|
|
24351
24396
|
}
|
|
24352
24397
|
if (shouldGenerate) {
|
|
24353
24398
|
totalCriteria = allCriteria.length;
|
|
24354
24399
|
const { getAgent: getAgent2 } = await Promise.resolve().then(() => (init_agents(), exports_agents));
|
|
24355
24400
|
const agent = (ctx.agentGetFn ?? _acceptanceSetupDeps.getAgent)(ctx.config.autoMode.defaultAgent);
|
|
24356
|
-
let
|
|
24401
|
+
let allRefinedCriteria;
|
|
24357
24402
|
if (ctx.config.acceptance.refinement) {
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
24364
|
-
|
|
24403
|
+
allRefinedCriteria = [];
|
|
24404
|
+
for (const story of nonFixStories) {
|
|
24405
|
+
const storyRefined = await _acceptanceSetupDeps.refine(story.acceptanceCriteria, {
|
|
24406
|
+
storyId: story.id,
|
|
24407
|
+
codebaseContext: "",
|
|
24408
|
+
config: ctx.config,
|
|
24409
|
+
testStrategy: ctx.config.acceptance.testStrategy,
|
|
24410
|
+
testFramework: ctx.config.acceptance.testFramework
|
|
24411
|
+
});
|
|
24412
|
+
allRefinedCriteria = allRefinedCriteria.concat(storyRefined);
|
|
24413
|
+
}
|
|
24365
24414
|
} else {
|
|
24366
|
-
|
|
24415
|
+
allRefinedCriteria = nonFixStories.flatMap((story) => story.acceptanceCriteria.map((c) => ({
|
|
24367
24416
|
original: c,
|
|
24368
24417
|
refined: c,
|
|
24369
24418
|
testable: true,
|
|
24370
|
-
storyId:
|
|
24371
|
-
}));
|
|
24419
|
+
storyId: story.id
|
|
24420
|
+
})));
|
|
24421
|
+
}
|
|
24422
|
+
testableCount = allRefinedCriteria.filter((r) => r.testable).length;
|
|
24423
|
+
for (const [workdir, group] of workdirGroups) {
|
|
24424
|
+
const packageDir = workdir ? path5.join(ctx.workdir, workdir) : ctx.workdir;
|
|
24425
|
+
const testPath = path5.join(packageDir, acceptanceTestFilename(language));
|
|
24426
|
+
const groupStoryIds = new Set(group.stories.map((s) => s.id));
|
|
24427
|
+
const groupRefined = allRefinedCriteria.filter((r) => groupStoryIds.has(r.storyId));
|
|
24428
|
+
const result = await _acceptanceSetupDeps.generate(group.stories, groupRefined, {
|
|
24429
|
+
featureName: ctx.prd.feature,
|
|
24430
|
+
workdir: packageDir,
|
|
24431
|
+
featureDir: ctx.featureDir,
|
|
24432
|
+
codebaseContext: "",
|
|
24433
|
+
modelTier: ctx.config.acceptance.model ?? "fast",
|
|
24434
|
+
modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
|
|
24435
|
+
config: ctx.config,
|
|
24436
|
+
testStrategy: ctx.config.acceptance.testStrategy,
|
|
24437
|
+
testFramework: ctx.config.acceptance.testFramework,
|
|
24438
|
+
adapter: agent ?? undefined
|
|
24439
|
+
});
|
|
24440
|
+
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
24372
24441
|
}
|
|
24373
|
-
testableCount = refinedCriteria.filter((r) => r.testable).length;
|
|
24374
|
-
const result = await _acceptanceSetupDeps.generate(ctx.prd.userStories, refinedCriteria, {
|
|
24375
|
-
featureName: ctx.prd.feature,
|
|
24376
|
-
workdir: ctx.workdir,
|
|
24377
|
-
featureDir: ctx.featureDir,
|
|
24378
|
-
codebaseContext: "",
|
|
24379
|
-
modelTier: ctx.config.acceptance.model ?? "fast",
|
|
24380
|
-
modelDef: resolveModel(ctx.config.models[ctx.config.acceptance.model ?? "fast"]),
|
|
24381
|
-
config: ctx.config,
|
|
24382
|
-
testStrategy: ctx.config.acceptance.testStrategy,
|
|
24383
|
-
testFramework: ctx.config.acceptance.testFramework,
|
|
24384
|
-
adapter: agent ?? undefined
|
|
24385
|
-
});
|
|
24386
|
-
await _acceptanceSetupDeps.writeFile(testPath, result.testCode);
|
|
24387
24442
|
const fingerprint = computeACFingerprint(allCriteria);
|
|
24388
24443
|
await _acceptanceSetupDeps.writeMeta(metaPath, {
|
|
24389
24444
|
generatedAt: new Date().toISOString(),
|
|
@@ -24393,22 +24448,32 @@ ${stderr}` };
|
|
|
24393
24448
|
generator: "nax"
|
|
24394
24449
|
});
|
|
24395
24450
|
}
|
|
24451
|
+
ctx.acceptanceTestPaths = testPaths;
|
|
24396
24452
|
if (ctx.config.acceptance.redGate === false) {
|
|
24397
24453
|
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
24398
24454
|
return { action: "continue" };
|
|
24399
24455
|
}
|
|
24400
24456
|
const effectiveConfig = ctx.effectiveConfig ?? ctx.config;
|
|
24401
|
-
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
|
|
24457
|
+
let redFailCount = 0;
|
|
24458
|
+
for (const { testPath, packageDir } of testPaths) {
|
|
24459
|
+
const runCmd = buildAcceptanceRunCommand(testPath, effectiveConfig.project?.testFramework, effectiveConfig.acceptance.command);
|
|
24460
|
+
getSafeLogger()?.info("acceptance-setup", "Running acceptance RED gate command", {
|
|
24461
|
+
cmd: runCmd.join(" "),
|
|
24462
|
+
packageDir
|
|
24463
|
+
});
|
|
24464
|
+
const { exitCode } = await _acceptanceSetupDeps.runTest(testPath, packageDir, runCmd);
|
|
24465
|
+
if (exitCode !== 0) {
|
|
24466
|
+
redFailCount++;
|
|
24467
|
+
}
|
|
24468
|
+
}
|
|
24469
|
+
if (redFailCount === 0) {
|
|
24405
24470
|
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount: 0 };
|
|
24406
24471
|
return {
|
|
24407
24472
|
action: "skip",
|
|
24408
24473
|
reason: "[acceptance-setup] Acceptance tests already pass \u2014 they are not testing new behavior. Skipping acceptance gate."
|
|
24409
24474
|
};
|
|
24410
24475
|
}
|
|
24411
|
-
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount
|
|
24476
|
+
ctx.acceptanceSetup = { totalCriteria, testableCount, redFailCount };
|
|
24412
24477
|
return { action: "continue" };
|
|
24413
24478
|
}
|
|
24414
24479
|
};
|
|
@@ -24736,7 +24801,11 @@ If the implementation looks correct, respond with { "passed": true, "findings":
|
|
|
24736
24801
|
}
|
|
24737
24802
|
function parseLLMResponse(raw) {
|
|
24738
24803
|
try {
|
|
24739
|
-
|
|
24804
|
+
let cleaned = raw.trim();
|
|
24805
|
+
const fenceMatch = cleaned.match(/^```(?:json)?\s*\n?([\s\S]*?)\n?\s*```$/);
|
|
24806
|
+
if (fenceMatch)
|
|
24807
|
+
cleaned = fenceMatch[1].trim();
|
|
24808
|
+
const parsed = JSON.parse(cleaned);
|
|
24740
24809
|
if (typeof parsed !== "object" || parsed === null)
|
|
24741
24810
|
return null;
|
|
24742
24811
|
const obj = parsed;
|
|
@@ -24784,6 +24853,7 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
24784
24853
|
durationMs: Date.now() - startTime
|
|
24785
24854
|
};
|
|
24786
24855
|
}
|
|
24856
|
+
logger?.info("review", "Running semantic check", { storyId: story.id, modelTier: semanticConfig.modelTier });
|
|
24787
24857
|
const rawDiff = await collectDiff(workdir, storyGitRef);
|
|
24788
24858
|
const diff = truncateDiff(rawDiff);
|
|
24789
24859
|
const agent = modelResolver(semanticConfig.modelTier);
|
|
@@ -24803,7 +24873,11 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
24803
24873
|
const prompt = buildPrompt(story, semanticConfig, diff);
|
|
24804
24874
|
let rawResponse;
|
|
24805
24875
|
try {
|
|
24806
|
-
rawResponse = await agent.complete(prompt
|
|
24876
|
+
rawResponse = await agent.complete(prompt, {
|
|
24877
|
+
sessionName: `nax-semantic-${story.id}`,
|
|
24878
|
+
workdir,
|
|
24879
|
+
timeoutMs: semanticConfig.timeoutMs
|
|
24880
|
+
});
|
|
24807
24881
|
} catch (err) {
|
|
24808
24882
|
logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
|
|
24809
24883
|
return {
|
|
@@ -24828,6 +24902,11 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
24828
24902
|
};
|
|
24829
24903
|
}
|
|
24830
24904
|
if (!parsed.passed && parsed.findings.length > 0) {
|
|
24905
|
+
const durationMs2 = Date.now() - startTime;
|
|
24906
|
+
logger?.warn("review", `Semantic review failed: ${parsed.findings.length} findings`, {
|
|
24907
|
+
storyId: story.id,
|
|
24908
|
+
durationMs: durationMs2
|
|
24909
|
+
});
|
|
24831
24910
|
const output = `Semantic review failed:
|
|
24832
24911
|
|
|
24833
24912
|
${formatFindings(parsed.findings)}`;
|
|
@@ -24837,17 +24916,21 @@ ${formatFindings(parsed.findings)}`;
|
|
|
24837
24916
|
command: "",
|
|
24838
24917
|
exitCode: 1,
|
|
24839
24918
|
output,
|
|
24840
|
-
durationMs:
|
|
24919
|
+
durationMs: durationMs2,
|
|
24841
24920
|
findings: toReviewFindings(parsed.findings)
|
|
24842
24921
|
};
|
|
24843
24922
|
}
|
|
24923
|
+
const durationMs = Date.now() - startTime;
|
|
24924
|
+
if (parsed.passed) {
|
|
24925
|
+
logger?.info("review", "Semantic review passed", { storyId: story.id, durationMs });
|
|
24926
|
+
}
|
|
24844
24927
|
return {
|
|
24845
24928
|
check: "semantic",
|
|
24846
24929
|
success: parsed.passed,
|
|
24847
24930
|
command: "",
|
|
24848
24931
|
exitCode: parsed.passed ? 0 : 1,
|
|
24849
24932
|
output: parsed.passed ? "Semantic review passed" : "Semantic review failed (no findings)",
|
|
24850
|
-
durationMs
|
|
24933
|
+
durationMs
|
|
24851
24934
|
};
|
|
24852
24935
|
}
|
|
24853
24936
|
var _semanticDeps, DIFF_CAP_BYTES = 12288, DEFAULT_RULES;
|
|
@@ -25030,7 +25113,8 @@ async function runReview(config2, workdir, executionConfig, qualityCommands, sto
|
|
|
25030
25113
|
/nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
25031
25114
|
/\.nax-verifier-verdict\.json$/,
|
|
25032
25115
|
/\.nax-pids$/,
|
|
25033
|
-
/\.nax-wt
|
|
25116
|
+
/\.nax-wt\//,
|
|
25117
|
+
/\.nax-acceptance[^/]*$/
|
|
25034
25118
|
];
|
|
25035
25119
|
const uncommittedFiles = allUncommittedFiles.filter((f) => !NAX_RUNTIME_PATTERNS.some((pattern) => pattern.test(f)));
|
|
25036
25120
|
if (uncommittedFiles.length > 0) {
|
|
@@ -25055,7 +25139,11 @@ Stage and commit these files before running review.`
|
|
|
25055
25139
|
description: story?.description ?? "",
|
|
25056
25140
|
acceptanceCriteria: story?.acceptanceCriteria ?? []
|
|
25057
25141
|
};
|
|
25058
|
-
const semanticCfg = config2.semantic ?? {
|
|
25142
|
+
const semanticCfg = config2.semantic ?? {
|
|
25143
|
+
modelTier: "balanced",
|
|
25144
|
+
rules: [],
|
|
25145
|
+
timeoutMs: 600000
|
|
25146
|
+
};
|
|
25059
25147
|
const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null));
|
|
25060
25148
|
checks3.push(result2);
|
|
25061
25149
|
if (!result2.success && !firstFailure) {
|
|
@@ -31090,7 +31178,7 @@ var init_init_context = __esm(() => {
|
|
|
31090
31178
|
|
|
31091
31179
|
// src/utils/path-security.ts
|
|
31092
31180
|
import { realpathSync as realpathSync3 } from "fs";
|
|
31093
|
-
import { dirname as dirname4, isAbsolute as
|
|
31181
|
+
import { dirname as dirname4, isAbsolute as isAbsolute3, join as join31, normalize as normalize2, resolve as resolve5 } from "path";
|
|
31094
31182
|
function safeRealpathForComparison(p) {
|
|
31095
31183
|
try {
|
|
31096
31184
|
return realpathSync3(p);
|
|
@@ -31107,7 +31195,7 @@ function validateModulePath(modulePath, allowedRoots) {
|
|
|
31107
31195
|
return { valid: false, error: "Module path is empty" };
|
|
31108
31196
|
}
|
|
31109
31197
|
const resolvedRoots = allowedRoots.map((r) => safeRealpathForComparison(resolve5(r)));
|
|
31110
|
-
if (
|
|
31198
|
+
if (isAbsolute3(modulePath)) {
|
|
31111
31199
|
const normalized = normalize2(modulePath);
|
|
31112
31200
|
const resolved = safeRealpathForComparison(normalized);
|
|
31113
31201
|
const isWithin = resolvedRoots.some((root) => resolved.startsWith(`${root}/`) || resolved === root);
|
|
@@ -31735,7 +31823,8 @@ var init_checks_git = __esm(() => {
|
|
|
31735
31823
|
/^.{2} \.nax\/features\/[^/]+\/acceptance-refined\.json$/,
|
|
31736
31824
|
/^.{2} \.nax-verifier-verdict\.json$/,
|
|
31737
31825
|
/^.{2} \.nax-pids$/,
|
|
31738
|
-
/^.{2} \.nax-wt
|
|
31826
|
+
/^.{2} \.nax-wt\//,
|
|
31827
|
+
/^.{2} .*\.nax-acceptance[^/]*$/
|
|
31739
31828
|
];
|
|
31740
31829
|
});
|
|
31741
31830
|
|
|
@@ -31948,7 +32037,7 @@ var init_checks_blockers = __esm(() => {
|
|
|
31948
32037
|
|
|
31949
32038
|
// src/precheck/checks-warnings.ts
|
|
31950
32039
|
import { existsSync as existsSync30 } from "fs";
|
|
31951
|
-
import { isAbsolute as
|
|
32040
|
+
import { isAbsolute as isAbsolute5 } from "path";
|
|
31952
32041
|
async function checkClaudeMdExists(workdir) {
|
|
31953
32042
|
const claudeMdPath = `${workdir}/CLAUDE.md`;
|
|
31954
32043
|
const passed = existsSync30(claudeMdPath);
|
|
@@ -32048,7 +32137,8 @@ async function checkGitignoreCoversNax(workdir) {
|
|
|
32048
32137
|
".nax/metrics.json",
|
|
32049
32138
|
".nax/features/*/status.json",
|
|
32050
32139
|
".nax-pids",
|
|
32051
|
-
".nax-wt/"
|
|
32140
|
+
".nax-wt/",
|
|
32141
|
+
"**/.nax-acceptance*"
|
|
32052
32142
|
];
|
|
32053
32143
|
const missing = patterns.filter((pattern) => !content.includes(pattern));
|
|
32054
32144
|
const passed = missing.length === 0;
|
|
@@ -32080,7 +32170,7 @@ async function checkPromptOverrideFiles(config2, workdir) {
|
|
|
32080
32170
|
}
|
|
32081
32171
|
async function checkHomeEnvValid() {
|
|
32082
32172
|
const home = process.env.HOME ?? "";
|
|
32083
|
-
const passed = home !== "" &&
|
|
32173
|
+
const passed = home !== "" && isAbsolute5(home);
|
|
32084
32174
|
return {
|
|
32085
32175
|
name: "home-env-valid",
|
|
32086
32176
|
tier: "warning",
|
|
@@ -32183,6 +32273,24 @@ async function checkLanguageTools(profile, workdir) {
|
|
|
32183
32273
|
message: `Missing ${language} tools: ${missing.join(", ")}. ${toolConfig.installHint}`
|
|
32184
32274
|
};
|
|
32185
32275
|
}
|
|
32276
|
+
function checkBuildCommandInReviewChecks(config2) {
|
|
32277
|
+
const hasBuildCmd = !!(config2.review?.commands?.build || config2.quality?.commands?.build);
|
|
32278
|
+
const buildInChecks = config2.review?.checks?.includes("build") ?? false;
|
|
32279
|
+
if (hasBuildCmd && !buildInChecks) {
|
|
32280
|
+
return {
|
|
32281
|
+
name: "build-command-in-review-checks",
|
|
32282
|
+
tier: "warning",
|
|
32283
|
+
passed: false,
|
|
32284
|
+
message: 'A build command is configured but "build" is not in review.checks \u2014 the build step will never run. Add "build" to review.checks to enable it.'
|
|
32285
|
+
};
|
|
32286
|
+
}
|
|
32287
|
+
return {
|
|
32288
|
+
name: "build-command-in-review-checks",
|
|
32289
|
+
tier: "warning",
|
|
32290
|
+
passed: true,
|
|
32291
|
+
message: "build command check OK"
|
|
32292
|
+
};
|
|
32293
|
+
}
|
|
32186
32294
|
var _languageToolsDeps;
|
|
32187
32295
|
var init_checks_warnings = __esm(() => {
|
|
32188
32296
|
_languageToolsDeps = {
|
|
@@ -32374,7 +32482,8 @@ function getEnvironmentWarnings(config2, workdir) {
|
|
|
32374
32482
|
() => checkHomeEnvValid(),
|
|
32375
32483
|
() => checkPromptOverrideFiles(config2, workdir),
|
|
32376
32484
|
() => checkLanguageTools(config2.project, workdir),
|
|
32377
|
-
() => checkMultiAgentHealth()
|
|
32485
|
+
() => checkMultiAgentHealth(),
|
|
32486
|
+
() => Promise.resolve(checkBuildCommandInReviewChecks(config2))
|
|
32378
32487
|
];
|
|
32379
32488
|
}
|
|
32380
32489
|
function getProjectBlockers(prd) {
|
|
@@ -34711,12 +34820,12 @@ var init_parallel_executor = __esm(() => {
|
|
|
34711
34820
|
|
|
34712
34821
|
// src/pipeline/subscribers/events-writer.ts
|
|
34713
34822
|
import { appendFile as appendFile2, mkdir as mkdir2 } from "fs/promises";
|
|
34714
|
-
import { homedir as
|
|
34823
|
+
import { homedir as homedir5 } from "os";
|
|
34715
34824
|
import { basename as basename5, join as join49 } from "path";
|
|
34716
34825
|
function wireEventsWriter(bus, feature, runId, workdir) {
|
|
34717
34826
|
const logger = getSafeLogger();
|
|
34718
34827
|
const project = basename5(workdir);
|
|
34719
|
-
const eventsDir = join49(
|
|
34828
|
+
const eventsDir = join49(homedir5(), ".nax", "events", project);
|
|
34720
34829
|
const eventsFile = join49(eventsDir, "events.jsonl");
|
|
34721
34830
|
let dirReady = false;
|
|
34722
34831
|
const write = (line) => {
|
|
@@ -34890,12 +34999,12 @@ var init_interaction2 = __esm(() => {
|
|
|
34890
34999
|
|
|
34891
35000
|
// src/pipeline/subscribers/registry.ts
|
|
34892
35001
|
import { mkdir as mkdir3, writeFile } from "fs/promises";
|
|
34893
|
-
import { homedir as
|
|
35002
|
+
import { homedir as homedir6 } from "os";
|
|
34894
35003
|
import { basename as basename6, join as join50 } from "path";
|
|
34895
35004
|
function wireRegistry(bus, feature, runId, workdir) {
|
|
34896
35005
|
const logger = getSafeLogger();
|
|
34897
35006
|
const project = basename6(workdir);
|
|
34898
|
-
const runDir = join50(
|
|
35007
|
+
const runDir = join50(homedir6(), ".nax", "runs", `${project}-${feature}-${runId}`);
|
|
34899
35008
|
const metaFile = join50(runDir, "meta.json");
|
|
34900
35009
|
const unsub = bus.on("run:started", (_ev) => {
|
|
34901
35010
|
(async () => {
|
|
@@ -35759,7 +35868,8 @@ async function executeSequential(ctx, initialPrd) {
|
|
|
35759
35868
|
story: prd.userStories[0],
|
|
35760
35869
|
stories: prd.userStories,
|
|
35761
35870
|
routing: { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "" },
|
|
35762
|
-
hooks: ctx.hooks
|
|
35871
|
+
hooks: ctx.hooks,
|
|
35872
|
+
agentGetFn: ctx.agentGetFn
|
|
35763
35873
|
};
|
|
35764
35874
|
await runPipeline(preRunPipeline, preRunCtx, ctx.eventEmitter);
|
|
35765
35875
|
while (iterations < ctx.config.execution.maxIterations) {
|
|
@@ -67558,7 +67668,7 @@ var require_jsx_dev_runtime = __commonJS((exports, module) => {
|
|
|
67558
67668
|
// bin/nax.ts
|
|
67559
67669
|
init_source();
|
|
67560
67670
|
import { existsSync as existsSync34, mkdirSync as mkdirSync6 } from "fs";
|
|
67561
|
-
import { homedir as
|
|
67671
|
+
import { homedir as homedir8 } from "os";
|
|
67562
67672
|
import { join as join56 } from "path";
|
|
67563
67673
|
|
|
67564
67674
|
// node_modules/commander/esm.mjs
|
|
@@ -71101,10 +71211,10 @@ import { readdir as readdir3 } from "fs/promises";
|
|
|
71101
71211
|
import { join as join39 } from "path";
|
|
71102
71212
|
|
|
71103
71213
|
// src/utils/paths.ts
|
|
71104
|
-
import { homedir as
|
|
71214
|
+
import { homedir as homedir4 } from "os";
|
|
71105
71215
|
import { join as join38 } from "path";
|
|
71106
71216
|
function getRunsDir() {
|
|
71107
|
-
return process.env.NAX_RUNS_DIR ?? join38(
|
|
71217
|
+
return process.env.NAX_RUNS_DIR ?? join38(homedir4(), ".nax", "runs");
|
|
71108
71218
|
}
|
|
71109
71219
|
|
|
71110
71220
|
// src/commands/logs-reader.ts
|
|
@@ -79668,7 +79778,7 @@ program2.command("run").description("Run the orchestration loop for a feature").
|
|
|
79668
79778
|
config2.autoMode.defaultAgent = options.agent;
|
|
79669
79779
|
}
|
|
79670
79780
|
config2.execution.maxIterations = Number.parseInt(options.maxIterations, 10);
|
|
79671
|
-
const globalNaxDir = join56(
|
|
79781
|
+
const globalNaxDir = join56(homedir8(), ".nax");
|
|
79672
79782
|
const hooks = await loadHooksConfig(naxDir, globalNaxDir);
|
|
79673
79783
|
const eventEmitter = new PipelineEventEmitter;
|
|
79674
79784
|
let tuiInstance;
|