@nathapp/nax 0.21.0 → 0.22.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/.mcp.json +8 -0
- package/docs/ROADMAP.md +20 -5
- package/docs/adr/ADR-005-implementation-plan.md +655 -0
- package/docs/adr/ADR-005-pipeline-re-architecture.md +464 -0
- package/package.json +1 -1
- package/src/agents/claude.ts +44 -9
- package/src/config/types.ts +11 -0
- package/src/execution/dry-run.ts +81 -0
- package/src/execution/escalation/tier-outcome.ts +29 -44
- package/src/execution/executor-types.ts +65 -0
- package/src/execution/index.ts +0 -17
- package/src/execution/iteration-runner.ts +132 -0
- package/src/execution/lifecycle/index.ts +0 -1
- package/src/execution/lifecycle/run-regression.ts +5 -5
- package/src/execution/pipeline-result-handler.ts +51 -254
- package/src/execution/sequential-executor.ts +72 -316
- package/src/execution/story-selector.ts +75 -0
- package/src/pipeline/event-bus.ts +276 -0
- package/src/pipeline/runner.ts +51 -77
- package/src/pipeline/stages/autofix.ts +133 -0
- package/src/pipeline/stages/completion.ts +22 -30
- package/src/pipeline/stages/index.ts +30 -13
- package/src/pipeline/stages/rectify.ts +93 -0
- package/src/pipeline/stages/regression.ts +88 -0
- package/src/pipeline/stages/review.ts +19 -153
- package/src/pipeline/stages/verify.ts +18 -2
- package/src/pipeline/subscribers/hooks.ts +133 -0
- package/src/pipeline/subscribers/interaction.ts +68 -0
- package/src/pipeline/subscribers/reporters.ts +174 -0
- package/src/pipeline/types.ts +10 -1
- package/src/review/orchestrator.ts +105 -0
- package/src/tdd/prompts.ts +1 -1
- package/src/verification/index.ts +1 -1
- package/src/verification/orchestrator-types.ts +145 -0
- package/src/verification/orchestrator.ts +76 -0
- package/src/{execution/post-verify-rectification.ts → verification/rectification-loop.ts} +13 -20
- package/src/verification/{gate.ts → runners.ts} +17 -105
- package/src/verification/strategies/acceptance.ts +133 -0
- package/src/verification/strategies/regression.ts +90 -0
- package/src/verification/strategies/scoped.ts +123 -0
- package/test/COVERAGE-GAPS.md +333 -0
- package/test/{acceptance → e2e}/cm-003-default-view.test.ts +1 -0
- package/test/{integration/e2e.test.ts → e2e/plan-analyze-run.test.ts} +1 -0
- package/test/integration/{agent-validation.test.ts → cli/agent-validation.test.ts} +3 -3
- package/test/integration/{cli-config-default-edge-cases.test.ts → cli/cli-config-default-edge-cases.test.ts} +6 -5
- package/test/integration/{cli-config-default-view.test.ts → cli/cli-config-default-view.test.ts} +8 -7
- package/test/integration/{cli-config-diff.test.ts → cli/cli-config-diff.test.ts} +3 -2
- package/test/integration/{cli-config.test.ts → cli/cli-config.test.ts} +3 -2
- package/test/integration/{cli-diagnose.test.ts → cli/cli-diagnose.test.ts} +5 -4
- package/test/integration/{cli-logs.test.ts → cli/cli-logs.test.ts} +12 -3
- package/test/integration/{cli-plugins.test.ts → cli/cli-plugins.test.ts} +4 -3
- package/test/integration/{cli-precheck.test.ts → cli/cli-precheck.test.ts} +4 -3
- package/test/integration/{cli-run-headless.test.ts → cli/cli-run-headless.test.ts} +3 -2
- package/test/integration/{cli.test.ts → cli/cli.test.ts} +2 -1
- package/test/integration/{precheck-integration.test.ts → cli/precheck-integration.test.ts} +10 -9
- package/test/integration/{precheck-orchestrator.test.ts → cli/precheck-orchestrator.test.ts} +4 -3
- package/test/integration/{precheck.test.ts → cli/precheck.test.ts} +5 -4
- package/test/integration/{config-loader.test.ts → config/config-loader.test.ts} +2 -1
- package/test/integration/{config.test.ts → config/config.test.ts} +2 -2
- package/test/integration/config/merger.test.ts +1 -0
- package/test/integration/config/paths.test.ts +1 -0
- package/test/integration/{security-loader.test.ts → config/security-loader.test.ts} +2 -2
- package/test/integration/{context-integration.test.ts → context/context-integration.test.ts} +7 -6
- package/test/integration/{path-security.test.ts → context/context-path-security.test.ts} +2 -2
- package/test/integration/{context-provider-injection.test.ts → context/context-provider-injection.test.ts} +7 -6
- package/test/integration/{context-verification-integration.test.ts → context/context-verification-integration.test.ts} +5 -4
- package/test/integration/{s5-greenfield-fallback.test.ts → context/s5-greenfield-fallback.test.ts} +4 -3
- package/test/integration/{isolation.test.ts → execution/execution-isolation.test.ts} +1 -1
- package/test/integration/{execution.test.ts → execution/execution.test.ts} +8 -8
- package/test/integration/{parallel.test.ts → execution/parallel.test.ts} +2 -1
- package/test/integration/{prd-pause.test.ts → execution/prd-pause.test.ts} +2 -2
- package/test/integration/{prd-resolvers.test.ts → execution/prd-resolvers.test.ts} +3 -2
- package/test/integration/{progress.test.ts → execution/progress.test.ts} +1 -1
- package/test/integration/execution/runner-batching.test.ts +682 -0
- package/test/integration/{runner-config-plugins.test.ts → execution/runner-config-plugins.test.ts} +3 -2
- package/test/integration/execution/runner-escalation.test.ts +561 -0
- package/test/integration/{runner-fixes.test.ts → execution/runner-fixes.test.ts} +4 -3
- package/test/integration/{runner-plugin-integration.test.ts → execution/runner-plugin-integration.test.ts} +6 -5
- package/test/integration/execution/runner-queue-and-attempts.test.ts +476 -0
- package/test/integration/{status-file-integration.test.ts → execution/status-file-integration.test.ts} +9 -8
- package/test/integration/{status-file.test.ts → execution/status-file.test.ts} +3 -2
- package/test/integration/{status-writer.test.ts → execution/status-writer.test.ts} +5 -4
- package/test/integration/{story-id-in-events.test.ts → execution/story-id-in-events.test.ts} +9 -8
- package/test/integration/{interaction-chain-pipeline.test.ts → interaction/interaction-chain-pipeline.test.ts} +26 -14
- package/test/integration/{hooks.test.ts → pipeline/hooks.test.ts} +4 -2
- package/test/integration/{pipeline-acceptance.test.ts → pipeline/pipeline-acceptance.test.ts} +7 -6
- package/test/integration/{pipeline-events.test.ts → pipeline/pipeline-events.test.ts} +7 -6
- package/test/integration/{pipeline.test.ts → pipeline/pipeline.test.ts} +9 -7
- package/test/integration/{reporter-lifecycle.test.ts → pipeline/reporter-lifecycle.test.ts} +9 -7
- package/test/integration/{verify-stage.test.ts → pipeline/verify-stage.test.ts} +7 -5
- package/test/integration/{analyze-integration.test.ts → plan/analyze-integration.test.ts} +3 -2
- package/test/integration/{analyze-scanner.test.ts → plan/analyze-scanner.test.ts} +8 -7
- package/test/integration/{logger.test.ts → plan/logger.test.ts} +1 -1
- package/test/integration/{plan.test.ts → plan/plan.test.ts} +3 -3
- package/test/integration/plugins/config-integration.test.ts +1 -0
- package/test/integration/plugins/config-resolution.test.ts +1 -0
- package/test/integration/plugins/loader.test.ts +1 -0
- package/test/integration/plugins/{registry.test.ts → plugins-registry.test.ts} +1 -0
- package/test/integration/plugins/validator.test.ts +1 -0
- package/test/integration/{review-config-commands.test.ts → review/review-config-commands.test.ts} +4 -3
- package/test/integration/{review-config-schema.test.ts → review/review-config-schema.test.ts} +3 -2
- package/test/integration/{review-plugin-integration.test.ts → review/review-plugin-integration.test.ts} +5 -4
- package/test/integration/{review.test.ts → review/review.test.ts} +3 -2
- package/test/integration/routing/plugin-routing-advanced.test.ts +461 -0
- package/test/integration/{plugin-routing.test.ts → routing/plugin-routing-core.test.ts} +9 -403
- package/test/integration/{routing-stage-bug-021.test.ts → routing/routing-stage-bug-021.test.ts} +8 -7
- package/test/integration/{routing-stage-greenfield.test.ts → routing/routing-stage-greenfield.test.ts} +7 -6
- package/test/integration/{tdd-cleanup.test.ts → tdd/tdd-cleanup.test.ts} +1 -1
- package/test/integration/tdd/tdd-orchestrator-core.test.ts +565 -0
- package/test/integration/tdd/tdd-orchestrator-failureCategory.test.ts +355 -0
- package/test/integration/tdd/tdd-orchestrator-fallback.test.ts +311 -0
- package/test/integration/tdd/tdd-orchestrator-lite.test.ts +289 -0
- package/test/integration/tdd/tdd-orchestrator-prompts.test.ts +260 -0
- package/test/integration/tdd/tdd-orchestrator-verdict.test.ts +536 -0
- package/test/integration/tmp/headless-test/test.jsonl +30 -0
- package/test/integration/{test-scanner.test.ts → verification/test-scanner.test.ts} +1 -1
- package/test/integration/{verification-asset-check.test.ts → verification/verification-asset-check.test.ts} +3 -2
- package/test/unit/acceptance.test.ts +1 -0
- package/test/unit/agent-stderr-capture.test.ts +1 -0
- package/test/unit/agents/claude.test.ts +1 -0
- package/test/unit/analyze-classifier.test.ts +1 -0
- package/test/unit/auto-detect.test.ts +1 -0
- package/test/unit/cli-status.test.ts +1 -0
- package/test/unit/commands/common.test.ts +1 -0
- package/test/unit/commands/logs.test.ts +1 -0
- package/test/unit/commands/unlock.test.ts +1 -0
- package/test/unit/config/defaults.test.ts +1 -0
- package/test/unit/config/regression-gate-schema.test.ts +1 -0
- package/test/unit/config/smart-runner-flag.test.ts +1 -0
- package/test/unit/constitution-generators.test.ts +1 -0
- package/test/unit/constitution.test.ts +1 -0
- package/test/unit/context/context-autodetect.test.ts +297 -0
- package/test/unit/context/context-build.test.ts +575 -0
- package/test/unit/context/context-coverage.test.ts +236 -0
- package/test/unit/context/context-error.test.ts +93 -0
- package/test/unit/context/context-estimate-tokens.test.ts +201 -0
- package/test/unit/context/context-format.test.ts +302 -0
- package/test/unit/context/context-isolation.test.ts +267 -0
- package/test/unit/context/context-sort.test.ts +93 -0
- package/test/unit/context/context-story.test.ts +108 -0
- package/test/{context → unit/context}/prior-failures.test.ts +5 -4
- package/test/unit/context.test.ts +1 -0
- package/test/unit/crash-recovery.test.ts +1 -0
- package/test/unit/escalation.test.ts +1 -0
- package/test/unit/execution/lifecycle/run-completion.test.ts +1 -0
- package/test/unit/execution/lifecycle/run-regression.test.ts +2 -0
- package/test/{execution → unit/execution}/pid-registry.test.ts +2 -1
- package/test/{execution → unit/execution}/structured-failure.test.ts +3 -2
- package/test/unit/execution-logging-stderr.test.ts +1 -0
- package/test/unit/execution-stage.test.ts +1 -0
- package/test/unit/fix-generator.test.ts +1 -0
- package/test/unit/greenfield.test.ts +1 -0
- package/test/unit/interaction/human-review-trigger.test.ts +1 -0
- package/test/unit/interaction-network-failures.test.ts +1 -0
- package/test/unit/interaction-plugins.test.ts +1 -0
- package/test/unit/logging/formatter.test.ts +1 -0
- package/test/unit/merge.test.ts +1 -0
- package/test/unit/pipeline/event-bus.test.ts +105 -0
- package/test/unit/pipeline/routing-partial-override.test.ts +1 -0
- package/test/unit/pipeline/runner-retry.test.ts +89 -0
- package/test/unit/pipeline/stages/autofix.test.ts +97 -0
- package/test/unit/pipeline/stages/rectify.test.ts +101 -0
- package/test/unit/pipeline/stages/regression-stage.test.ts +69 -0
- package/test/unit/pipeline/stages/verify.test.ts +1 -0
- package/test/unit/pipeline/subscribers/hooks.test.ts +45 -0
- package/test/unit/pipeline/subscribers/interaction.test.ts +31 -0
- package/test/unit/pipeline/subscribers/reporters.test.ts +90 -0
- package/test/unit/pipeline/verify-smart-runner.test.ts +1 -0
- package/test/unit/prd-auto-default.test.ts +1 -0
- package/test/unit/prd-failure-category.test.ts +1 -0
- package/test/unit/prd-get-next-story.test.ts +1 -0
- package/test/unit/precheck-checks.test.ts +1 -0
- package/test/unit/precheck-story-size-gate.test.ts +1 -0
- package/test/unit/precheck-types.test.ts +1 -0
- package/test/unit/prompts.test.ts +1 -0
- package/test/unit/rectification.test.ts +2 -1
- package/test/unit/registry.test.ts +1 -0
- package/test/unit/routing/routing-stability.test.ts +1 -0
- package/test/unit/routing/strategies/llm.test.ts +1 -0
- package/test/unit/routing-advanced.test.ts +313 -0
- package/test/unit/routing-core.test.ts +341 -0
- package/test/unit/routing-strategies.test.ts +442 -0
- package/test/unit/storyid-events.test.ts +1 -0
- package/test/{ui → unit/ui}/tui-controls.test.ts +8 -7
- package/test/{ui → unit/ui}/tui-cost-and-pty.test.ts +4 -3
- package/test/{ui → unit/ui}/tui-layout.test.ts +5 -4
- package/test/{ui → unit/ui}/tui-stories.test.ts +5 -4
- package/test/unit/{isolation.test.ts → unit-isolation.test.ts} +1 -0
- package/test/unit/{helpers.test.ts → utils-helpers.test.ts} +1 -0
- package/test/unit/verdict.test.ts +1 -0
- package/test/unit/verification/orchestrator-types.test.ts +54 -0
- package/test/unit/verification/orchestrator.test.ts +66 -0
- package/test/unit/verification/smart-runner-config.test.ts +1 -0
- package/test/unit/verification/smart-runner-discovery.test.ts +8 -7
- package/test/unit/verification/strategies/acceptance.test.ts +33 -0
- package/test/unit/verification/strategies/regression.test.ts +87 -0
- package/test/unit/verification/strategies/scoped.test.ts +100 -0
- package/test/unit/worktree-manager.test.ts +1 -0
- package/src/execution/lifecycle/story-hooks.ts +0 -38
- package/src/execution/post-verify.ts +0 -193
- package/src/execution/rectification.ts +0 -13
- package/src/execution/verification.ts +0 -72
- package/test/integration/rectification-flow.test.ts +0 -512
- package/test/integration/runner.test.ts +0 -1679
- package/test/integration/tdd-orchestrator.test.ts +0 -1762
- package/test/unit/execution/post-verify-regression.test.ts +0 -362
- package/test/unit/execution/post-verify.test.ts +0 -236
- package/test/unit/routing.test.ts +0 -1039
- /package/test/{integration → helpers}/helpers.test.ts +0 -0
- /package/test/integration/worktree/{merge.test.ts → worktree-merge.test.ts} +0 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
/**
|
|
3
|
+
* Tests for context builder module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// RE-ARCH: keep
|
|
7
|
+
/**
|
|
8
|
+
* Tests for context builder module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
buildContext,
|
|
17
|
+
createDependencyContext,
|
|
18
|
+
createErrorContext,
|
|
19
|
+
createFileContext,
|
|
20
|
+
createProgressContext,
|
|
21
|
+
createStoryContext,
|
|
22
|
+
estimateTokens,
|
|
23
|
+
formatContextAsMarkdown,
|
|
24
|
+
sortContextElements,
|
|
25
|
+
} from "../../../src/context/builder";
|
|
26
|
+
import type { ContextBudget, ContextElement, StoryContext } from "../../../src/context/types";
|
|
27
|
+
import type { PRD, UserStory } from "../../../src/prd";
|
|
28
|
+
|
|
29
|
+
// Helper to create test PRD
|
|
30
|
+
const createTestPRD = (stories: Partial<UserStory>[]): PRD => ({
|
|
31
|
+
project: "test-project",
|
|
32
|
+
feature: "test-feature",
|
|
33
|
+
branchName: "test-branch",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
updatedAt: new Date().toISOString(),
|
|
36
|
+
userStories: stories.map((s, i) => ({
|
|
37
|
+
id: s.id || `US-${String(i + 1).padStart(3, "0")}`,
|
|
38
|
+
title: s.title || "Test Story",
|
|
39
|
+
description: s.description || "Test description",
|
|
40
|
+
acceptanceCriteria: s.acceptanceCriteria || ["AC1"],
|
|
41
|
+
dependencies: s.dependencies || [],
|
|
42
|
+
tags: s.tags || [],
|
|
43
|
+
status: s.status || "pending",
|
|
44
|
+
passes: s.passes ?? false,
|
|
45
|
+
escalations: s.escalations || [],
|
|
46
|
+
attempts: s.attempts || 0,
|
|
47
|
+
routing: s.routing,
|
|
48
|
+
priorErrors: s.priorErrors,
|
|
49
|
+
relevantFiles: s.relevantFiles,
|
|
50
|
+
contextFiles: s.contextFiles,
|
|
51
|
+
expectedFiles: s.expectedFiles,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("Context Builder", () => {
|
|
56
|
+
describe("test coverage scoping", () => {
|
|
57
|
+
test("should scope test coverage to story contextFiles", async () => {
|
|
58
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
// Create test directory and files
|
|
62
|
+
const testDir = path.join(tempDir, "test");
|
|
63
|
+
await fs.mkdir(testDir);
|
|
64
|
+
|
|
65
|
+
// Create multiple test files
|
|
66
|
+
await fs.writeFile(
|
|
67
|
+
path.join(testDir, "health.service.test.ts"),
|
|
68
|
+
'describe("Health Service", () => { test("checks health", () => {}); });',
|
|
69
|
+
);
|
|
70
|
+
await fs.writeFile(
|
|
71
|
+
path.join(testDir, "auth.service.test.ts"),
|
|
72
|
+
'describe("Auth Service", () => { test("authenticates", () => {}); });',
|
|
73
|
+
);
|
|
74
|
+
await fs.writeFile(
|
|
75
|
+
path.join(testDir, "db.connection.test.ts"),
|
|
76
|
+
'describe("DB Connection", () => { test("connects", () => {}); });',
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
const prd = createTestPRD([
|
|
80
|
+
{
|
|
81
|
+
id: "US-001",
|
|
82
|
+
title: "Implement health service",
|
|
83
|
+
description: "Create health check service",
|
|
84
|
+
acceptanceCriteria: ["Service works"],
|
|
85
|
+
contextFiles: ["src/health.service.ts"], // Only health service
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const storyContext: StoryContext = {
|
|
90
|
+
prd,
|
|
91
|
+
currentStoryId: "US-001",
|
|
92
|
+
workdir: tempDir,
|
|
93
|
+
config: {
|
|
94
|
+
context: {
|
|
95
|
+
testCoverage: {
|
|
96
|
+
enabled: true,
|
|
97
|
+
scopeToStory: true, // Enable scoping
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
} as any,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const budget: ContextBudget = {
|
|
104
|
+
maxTokens: 10000,
|
|
105
|
+
reservedForInstructions: 1000,
|
|
106
|
+
availableForContext: 9000,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const built = await buildContext(storyContext, budget);
|
|
110
|
+
const markdown = formatContextAsMarkdown(built);
|
|
111
|
+
|
|
112
|
+
// Should include test coverage element
|
|
113
|
+
expect(built.elements.some((e) => e.type === "test-coverage")).toBe(true);
|
|
114
|
+
|
|
115
|
+
// Should only mention health.service.test.ts, not auth or db tests
|
|
116
|
+
expect(markdown).toContain("health.service.test.ts");
|
|
117
|
+
expect(markdown).not.toContain("auth.service.test.ts");
|
|
118
|
+
expect(markdown).not.toContain("db.connection.test.ts");
|
|
119
|
+
} finally {
|
|
120
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("should scan all tests when scopeToStory=false", async () => {
|
|
125
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const testDir = path.join(tempDir, "test");
|
|
129
|
+
await fs.mkdir(testDir);
|
|
130
|
+
|
|
131
|
+
await fs.writeFile(
|
|
132
|
+
path.join(testDir, "health.test.ts"),
|
|
133
|
+
'describe("Health", () => { test("works", () => {}); });',
|
|
134
|
+
);
|
|
135
|
+
await fs.writeFile(path.join(testDir, "auth.test.ts"), 'describe("Auth", () => { test("works", () => {}); });');
|
|
136
|
+
|
|
137
|
+
const prd = createTestPRD([
|
|
138
|
+
{
|
|
139
|
+
id: "US-001",
|
|
140
|
+
title: "Story",
|
|
141
|
+
description: "Test",
|
|
142
|
+
acceptanceCriteria: ["AC1"],
|
|
143
|
+
contextFiles: ["src/health.ts"],
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const storyContext: StoryContext = {
|
|
148
|
+
prd,
|
|
149
|
+
currentStoryId: "US-001",
|
|
150
|
+
workdir: tempDir,
|
|
151
|
+
config: {
|
|
152
|
+
context: {
|
|
153
|
+
testCoverage: {
|
|
154
|
+
enabled: true,
|
|
155
|
+
scopeToStory: false, // Disabled - should scan all
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
} as any,
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const budget: ContextBudget = {
|
|
162
|
+
maxTokens: 10000,
|
|
163
|
+
reservedForInstructions: 1000,
|
|
164
|
+
availableForContext: 9000,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const built = await buildContext(storyContext, budget);
|
|
168
|
+
const markdown = formatContextAsMarkdown(built);
|
|
169
|
+
|
|
170
|
+
// Should include both test files
|
|
171
|
+
expect(markdown).toContain("health.test.ts");
|
|
172
|
+
expect(markdown).toContain("auth.test.ts");
|
|
173
|
+
} finally {
|
|
174
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test("should fall back to full scan when no contextFiles provided", async () => {
|
|
179
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
const testDir = path.join(tempDir, "test");
|
|
183
|
+
await fs.mkdir(testDir);
|
|
184
|
+
|
|
185
|
+
await fs.writeFile(
|
|
186
|
+
path.join(testDir, "test1.test.ts"),
|
|
187
|
+
'describe("Test1", () => { test("works", () => {}); });',
|
|
188
|
+
);
|
|
189
|
+
await fs.writeFile(
|
|
190
|
+
path.join(testDir, "test2.test.ts"),
|
|
191
|
+
'describe("Test2", () => { test("works", () => {}); });',
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
const prd = createTestPRD([
|
|
195
|
+
{
|
|
196
|
+
id: "US-001",
|
|
197
|
+
title: "Story without contextFiles",
|
|
198
|
+
description: "Test",
|
|
199
|
+
acceptanceCriteria: ["AC1"],
|
|
200
|
+
// No contextFiles
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const storyContext: StoryContext = {
|
|
205
|
+
prd,
|
|
206
|
+
currentStoryId: "US-001",
|
|
207
|
+
workdir: tempDir,
|
|
208
|
+
config: {
|
|
209
|
+
context: {
|
|
210
|
+
testCoverage: {
|
|
211
|
+
enabled: true,
|
|
212
|
+
scopeToStory: true, // true but no contextFiles
|
|
213
|
+
},
|
|
214
|
+
},
|
|
215
|
+
} as any,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const budget: ContextBudget = {
|
|
219
|
+
maxTokens: 10000,
|
|
220
|
+
reservedForInstructions: 1000,
|
|
221
|
+
availableForContext: 9000,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
const built = await buildContext(storyContext, budget);
|
|
225
|
+
const markdown = formatContextAsMarkdown(built);
|
|
226
|
+
|
|
227
|
+
// Should fall back to scanning all files
|
|
228
|
+
expect(markdown).toContain("test1.test.ts");
|
|
229
|
+
expect(markdown).toContain("test2.test.ts");
|
|
230
|
+
} finally {
|
|
231
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
/**
|
|
3
|
+
* Tests for context builder module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// RE-ARCH: keep
|
|
7
|
+
/**
|
|
8
|
+
* Tests for context builder module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
buildContext,
|
|
17
|
+
createDependencyContext,
|
|
18
|
+
createErrorContext,
|
|
19
|
+
createFileContext,
|
|
20
|
+
createProgressContext,
|
|
21
|
+
createStoryContext,
|
|
22
|
+
estimateTokens,
|
|
23
|
+
formatContextAsMarkdown,
|
|
24
|
+
sortContextElements,
|
|
25
|
+
} from "../../../src/context/builder";
|
|
26
|
+
import type { ContextBudget, ContextElement, StoryContext } from "../../../src/context/types";
|
|
27
|
+
import type { PRD, UserStory } from "../../../src/prd";
|
|
28
|
+
|
|
29
|
+
// Helper to create test PRD
|
|
30
|
+
const createTestPRD = (stories: Partial<UserStory>[]): PRD => ({
|
|
31
|
+
project: "test-project",
|
|
32
|
+
feature: "test-feature",
|
|
33
|
+
branchName: "test-branch",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
updatedAt: new Date().toISOString(),
|
|
36
|
+
userStories: stories.map((s, i) => ({
|
|
37
|
+
id: s.id || `US-${String(i + 1).padStart(3, "0")}`,
|
|
38
|
+
title: s.title || "Test Story",
|
|
39
|
+
description: s.description || "Test description",
|
|
40
|
+
acceptanceCriteria: s.acceptanceCriteria || ["AC1"],
|
|
41
|
+
dependencies: s.dependencies || [],
|
|
42
|
+
tags: s.tags || [],
|
|
43
|
+
status: s.status || "pending",
|
|
44
|
+
passes: s.passes ?? false,
|
|
45
|
+
escalations: s.escalations || [],
|
|
46
|
+
attempts: s.attempts || 0,
|
|
47
|
+
routing: s.routing,
|
|
48
|
+
priorErrors: s.priorErrors,
|
|
49
|
+
relevantFiles: s.relevantFiles,
|
|
50
|
+
contextFiles: s.contextFiles,
|
|
51
|
+
expectedFiles: s.expectedFiles,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("Context Builder", () => {
|
|
56
|
+
describe("createErrorContext", () => {
|
|
57
|
+
test("should create error context element", () => {
|
|
58
|
+
const error = "TypeError: Cannot read property";
|
|
59
|
+
const element = createErrorContext(error, 90);
|
|
60
|
+
|
|
61
|
+
expect(element.type).toBe("error");
|
|
62
|
+
expect(element.content).toBe(error);
|
|
63
|
+
expect(element.priority).toBe(90);
|
|
64
|
+
expect(element.tokens).toBeGreaterThan(0);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("createProgressContext", () => {
|
|
69
|
+
test("should create progress context element", () => {
|
|
70
|
+
const progress = "Progress: 5/12 stories complete (4 passed, 1 failed)";
|
|
71
|
+
const element = createProgressContext(progress, 100);
|
|
72
|
+
|
|
73
|
+
expect(element.type).toBe("progress");
|
|
74
|
+
expect(element.content).toBe(progress);
|
|
75
|
+
expect(element.priority).toBe(100);
|
|
76
|
+
expect(element.tokens).toBeGreaterThan(0);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("createFileContext", () => {
|
|
81
|
+
test("should create file context element", () => {
|
|
82
|
+
const filePath = "src/utils/helper.ts";
|
|
83
|
+
const content = 'export function helper() { return "test"; }';
|
|
84
|
+
const element = createFileContext(filePath, content, 60);
|
|
85
|
+
|
|
86
|
+
expect(element.type).toBe("file");
|
|
87
|
+
expect(element.filePath).toBe(filePath);
|
|
88
|
+
expect(element.content).toBe(content);
|
|
89
|
+
expect(element.priority).toBe(60);
|
|
90
|
+
expect(element.tokens).toBeGreaterThan(0);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
/**
|
|
3
|
+
* Tests for context builder module
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// RE-ARCH: keep
|
|
7
|
+
/**
|
|
8
|
+
* Tests for context builder module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
import os from "node:os";
|
|
14
|
+
import path from "node:path";
|
|
15
|
+
import {
|
|
16
|
+
buildContext,
|
|
17
|
+
createDependencyContext,
|
|
18
|
+
createErrorContext,
|
|
19
|
+
createFileContext,
|
|
20
|
+
createProgressContext,
|
|
21
|
+
createStoryContext,
|
|
22
|
+
estimateTokens,
|
|
23
|
+
formatContextAsMarkdown,
|
|
24
|
+
sortContextElements,
|
|
25
|
+
} from "../../../src/context/builder";
|
|
26
|
+
import type { ContextBudget, ContextElement, StoryContext } from "../../../src/context/types";
|
|
27
|
+
import type { PRD, UserStory } from "../../../src/prd";
|
|
28
|
+
|
|
29
|
+
// Helper to create test PRD
|
|
30
|
+
const createTestPRD = (stories: Partial<UserStory>[]): PRD => ({
|
|
31
|
+
project: "test-project",
|
|
32
|
+
feature: "test-feature",
|
|
33
|
+
branchName: "test-branch",
|
|
34
|
+
createdAt: new Date().toISOString(),
|
|
35
|
+
updatedAt: new Date().toISOString(),
|
|
36
|
+
userStories: stories.map((s, i) => ({
|
|
37
|
+
id: s.id || `US-${String(i + 1).padStart(3, "0")}`,
|
|
38
|
+
title: s.title || "Test Story",
|
|
39
|
+
description: s.description || "Test description",
|
|
40
|
+
acceptanceCriteria: s.acceptanceCriteria || ["AC1"],
|
|
41
|
+
dependencies: s.dependencies || [],
|
|
42
|
+
tags: s.tags || [],
|
|
43
|
+
status: s.status || "pending",
|
|
44
|
+
passes: s.passes ?? false,
|
|
45
|
+
escalations: s.escalations || [],
|
|
46
|
+
attempts: s.attempts || 0,
|
|
47
|
+
routing: s.routing,
|
|
48
|
+
priorErrors: s.priorErrors,
|
|
49
|
+
relevantFiles: s.relevantFiles,
|
|
50
|
+
contextFiles: s.contextFiles,
|
|
51
|
+
expectedFiles: s.expectedFiles,
|
|
52
|
+
})),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("Context Builder", () => {
|
|
56
|
+
describe("estimateTokens", () => {
|
|
57
|
+
test("should estimate tokens correctly", () => {
|
|
58
|
+
expect(estimateTokens("test")).toBe(2); // 4 chars = 2 tokens (1 token ≈ 3 chars)
|
|
59
|
+
expect(estimateTokens("hello world")).toBe(4); // 11 chars = 4 tokens
|
|
60
|
+
expect(estimateTokens("")).toBe(0);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe("defensive checks", () => {
|
|
65
|
+
test("should handle story with null acceptanceCriteria", async () => {
|
|
66
|
+
// Create PRD directly to bypass helper defaults
|
|
67
|
+
const prd: PRD = {
|
|
68
|
+
project: "test-project",
|
|
69
|
+
feature: "test-feature",
|
|
70
|
+
branchName: "test-branch",
|
|
71
|
+
createdAt: new Date().toISOString(),
|
|
72
|
+
updatedAt: new Date().toISOString(),
|
|
73
|
+
userStories: [
|
|
74
|
+
{
|
|
75
|
+
id: "US-001",
|
|
76
|
+
title: "Malformed Story",
|
|
77
|
+
description: "Test",
|
|
78
|
+
acceptanceCriteria: null as any, // Simulate malformed data
|
|
79
|
+
dependencies: [],
|
|
80
|
+
tags: [],
|
|
81
|
+
status: "pending",
|
|
82
|
+
passes: false,
|
|
83
|
+
escalations: [],
|
|
84
|
+
attempts: 0,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const context: StoryContext = {
|
|
90
|
+
prd,
|
|
91
|
+
currentStoryId: "US-001",
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const budget: ContextBudget = {
|
|
95
|
+
maxTokens: 10000,
|
|
96
|
+
reservedForInstructions: 1000,
|
|
97
|
+
availableForContext: 9000,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const built = await buildContext(context, budget);
|
|
101
|
+
expect(built.elements.length).toBeGreaterThan(0);
|
|
102
|
+
const storyElement = built.elements.find((e) => e.type === "story");
|
|
103
|
+
expect(storyElement?.content).toContain("(No acceptance criteria defined)");
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("should handle story with undefined acceptanceCriteria", async () => {
|
|
107
|
+
// Create PRD directly to bypass helper defaults
|
|
108
|
+
const prd: PRD = {
|
|
109
|
+
project: "test-project",
|
|
110
|
+
feature: "test-feature",
|
|
111
|
+
branchName: "test-branch",
|
|
112
|
+
createdAt: new Date().toISOString(),
|
|
113
|
+
updatedAt: new Date().toISOString(),
|
|
114
|
+
userStories: [
|
|
115
|
+
{
|
|
116
|
+
id: "US-001",
|
|
117
|
+
title: "Malformed Story",
|
|
118
|
+
description: "Test",
|
|
119
|
+
acceptanceCriteria: undefined as any, // Simulate malformed data
|
|
120
|
+
dependencies: [],
|
|
121
|
+
tags: [],
|
|
122
|
+
status: "pending",
|
|
123
|
+
passes: false,
|
|
124
|
+
escalations: [],
|
|
125
|
+
attempts: 0,
|
|
126
|
+
},
|
|
127
|
+
],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const context: StoryContext = {
|
|
131
|
+
prd,
|
|
132
|
+
currentStoryId: "US-001",
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const budget: ContextBudget = {
|
|
136
|
+
maxTokens: 10000,
|
|
137
|
+
reservedForInstructions: 1000,
|
|
138
|
+
availableForContext: 9000,
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const built = await buildContext(context, budget);
|
|
142
|
+
expect(built.elements.length).toBeGreaterThan(0);
|
|
143
|
+
const storyElement = built.elements.find((e) => e.type === "story");
|
|
144
|
+
expect(storyElement?.content).toContain("(No acceptance criteria defined)");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("should log warning for missing dependency story", async () => {
|
|
148
|
+
const prd = createTestPRD([
|
|
149
|
+
{
|
|
150
|
+
id: "US-001",
|
|
151
|
+
title: "Story with Missing Dependency",
|
|
152
|
+
description: "Test",
|
|
153
|
+
acceptanceCriteria: ["AC1"],
|
|
154
|
+
dependencies: ["US-999"], // Non-existent dependency
|
|
155
|
+
},
|
|
156
|
+
]);
|
|
157
|
+
|
|
158
|
+
const context: StoryContext = {
|
|
159
|
+
prd,
|
|
160
|
+
currentStoryId: "US-001",
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const budget: ContextBudget = {
|
|
164
|
+
maxTokens: 10000,
|
|
165
|
+
reservedForInstructions: 1000,
|
|
166
|
+
availableForContext: 9000,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const built = await buildContext(context, budget);
|
|
170
|
+
|
|
171
|
+
// Should not include the missing dependency in the context
|
|
172
|
+
expect(built.elements.find((e) => e.type === "dependency")).toBeUndefined();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("should handle story with non-array priorErrors", async () => {
|
|
176
|
+
const prd = createTestPRD([
|
|
177
|
+
{
|
|
178
|
+
id: "US-001",
|
|
179
|
+
title: "Story with Malformed Errors",
|
|
180
|
+
description: "Test",
|
|
181
|
+
acceptanceCriteria: ["AC1"],
|
|
182
|
+
priorErrors: "not an array" as any, // Malformed data
|
|
183
|
+
},
|
|
184
|
+
]);
|
|
185
|
+
|
|
186
|
+
const context: StoryContext = {
|
|
187
|
+
prd,
|
|
188
|
+
currentStoryId: "US-001",
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const budget: ContextBudget = {
|
|
192
|
+
maxTokens: 10000,
|
|
193
|
+
reservedForInstructions: 1000,
|
|
194
|
+
availableForContext: 9000,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const built = await buildContext(context, budget);
|
|
198
|
+
expect(built.elements.find((e) => e.type === "error")).toBeUndefined();
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
});
|