@nathapp/nax 0.54.6 → 0.54.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/nax.js +108 -55
- package/package.json +1 -1
package/dist/nax.js
CHANGED
|
@@ -17982,7 +17982,8 @@ var init_schemas3 = __esm(() => {
|
|
|
17982
17982
|
SemanticReviewConfigSchema = exports_external.object({
|
|
17983
17983
|
modelTier: ModelTierSchema.default("balanced"),
|
|
17984
17984
|
rules: exports_external.array(exports_external.string()).default([]),
|
|
17985
|
-
timeoutMs: exports_external.number().int().positive().default(600000)
|
|
17985
|
+
timeoutMs: exports_external.number().int().positive().default(600000),
|
|
17986
|
+
excludePatterns: exports_external.array(exports_external.string()).default([":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"])
|
|
17986
17987
|
});
|
|
17987
17988
|
ReviewConfigSchema = exports_external.object({
|
|
17988
17989
|
enabled: exports_external.boolean(),
|
|
@@ -18278,7 +18279,8 @@ var init_defaults = __esm(() => {
|
|
|
18278
18279
|
semantic: {
|
|
18279
18280
|
modelTier: "balanced",
|
|
18280
18281
|
rules: [],
|
|
18281
|
-
timeoutMs: 600000
|
|
18282
|
+
timeoutMs: 600000,
|
|
18283
|
+
excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
|
|
18282
18284
|
}
|
|
18283
18285
|
},
|
|
18284
18286
|
plan: {
|
|
@@ -19927,14 +19929,13 @@ function extractQuestion(output) {
|
|
|
19927
19929
|
const text = output.trim();
|
|
19928
19930
|
if (!text)
|
|
19929
19931
|
return null;
|
|
19930
|
-
const
|
|
19931
|
-
|
|
19932
|
-
|
|
19933
|
-
|
|
19934
|
-
|
|
19935
|
-
|
|
19936
|
-
|
|
19937
|
-
const lower = text.toLowerCase();
|
|
19932
|
+
const lines = text.split(`
|
|
19933
|
+
`).filter((l) => l.trim().length > 0);
|
|
19934
|
+
const lastLine = lines.at(-1)?.trim() ?? "";
|
|
19935
|
+
if (lastLine.endsWith("?") && lastLine.length > 10) {
|
|
19936
|
+
return lastLine;
|
|
19937
|
+
}
|
|
19938
|
+
const lower = lastLine.toLowerCase();
|
|
19938
19939
|
const markers = [
|
|
19939
19940
|
"please confirm",
|
|
19940
19941
|
"please specify",
|
|
@@ -19946,7 +19947,7 @@ function extractQuestion(output) {
|
|
|
19946
19947
|
];
|
|
19947
19948
|
for (const marker of markers) {
|
|
19948
19949
|
if (lower.includes(marker)) {
|
|
19949
|
-
return
|
|
19950
|
+
return lastLine;
|
|
19950
19951
|
}
|
|
19951
19952
|
}
|
|
19952
19953
|
return null;
|
|
@@ -20133,7 +20134,19 @@ class AcpAgentAdapter {
|
|
|
20133
20134
|
};
|
|
20134
20135
|
}
|
|
20135
20136
|
async complete(prompt, _options) {
|
|
20136
|
-
|
|
20137
|
+
let model = _options?.model;
|
|
20138
|
+
if (!model && _options?.modelTier && _options?.config?.models) {
|
|
20139
|
+
const tier = _options.modelTier;
|
|
20140
|
+
const { resolveModel: resolveModel2 } = await Promise.resolve().then(() => (init_schema(), exports_schema));
|
|
20141
|
+
const models = _options.config.models;
|
|
20142
|
+
const entry = models[tier] ?? models.balanced;
|
|
20143
|
+
if (entry) {
|
|
20144
|
+
try {
|
|
20145
|
+
model = resolveModel2(entry).model;
|
|
20146
|
+
} catch {}
|
|
20147
|
+
}
|
|
20148
|
+
}
|
|
20149
|
+
model ??= "default";
|
|
20137
20150
|
const timeoutMs = _options?.timeoutMs ?? 120000;
|
|
20138
20151
|
const permissionMode = resolvePermissions(_options?.config, "complete").mode;
|
|
20139
20152
|
const workdir = _options?.workdir;
|
|
@@ -22352,7 +22365,7 @@ var package_default;
|
|
|
22352
22365
|
var init_package = __esm(() => {
|
|
22353
22366
|
package_default = {
|
|
22354
22367
|
name: "@nathapp/nax",
|
|
22355
|
-
version: "0.54.
|
|
22368
|
+
version: "0.54.8",
|
|
22356
22369
|
description: "AI Coding Agent Orchestrator \u2014 loops until done",
|
|
22357
22370
|
type: "module",
|
|
22358
22371
|
bin: {
|
|
@@ -22429,8 +22442,8 @@ var init_version = __esm(() => {
|
|
|
22429
22442
|
NAX_VERSION = package_default.version;
|
|
22430
22443
|
NAX_COMMIT = (() => {
|
|
22431
22444
|
try {
|
|
22432
|
-
if (/^[0-9a-f]{6,10}$/.test("
|
|
22433
|
-
return "
|
|
22445
|
+
if (/^[0-9a-f]{6,10}$/.test("6f97ec3"))
|
|
22446
|
+
return "6f97ec3";
|
|
22434
22447
|
} catch {}
|
|
22435
22448
|
try {
|
|
22436
22449
|
const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
|
|
@@ -22478,7 +22491,7 @@ function buildInteractionBridge(chain, context, timeoutMs = DEFAULT_INTERACTION_
|
|
|
22478
22491
|
}
|
|
22479
22492
|
var QUESTION_PATTERNS, DEFAULT_INTERACTION_TIMEOUT_MS = 120000;
|
|
22480
22493
|
var init_bridge_builder = __esm(() => {
|
|
22481
|
-
QUESTION_PATTERNS = [
|
|
22494
|
+
QUESTION_PATTERNS = [/\?\s*$/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
|
|
22482
22495
|
});
|
|
22483
22496
|
|
|
22484
22497
|
// src/interaction/chain.ts
|
|
@@ -24853,9 +24866,13 @@ var init_language_commands = __esm(() => {
|
|
|
24853
24866
|
|
|
24854
24867
|
// src/review/semantic.ts
|
|
24855
24868
|
var {spawn: spawn2 } = globalThis.Bun;
|
|
24856
|
-
async function collectDiff(workdir, storyGitRef) {
|
|
24869
|
+
async function collectDiff(workdir, storyGitRef, excludePatterns) {
|
|
24870
|
+
const cmd = ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`];
|
|
24871
|
+
if (excludePatterns.length > 0) {
|
|
24872
|
+
cmd.push("--", ".", ...excludePatterns);
|
|
24873
|
+
}
|
|
24857
24874
|
const proc = _semanticDeps.spawn({
|
|
24858
|
-
cmd
|
|
24875
|
+
cmd,
|
|
24859
24876
|
cwd: workdir,
|
|
24860
24877
|
stdout: "pipe",
|
|
24861
24878
|
stderr: "pipe"
|
|
@@ -24870,26 +24887,44 @@ async function collectDiff(workdir, storyGitRef) {
|
|
|
24870
24887
|
}
|
|
24871
24888
|
return stdout;
|
|
24872
24889
|
}
|
|
24873
|
-
function
|
|
24890
|
+
async function collectDiffStat(workdir, storyGitRef) {
|
|
24891
|
+
const proc = _semanticDeps.spawn({
|
|
24892
|
+
cmd: ["git", "diff", "--stat", `${storyGitRef}..HEAD`],
|
|
24893
|
+
cwd: workdir,
|
|
24894
|
+
stdout: "pipe",
|
|
24895
|
+
stderr: "pipe"
|
|
24896
|
+
});
|
|
24897
|
+
const [exitCode, stdout] = await Promise.all([
|
|
24898
|
+
proc.exited,
|
|
24899
|
+
new Response(proc.stdout).text(),
|
|
24900
|
+
new Response(proc.stderr).text()
|
|
24901
|
+
]);
|
|
24902
|
+
return exitCode === 0 ? stdout.trim() : "";
|
|
24903
|
+
}
|
|
24904
|
+
function truncateDiff(diff, stat) {
|
|
24874
24905
|
if (diff.length <= DIFF_CAP_BYTES) {
|
|
24875
24906
|
return diff;
|
|
24876
24907
|
}
|
|
24877
24908
|
const truncated = diff.slice(0, DIFF_CAP_BYTES);
|
|
24878
|
-
const
|
|
24879
|
-
|
|
24880
|
-
|
|
24909
|
+
const visibleFiles = (truncated.match(/^diff --git/gm) ?? []).length;
|
|
24910
|
+
const totalFiles = (diff.match(/^diff --git/gm) ?? []).length;
|
|
24911
|
+
const statPreamble = stat ? `## File Summary (all changed files)
|
|
24912
|
+
${stat}
|
|
24913
|
+
|
|
24914
|
+
## Diff (truncated \u2014 ${visibleFiles}/${totalFiles} files shown)
|
|
24915
|
+
` : "";
|
|
24916
|
+
return `${statPreamble}${truncated}
|
|
24917
|
+
... (truncated at ${DIFF_CAP_BYTES} bytes, showing ${visibleFiles}/${totalFiles} files)`;
|
|
24881
24918
|
}
|
|
24882
24919
|
function buildPrompt(story, semanticConfig, diff) {
|
|
24883
24920
|
const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
|
|
24884
|
-
`);
|
|
24885
|
-
const defaultRulesText = DEFAULT_RULES.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
24886
24921
|
`);
|
|
24887
24922
|
const customRulesSection = semanticConfig.rules.length > 0 ? `
|
|
24888
|
-
##
|
|
24923
|
+
## Additional Review Rules
|
|
24889
24924
|
${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
|
|
24890
24925
|
`)}
|
|
24891
24926
|
` : "";
|
|
24892
|
-
return `You are a code reviewer.
|
|
24927
|
+
return `You are a semantic code reviewer. Your job is to verify that the implementation satisfies the story's acceptance criteria (ACs). You are NOT a linter or style checker \u2014 lint, typecheck, and convention checks are handled separately.
|
|
24893
24928
|
|
|
24894
24929
|
## Story: ${story.title}
|
|
24895
24930
|
|
|
@@ -24898,27 +24933,29 @@ ${story.description}
|
|
|
24898
24933
|
|
|
24899
24934
|
### Acceptance Criteria
|
|
24900
24935
|
${acList}
|
|
24901
|
-
|
|
24902
|
-
## Review Rules
|
|
24903
|
-
|
|
24904
|
-
### Default Rules
|
|
24905
|
-
${defaultRulesText}
|
|
24906
24936
|
${customRulesSection}
|
|
24907
|
-
## Git Diff
|
|
24937
|
+
## Git Diff (production code only \u2014 test files excluded)
|
|
24908
24938
|
|
|
24909
24939
|
\`\`\`diff
|
|
24910
24940
|
${diff}\`\`\`
|
|
24911
24941
|
|
|
24912
24942
|
## Instructions
|
|
24913
24943
|
|
|
24914
|
-
|
|
24915
|
-
|
|
24944
|
+
For each acceptance criterion, verify the diff implements it correctly. Flag issues only when:
|
|
24945
|
+
1. An AC is not implemented or partially implemented
|
|
24946
|
+
2. The implementation contradicts what the AC specifies
|
|
24947
|
+
3. New code has dead paths that will never execute (stubs, noops, unreachable branches)
|
|
24948
|
+
4. New code is not wired into callers/exports (written but never used)
|
|
24949
|
+
|
|
24950
|
+
Do NOT flag: style issues, naming conventions, import ordering, file length, or anything lint handles.
|
|
24951
|
+
|
|
24952
|
+
Respond in JSON format:
|
|
24916
24953
|
{
|
|
24917
24954
|
"passed": boolean,
|
|
24918
24955
|
"findings": [
|
|
24919
24956
|
{
|
|
24920
24957
|
"severity": "error" | "warn" | "info",
|
|
24921
|
-
"file": "path/to/file
|
|
24958
|
+
"file": "path/to/file",
|
|
24922
24959
|
"line": 42,
|
|
24923
24960
|
"issue": "description of the issue",
|
|
24924
24961
|
"suggestion": "how to fix it"
|
|
@@ -24926,7 +24963,7 @@ Format:
|
|
|
24926
24963
|
]
|
|
24927
24964
|
}
|
|
24928
24965
|
|
|
24929
|
-
If
|
|
24966
|
+
If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`;
|
|
24930
24967
|
}
|
|
24931
24968
|
function parseLLMResponse(raw) {
|
|
24932
24969
|
try {
|
|
@@ -24969,7 +25006,7 @@ function toReviewFindings(findings) {
|
|
|
24969
25006
|
source: "semantic-review"
|
|
24970
25007
|
}));
|
|
24971
25008
|
}
|
|
24972
|
-
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver) {
|
|
25009
|
+
async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig) {
|
|
24973
25010
|
const startTime = Date.now();
|
|
24974
25011
|
const logger = getSafeLogger();
|
|
24975
25012
|
if (!storyGitRef) {
|
|
@@ -24982,9 +25019,15 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
24982
25019
|
durationMs: Date.now() - startTime
|
|
24983
25020
|
};
|
|
24984
25021
|
}
|
|
24985
|
-
logger?.info("review", "Running semantic check", {
|
|
24986
|
-
|
|
24987
|
-
|
|
25022
|
+
logger?.info("review", "Running semantic check", {
|
|
25023
|
+
storyId: story.id,
|
|
25024
|
+
modelTier: semanticConfig.modelTier,
|
|
25025
|
+
configProvided: !!naxConfig
|
|
25026
|
+
});
|
|
25027
|
+
const rawDiff = await collectDiff(workdir, storyGitRef, semanticConfig.excludePatterns);
|
|
25028
|
+
const needsTruncation = rawDiff.length > DIFF_CAP_BYTES;
|
|
25029
|
+
const stat = needsTruncation ? await collectDiffStat(workdir, storyGitRef) : undefined;
|
|
25030
|
+
const diff = truncateDiff(rawDiff, stat);
|
|
24988
25031
|
const agent = modelResolver(semanticConfig.modelTier);
|
|
24989
25032
|
if (!agent) {
|
|
24990
25033
|
logger?.warn("semantic", "No agent available for semantic review \u2014 skipping", {
|
|
@@ -25005,7 +25048,9 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
25005
25048
|
rawResponse = await agent.complete(prompt, {
|
|
25006
25049
|
sessionName: `nax-semantic-${story.id}`,
|
|
25007
25050
|
workdir,
|
|
25008
|
-
timeoutMs: semanticConfig.timeoutMs
|
|
25051
|
+
timeoutMs: semanticConfig.timeoutMs,
|
|
25052
|
+
modelTier: semanticConfig.modelTier,
|
|
25053
|
+
config: naxConfig
|
|
25009
25054
|
});
|
|
25010
25055
|
} catch (err) {
|
|
25011
25056
|
logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
|
|
@@ -25020,6 +25065,20 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
|
|
|
25020
25065
|
}
|
|
25021
25066
|
const parsed = parseLLMResponse(rawResponse);
|
|
25022
25067
|
if (!parsed) {
|
|
25068
|
+
const looksLikeFail = /"passed"\s*:\s*false/.test(rawResponse);
|
|
25069
|
+
if (looksLikeFail) {
|
|
25070
|
+
logger?.warn("semantic", "LLM returned truncated JSON with passed:false \u2014 treating as failure", {
|
|
25071
|
+
rawResponse: rawResponse.slice(0, 200)
|
|
25072
|
+
});
|
|
25073
|
+
return {
|
|
25074
|
+
check: "semantic",
|
|
25075
|
+
success: false,
|
|
25076
|
+
command: "",
|
|
25077
|
+
exitCode: 1,
|
|
25078
|
+
output: "semantic review: LLM response truncated but indicated failure (passed:false found in partial response)",
|
|
25079
|
+
durationMs: Date.now() - startTime
|
|
25080
|
+
};
|
|
25081
|
+
}
|
|
25023
25082
|
logger?.warn("semantic", "LLM returned invalid JSON \u2014 fail-open", { rawResponse: rawResponse.slice(0, 200) });
|
|
25024
25083
|
return {
|
|
25025
25084
|
check: "semantic",
|
|
@@ -25072,19 +25131,12 @@ ${formatFindings(parsed.findings)}`;
|
|
|
25072
25131
|
durationMs
|
|
25073
25132
|
};
|
|
25074
25133
|
}
|
|
25075
|
-
var _semanticDeps, DIFF_CAP_BYTES =
|
|
25134
|
+
var _semanticDeps, DIFF_CAP_BYTES = 51200;
|
|
25076
25135
|
var init_semantic = __esm(() => {
|
|
25077
25136
|
init_logger2();
|
|
25078
25137
|
_semanticDeps = {
|
|
25079
25138
|
spawn: spawn2
|
|
25080
25139
|
};
|
|
25081
|
-
DEFAULT_RULES = [
|
|
25082
|
-
"No stubs or noops left in production code paths",
|
|
25083
|
-
"No placeholder values (TODO, FIXME, hardcoded dummy data)",
|
|
25084
|
-
"No unrelated changes outside the story scope",
|
|
25085
|
-
"All new code is properly wired into callers and exports",
|
|
25086
|
-
"No silent error swallowing (catch blocks that discard errors without logging)"
|
|
25087
|
-
];
|
|
25088
25140
|
});
|
|
25089
25141
|
|
|
25090
25142
|
// src/review/runner.ts
|
|
@@ -25231,7 +25283,7 @@ async function getUncommittedFilesImpl(workdir) {
|
|
|
25231
25283
|
return [];
|
|
25232
25284
|
}
|
|
25233
25285
|
}
|
|
25234
|
-
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver) {
|
|
25286
|
+
async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig) {
|
|
25235
25287
|
const startTime = Date.now();
|
|
25236
25288
|
const logger = getSafeLogger();
|
|
25237
25289
|
const checks3 = [];
|
|
@@ -25281,9 +25333,10 @@ Stage and commit these files before running review.`
|
|
|
25281
25333
|
const semanticCfg = config2.semantic ?? {
|
|
25282
25334
|
modelTier: "balanced",
|
|
25283
25335
|
rules: [],
|
|
25284
|
-
timeoutMs: 600000
|
|
25336
|
+
timeoutMs: 600000,
|
|
25337
|
+
excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
|
|
25285
25338
|
};
|
|
25286
|
-
const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null));
|
|
25339
|
+
const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig);
|
|
25287
25340
|
checks3.push(result2);
|
|
25288
25341
|
if (!result2.success && !firstFailure) {
|
|
25289
25342
|
firstFailure = `${checkName} failed`;
|
|
@@ -25365,9 +25418,9 @@ async function getChangedFiles(workdir, baseRef) {
|
|
|
25365
25418
|
}
|
|
25366
25419
|
|
|
25367
25420
|
class ReviewOrchestrator {
|
|
25368
|
-
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver) {
|
|
25421
|
+
async review(reviewConfig, workdir, executionConfig, plugins, storyGitRef, scopePrefix, qualityCommands, storyId, story, modelResolver, naxConfig) {
|
|
25369
25422
|
const logger = getSafeLogger();
|
|
25370
|
-
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver);
|
|
25423
|
+
const builtIn = await runReview(reviewConfig, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig);
|
|
25371
25424
|
if (!builtIn.success) {
|
|
25372
25425
|
return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
|
|
25373
25426
|
}
|
|
@@ -25466,7 +25519,7 @@ var init_review = __esm(() => {
|
|
|
25466
25519
|
title: ctx.story.title,
|
|
25467
25520
|
description: ctx.story.description,
|
|
25468
25521
|
acceptanceCriteria: ctx.story.acceptanceCriteria
|
|
25469
|
-
}, modelResolver);
|
|
25522
|
+
}, modelResolver, ctx.config);
|
|
25470
25523
|
ctx.reviewResult = result.builtIn;
|
|
25471
25524
|
if (!result.success) {
|
|
25472
25525
|
const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
|