@nathapp/nax 0.20.0 → 0.22.0
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/.claude/settings.json +15 -0
- package/.mcp.json +8 -0
- package/docs/20260304-review-nax.md +492 -0
- package/docs/ROADMAP.md +65 -18
- package/docs/adr/ADR-005-implementation-plan.md +655 -0
- package/docs/adr/ADR-005-pipeline-re-architecture.md +464 -0
- package/docs/specs/bug-039-orphan-processes.md +131 -0
- package/docs/specs/bug-040-review-rectification.md +82 -0
- package/docs/specs/bug-041-cross-story-test-isolation.md +88 -0
- package/docs/specs/bug-042-verifier-failure-capture.md +117 -0
- package/docs/specs/feat-010-smart-runner-git-history.md +96 -0
- package/docs/specs/feat-011-file-context-strategy.md +73 -0
- package/docs/specs/feat-012-tdd-writer-tier.md +79 -0
- package/docs/specs/feat-013-test-after-review.md +89 -0
- package/docs/specs/feat-014-heartbeat-observability.md +127 -0
- package/memory/topic/feat-010-baseref.md +28 -0
- package/memory/topic/feat-013-test-after-deprecation.md +22 -0
- package/nax/config.json +7 -4
- package/nax/features/bug-039-medium/prd.json +45 -0
- package/package.json +2 -2
- package/src/agents/claude.ts +109 -15
- package/src/config/types.ts +11 -0
- package/src/context/builder.ts +9 -1
- 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 -315
- 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 +19 -3
- 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 +12 -1
- package/src/review/orchestrator.ts +105 -0
- package/src/review/runner.ts +39 -4
- package/src/routing/router.ts +3 -3
- package/src/routing/strategies/keyword.ts +5 -2
- package/src/routing/strategies/llm.ts +27 -1
- package/src/tdd/prompts.ts +1 -1
- package/src/utils/git.ts +49 -25
- package/src/verification/executor.ts +8 -2
- 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/smart-runner.ts +6 -10
- 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} +10 -404
- 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 +107 -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 +7 -3
- 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 +2 -1
- package/test/unit/prd-auto-default.test.ts +3 -2
- 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 +2 -1
- package/test/unit/routing/strategies/llm.test.ts +251 -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,107 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
/**
|
|
3
|
+
* Tests for ClaudeCodeAdapter.runOnce() timeout behavior
|
|
4
|
+
*
|
|
5
|
+
* Covers: US-001 - runOnce() SIGKILL follow-up after grace period
|
|
6
|
+
* - SIGTERM is sent first on timeout
|
|
7
|
+
* - PID is always unregistered in finally block, even if kill() throws
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
11
|
+
import { existsSync, mkdtempSync, rmSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { ClaudeCodeAdapter, _runOnceDeps } from "../../../src/agents/claude";
|
|
15
|
+
import type { AgentRunOptions } from "../../../src/agents/types";
|
|
16
|
+
|
|
17
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
18
|
+
// Test adapter — overrides buildCommand to avoid requiring the claude binary
|
|
19
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
class TestAdapter extends ClaudeCodeAdapter {
|
|
22
|
+
private readonly testCmd: string[];
|
|
23
|
+
|
|
24
|
+
constructor(cmd: string[]) {
|
|
25
|
+
super();
|
|
26
|
+
this.testCmd = cmd;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
override buildCommand(_options: AgentRunOptions): string[] {
|
|
30
|
+
return this.testCmd;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
35
|
+
// Helpers
|
|
36
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
function makeRunOptions(workdir: string, timeoutSeconds: number): AgentRunOptions {
|
|
39
|
+
return {
|
|
40
|
+
workdir,
|
|
41
|
+
prompt: "test",
|
|
42
|
+
modelTier: "balanced",
|
|
43
|
+
modelDef: { provider: "anthropic", model: "claude-sonnet-4-5", env: {} },
|
|
44
|
+
timeoutSeconds,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
// Tests
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
describe("runOnce() timeout behavior", () => {
|
|
53
|
+
let tempDir: string;
|
|
54
|
+
const origKillProc = _runOnceDeps.killProc;
|
|
55
|
+
|
|
56
|
+
beforeEach(() => {
|
|
57
|
+
tempDir = mkdtempSync(join(tmpdir(), "nax-claude-test-"));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
// Restore original killProc after each test
|
|
62
|
+
_runOnceDeps.killProc = origKillProc;
|
|
63
|
+
if (existsSync(tempDir)) {
|
|
64
|
+
rmSync(tempDir, { recursive: true });
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("timeout path sends SIGTERM to process first", async () => {
|
|
69
|
+
const sentSignals: string[] = [];
|
|
70
|
+
|
|
71
|
+
// Record signals sent but still kill the process so proc.exited resolves
|
|
72
|
+
_runOnceDeps.killProc = (proc, signal) => {
|
|
73
|
+
sentSignals.push(String(signal));
|
|
74
|
+
origKillProc(proc, signal);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Long-running process: will be killed by the 100ms timeout
|
|
78
|
+
const adapter = new TestAdapter(["/bin/sh", "-c", "sleep 100"]);
|
|
79
|
+
const result = await adapter.run(makeRunOptions(tempDir, 0.1));
|
|
80
|
+
|
|
81
|
+
expect(result.exitCode).toBe(124); // timeout exit code
|
|
82
|
+
expect(sentSignals[0]).toBe("SIGTERM"); // SIGTERM sent first
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("timeout path: unregisters PID even if killProc throws", async () => {
|
|
86
|
+
// Override killProc to throw — simulates kill() failing (e.g., process already gone)
|
|
87
|
+
_runOnceDeps.killProc = (_proc, _signal) => {
|
|
88
|
+
throw new Error("kill failed");
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// Use a short-lived process (0.5s) with a timeout that fires first (50ms).
|
|
92
|
+
// killProc throws (process not killed), so proc exits naturally at ~0.5s.
|
|
93
|
+
// The finally block must still call unregister regardless.
|
|
94
|
+
const adapter = new TestAdapter(["/bin/sh", "-c", "sleep 0.5"]);
|
|
95
|
+
const options = makeRunOptions(tempDir, 0.05); // 50ms timeout
|
|
96
|
+
|
|
97
|
+
// Should not throw — kill errors are caught internally
|
|
98
|
+
await adapter.run(options);
|
|
99
|
+
|
|
100
|
+
// PID must have been unregistered (file empty or absent)
|
|
101
|
+
const pidsFile = join(tempDir, ".nax-pids");
|
|
102
|
+
if (existsSync(pidsFile)) {
|
|
103
|
+
const content = await Bun.file(pidsFile).text();
|
|
104
|
+
expect(content.trim()).toBe("");
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
});
|
|
@@ -0,0 +1,297 @@
|
|
|
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("context auto-detection (BUG-006)", () => {
|
|
57
|
+
test("should auto-detect files when contextFiles is empty", async () => {
|
|
58
|
+
// Create temp git repo
|
|
59
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Initialize git
|
|
63
|
+
await Bun.spawn(["git", "init"], { cwd: tempDir }).exited;
|
|
64
|
+
await Bun.spawn(["git", "config", "user.email", "test@test.com"], { cwd: tempDir }).exited;
|
|
65
|
+
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: tempDir }).exited;
|
|
66
|
+
|
|
67
|
+
// Create files matching story keywords
|
|
68
|
+
await fs.mkdir(path.join(tempDir, "src/routing"), { recursive: true });
|
|
69
|
+
await fs.writeFile(path.join(tempDir, "src/routing/router.ts"), "export class Router { /* routing logic */ }");
|
|
70
|
+
await fs.writeFile(
|
|
71
|
+
path.join(tempDir, "src/routing/chain.ts"),
|
|
72
|
+
"export class RouterChain { /* chain logic */ }",
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// Commit so git grep can find them
|
|
76
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
77
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
78
|
+
|
|
79
|
+
const prd = createTestPRD([
|
|
80
|
+
{
|
|
81
|
+
id: "US-001",
|
|
82
|
+
title: "Fix routing chain bug",
|
|
83
|
+
description: "Fix issue in router chain",
|
|
84
|
+
acceptanceCriteria: ["Router chain works correctly"],
|
|
85
|
+
// No contextFiles - should auto-detect
|
|
86
|
+
},
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const storyContext: StoryContext = {
|
|
90
|
+
prd,
|
|
91
|
+
currentStoryId: "US-001",
|
|
92
|
+
workdir: tempDir,
|
|
93
|
+
config: {
|
|
94
|
+
context: {
|
|
95
|
+
autoDetect: {
|
|
96
|
+
enabled: true,
|
|
97
|
+
maxFiles: 5,
|
|
98
|
+
traceImports: false,
|
|
99
|
+
},
|
|
100
|
+
testCoverage: {
|
|
101
|
+
enabled: false, // Disable to isolate auto-detect test
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
} as any,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const budget: ContextBudget = {
|
|
108
|
+
maxTokens: 10000,
|
|
109
|
+
reservedForInstructions: 1000,
|
|
110
|
+
availableForContext: 9000,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const built = await buildContext(storyContext, budget);
|
|
114
|
+
const fileElements = built.elements.filter((e) => e.type === "file");
|
|
115
|
+
|
|
116
|
+
// Should auto-detect routing files
|
|
117
|
+
expect(fileElements.length).toBeGreaterThan(0);
|
|
118
|
+
const filePaths = fileElements.map((e) => e.filePath);
|
|
119
|
+
expect(filePaths).toContain("src/routing/router.ts");
|
|
120
|
+
expect(filePaths).toContain("src/routing/chain.ts");
|
|
121
|
+
} finally {
|
|
122
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test("should skip auto-detection when contextFiles is provided", async () => {
|
|
127
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
await Bun.spawn(["git", "init"], { cwd: tempDir }).exited;
|
|
131
|
+
await Bun.spawn(["git", "config", "user.email", "test@test.com"], { cwd: tempDir }).exited;
|
|
132
|
+
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: tempDir }).exited;
|
|
133
|
+
|
|
134
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
135
|
+
await fs.writeFile(path.join(tempDir, "src/explicit.ts"), "export const explicit = true;");
|
|
136
|
+
await fs.writeFile(path.join(tempDir, "src/routing.ts"), "export const routing = true;");
|
|
137
|
+
|
|
138
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
139
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
140
|
+
|
|
141
|
+
const prd = createTestPRD([
|
|
142
|
+
{
|
|
143
|
+
id: "US-001",
|
|
144
|
+
title: "Fix routing bug",
|
|
145
|
+
description: "Fix routing",
|
|
146
|
+
acceptanceCriteria: ["Works"],
|
|
147
|
+
contextFiles: ["src/explicit.ts"], // Explicit file provided
|
|
148
|
+
},
|
|
149
|
+
]);
|
|
150
|
+
|
|
151
|
+
const storyContext: StoryContext = {
|
|
152
|
+
prd,
|
|
153
|
+
currentStoryId: "US-001",
|
|
154
|
+
workdir: tempDir,
|
|
155
|
+
config: {
|
|
156
|
+
context: {
|
|
157
|
+
autoDetect: {
|
|
158
|
+
enabled: true,
|
|
159
|
+
maxFiles: 5,
|
|
160
|
+
traceImports: false,
|
|
161
|
+
},
|
|
162
|
+
testCoverage: {
|
|
163
|
+
enabled: false,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
} as any,
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const budget: ContextBudget = {
|
|
170
|
+
maxTokens: 10000,
|
|
171
|
+
reservedForInstructions: 1000,
|
|
172
|
+
availableForContext: 9000,
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const built = await buildContext(storyContext, budget);
|
|
176
|
+
const fileElements = built.elements.filter((e) => e.type === "file");
|
|
177
|
+
|
|
178
|
+
// Should only load explicit file, NOT auto-detect
|
|
179
|
+
expect(fileElements.length).toBe(1);
|
|
180
|
+
expect(fileElements[0].filePath).toBe("src/explicit.ts");
|
|
181
|
+
} finally {
|
|
182
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("should skip auto-detection when disabled in config", async () => {
|
|
187
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
188
|
+
|
|
189
|
+
try {
|
|
190
|
+
await Bun.spawn(["git", "init"], { cwd: tempDir }).exited;
|
|
191
|
+
await Bun.spawn(["git", "config", "user.email", "test@test.com"], { cwd: tempDir }).exited;
|
|
192
|
+
await Bun.spawn(["git", "config", "user.name", "Test User"], { cwd: tempDir }).exited;
|
|
193
|
+
|
|
194
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
195
|
+
await fs.writeFile(path.join(tempDir, "src/routing.ts"), "export const routing = true;");
|
|
196
|
+
|
|
197
|
+
await Bun.spawn(["git", "add", "."], { cwd: tempDir }).exited;
|
|
198
|
+
await Bun.spawn(["git", "commit", "-m", "initial"], { cwd: tempDir }).exited;
|
|
199
|
+
|
|
200
|
+
const prd = createTestPRD([
|
|
201
|
+
{
|
|
202
|
+
id: "US-001",
|
|
203
|
+
title: "Fix routing bug",
|
|
204
|
+
description: "Fix routing",
|
|
205
|
+
acceptanceCriteria: ["Works"],
|
|
206
|
+
// No contextFiles
|
|
207
|
+
},
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
const storyContext: StoryContext = {
|
|
211
|
+
prd,
|
|
212
|
+
currentStoryId: "US-001",
|
|
213
|
+
workdir: tempDir,
|
|
214
|
+
config: {
|
|
215
|
+
context: {
|
|
216
|
+
autoDetect: {
|
|
217
|
+
enabled: false, // Disabled
|
|
218
|
+
maxFiles: 5,
|
|
219
|
+
traceImports: false,
|
|
220
|
+
},
|
|
221
|
+
testCoverage: {
|
|
222
|
+
enabled: false,
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
} as any,
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
const budget: ContextBudget = {
|
|
229
|
+
maxTokens: 10000,
|
|
230
|
+
reservedForInstructions: 1000,
|
|
231
|
+
availableForContext: 9000,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const built = await buildContext(storyContext, budget);
|
|
235
|
+
const fileElements = built.elements.filter((e) => e.type === "file");
|
|
236
|
+
|
|
237
|
+
// Should NOT auto-detect when disabled
|
|
238
|
+
expect(fileElements.length).toBe(0);
|
|
239
|
+
} finally {
|
|
240
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("should handle auto-detection failure gracefully", async () => {
|
|
245
|
+
// Non-git directory - git grep will fail
|
|
246
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
await fs.mkdir(path.join(tempDir, "src"), { recursive: true });
|
|
250
|
+
await fs.writeFile(path.join(tempDir, "src/file.ts"), "export const test = true;");
|
|
251
|
+
|
|
252
|
+
const prd = createTestPRD([
|
|
253
|
+
{
|
|
254
|
+
id: "US-001",
|
|
255
|
+
title: "Fix test bug",
|
|
256
|
+
description: "Fix test",
|
|
257
|
+
acceptanceCriteria: ["Works"],
|
|
258
|
+
// No contextFiles
|
|
259
|
+
},
|
|
260
|
+
]);
|
|
261
|
+
|
|
262
|
+
const storyContext: StoryContext = {
|
|
263
|
+
prd,
|
|
264
|
+
currentStoryId: "US-001",
|
|
265
|
+
workdir: tempDir,
|
|
266
|
+
config: {
|
|
267
|
+
context: {
|
|
268
|
+
autoDetect: {
|
|
269
|
+
enabled: true,
|
|
270
|
+
maxFiles: 5,
|
|
271
|
+
traceImports: false,
|
|
272
|
+
},
|
|
273
|
+
testCoverage: {
|
|
274
|
+
enabled: false,
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
} as any,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const budget: ContextBudget = {
|
|
281
|
+
maxTokens: 10000,
|
|
282
|
+
reservedForInstructions: 1000,
|
|
283
|
+
availableForContext: 9000,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
// Should not throw, just log warning and continue
|
|
287
|
+
const built = await buildContext(storyContext, budget);
|
|
288
|
+
const fileElements = built.elements.filter((e) => e.type === "file");
|
|
289
|
+
|
|
290
|
+
// No files loaded (graceful failure)
|
|
291
|
+
expect(fileElements.length).toBe(0);
|
|
292
|
+
} finally {
|
|
293
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
});
|
|
297
|
+
});
|