@nathapp/nax 0.54.7 → 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.
Files changed (2) hide show
  1. package/dist/nax.js +67 -50
  2. 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 sentences = text.split(/(?<=[.!?])\s+/);
19931
- const questionSentences = sentences.filter((s) => s.trim().endsWith("?"));
19932
- if (questionSentences.length > 0) {
19933
- const q = questionSentences[questionSentences.length - 1].trim();
19934
- if (q.length > 10)
19935
- return q;
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 text.slice(-200).trim();
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
- const model = _options?.model ?? "default";
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.7",
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("ccfd70c"))
22433
- return "ccfd70c";
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 = [/\?/, /\bwhich\b/i, /\bshould i\b/i, /\bunclear\b/i, /\bplease clarify\b/i];
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: ["git", "diff", "--unified=3", `${storyGitRef}..HEAD`],
24875
+ cmd,
24859
24876
  cwd: workdir,
24860
24877
  stdout: "pipe",
24861
24878
  stderr: "pipe"
@@ -24901,15 +24918,13 @@ ${stat}
24901
24918
  }
24902
24919
  function buildPrompt(story, semanticConfig, diff) {
24903
24920
  const acList = story.acceptanceCriteria.map((ac, i) => `${i + 1}. ${ac}`).join(`
24904
- `);
24905
- const defaultRulesText = DEFAULT_RULES.map((r, i) => `${i + 1}. ${r}`).join(`
24906
24921
  `);
24907
24922
  const customRulesSection = semanticConfig.rules.length > 0 ? `
24908
- ## Custom Rules
24923
+ ## Additional Review Rules
24909
24924
  ${semanticConfig.rules.map((r, i) => `${i + 1}. ${r}`).join(`
24910
24925
  `)}
24911
24926
  ` : "";
24912
- return `You are a code reviewer. Review the following git diff against the story requirements and rules.
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.
24913
24928
 
24914
24929
  ## Story: ${story.title}
24915
24930
 
@@ -24918,27 +24933,29 @@ ${story.description}
24918
24933
 
24919
24934
  ### Acceptance Criteria
24920
24935
  ${acList}
24921
-
24922
- ## Review Rules
24923
-
24924
- ### Default Rules
24925
- ${defaultRulesText}
24926
24936
  ${customRulesSection}
24927
- ## Git Diff
24937
+ ## Git Diff (production code only \u2014 test files excluded)
24928
24938
 
24929
24939
  \`\`\`diff
24930
24940
  ${diff}\`\`\`
24931
24941
 
24932
24942
  ## Instructions
24933
24943
 
24934
- Respond with JSON only. No markdown fences around the JSON response itself.
24935
- Format:
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:
24936
24953
  {
24937
24954
  "passed": boolean,
24938
24955
  "findings": [
24939
24956
  {
24940
24957
  "severity": "error" | "warn" | "info",
24941
- "file": "path/to/file.ts",
24958
+ "file": "path/to/file",
24942
24959
  "line": 42,
24943
24960
  "issue": "description of the issue",
24944
24961
  "suggestion": "how to fix it"
@@ -24946,7 +24963,7 @@ Format:
24946
24963
  ]
24947
24964
  }
24948
24965
 
24949
- If the implementation looks correct, respond with { "passed": true, "findings": [] }.`;
24966
+ If all ACs are correctly implemented, respond with { "passed": true, "findings": [] }.`;
24950
24967
  }
24951
24968
  function parseLLMResponse(raw) {
24952
24969
  try {
@@ -24989,7 +25006,7 @@ function toReviewFindings(findings) {
24989
25006
  source: "semantic-review"
24990
25007
  }));
24991
25008
  }
24992
- async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver) {
25009
+ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, modelResolver, naxConfig) {
24993
25010
  const startTime = Date.now();
24994
25011
  const logger = getSafeLogger();
24995
25012
  if (!storyGitRef) {
@@ -25002,8 +25019,12 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
25002
25019
  durationMs: Date.now() - startTime
25003
25020
  };
25004
25021
  }
25005
- logger?.info("review", "Running semantic check", { storyId: story.id, modelTier: semanticConfig.modelTier });
25006
- const rawDiff = await collectDiff(workdir, storyGitRef);
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);
25007
25028
  const needsTruncation = rawDiff.length > DIFF_CAP_BYTES;
25008
25029
  const stat = needsTruncation ? await collectDiffStat(workdir, storyGitRef) : undefined;
25009
25030
  const diff = truncateDiff(rawDiff, stat);
@@ -25027,7 +25048,9 @@ async function runSemanticReview(workdir, storyGitRef, story, semanticConfig, mo
25027
25048
  rawResponse = await agent.complete(prompt, {
25028
25049
  sessionName: `nax-semantic-${story.id}`,
25029
25050
  workdir,
25030
- timeoutMs: semanticConfig.timeoutMs
25051
+ timeoutMs: semanticConfig.timeoutMs,
25052
+ modelTier: semanticConfig.modelTier,
25053
+ config: naxConfig
25031
25054
  });
25032
25055
  } catch (err) {
25033
25056
  logger?.warn("semantic", "LLM call failed \u2014 fail-open", { cause: String(err) });
@@ -25108,19 +25131,12 @@ ${formatFindings(parsed.findings)}`;
25108
25131
  durationMs
25109
25132
  };
25110
25133
  }
25111
- var _semanticDeps, DIFF_CAP_BYTES = 102400, DEFAULT_RULES;
25134
+ var _semanticDeps, DIFF_CAP_BYTES = 51200;
25112
25135
  var init_semantic = __esm(() => {
25113
25136
  init_logger2();
25114
25137
  _semanticDeps = {
25115
25138
  spawn: spawn2
25116
25139
  };
25117
- DEFAULT_RULES = [
25118
- "No stubs or noops left in production code paths",
25119
- "No placeholder values (TODO, FIXME, hardcoded dummy data)",
25120
- "No unrelated changes outside the story scope",
25121
- "All new code is properly wired into callers and exports",
25122
- "No silent error swallowing (catch blocks that discard errors without logging)"
25123
- ];
25124
25140
  });
25125
25141
 
25126
25142
  // src/review/runner.ts
@@ -25267,7 +25283,7 @@ async function getUncommittedFilesImpl(workdir) {
25267
25283
  return [];
25268
25284
  }
25269
25285
  }
25270
- async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver) {
25286
+ async function runReview(config2, workdir, executionConfig, qualityCommands, storyId, storyGitRef, story, modelResolver, naxConfig) {
25271
25287
  const startTime = Date.now();
25272
25288
  const logger = getSafeLogger();
25273
25289
  const checks3 = [];
@@ -25317,9 +25333,10 @@ Stage and commit these files before running review.`
25317
25333
  const semanticCfg = config2.semantic ?? {
25318
25334
  modelTier: "balanced",
25319
25335
  rules: [],
25320
- timeoutMs: 600000
25336
+ timeoutMs: 600000,
25337
+ excludePatterns: [":!test/", ":!tests/", ":!*_test.go", ":!*.test.ts", ":!*.spec.ts", ":!**/__tests__/"]
25321
25338
  };
25322
- const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null));
25339
+ const result2 = await _reviewSemanticDeps.runSemanticReview(workdir, storyGitRef, semanticStory, semanticCfg, modelResolver ?? (() => null), naxConfig);
25323
25340
  checks3.push(result2);
25324
25341
  if (!result2.success && !firstFailure) {
25325
25342
  firstFailure = `${checkName} failed`;
@@ -25401,9 +25418,9 @@ async function getChangedFiles(workdir, baseRef) {
25401
25418
  }
25402
25419
 
25403
25420
  class ReviewOrchestrator {
25404
- 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) {
25405
25422
  const logger = getSafeLogger();
25406
- 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);
25407
25424
  if (!builtIn.success) {
25408
25425
  return { builtIn, success: false, failureReason: builtIn.failureReason, pluginFailed: false };
25409
25426
  }
@@ -25502,7 +25519,7 @@ var init_review = __esm(() => {
25502
25519
  title: ctx.story.title,
25503
25520
  description: ctx.story.description,
25504
25521
  acceptanceCriteria: ctx.story.acceptanceCriteria
25505
- }, modelResolver);
25522
+ }, modelResolver, ctx.config);
25506
25523
  ctx.reviewResult = result.builtIn;
25507
25524
  if (!result.success) {
25508
25525
  const pluginFindings = result.builtIn.pluginReviewers?.flatMap((pr) => pr.findings ?? []) ?? [];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nathapp/nax",
3
- "version": "0.54.7",
3
+ "version": "0.54.8",
4
4
  "description": "AI Coding Agent Orchestrator — loops until done",
5
5
  "type": "module",
6
6
  "bin": {