@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 +223 -106
- package/package.json +1 -1
- package/src/agents/claude-decompose.ts +3 -3
- package/src/cli/constitution.ts +0 -92
- package/src/constitution/generator.ts +0 -33
- package/src/constitution/index.ts +2 -1
- package/src/constitution/loader.ts +1 -13
- package/src/context/builder.ts +1 -2
- package/src/context/elements.ts +1 -12
- package/src/context/index.ts +2 -1
- package/src/context/test-scanner.ts +1 -1
- package/src/interaction/chain.ts +17 -1
- package/src/pipeline/stages/execution.ts +23 -1
- package/src/prompts/builder.ts +13 -6
- package/src/prompts/sections/conventions.ts +5 -7
- package/src/prompts/sections/isolation.ts +7 -7
- package/src/prompts/sections/role-task.ts +64 -64
- package/src/review/orchestrator.ts +11 -1
- package/src/routing/strategies/llm-prompts.ts +1 -1
- package/src/routing/strategies/llm.ts +3 -3
- package/src/tdd/index.ts +2 -3
- package/src/tdd/isolation.ts +0 -13
- package/src/tdd/orchestrator.ts +5 -0
- package/src/tdd/prompts.ts +1 -231
- package/src/tdd/session-runner.ts +2 -0
- package/src/tdd/verdict.ts +20 -2
- package/src/verification/parser.ts +0 -10
- package/src/verification/rectification-loop.ts +2 -51
- package/src/worktree/dispatcher.ts +0 -59
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:
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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.
|
|
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("
|
|
20722
|
-
return "
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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 =
|
|
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 (
|
|
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 =
|
|
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,
|
|
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
|
-
|
|
24923
|
+
Follow existing code patterns and conventions. Write idiomatic, maintainable code.
|
|
24912
24924
|
|
|
24913
|
-
|
|
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 =
|
|
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}
|
|
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}
|
|
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}
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
-
|
|
24984
|
+
Your task: make failing tests pass.
|
|
24963
24985
|
|
|
24964
|
-
|
|
24965
|
-
|
|
24966
|
-
|
|
24967
|
-
|
|
24968
|
-
|
|
24969
|
-
|
|
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
|
-
|
|
24995
|
+
Your task: Write tests AND implement the feature in a single session.
|
|
24974
24996
|
|
|
24975
|
-
|
|
24976
|
-
|
|
24977
|
-
|
|
24978
|
-
|
|
24979
|
-
|
|
24980
|
-
|
|
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
|
-
|
|
25007
|
+
Your task: Write comprehensive failing tests for the feature.
|
|
24986
25008
|
|
|
24987
|
-
|
|
24988
|
-
|
|
24989
|
-
|
|
24990
|
-
|
|
24991
|
-
|
|
24992
|
-
|
|
24993
|
-
|
|
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
|
-
|
|
25020
|
+
Your task: Review and verify the implementation against acceptance criteria.
|
|
24999
25021
|
|
|
25000
|
-
|
|
25001
|
-
|
|
25002
|
-
|
|
25003
|
-
|
|
25004
|
-
|
|
25005
|
-
|
|
25006
|
-
|
|
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
|
-
|
|
25033
|
+
Your task: Write tests AND implement the feature in a single focused session.
|
|
25012
25034
|
|
|
25013
|
-
|
|
25014
|
-
|
|
25015
|
-
|
|
25016
|
-
|
|
25017
|
-
|
|
25018
|
-
|
|
25019
|
-
|
|
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
|
-
|
|
25045
|
+
Your task: Write failing tests FIRST, then implement to make them pass.
|
|
25024
25046
|
|
|
25025
|
-
|
|
25026
|
-
|
|
25027
|
-
|
|
25028
|
-
|
|
25029
|
-
|
|
25030
|
-
|
|
25031
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
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 =
|
|
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();
|