@nathapp/nax 0.36.0 → 0.36.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 CHANGED
@@ -3914,7 +3914,7 @@ ${output.slice(0, 500)}`);
3914
3914
  acceptanceCriteria: Array.isArray(record.acceptanceCriteria) ? record.acceptanceCriteria : ["Implementation complete"],
3915
3915
  tags: Array.isArray(record.tags) ? record.tags : [],
3916
3916
  dependencies: Array.isArray(record.dependencies) ? record.dependencies : [],
3917
- complexity: validateComplexity(record.complexity),
3917
+ complexity: coerceComplexity(record.complexity),
3918
3918
  contextFiles: Array.isArray(record.contextFiles) ? record.contextFiles : Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
3919
3919
  relevantFiles: Array.isArray(record.relevantFiles) ? record.relevantFiles : [],
3920
3920
  reasoning: String(record.reasoning || "No reasoning provided"),
@@ -3928,7 +3928,7 @@ ${output.slice(0, 500)}`);
3928
3928
  }
3929
3929
  return stories;
3930
3930
  }
3931
- function validateComplexity(value) {
3931
+ function coerceComplexity(value) {
3932
3932
  if (value === "simple" || value === "medium" || value === "complex" || value === "expert") {
3933
3933
  return value;
3934
3934
  }
@@ -19471,7 +19471,7 @@ Your complexity classification will determine the execution strategy:
19471
19471
  Respond with ONLY this JSON (no markdown, no explanation):
19472
19472
  {"complexity":"simple|medium|complex|expert","modelTier":"fast|balanced|powerful","reasoning":"<one line>"}`;
19473
19473
  }
19474
- function buildBatchPrompt(stories, config2) {
19474
+ function buildBatchRoutingPrompt(stories, config2) {
19475
19475
  const storyBlocks = stories.map((story, idx) => {
19476
19476
  const criteria = story.acceptanceCriteria.map((c, i) => ` ${i + 1}. ${c}`).join(`
19477
19477
  `);
@@ -19651,7 +19651,7 @@ async function routeBatch(stories, context) {
19651
19651
  throw new Error("No agent adapter available for batch routing (AA-003)");
19652
19652
  }
19653
19653
  const modelTier = llmConfig.model ?? "fast";
19654
- const prompt = buildBatchPrompt(stories, config2);
19654
+ const prompt = buildBatchRoutingPrompt(stories, config2);
19655
19655
  try {
19656
19656
  const output = await callLlm(adapter, modelTier, prompt, config2);
19657
19657
  const decisions = parseBatchResponse(output, stories, config2);
@@ -20073,7 +20073,7 @@ var init_routing = __esm(() => {
20073
20073
  });
20074
20074
 
20075
20075
  // src/decompose/validators/complexity.ts
20076
- function validateComplexity2(substories, maxComplexity) {
20076
+ function validateComplexity(substories, maxComplexity) {
20077
20077
  const errors3 = [];
20078
20078
  const warnings = [];
20079
20079
  const maxOrder = COMPLEXITY_ORDER[maxComplexity];
@@ -20385,7 +20385,7 @@ function runAllValidators(originalStory, substories, existingStories, config2) {
20385
20385
  const results = [
20386
20386
  validateOverlap(substories, existingStories),
20387
20387
  validateCoverage(originalStory, substories),
20388
- validateComplexity2(substories, maxComplexity),
20388
+ validateComplexity(substories, maxComplexity),
20389
20389
  validateDependencies(substories, existingIds)
20390
20390
  ];
20391
20391
  const errors3 = results.flatMap((r) => r.errors);
@@ -20657,7 +20657,7 @@ var package_default;
20657
20657
  var init_package = __esm(() => {
20658
20658
  package_default = {
20659
20659
  name: "@nathapp/nax",
20660
- version: "0.36.0",
20660
+ version: "0.36.1",
20661
20661
  description: "AI Coding Agent Orchestrator \u2014 loops until done",
20662
20662
  type: "module",
20663
20663
  bin: {
@@ -20718,8 +20718,8 @@ var init_version = __esm(() => {
20718
20718
  NAX_VERSION = package_default.version;
20719
20719
  NAX_COMMIT = (() => {
20720
20720
  try {
20721
- if (/^[0-9a-f]{6,10}$/.test("78b52b0"))
20722
- return "78b52b0";
20721
+ if (/^[0-9a-f]{6,10}$/.test("b241bab"))
20722
+ return "b241bab";
20723
20723
  } catch {}
20724
20724
  try {
20725
20725
  const result = Bun.spawnSync(["git", "rev-parse", "--short", "HEAD"], {
@@ -21092,6 +21092,12 @@ class InteractionChain {
21092
21092
  async prompt(request) {
21093
21093
  await this.send(request);
21094
21094
  const response = await this.receive(request.id, request.timeout);
21095
+ if (response.action === "choose" && response.value && request.options) {
21096
+ const matched = request.options.find((o) => o.key === response.value);
21097
+ if (matched) {
21098
+ return { ...response, action: matched.key };
21099
+ }
21100
+ }
21095
21101
  return response;
21096
21102
  }
21097
21103
  async cancel(requestId) {
@@ -22681,9 +22687,17 @@ class ReviewOrchestrator {
22681
22687
  const changedFiles = await getChangedFiles(workdir);
22682
22688
  const pluginResults = [];
22683
22689
  for (const reviewer of reviewers) {
22684
- logger?.info("review", `Running plugin reviewer: ${reviewer.name}`);
22690
+ logger?.info("review", `Running plugin reviewer: ${reviewer.name}`, {
22691
+ changedFiles: changedFiles.length
22692
+ });
22685
22693
  try {
22686
22694
  const result = await reviewer.check(workdir, changedFiles);
22695
+ logger?.info("review", `Plugin reviewer result: ${reviewer.name}`, {
22696
+ passed: result.passed,
22697
+ exitCode: result.exitCode,
22698
+ output: result.output?.slice(0, 500),
22699
+ findings: result.findings?.length ?? 0
22700
+ });
22687
22701
  pluginResults.push({
22688
22702
  name: reviewer.name,
22689
22703
  passed: result.passed,
@@ -22702,6 +22716,7 @@ class ReviewOrchestrator {
22702
22716
  }
22703
22717
  } catch (error48) {
22704
22718
  const errorMsg = error48 instanceof Error ? error48.message : String(error48);
22719
+ logger?.warn("review", `Plugin reviewer threw error: ${reviewer.name}`, { error: errorMsg });
22705
22720
  pluginResults.push({ name: reviewer.name, passed: false, output: "", error: errorMsg });
22706
22721
  builtIn.pluginReviewers = pluginResults;
22707
22722
  return {
@@ -22959,12 +22974,14 @@ var init_completion = __esm(() => {
22959
22974
  };
22960
22975
  });
22961
22976
 
22977
+ // src/optimizer/types.ts
22978
+ function estimateTokens(text) {
22979
+ return Math.ceil(text.length / 4);
22980
+ }
22981
+
22962
22982
  // src/constitution/loader.ts
22963
22983
  import { existsSync as existsSync13 } from "fs";
22964
22984
  import { join as join14 } from "path";
22965
- function estimateTokens(text) {
22966
- return Math.ceil(text.length / 3);
22967
- }
22968
22985
  function truncateToTokens(text, maxTokens) {
22969
22986
  const maxChars = maxTokens * 3;
22970
22987
  if (text.length <= maxChars) {
@@ -23214,32 +23231,29 @@ var init_auto_detect = __esm(() => {
23214
23231
  });
23215
23232
 
23216
23233
  // src/context/elements.ts
23217
- function estimateTokens2(text) {
23218
- return Math.ceil(text.length / CHARS_PER_TOKEN);
23219
- }
23220
23234
  function createStoryContext(story, priority) {
23221
23235
  const content = formatStoryAsText(story);
23222
- return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens2(content) };
23236
+ return { type: "story", storyId: story.id, content, priority, tokens: estimateTokens(content) };
23223
23237
  }
23224
23238
  function createDependencyContext(story, priority) {
23225
23239
  const content = formatStoryAsText(story);
23226
- return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens2(content) };
23240
+ return { type: "dependency", storyId: story.id, content, priority, tokens: estimateTokens(content) };
23227
23241
  }
23228
23242
  function createErrorContext(errorMessage, priority) {
23229
- return { type: "error", content: errorMessage, priority, tokens: estimateTokens2(errorMessage) };
23243
+ return { type: "error", content: errorMessage, priority, tokens: estimateTokens(errorMessage) };
23230
23244
  }
23231
23245
  function createProgressContext(progressText, priority) {
23232
- return { type: "progress", content: progressText, priority, tokens: estimateTokens2(progressText) };
23246
+ return { type: "progress", content: progressText, priority, tokens: estimateTokens(progressText) };
23233
23247
  }
23234
23248
  function createFileContext(filePath, content, priority) {
23235
- return { type: "file", filePath, content, priority, tokens: estimateTokens2(content) };
23249
+ return { type: "file", filePath, content, priority, tokens: estimateTokens(content) };
23236
23250
  }
23237
23251
  function createTestCoverageContext(content, tokens, priority) {
23238
23252
  return { type: "test-coverage", content, priority, tokens };
23239
23253
  }
23240
23254
  function createPriorFailuresContext(failures, priority) {
23241
23255
  const content = formatPriorFailures(failures);
23242
- return { type: "prior-failures", content, priority, tokens: estimateTokens2(content) };
23256
+ return { type: "prior-failures", content, priority, tokens: estimateTokens(content) };
23243
23257
  }
23244
23258
  function formatPriorFailures(failures) {
23245
23259
  if (!failures || failures.length === 0) {
@@ -23310,7 +23324,6 @@ function formatStoryAsText(story) {
23310
23324
  return parts.join(`
23311
23325
  `);
23312
23326
  }
23313
- var CHARS_PER_TOKEN = 3;
23314
23327
  var init_elements = __esm(() => {
23315
23328
  init_logger2();
23316
23329
  });
@@ -23467,7 +23480,7 @@ function truncateToTokenBudget(files, maxTokens, preferredDetail) {
23467
23480
  for (let i = startIndex;i < detailLevels.length; i++) {
23468
23481
  const detail = detailLevels[i];
23469
23482
  const summary = formatTestSummary(files, detail);
23470
- const tokens = estimateTokens2(summary);
23483
+ const tokens = estimateTokens(summary);
23471
23484
  if (tokens <= maxTokens) {
23472
23485
  return { summary, detail, truncated: i !== startIndex };
23473
23486
  }
@@ -23477,7 +23490,7 @@ function truncateToTokenBudget(files, maxTokens, preferredDetail) {
23477
23490
  truncatedFiles = truncatedFiles.slice(0, truncatedFiles.length - 1);
23478
23491
  const summary = `${formatTestSummary(truncatedFiles, "names-only")}
23479
23492
  ... and ${files.length - truncatedFiles.length} more test files`;
23480
- if (estimateTokens2(summary) <= maxTokens) {
23493
+ if (estimateTokens(summary) <= maxTokens) {
23481
23494
  return { summary, detail: "names-only", truncated: true };
23482
23495
  }
23483
23496
  }
@@ -23500,13 +23513,12 @@ async function generateTestCoverageSummary(options) {
23500
23513
  }
23501
23514
  const totalTests = files.reduce((sum, f) => sum + f.testCount, 0);
23502
23515
  const { summary } = truncateToTokenBudget(files, maxTokens, detail);
23503
- const tokens = estimateTokens2(summary);
23516
+ const tokens = estimateTokens(summary);
23504
23517
  return { files, totalTests, summary, tokens };
23505
23518
  }
23506
23519
  var COMMON_TEST_DIRS;
23507
23520
  var init_test_scanner = __esm(() => {
23508
23521
  init_logger2();
23509
- init_builder3();
23510
23522
  COMMON_TEST_DIRS = ["test", "tests", "__tests__", "src/__tests__", "spec"];
23511
23523
  });
23512
23524
 
@@ -24758,7 +24770,7 @@ var init_cleanup = __esm(() => {
24758
24770
  });
24759
24771
 
24760
24772
  // src/tdd/prompts.ts
24761
- function buildImplementerRectificationPrompt(failures, story, contextMarkdown, config2) {
24773
+ function buildImplementerRectificationPrompt(failures, story, _contextMarkdown, config2) {
24762
24774
  return createRectificationPrompt(failures, story, config2);
24763
24775
  }
24764
24776
  var init_prompts = __esm(() => {
@@ -24908,9 +24920,9 @@ var init_rectification_gate = __esm(() => {
24908
24920
  function buildConventionsSection() {
24909
24921
  return `# Conventions
24910
24922
 
24911
- ` + `Follow existing code patterns and conventions. Write idiomatic, maintainable code.
24923
+ Follow existing code patterns and conventions. Write idiomatic, maintainable code.
24912
24924
 
24913
- ` + "When running tests, run ONLY test files related to your changes (e.g. `bun test ./test/specific.test.ts`). " + "NEVER run `bun test` without a file filter \u2014 full suite output will flood your context window and cause failures.\n\n" + "Commit your changes when done using conventional commit format (e.g. `feat:`, `fix:`, `test:`).";
24925
+ Commit your changes when done using conventional commit format (e.g. \`feat:\`, \`fix:\`, \`test:\`).`;
24914
24926
  }
24915
24927
 
24916
24928
  // src/prompts/sections/isolation.ts
@@ -24919,29 +24931,39 @@ function buildIsolationSection(roleOrMode, mode) {
24919
24931
  return buildIsolationSection("test-writer", roleOrMode);
24920
24932
  }
24921
24933
  const role = roleOrMode;
24922
- const header = `# Isolation Rules
24923
-
24924
- `;
24934
+ const header = "# Isolation Rules";
24925
24935
  const footer = `
24926
24936
 
24927
24937
  ${TEST_FILTER_RULE}`;
24928
24938
  if (role === "test-writer") {
24929
24939
  const m = mode ?? "strict";
24930
24940
  if (m === "strict") {
24931
- return `${header}isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
24941
+ return `${header}
24942
+
24943
+ isolation scope: Only create or modify files in the test/ directory. Tests must fail because the feature is not yet implemented. Do NOT modify any source files in src/.${footer}`;
24932
24944
  }
24933
- return `${header}isolation scope: Create test files in test/. MAY read src/ files and MAY import from src/ to ensure correct types/interfaces. May create minimal stubs in src/ if needed to make imports work, but do NOT implement real logic.${footer}`;
24945
+ return `${header}
24946
+
24947
+ isolation scope: Create test files in test/. MAY read src/ files and MAY import from src/ to ensure correct types/interfaces. May create minimal stubs in src/ if needed to make imports work, but do NOT implement real logic.${footer}`;
24934
24948
  }
24935
24949
  if (role === "implementer") {
24936
- return `${header}isolation scope: Implement source code in src/ to make tests pass. Do not modify test files. Run tests frequently to track progress.${footer}`;
24950
+ return `${header}
24951
+
24952
+ isolation scope: Implement source code in src/ to make tests pass. Do not modify test files. Run tests frequently to track progress.${footer}`;
24937
24953
  }
24938
24954
  if (role === "verifier") {
24939
- return `${header}isolation scope: Read-only inspection. Review all test results, implementation code, and acceptance criteria compliance. You MAY write a verdict file (.nax-verifier-verdict.json) and apply legitimate fixes if needed.${footer}`;
24955
+ return `${header}
24956
+
24957
+ isolation scope: Read-only inspection. Review all test results, implementation code, and acceptance criteria compliance. You MAY write a verdict file (.nax-verifier-verdict.json) and apply legitimate fixes if needed.${footer}`;
24940
24958
  }
24941
24959
  if (role === "single-session") {
24942
- return `${header}isolation scope: Create test files in test/ directory, then implement source code in src/ to make tests pass. Both directories are in scope for this session.${footer}`;
24960
+ return `${header}
24961
+
24962
+ isolation scope: Create test files in test/ directory, then implement source code in src/ to make tests pass. Both directories are in scope for this session.${footer}`;
24943
24963
  }
24944
- return `${header}isolation scope: You may modify both src/ and test/ files. Write failing tests FIRST, then implement to make them pass.`;
24964
+ return `${header}
24965
+
24966
+ isolation scope: You may modify both src/ and test/ files. Write failing tests FIRST, then implement to make them pass.`;
24945
24967
  }
24946
24968
  var TEST_FILTER_RULE;
24947
24969
  var init_isolation2 = __esm(() => {
@@ -24959,76 +24981,76 @@ function buildRoleTaskSection(roleOrVariant, variant) {
24959
24981
  if (v === "standard") {
24960
24982
  return `# Role: Implementer
24961
24983
 
24962
- ` + `Your task: make failing tests pass.
24984
+ Your task: make failing tests pass.
24963
24985
 
24964
- ` + `Instructions:
24965
- ` + `- Implement source code in src/ to make tests pass
24966
- ` + `- Do NOT modify test files
24967
- ` + `- Run tests frequently to track progress
24968
- ` + `- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
24969
- ` + "- Goal: all tests green, all changes committed";
24986
+ Instructions:
24987
+ - Implement source code in src/ to make tests pass
24988
+ - Do NOT modify test files
24989
+ - Run tests frequently to track progress
24990
+ - When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
24991
+ - Goal: all tests green, all changes committed`;
24970
24992
  }
24971
24993
  return `# Role: Implementer (Lite)
24972
24994
 
24973
- ` + `Your task: Write tests AND implement the feature in a single session.
24995
+ Your task: Write tests AND implement the feature in a single session.
24974
24996
 
24975
- ` + `Instructions:
24976
- ` + `- Write tests first (test/ directory), then implement (src/ directory)
24977
- ` + `- All tests must pass by the end
24978
- ` + `- Use Bun test (describe/test/expect)
24979
- ` + `- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
24980
- ` + "- Goal: all tests green, all criteria met, all changes committed";
24997
+ Instructions:
24998
+ - Write tests first (test/ directory), then implement (src/ directory)
24999
+ - All tests must pass by the end
25000
+ - Use Bun test (describe/test/expect)
25001
+ - When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
25002
+ - Goal: all tests green, all criteria met, all changes committed`;
24981
25003
  }
24982
25004
  if (role === "test-writer") {
24983
25005
  return `# Role: Test-Writer
24984
25006
 
24985
- ` + `Your task: Write comprehensive failing tests for the feature.
25007
+ Your task: Write comprehensive failing tests for the feature.
24986
25008
 
24987
- ` + `Instructions:
24988
- ` + `- Create test files in test/ directory that cover acceptance criteria
24989
- ` + `- Tests must fail initially (RED phase) \u2014 the feature is not yet implemented
24990
- ` + `- Use Bun test (describe/test/expect)
24991
- ` + `- Write clear test names that document expected behavior
24992
- ` + `- Focus on behavior, not implementation details
24993
- ` + "- Goal: comprehensive test suite ready for implementation";
25009
+ Instructions:
25010
+ - Create test files in test/ directory that cover acceptance criteria
25011
+ - Tests must fail initially (RED phase) \u2014 the feature is not yet implemented
25012
+ - Use Bun test (describe/test/expect)
25013
+ - Write clear test names that document expected behavior
25014
+ - Focus on behavior, not implementation details
25015
+ - Goal: comprehensive test suite ready for implementation`;
24994
25016
  }
24995
25017
  if (role === "verifier") {
24996
25018
  return `# Role: Verifier
24997
25019
 
24998
- ` + `Your task: Review and verify the implementation against acceptance criteria.
25020
+ Your task: Review and verify the implementation against acceptance criteria.
24999
25021
 
25000
- ` + `Instructions:
25001
- ` + `- Review all test results \u2014 verify tests pass
25002
- ` + `- Check that implementation meets all acceptance criteria
25003
- ` + `- Inspect code quality, error handling, and edge cases
25004
- ` + `- Verify test modifications (if any) are legitimate fixes
25005
- ` + `- Write a detailed verdict with reasoning
25006
- ` + "- Goal: provide comprehensive verification and quality assurance";
25022
+ Instructions:
25023
+ - Review all test results \u2014 verify tests pass
25024
+ - Check that implementation meets all acceptance criteria
25025
+ - Inspect code quality, error handling, and edge cases
25026
+ - Verify test modifications (if any) are legitimate fixes
25027
+ - Write a detailed verdict with reasoning
25028
+ - Goal: provide comprehensive verification and quality assurance`;
25007
25029
  }
25008
25030
  if (role === "single-session") {
25009
25031
  return `# Role: Single-Session
25010
25032
 
25011
- ` + `Your task: Write tests AND implement the feature in a single focused session.
25033
+ Your task: Write tests AND implement the feature in a single focused session.
25012
25034
 
25013
- ` + `Instructions:
25014
- ` + `- Phase 1: Write comprehensive tests (test/ directory)
25015
- ` + `- Phase 2: Implement to make all tests pass (src/ directory)
25016
- ` + `- Use Bun test (describe/test/expect)
25017
- ` + `- Run tests frequently throughout implementation
25018
- ` + `- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
25019
- ` + "- Goal: all tests passing, all changes committed, full story complete";
25035
+ Instructions:
25036
+ - Phase 1: Write comprehensive tests (test/ directory)
25037
+ - Phase 2: Implement to make all tests pass (src/ directory)
25038
+ - Use Bun test (describe/test/expect)
25039
+ - Run tests frequently throughout implementation
25040
+ - When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
25041
+ - Goal: all tests passing, all changes committed, full story complete`;
25020
25042
  }
25021
25043
  return `# Role: TDD-Simple
25022
25044
 
25023
- ` + `Your task: Write failing tests FIRST, then implement to make them pass.
25045
+ Your task: Write failing tests FIRST, then implement to make them pass.
25024
25046
 
25025
- ` + `Instructions:
25026
- ` + `- RED phase: Write failing tests FIRST for the acceptance criteria
25027
- ` + `- RED phase: Run the tests to confirm they fail
25028
- ` + `- GREEN phase: Implement the minimum code to make tests pass
25029
- ` + `- REFACTOR phase: Refactor while keeping tests green
25030
- ` + `- When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
25031
- ` + "- Goal: all tests passing, feature complete, all changes committed";
25047
+ Instructions:
25048
+ - RED phase: Write failing tests FIRST for the acceptance criteria
25049
+ - RED phase: Run the tests to confirm they fail
25050
+ - GREEN phase: Implement the minimum code to make tests pass
25051
+ - REFACTOR phase: Refactor while keeping tests green
25052
+ - When all tests are green, stage and commit ALL changed files with: git commit -m 'feat: <description>'
25053
+ - Goal: all tests passing, feature complete, all changes committed`;
25032
25054
  }
25033
25055
 
25034
25056
  // src/prompts/sections/story.ts
@@ -25046,6 +25068,69 @@ ${story.description}
25046
25068
  ${criteria}`;
25047
25069
  }
25048
25070
 
25071
+ // src/prompts/sections/verdict.ts
25072
+ function buildVerdictSection(story) {
25073
+ return `# Verdict Instructions
25074
+
25075
+ ## Write Verdict File
25076
+
25077
+ After completing your verification, you **MUST** write a verdict file at the **project root**:
25078
+
25079
+ **File:** \`.nax-verifier-verdict.json\`
25080
+
25081
+ Set \`approved: true\` when ALL of these conditions are met:
25082
+ - All tests pass
25083
+ - Implementation is clean and follows conventions
25084
+ - All acceptance criteria met
25085
+ - Any test modifications by implementer are legitimate fixes
25086
+
25087
+ Set \`approved: false\` when ANY of these conditions are true:
25088
+ - Tests are failing and you cannot fix them
25089
+ - The implementer loosened test assertions to mask bugs
25090
+ - Critical acceptance criteria are not met
25091
+ - Code quality is poor (security issues, severe bugs, etc.)
25092
+
25093
+ **Full JSON schema example** (fill in all fields with real values):
25094
+
25095
+ \`\`\`json
25096
+ {
25097
+ "version": 1,
25098
+ "approved": true,
25099
+ "tests": {
25100
+ "allPassing": true,
25101
+ "passCount": 42,
25102
+ "failCount": 0
25103
+ },
25104
+ "testModifications": {
25105
+ "detected": false,
25106
+ "files": [],
25107
+ "legitimate": true,
25108
+ "reasoning": "No test files were modified by the implementer"
25109
+ },
25110
+ "acceptanceCriteria": {
25111
+ "allMet": true,
25112
+ "criteria": [
25113
+ { "criterion": "Example criterion", "met": true }
25114
+ ]
25115
+ },
25116
+ "quality": {
25117
+ "rating": "good",
25118
+ "issues": []
25119
+ },
25120
+ "fixes": [],
25121
+ "reasoning": "All tests pass, implementation is clean, all acceptance criteria are met."
25122
+ }
25123
+ \`\`\`
25124
+
25125
+ **Field notes:**
25126
+ - \`quality.rating\` must be one of: \`"good"\`, \`"acceptable"\`, \`"poor"\`
25127
+ - \`testModifications.files\` \u2014 list any test files the implementer changed
25128
+ - \`fixes\` \u2014 list any fixes you applied yourself during this verification session
25129
+ - \`reasoning\` \u2014 brief summary of your overall assessment
25130
+
25131
+ When done, commit any fixes with message: "fix: verify and adjust ${story.title}"`;
25132
+ }
25133
+
25049
25134
  // src/prompts/loader.ts
25050
25135
  var exports_loader = {};
25051
25136
  __export(exports_loader, {
@@ -25121,6 +25206,9 @@ ${this._constitution}`);
25121
25206
  if (this._story) {
25122
25207
  sections.push(buildStorySection(this._story));
25123
25208
  }
25209
+ if (this._role === "verifier" && this._story) {
25210
+ sections.push(buildVerdictSection(this._story));
25211
+ }
25124
25212
  const isolation = this._options.isolation;
25125
25213
  sections.push(buildIsolationSection(this._role, isolation));
25126
25214
  if (this._contextMd) {
@@ -25209,7 +25297,7 @@ async function rollbackToRef(workdir, ref) {
25209
25297
  }
25210
25298
  logger.info("tdd", "Successfully rolled back git changes", { ref });
25211
25299
  }
25212
- async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false) {
25300
+ async function runTddSession(role, agent, story, config2, workdir, modelTier, beforeRef, contextMarkdown, lite = false, skipIsolation = false, constitution) {
25213
25301
  const startTime = Date.now();
25214
25302
  let prompt;
25215
25303
  switch (role) {
@@ -25217,7 +25305,7 @@ async function runTddSession(role, agent, story, config2, workdir, modelTier, be
25217
25305
  prompt = await PromptBuilder.for("test-writer", { isolation: lite ? "lite" : "strict" }).withLoader(workdir, config2).story(story).context(contextMarkdown).build();
25218
25306
  break;
25219
25307
  case "implementer":
25220
- prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" }).withLoader(workdir, config2).story(story).context(contextMarkdown).build();
25308
+ prompt = await PromptBuilder.for("implementer", { variant: lite ? "lite" : "standard" }).withLoader(workdir, config2).story(story).context(contextMarkdown).constitution(constitution).build();
25221
25309
  break;
25222
25310
  case "verifier":
25223
25311
  prompt = await PromptBuilder.for("verifier").withLoader(workdir, config2).story(story).context(contextMarkdown).build();
@@ -25361,7 +25449,7 @@ function isValidVerdict(obj) {
25361
25449
  function coerceVerdict(obj) {
25362
25450
  try {
25363
25451
  const verdictStr = String(obj.verdict ?? "").toUpperCase();
25364
- const approved = verdictStr === "PASS" || verdictStr === "APPROVED" || obj.approved === true;
25452
+ const approved = verdictStr === "PASS" || verdictStr === "APPROVED" || verdictStr.startsWith("VERIFIED") || verdictStr.includes("ALL ACCEPTANCE CRITERIA MET") || obj.approved === true;
25365
25453
  let passCount = 0;
25366
25454
  let failCount = 0;
25367
25455
  let allPassing = approved;
@@ -25456,13 +25544,24 @@ async function readVerdict(workdir) {
25456
25544
  if (!exists) {
25457
25545
  return null;
25458
25546
  }
25547
+ let rawText;
25548
+ try {
25549
+ rawText = await file2.text();
25550
+ } catch (readErr) {
25551
+ logger.warn("tdd", "Failed to read verifier verdict file", {
25552
+ path: verdictPath,
25553
+ error: String(readErr)
25554
+ });
25555
+ return null;
25556
+ }
25459
25557
  let parsed;
25460
25558
  try {
25461
- parsed = await file2.json();
25559
+ parsed = JSON.parse(rawText);
25462
25560
  } catch (parseErr) {
25463
25561
  logger.warn("tdd", "Verifier verdict file is not valid JSON \u2014 ignoring", {
25464
25562
  path: verdictPath,
25465
- error: String(parseErr)
25563
+ error: String(parseErr),
25564
+ rawContent: rawText.slice(0, 1000)
25466
25565
  });
25467
25566
  return null;
25468
25567
  }
@@ -25564,6 +25663,7 @@ async function runThreeSessionTdd(options) {
25564
25663
  workdir,
25565
25664
  modelTier,
25566
25665
  contextMarkdown,
25666
+ constitution,
25567
25667
  dryRun = false,
25568
25668
  lite = false,
25569
25669
  _recursionDepth = 0
@@ -25625,7 +25725,7 @@ async function runThreeSessionTdd(options) {
25625
25725
  let session1;
25626
25726
  if (!isRetry) {
25627
25727
  const testWriterTier = config2.tdd.sessionTiers?.testWriter ?? "balanced";
25628
- session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite);
25728
+ session1 = await runTddSession("test-writer", agent, story, config2, workdir, testWriterTier, session1Ref, contextMarkdown, lite, lite, constitution);
25629
25729
  sessions.push(session1);
25630
25730
  }
25631
25731
  if (session1 && !session1.success) {
@@ -25687,7 +25787,7 @@ async function runThreeSessionTdd(options) {
25687
25787
  });
25688
25788
  const session2Ref = await captureGitRef(workdir) ?? "HEAD";
25689
25789
  const implementerTier = config2.tdd.sessionTiers?.implementer ?? modelTier;
25690
- const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite);
25790
+ const session2 = await runTddSession("implementer", agent, story, config2, workdir, implementerTier, session2Ref, contextMarkdown, lite, lite, constitution);
25691
25791
  sessions.push(session2);
25692
25792
  if (!session2.success) {
25693
25793
  needsHumanReview = true;
@@ -25706,7 +25806,7 @@ async function runThreeSessionTdd(options) {
25706
25806
  const fullSuiteGatePassed = await runFullSuiteGate(story, config2, workdir, agent, implementerTier, contextMarkdown, lite, logger);
25707
25807
  const session3Ref = await captureGitRef(workdir) ?? "HEAD";
25708
25808
  const verifierTier = config2.tdd.sessionTiers?.verifier ?? "fast";
25709
- const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false);
25809
+ const session3 = await runTddSession("verifier", agent, story, config2, workdir, verifierTier, session3Ref, undefined, false, false, constitution);
25710
25810
  sessions.push(session3);
25711
25811
  const verdict = await readVerdict(workdir);
25712
25812
  await cleanupVerdict(workdir);
@@ -25886,6 +25986,7 @@ var init_execution = __esm(() => {
25886
25986
  workdir: ctx.workdir,
25887
25987
  modelTier: ctx.routing.modelTier,
25888
25988
  contextMarkdown: ctx.contextMarkdown,
25989
+ constitution: ctx.constitution?.content,
25889
25990
  dryRun: false,
25890
25991
  lite: isLiteMode
25891
25992
  });
@@ -25909,6 +26010,28 @@ var init_execution = __esm(() => {
25909
26010
  lite: tddResult.lite,
25910
26011
  failureCategory: tddResult.failureCategory
25911
26012
  });
26013
+ if (ctx.interaction) {
26014
+ try {
26015
+ await ctx.interaction.send({
26016
+ id: `human-review-${ctx.story.id}-${Date.now()}`,
26017
+ type: "notify",
26018
+ featureName: ctx.featureDir ? ctx.featureDir.split("/").pop() ?? "unknown" : "unknown",
26019
+ storyId: ctx.story.id,
26020
+ stage: "execution",
26021
+ summary: `\u26A0\uFE0F Human review needed: ${ctx.story.id}`,
26022
+ detail: `Story: ${ctx.story.title}
26023
+ Reason: ${tddResult.reviewReason ?? "No reason provided"}
26024
+ Category: ${tddResult.failureCategory ?? "unknown"}`,
26025
+ fallback: "continue",
26026
+ createdAt: Date.now()
26027
+ });
26028
+ } catch (notifyErr) {
26029
+ logger.warn("execution", "Failed to send human review notification", {
26030
+ storyId: ctx.story.id,
26031
+ error: String(notifyErr)
26032
+ });
26033
+ }
26034
+ }
25912
26035
  }
25913
26036
  return routeTddFailure(tddResult.failureCategory, isLiteMode, ctx, tddResult.reviewReason);
25914
26037
  }
@@ -25979,16 +26102,11 @@ var init_execution = __esm(() => {
25979
26102
  };
25980
26103
  });
25981
26104
 
25982
- // src/optimizer/types.ts
25983
- function estimateTokens4(text) {
25984
- return Math.ceil(text.length / 4);
25985
- }
25986
-
25987
26105
  // src/optimizer/noop.optimizer.ts
25988
26106
  class NoopOptimizer {
25989
26107
  name = "noop";
25990
26108
  async optimize(input) {
25991
- const tokens = estimateTokens4(input.prompt);
26109
+ const tokens = estimateTokens(input.prompt);
25992
26110
  return {
25993
26111
  prompt: input.prompt,
25994
26112
  originalTokens: tokens,
@@ -26004,7 +26122,7 @@ var init_noop_optimizer = () => {};
26004
26122
  class RuleBasedOptimizer {
26005
26123
  name = "rule-based";
26006
26124
  async optimize(input) {
26007
- const originalTokens = estimateTokens4(input.prompt);
26125
+ const originalTokens = estimateTokens(input.prompt);
26008
26126
  const appliedRules = [];
26009
26127
  let optimized = input.prompt;
26010
26128
  const config2 = {
@@ -26033,13 +26151,13 @@ class RuleBasedOptimizer {
26033
26151
  }
26034
26152
  }
26035
26153
  if (config2.maxPromptTokens) {
26036
- const currentTokens = estimateTokens4(optimized);
26154
+ const currentTokens = estimateTokens(optimized);
26037
26155
  if (currentTokens > config2.maxPromptTokens) {
26038
26156
  optimized = this.trimToMaxTokens(optimized, config2.maxPromptTokens);
26039
26157
  appliedRules.push("maxPromptTokens");
26040
26158
  }
26041
26159
  }
26042
- const optimizedTokens = estimateTokens4(optimized);
26160
+ const optimizedTokens = estimateTokens(optimized);
26043
26161
  const savings = originalTokens > 0 ? (originalTokens - optimizedTokens) / originalTokens : 0;
26044
26162
  return {
26045
26163
  prompt: optimized,
@@ -26082,7 +26200,7 @@ ${newContextSection}`);
26082
26200
  return prompt;
26083
26201
  }
26084
26202
  trimToMaxTokens(prompt, maxTokens) {
26085
- const currentTokens = estimateTokens4(prompt);
26203
+ const currentTokens = estimateTokens(prompt);
26086
26204
  if (currentTokens <= maxTokens) {
26087
26205
  return prompt;
26088
26206
  }
@@ -26221,7 +26339,7 @@ var init_optimizer2 = __esm(() => {
26221
26339
  });
26222
26340
 
26223
26341
  // src/execution/prompts.ts
26224
- function buildBatchPrompt2(stories, contextMarkdown, constitution) {
26342
+ function buildBatchPrompt(stories, contextMarkdown, constitution) {
26225
26343
  const storyPrompts = stories.map((story, idx) => {
26226
26344
  return `## Story ${idx + 1}: ${story.id} \u2014 ${story.title}
26227
26345
 
@@ -26279,7 +26397,7 @@ var init_prompt = __esm(() => {
26279
26397
  const isBatch = ctx.stories.length > 1;
26280
26398
  let prompt;
26281
26399
  if (isBatch) {
26282
- prompt = buildBatchPrompt2(ctx.stories, ctx.contextMarkdown, ctx.constitution);
26400
+ prompt = buildBatchPrompt(ctx.stories, ctx.contextMarkdown, ctx.constitution);
26283
26401
  } else {
26284
26402
  const role = ctx.routing.testStrategy === "tdd-simple" ? "tdd-simple" : "single-session";
26285
26403
  const builder = PromptBuilder.for(role).withLoader(ctx.workdir, ctx.config).story(ctx.story).context(ctx.contextMarkdown).constitution(ctx.constitution?.content);
@@ -26567,7 +26685,6 @@ ${rectificationPrompt}`;
26567
26685
  var init_rectification_loop = __esm(() => {
26568
26686
  init_agents();
26569
26687
  init_config();
26570
- init_progress();
26571
26688
  init_test_output_parser();
26572
26689
  init_logger2();
26573
26690
  init_prd();