@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,442 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
/**
|
|
3
|
+
* Routing Tests
|
|
4
|
+
*
|
|
5
|
+
* Consolidated test suite for routing system including:
|
|
6
|
+
* - Core routing logic (classifyComplexity, determineTestStrategy, routeTask)
|
|
7
|
+
* - Routing strategies (keyword, llm, manual, adaptive)
|
|
8
|
+
* - Strategy chain execution
|
|
9
|
+
* - Async support and chain delegation
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { beforeEach, describe, expect, mock, spyOn, test } from "bun:test";
|
|
13
|
+
import { DEFAULT_CONFIG } from "../../src/config";
|
|
14
|
+
import type { NaxConfig } from "../../src/config";
|
|
15
|
+
import { escalateTier } from "../../src/execution/runner";
|
|
16
|
+
import type { AggregateMetrics } from "../../src/metrics/types";
|
|
17
|
+
import type { UserStory } from "../../src/prd/types";
|
|
18
|
+
import { classifyComplexity, determineTestStrategy, routeTask } from "../../src/routing";
|
|
19
|
+
import { buildStrategyChain } from "../../src/routing/builder";
|
|
20
|
+
import { StrategyChain } from "../../src/routing/chain";
|
|
21
|
+
import { keywordStrategy, llmStrategy, manualStrategy } from "../../src/routing/strategies";
|
|
22
|
+
import { adaptiveStrategy } from "../../src/routing/strategies/adaptive";
|
|
23
|
+
import {
|
|
24
|
+
buildBatchPrompt,
|
|
25
|
+
buildRoutingPrompt,
|
|
26
|
+
clearCache,
|
|
27
|
+
clearCacheForStory,
|
|
28
|
+
getCacheSize,
|
|
29
|
+
llmStrategy as llmStrategyFull,
|
|
30
|
+
parseRoutingResponse,
|
|
31
|
+
routeBatch,
|
|
32
|
+
stripCodeFences,
|
|
33
|
+
validateRoutingDecision,
|
|
34
|
+
} from "../../src/routing/strategies/llm";
|
|
35
|
+
import type { RoutingContext, RoutingDecision, RoutingStrategy } from "../../src/routing/strategy";
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const simpleStory: UserStory = {
|
|
39
|
+
id: "US-001",
|
|
40
|
+
title: "Fix typo in README",
|
|
41
|
+
description: "Correct spelling mistake",
|
|
42
|
+
acceptanceCriteria: ["Update README.md with correct spelling"],
|
|
43
|
+
tags: ["docs"],
|
|
44
|
+
dependencies: [],
|
|
45
|
+
status: "pending",
|
|
46
|
+
passes: false,
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const complexStory: UserStory = {
|
|
50
|
+
id: "US-002",
|
|
51
|
+
title: "Add JWT authentication",
|
|
52
|
+
description: "Implement JWT authentication with refresh tokens",
|
|
53
|
+
acceptanceCriteria: ["Secure token storage", "Token refresh endpoint", "Expiry handling", "Logout functionality"],
|
|
54
|
+
tags: ["security", "auth"],
|
|
55
|
+
dependencies: [],
|
|
56
|
+
status: "pending",
|
|
57
|
+
passes: false,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const testContext: RoutingContext = {
|
|
61
|
+
config: DEFAULT_CONFIG,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
describe("StrategyChain", () => {
|
|
65
|
+
test("uses first strategy that returns non-null", async () => {
|
|
66
|
+
const alwaysNullStrategy: RoutingStrategy = {
|
|
67
|
+
name: "always-null",
|
|
68
|
+
route: () => null,
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const alwaysReturnStrategy: RoutingStrategy = {
|
|
72
|
+
name: "always-return",
|
|
73
|
+
route: () => ({
|
|
74
|
+
complexity: "simple",
|
|
75
|
+
modelTier: "fast",
|
|
76
|
+
testStrategy: "test-after",
|
|
77
|
+
reasoning: "Always return strategy",
|
|
78
|
+
}),
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const chain = new StrategyChain([alwaysNullStrategy, alwaysReturnStrategy]);
|
|
82
|
+
|
|
83
|
+
const story: UserStory = {
|
|
84
|
+
id: "US-001",
|
|
85
|
+
title: "Test story",
|
|
86
|
+
description: "Test",
|
|
87
|
+
acceptanceCriteria: [],
|
|
88
|
+
tags: [],
|
|
89
|
+
dependencies: [],
|
|
90
|
+
status: "pending",
|
|
91
|
+
passes: false,
|
|
92
|
+
escalations: [],
|
|
93
|
+
attempts: 0,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
97
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
98
|
+
const decision = await chain.route(story, context);
|
|
99
|
+
|
|
100
|
+
expect(decision.reasoning).toBe("Always return strategy");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("throws error if all strategies return null", async () => {
|
|
104
|
+
const alwaysNullStrategy: RoutingStrategy = {
|
|
105
|
+
name: "always-null",
|
|
106
|
+
route: () => null,
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const chain = new StrategyChain([alwaysNullStrategy]);
|
|
110
|
+
|
|
111
|
+
const story: UserStory = {
|
|
112
|
+
id: "US-001",
|
|
113
|
+
title: "Test story",
|
|
114
|
+
description: "Test",
|
|
115
|
+
acceptanceCriteria: [],
|
|
116
|
+
tags: [],
|
|
117
|
+
dependencies: [],
|
|
118
|
+
status: "pending",
|
|
119
|
+
passes: false,
|
|
120
|
+
escalations: [],
|
|
121
|
+
attempts: 0,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
125
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
126
|
+
|
|
127
|
+
await expect(chain.route(story, context)).rejects.toThrow("No routing strategy returned a decision");
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
test("getStrategyNames returns strategy names", () => {
|
|
131
|
+
const chain = new StrategyChain([keywordStrategy, llmStrategy]);
|
|
132
|
+
expect(chain.getStrategyNames()).toEqual(["keyword", "llm"]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
describe("async support", () => {
|
|
136
|
+
test("handles async strategy that returns decision", async () => {
|
|
137
|
+
const asyncStrategy: RoutingStrategy = {
|
|
138
|
+
name: "async-test",
|
|
139
|
+
route: async () => {
|
|
140
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
141
|
+
return {
|
|
142
|
+
complexity: "medium",
|
|
143
|
+
modelTier: "balanced",
|
|
144
|
+
testStrategy: "test-after",
|
|
145
|
+
reasoning: "Async strategy result",
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const chain = new StrategyChain([asyncStrategy]);
|
|
151
|
+
|
|
152
|
+
const story: UserStory = {
|
|
153
|
+
id: "US-001",
|
|
154
|
+
title: "Test async story",
|
|
155
|
+
description: "Test async routing",
|
|
156
|
+
acceptanceCriteria: [],
|
|
157
|
+
tags: [],
|
|
158
|
+
dependencies: [],
|
|
159
|
+
status: "pending",
|
|
160
|
+
passes: false,
|
|
161
|
+
escalations: [],
|
|
162
|
+
attempts: 0,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const context: RoutingContext = { config: DEFAULT_CONFIG };
|
|
166
|
+
const decision = await chain.route(story, context);
|
|
167
|
+
|
|
168
|
+
expect(decision.reasoning).toBe("Async strategy result");
|
|
169
|
+
expect(decision.complexity).toBe("medium");
|
|
170
|
+
expect(decision.modelTier).toBe("balanced");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("handles mixed sync and async strategies", async () => {
|
|
174
|
+
const syncStrategy: RoutingStrategy = {
|
|
175
|
+
name: "sync-first",
|
|
176
|
+
route: () => null,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const asyncStrategy: RoutingStrategy = {
|
|
180
|
+
name: "async-second",
|
|
181
|
+
route: async () => {
|
|
182
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
183
|
+
return {
|
|
184
|
+
complexity: "complex",
|
|
185
|
+
modelTier: "powerful",
|
|
186
|
+
testStrategy: "three-session-tdd",
|
|
187
|
+
reasoning: "Mixed chain result",
|
|
188
|
+
};
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const chain = new StrategyChain([syncStrategy, asyncStrategy]);
|
|
193
|
+
|
|
194
|
+
const story: UserStory = {
|
|
195
|
+
id: "US-003",
|
|
196
|
+
title: "Test mixed",
|
|
197
|
+
description: "Test mixed sync/async",
|
|
198
|
+
acceptanceCriteria: [],
|
|
199
|
+
tags: [],
|
|
200
|
+
dependencies: [],
|
|
201
|
+
status: "pending",
|
|
202
|
+
passes: false,
|
|
203
|
+
escalations: [],
|
|
204
|
+
attempts: 0,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const context: RoutingContext = { config: DEFAULT_CONFIG };
|
|
208
|
+
const decision = await chain.route(story, context);
|
|
209
|
+
|
|
210
|
+
expect(decision.reasoning).toBe("Mixed chain result");
|
|
211
|
+
expect(decision.testStrategy).toBe("three-session-tdd");
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
describe("keywordStrategy", () => {
|
|
217
|
+
test("classifies simple story correctly", () => {
|
|
218
|
+
const story: UserStory = {
|
|
219
|
+
id: "US-001",
|
|
220
|
+
title: "Update button color",
|
|
221
|
+
description: "Change button to blue",
|
|
222
|
+
acceptanceCriteria: ["Button is blue"],
|
|
223
|
+
tags: [],
|
|
224
|
+
dependencies: [],
|
|
225
|
+
status: "pending",
|
|
226
|
+
passes: false,
|
|
227
|
+
escalations: [],
|
|
228
|
+
attempts: 0,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
232
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
233
|
+
const decision = keywordStrategy.route(story, context);
|
|
234
|
+
|
|
235
|
+
expect(decision).not.toBeNull();
|
|
236
|
+
expect(decision!.complexity).toBe("simple");
|
|
237
|
+
expect(decision!.modelTier).toBe("fast");
|
|
238
|
+
expect(decision!.testStrategy).toBe("three-session-tdd-lite");
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
test("classifies complex story with security keywords", () => {
|
|
242
|
+
const story: UserStory = {
|
|
243
|
+
id: "US-002",
|
|
244
|
+
title: "Add JWT authentication",
|
|
245
|
+
description: "Implement JWT auth with refresh tokens",
|
|
246
|
+
acceptanceCriteria: ["Token storage", "Refresh logic", "Expiry"],
|
|
247
|
+
tags: ["security", "auth"],
|
|
248
|
+
dependencies: [],
|
|
249
|
+
status: "pending",
|
|
250
|
+
passes: false,
|
|
251
|
+
escalations: [],
|
|
252
|
+
attempts: 0,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
256
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
257
|
+
const decision = keywordStrategy.route(story, context);
|
|
258
|
+
|
|
259
|
+
expect(decision).not.toBeNull();
|
|
260
|
+
expect(decision!.complexity).toBe("complex");
|
|
261
|
+
expect(decision!.modelTier).toBe("powerful");
|
|
262
|
+
expect(decision!.testStrategy).toBe("three-session-tdd");
|
|
263
|
+
expect(decision!.reasoning).toContain("security-critical");
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("uses three-session-tdd for public API", () => {
|
|
267
|
+
const story: UserStory = {
|
|
268
|
+
id: "US-005",
|
|
269
|
+
title: "Add public API endpoint",
|
|
270
|
+
description: "Create external API for consumers",
|
|
271
|
+
acceptanceCriteria: ["Endpoint returns JSON"],
|
|
272
|
+
tags: ["public api"],
|
|
273
|
+
dependencies: [],
|
|
274
|
+
status: "pending",
|
|
275
|
+
passes: false,
|
|
276
|
+
escalations: [],
|
|
277
|
+
attempts: 0,
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
281
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
282
|
+
const decision = keywordStrategy.route(story, context);
|
|
283
|
+
|
|
284
|
+
expect(decision).not.toBeNull();
|
|
285
|
+
expect(decision!.testStrategy).toBe("three-session-tdd");
|
|
286
|
+
expect(decision!.reasoning).toContain("public-api");
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
describe("manualStrategy", () => {
|
|
291
|
+
test("returns decision from story.routing metadata", () => {
|
|
292
|
+
const story: UserStory = {
|
|
293
|
+
id: "US-006",
|
|
294
|
+
title: "Manual override test",
|
|
295
|
+
description: "Story with manual routing",
|
|
296
|
+
acceptanceCriteria: [],
|
|
297
|
+
tags: [],
|
|
298
|
+
dependencies: [],
|
|
299
|
+
status: "pending",
|
|
300
|
+
passes: false,
|
|
301
|
+
escalations: [],
|
|
302
|
+
attempts: 0,
|
|
303
|
+
routing: {
|
|
304
|
+
complexity: "expert",
|
|
305
|
+
modelTier: "powerful",
|
|
306
|
+
testStrategy: "three-session-tdd",
|
|
307
|
+
reasoning: "Manual override for critical task",
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
312
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
313
|
+
const decision = manualStrategy.route(story, context);
|
|
314
|
+
|
|
315
|
+
expect(decision).not.toBeNull();
|
|
316
|
+
expect(decision!.complexity).toBe("expert");
|
|
317
|
+
expect(decision!.modelTier).toBe("powerful");
|
|
318
|
+
expect(decision!.testStrategy).toBe("three-session-tdd");
|
|
319
|
+
expect(decision!.reasoning).toBe("Manual override for critical task");
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
test("returns null when no routing metadata", () => {
|
|
323
|
+
const story: UserStory = {
|
|
324
|
+
id: "US-007",
|
|
325
|
+
title: "No manual routing",
|
|
326
|
+
description: "Story without routing metadata",
|
|
327
|
+
acceptanceCriteria: [],
|
|
328
|
+
tags: [],
|
|
329
|
+
dependencies: [],
|
|
330
|
+
status: "pending",
|
|
331
|
+
passes: false,
|
|
332
|
+
escalations: [],
|
|
333
|
+
attempts: 0,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const configWithoutLlm = { ...DEFAULT_CONFIG, routing: { ...DEFAULT_CONFIG.routing, llm: undefined } };
|
|
337
|
+
const context: RoutingContext = { config: configWithoutLlm };
|
|
338
|
+
const decision = manualStrategy.route(story, context);
|
|
339
|
+
|
|
340
|
+
expect(decision).toBeNull();
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("LLM Routing Strategy - Prompt Building", () => {
|
|
345
|
+
test("buildRoutingPrompt formats story correctly", () => {
|
|
346
|
+
const prompt = buildRoutingPrompt(simpleStory, DEFAULT_CONFIG);
|
|
347
|
+
|
|
348
|
+
expect(prompt).toContain("Title: Fix typo in README");
|
|
349
|
+
expect(prompt).toContain("Description: Correct spelling mistake");
|
|
350
|
+
expect(prompt).toContain("1. Update README.md with correct spelling");
|
|
351
|
+
expect(prompt).toContain("Tags: docs");
|
|
352
|
+
expect(prompt).toContain("fast: Simple changes");
|
|
353
|
+
expect(prompt).toContain("balanced: Standard features");
|
|
354
|
+
expect(prompt).toContain("powerful: Complex architecture");
|
|
355
|
+
expect(prompt).toContain("test-after: Write implementation first");
|
|
356
|
+
expect(prompt).toContain("three-session-tdd: Separate test-writer");
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
test("buildBatchPrompt formats multiple stories", () => {
|
|
360
|
+
const stories = [simpleStory, complexStory];
|
|
361
|
+
const prompt = buildBatchPrompt(stories, DEFAULT_CONFIG);
|
|
362
|
+
|
|
363
|
+
expect(prompt).toContain("1. US-001: Fix typo in README");
|
|
364
|
+
expect(prompt).toContain("2. US-002: Add JWT authentication");
|
|
365
|
+
expect(prompt).toContain("Tags: docs");
|
|
366
|
+
expect(prompt).toContain("Tags: security, auth");
|
|
367
|
+
expect(prompt).toContain('{"id":"US-001"');
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
describe("LLM Routing Strategy - Response Parsing", () => {
|
|
372
|
+
test("parseRoutingResponse handles valid JSON", () => {
|
|
373
|
+
const output =
|
|
374
|
+
'{"complexity":"simple","modelTier":"fast","testStrategy":"test-after","reasoning":"Simple documentation fix"}';
|
|
375
|
+
const decision = parseRoutingResponse(output, simpleStory, DEFAULT_CONFIG);
|
|
376
|
+
|
|
377
|
+
expect(decision.complexity).toBe("simple");
|
|
378
|
+
expect(decision.modelTier).toBe("fast");
|
|
379
|
+
expect(decision.testStrategy).toBe("test-after");
|
|
380
|
+
expect(decision.reasoning).toBe("Simple documentation fix");
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
test("parseRoutingResponse strips markdown code blocks", () => {
|
|
384
|
+
const output =
|
|
385
|
+
'```json\n{"complexity":"complex","modelTier":"powerful","testStrategy":"three-session-tdd","reasoning":"Security-critical"}\n```';
|
|
386
|
+
const decision = parseRoutingResponse(output, complexStory, DEFAULT_CONFIG);
|
|
387
|
+
|
|
388
|
+
expect(decision.complexity).toBe("complex");
|
|
389
|
+
expect(decision.modelTier).toBe("powerful");
|
|
390
|
+
expect(decision.testStrategy).toBe("three-session-tdd");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("parseRoutingResponse throws on invalid JSON", () => {
|
|
394
|
+
const output = "This is not JSON";
|
|
395
|
+
expect(() => parseRoutingResponse(output, simpleStory, DEFAULT_CONFIG)).toThrow();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
test("parseRoutingResponse throws on missing fields", () => {
|
|
399
|
+
const output = '{"complexity":"simple","modelTier":"fast"}';
|
|
400
|
+
expect(() => parseRoutingResponse(output, simpleStory, DEFAULT_CONFIG)).toThrow("Missing required fields");
|
|
401
|
+
});
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
describe("stripCodeFences", () => {
|
|
405
|
+
test("returns plain JSON unchanged", () => {
|
|
406
|
+
const input = '{"complexity":"simple"}';
|
|
407
|
+
expect(stripCodeFences(input)).toBe('{"complexity":"simple"}');
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
test("strips ```json ... ``` fences", () => {
|
|
411
|
+
const input = '```json\n{"complexity":"simple"}\n```';
|
|
412
|
+
expect(stripCodeFences(input)).toBe('{"complexity":"simple"}');
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
test("strips leading 'json' keyword (no backticks)", () => {
|
|
416
|
+
const input = 'json\n{"complexity":"simple"}';
|
|
417
|
+
expect(stripCodeFences(input)).toBe('{"complexity":"simple"}');
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("validateRoutingDecision", () => {
|
|
422
|
+
test("returns valid decision for correct input", () => {
|
|
423
|
+
const input = { complexity: "simple", modelTier: "fast", testStrategy: "test-after", reasoning: "trivial" };
|
|
424
|
+
const result = validateRoutingDecision(input, DEFAULT_CONFIG);
|
|
425
|
+
expect(result).toEqual({
|
|
426
|
+
complexity: "simple",
|
|
427
|
+
modelTier: "fast",
|
|
428
|
+
testStrategy: "test-after",
|
|
429
|
+
reasoning: "trivial",
|
|
430
|
+
});
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
test("throws on missing complexity", () => {
|
|
434
|
+
const input = { modelTier: "fast", testStrategy: "test-after", reasoning: "test" };
|
|
435
|
+
expect(() => validateRoutingDecision(input, DEFAULT_CONFIG)).toThrow("Missing required fields");
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
test("throws on invalid complexity value", () => {
|
|
439
|
+
const input = { complexity: "mega", modelTier: "fast", testStrategy: "test-after", reasoning: "test" };
|
|
440
|
+
expect(() => validateRoutingDecision(input, DEFAULT_CONFIG)).toThrow("Invalid complexity: mega");
|
|
441
|
+
});
|
|
442
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
1
2
|
/**
|
|
2
3
|
* TUI Controls Tests
|
|
3
4
|
*
|
|
@@ -9,13 +10,13 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
|
9
10
|
import { unlink } from "node:fs/promises";
|
|
10
11
|
import { render } from "ink-testing-library";
|
|
11
12
|
import { createElement } from "react";
|
|
12
|
-
import type { UserStory } from "
|
|
13
|
-
import { CostOverlay } from "
|
|
14
|
-
import { HelpOverlay } from "
|
|
15
|
-
import type { KeyboardAction } from "
|
|
16
|
-
import { PanelFocus } from "
|
|
17
|
-
import type { StoryDisplayState } from "
|
|
18
|
-
import { writeQueueCommand } from "
|
|
13
|
+
import type { UserStory } from "../../../src/prd/types";
|
|
14
|
+
import { CostOverlay } from "../../../src/tui/components/CostOverlay";
|
|
15
|
+
import { HelpOverlay } from "../../../src/tui/components/HelpOverlay";
|
|
16
|
+
import type { KeyboardAction } from "../../../src/tui/hooks/useKeyboard";
|
|
17
|
+
import { PanelFocus } from "../../../src/tui/types";
|
|
18
|
+
import type { StoryDisplayState } from "../../../src/tui/types";
|
|
19
|
+
import { writeQueueCommand } from "../../../src/utils/queue-writer";
|
|
19
20
|
|
|
20
21
|
// Helper to create mock stories
|
|
21
22
|
function createMockStory(id: string, status: StoryDisplayState["status"], cost = 0.01): StoryDisplayState {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
1
2
|
/**
|
|
2
3
|
* TUI Cost Accumulation and PTY Line Length Tests
|
|
3
4
|
*
|
|
@@ -5,9 +6,9 @@
|
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import { describe, expect, test } from "bun:test";
|
|
8
|
-
import { PipelineEventEmitter } from "
|
|
9
|
-
import type { StageResult } from "
|
|
10
|
-
import type { UserStory } from "
|
|
9
|
+
import { PipelineEventEmitter } from "../../../src/pipeline/events";
|
|
10
|
+
import type { StageResult } from "../../../src/pipeline/types";
|
|
11
|
+
import type { UserStory } from "../../../src/prd/types";
|
|
11
12
|
|
|
12
13
|
// ── Test Fixtures ────────────────────────────────────
|
|
13
14
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
1
2
|
/**
|
|
2
3
|
* TUI Layout Tests
|
|
3
4
|
*
|
|
@@ -8,10 +9,10 @@
|
|
|
8
9
|
import { describe, expect, test } from "bun:test";
|
|
9
10
|
import { render } from "ink-testing-library";
|
|
10
11
|
import { createElement } from "react";
|
|
11
|
-
import type { UserStory } from "
|
|
12
|
-
import { StoriesPanel } from "
|
|
13
|
-
import { COMPACT_MAX_VISIBLE_STORIES, MAX_VISIBLE_STORIES, MIN_TERMINAL_WIDTH } from "
|
|
14
|
-
import type { StoryDisplayState } from "
|
|
12
|
+
import type { UserStory } from "../../../src/prd/types";
|
|
13
|
+
import { StoriesPanel } from "../../../src/tui/components/StoriesPanel";
|
|
14
|
+
import { COMPACT_MAX_VISIBLE_STORIES, MAX_VISIBLE_STORIES, MIN_TERMINAL_WIDTH } from "../../../src/tui/hooks/useLayout";
|
|
15
|
+
import type { StoryDisplayState } from "../../../src/tui/types";
|
|
15
16
|
|
|
16
17
|
// Helper to create mock stories
|
|
17
18
|
function createMockStory(id: string, status: StoryDisplayState["status"]): StoryDisplayState {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
1
2
|
/**
|
|
2
3
|
* TUI Stories Panel Tests
|
|
3
4
|
*
|
|
@@ -8,10 +9,10 @@
|
|
|
8
9
|
import { describe, expect, test } from "bun:test";
|
|
9
10
|
import { render } from "ink-testing-library";
|
|
10
11
|
import { createElement } from "react";
|
|
11
|
-
import type { UserStory } from "
|
|
12
|
-
import { StatusBar } from "
|
|
13
|
-
import { StoriesPanel } from "
|
|
14
|
-
import type { StoryDisplayState } from "
|
|
12
|
+
import type { UserStory } from "../../../src/prd/types";
|
|
13
|
+
import { StatusBar } from "../../../src/tui/components/StatusBar";
|
|
14
|
+
import { StoriesPanel } from "../../../src/tui/components/StoriesPanel";
|
|
15
|
+
import type { StoryDisplayState } from "../../../src/tui/types";
|
|
15
16
|
|
|
16
17
|
// Helper to create mock stories
|
|
17
18
|
function createMockStory(id: string, status: StoryDisplayState["status"]): StoryDisplayState {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import {
|
|
4
|
+
makeSkippedResult,
|
|
5
|
+
makePassResult,
|
|
6
|
+
makeFailResult,
|
|
7
|
+
type VerifyResult,
|
|
8
|
+
} from "../../../src/verification/orchestrator-types";
|
|
9
|
+
|
|
10
|
+
describe("makeSkippedResult", () => {
|
|
11
|
+
test("returns SKIPPED success result", () => {
|
|
12
|
+
const r = makeSkippedResult("US-001", "scoped");
|
|
13
|
+
expect(r.success).toBe(true);
|
|
14
|
+
expect(r.status).toBe("SKIPPED");
|
|
15
|
+
expect(r.storyId).toBe("US-001");
|
|
16
|
+
expect(r.strategy).toBe("scoped");
|
|
17
|
+
expect(r.countsTowardEscalation).toBe(false);
|
|
18
|
+
expect(r.failures).toEqual([]);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("makePassResult", () => {
|
|
23
|
+
test("returns PASS success result", () => {
|
|
24
|
+
const r = makePassResult("US-002", "regression", { passCount: 42, durationMs: 1234 });
|
|
25
|
+
expect(r.success).toBe(true);
|
|
26
|
+
expect(r.status).toBe("PASS");
|
|
27
|
+
expect(r.passCount).toBe(42);
|
|
28
|
+
expect(r.failCount).toBe(0);
|
|
29
|
+
expect(r.totalCount).toBe(42);
|
|
30
|
+
expect(r.durationMs).toBe(1234);
|
|
31
|
+
expect(r.countsTowardEscalation).toBe(false);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("makeFailResult", () => {
|
|
36
|
+
test("returns failure result with correct totals", () => {
|
|
37
|
+
const r = makeFailResult("US-003", "scoped", "TEST_FAILURE", {
|
|
38
|
+
passCount: 10,
|
|
39
|
+
failCount: 3,
|
|
40
|
+
durationMs: 5000,
|
|
41
|
+
});
|
|
42
|
+
expect(r.success).toBe(false);
|
|
43
|
+
expect(r.status).toBe("TEST_FAILURE");
|
|
44
|
+
expect(r.passCount).toBe(10);
|
|
45
|
+
expect(r.failCount).toBe(3);
|
|
46
|
+
expect(r.totalCount).toBe(13);
|
|
47
|
+
expect(r.countsTowardEscalation).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("timeout does not count toward escalation by default when overridden", () => {
|
|
51
|
+
const r = makeFailResult("US-004", "scoped", "TIMEOUT", { countsTowardEscalation: false });
|
|
52
|
+
expect(r.countsTowardEscalation).toBe(false);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// RE-ARCH: keep
|
|
2
|
+
import { describe, expect, test } from "bun:test";
|
|
3
|
+
import { VerificationOrchestrator } from "../../../src/verification/orchestrator";
|
|
4
|
+
import type { IVerificationStrategy, VerifyContext, VerifyResult } from "../../../src/verification/orchestrator-types";
|
|
5
|
+
import { makePassResult, makeSkippedResult } from "../../../src/verification/orchestrator-types";
|
|
6
|
+
|
|
7
|
+
function makeCtx(): VerifyContext {
|
|
8
|
+
return {
|
|
9
|
+
workdir: "/tmp",
|
|
10
|
+
testCommand: "bun test",
|
|
11
|
+
timeoutSeconds: 60,
|
|
12
|
+
storyId: "US-001",
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makeStubStrategy(name: "scoped" | "regression" | "deferred-regression" | "acceptance", result: VerifyResult): IVerificationStrategy {
|
|
17
|
+
return {
|
|
18
|
+
name,
|
|
19
|
+
execute: async () => result,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("VerificationOrchestrator", () => {
|
|
24
|
+
test("delegates to scoped strategy", async () => {
|
|
25
|
+
const expected = makePassResult("US-001", "scoped");
|
|
26
|
+
const orch = new VerificationOrchestrator({
|
|
27
|
+
scoped: makeStubStrategy("scoped", expected),
|
|
28
|
+
});
|
|
29
|
+
const result = await orch.verifyScoped(makeCtx());
|
|
30
|
+
expect(result).toBe(expected);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("delegates to regression strategy", async () => {
|
|
34
|
+
const expected = makePassResult("US-001", "regression");
|
|
35
|
+
const orch = new VerificationOrchestrator({
|
|
36
|
+
regression: makeStubStrategy("regression", expected),
|
|
37
|
+
});
|
|
38
|
+
const result = await orch.verifyRegression(makeCtx());
|
|
39
|
+
expect(result).toBe(expected);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("delegates to deferred-regression strategy", async () => {
|
|
43
|
+
const expected = makeSkippedResult("US-001", "deferred-regression");
|
|
44
|
+
const orch = new VerificationOrchestrator({
|
|
45
|
+
"deferred-regression": makeStubStrategy("deferred-regression", expected),
|
|
46
|
+
});
|
|
47
|
+
const result = await orch.verifyDeferredRegression(makeCtx());
|
|
48
|
+
expect(result).toBe(expected);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("delegates to acceptance strategy", async () => {
|
|
52
|
+
const expected = makeSkippedResult("US-001", "acceptance");
|
|
53
|
+
const orch = new VerificationOrchestrator({
|
|
54
|
+
acceptance: makeStubStrategy("acceptance", expected),
|
|
55
|
+
});
|
|
56
|
+
const result = await orch.verifyAcceptance(makeCtx());
|
|
57
|
+
expect(result).toBe(expected);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("returns SKIPPED for unknown strategy", async () => {
|
|
61
|
+
const orch = new VerificationOrchestrator();
|
|
62
|
+
const result = await orch.verify(makeCtx(), "scoped");
|
|
63
|
+
// scoped strategy exists, just confirm it runs
|
|
64
|
+
expect(result.storyId).toBe("US-001");
|
|
65
|
+
});
|
|
66
|
+
});
|