@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,302 @@
|
|
|
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("formatContextAsMarkdown", () => {
|
|
57
|
+
test("should format context with all element types", async () => {
|
|
58
|
+
const prd = createTestPRD([
|
|
59
|
+
{
|
|
60
|
+
id: "US-001",
|
|
61
|
+
title: "Dependency",
|
|
62
|
+
description: "Dep description",
|
|
63
|
+
acceptanceCriteria: ["AC1"],
|
|
64
|
+
status: "passed",
|
|
65
|
+
passes: true,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "US-002",
|
|
69
|
+
title: "Current",
|
|
70
|
+
description: "Current description",
|
|
71
|
+
acceptanceCriteria: ["AC2"],
|
|
72
|
+
dependencies: ["US-001"],
|
|
73
|
+
priorErrors: ["Test error"],
|
|
74
|
+
},
|
|
75
|
+
]);
|
|
76
|
+
|
|
77
|
+
const storyContext: StoryContext = {
|
|
78
|
+
prd,
|
|
79
|
+
currentStoryId: "US-002",
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const budget: ContextBudget = {
|
|
83
|
+
maxTokens: 10000,
|
|
84
|
+
reservedForInstructions: 1000,
|
|
85
|
+
availableForContext: 9000,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const built = await buildContext(storyContext, budget);
|
|
89
|
+
const markdown = formatContextAsMarkdown(built);
|
|
90
|
+
|
|
91
|
+
expect(markdown).toContain("# Story Context");
|
|
92
|
+
expect(markdown).toContain("## Progress");
|
|
93
|
+
expect(markdown).toContain("## Prior Errors");
|
|
94
|
+
expect(markdown).toContain("## Current Story");
|
|
95
|
+
expect(markdown).toContain("## Dependency Stories");
|
|
96
|
+
expect(markdown).toContain("US-001");
|
|
97
|
+
expect(markdown).toContain("US-002");
|
|
98
|
+
expect(markdown).toContain("Test error");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("should include summary with token count", async () => {
|
|
102
|
+
const prd = createTestPRD([{ id: "US-001", title: "Story" }]);
|
|
103
|
+
|
|
104
|
+
const storyContext: StoryContext = {
|
|
105
|
+
prd,
|
|
106
|
+
currentStoryId: "US-001",
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const budget: ContextBudget = {
|
|
110
|
+
maxTokens: 10000,
|
|
111
|
+
reservedForInstructions: 1000,
|
|
112
|
+
availableForContext: 9000,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const built = await buildContext(storyContext, budget);
|
|
116
|
+
const markdown = formatContextAsMarkdown(built);
|
|
117
|
+
|
|
118
|
+
expect(markdown).toContain("Context:");
|
|
119
|
+
expect(markdown).toContain("tokens");
|
|
120
|
+
expect(markdown).toContain(built.totalTokens.toString());
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("should show truncation indicator", async () => {
|
|
124
|
+
const prd = createTestPRD([
|
|
125
|
+
{
|
|
126
|
+
id: "US-001",
|
|
127
|
+
description: "x".repeat(2000),
|
|
128
|
+
dependencies: ["US-002", "US-003"],
|
|
129
|
+
},
|
|
130
|
+
{ id: "US-002", description: "x".repeat(2000) },
|
|
131
|
+
{ id: "US-003", description: "x".repeat(2000) },
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
const storyContext: StoryContext = {
|
|
135
|
+
prd,
|
|
136
|
+
currentStoryId: "US-001",
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const budget: ContextBudget = {
|
|
140
|
+
maxTokens: 500,
|
|
141
|
+
reservedForInstructions: 250,
|
|
142
|
+
availableForContext: 250,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const built = await buildContext(storyContext, budget);
|
|
146
|
+
const markdown = formatContextAsMarkdown(built);
|
|
147
|
+
|
|
148
|
+
expect(markdown).toContain("[TRUNCATED]");
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("should format context with file elements", async () => {
|
|
152
|
+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "nax-test-"));
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await fs.writeFile(path.join(tempDir, "helper.ts"), "export function helper() {}");
|
|
156
|
+
|
|
157
|
+
const prd = createTestPRD([
|
|
158
|
+
{
|
|
159
|
+
id: "US-001",
|
|
160
|
+
title: "Story with File",
|
|
161
|
+
description: "Test",
|
|
162
|
+
acceptanceCriteria: ["AC1"],
|
|
163
|
+
contextFiles: ["helper.ts"],
|
|
164
|
+
},
|
|
165
|
+
]);
|
|
166
|
+
|
|
167
|
+
const storyContext: StoryContext = {
|
|
168
|
+
prd,
|
|
169
|
+
currentStoryId: "US-001",
|
|
170
|
+
workdir: tempDir,
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const budget: ContextBudget = {
|
|
174
|
+
maxTokens: 10000,
|
|
175
|
+
reservedForInstructions: 1000,
|
|
176
|
+
availableForContext: 9000,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const built = await buildContext(storyContext, budget);
|
|
180
|
+
const markdown = formatContextAsMarkdown(built);
|
|
181
|
+
|
|
182
|
+
expect(markdown).toContain("# Story Context");
|
|
183
|
+
expect(markdown).toContain("## Relevant Source Files");
|
|
184
|
+
expect(markdown).toContain("helper.ts");
|
|
185
|
+
expect(markdown).toContain("helper()");
|
|
186
|
+
} finally {
|
|
187
|
+
await fs.rm(tempDir, { recursive: true, force: true });
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("should format ASSET_CHECK_FAILED errors as mandatory instructions", async () => {
|
|
192
|
+
const prd = createTestPRD([
|
|
193
|
+
{
|
|
194
|
+
id: "US-001",
|
|
195
|
+
title: "Story with asset check failure",
|
|
196
|
+
description: "Test description",
|
|
197
|
+
acceptanceCriteria: ["AC1"],
|
|
198
|
+
priorErrors: [
|
|
199
|
+
"ASSET_CHECK_FAILED: Missing files: [src/finder.ts, test/finder.test.ts]\nAction: Create the missing files before tests can run.",
|
|
200
|
+
],
|
|
201
|
+
},
|
|
202
|
+
]);
|
|
203
|
+
|
|
204
|
+
const storyContext: StoryContext = {
|
|
205
|
+
prd,
|
|
206
|
+
currentStoryId: "US-001",
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
const budget: ContextBudget = {
|
|
210
|
+
maxTokens: 10000,
|
|
211
|
+
reservedForInstructions: 1000,
|
|
212
|
+
availableForContext: 9000,
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const built = await buildContext(storyContext, budget);
|
|
216
|
+
const markdown = formatContextAsMarkdown(built);
|
|
217
|
+
|
|
218
|
+
// Verify ASSET_CHECK errors are formatted prominently
|
|
219
|
+
expect(markdown).toContain("⚠️ MANDATORY: Missing Files from Previous Attempts");
|
|
220
|
+
expect(markdown).toContain("CRITICAL");
|
|
221
|
+
expect(markdown).toContain("You MUST create these exact files");
|
|
222
|
+
expect(markdown).toContain("Do NOT use alternative filenames");
|
|
223
|
+
expect(markdown).toContain("**Required files:**");
|
|
224
|
+
expect(markdown).toContain("`src/finder.ts`");
|
|
225
|
+
expect(markdown).toContain("`test/finder.test.ts`");
|
|
226
|
+
|
|
227
|
+
// Verify it's NOT in the generic "Prior Errors" section
|
|
228
|
+
expect(markdown).not.toContain("## Prior Errors");
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test("should format mixed ASSET_CHECK and other errors separately", async () => {
|
|
232
|
+
const prd = createTestPRD([
|
|
233
|
+
{
|
|
234
|
+
id: "US-001",
|
|
235
|
+
title: "Story with multiple error types",
|
|
236
|
+
description: "Test description",
|
|
237
|
+
acceptanceCriteria: ["AC1"],
|
|
238
|
+
priorErrors: [
|
|
239
|
+
"ASSET_CHECK_FAILED: Missing files: [src/utils.ts]\nAction: Create the missing files before tests can run.",
|
|
240
|
+
'TypeError: Cannot read property "foo" of undefined',
|
|
241
|
+
"Test execution failed",
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
245
|
+
|
|
246
|
+
const storyContext: StoryContext = {
|
|
247
|
+
prd,
|
|
248
|
+
currentStoryId: "US-001",
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const budget: ContextBudget = {
|
|
252
|
+
maxTokens: 10000,
|
|
253
|
+
reservedForInstructions: 1000,
|
|
254
|
+
availableForContext: 9000,
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const built = await buildContext(storyContext, budget);
|
|
258
|
+
const markdown = formatContextAsMarkdown(built);
|
|
259
|
+
|
|
260
|
+
// Verify ASSET_CHECK errors section exists
|
|
261
|
+
expect(markdown).toContain("⚠️ MANDATORY: Missing Files from Previous Attempts");
|
|
262
|
+
expect(markdown).toContain("`src/utils.ts`");
|
|
263
|
+
|
|
264
|
+
// Verify other errors are in separate section
|
|
265
|
+
expect(markdown).toContain("## Prior Errors");
|
|
266
|
+
expect(markdown).toContain('TypeError: Cannot read property "foo" of undefined');
|
|
267
|
+
expect(markdown).toContain("Test execution failed");
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
test("should handle non-ASSET_CHECK errors normally", async () => {
|
|
271
|
+
const prd = createTestPRD([
|
|
272
|
+
{
|
|
273
|
+
id: "US-001",
|
|
274
|
+
title: "Story with regular errors",
|
|
275
|
+
description: "Test description",
|
|
276
|
+
acceptanceCriteria: ["AC1"],
|
|
277
|
+
priorErrors: ['TypeError: Cannot read property "foo" of undefined', "Test execution failed"],
|
|
278
|
+
},
|
|
279
|
+
]);
|
|
280
|
+
|
|
281
|
+
const storyContext: StoryContext = {
|
|
282
|
+
prd,
|
|
283
|
+
currentStoryId: "US-001",
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const budget: ContextBudget = {
|
|
287
|
+
maxTokens: 10000,
|
|
288
|
+
reservedForInstructions: 1000,
|
|
289
|
+
availableForContext: 9000,
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
const built = await buildContext(storyContext, budget);
|
|
293
|
+
const markdown = formatContextAsMarkdown(built);
|
|
294
|
+
|
|
295
|
+
// Verify only "Prior Errors" section exists (no MANDATORY section)
|
|
296
|
+
expect(markdown).toContain("## Prior Errors");
|
|
297
|
+
expect(markdown).toContain('TypeError: Cannot read property "foo" of undefined');
|
|
298
|
+
expect(markdown).toContain("Test execution failed");
|
|
299
|
+
expect(markdown).not.toContain("⚠️ MANDATORY");
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
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 isolation", () => {
|
|
57
|
+
test("should only include current story and declared dependencies — no other stories", async () => {
|
|
58
|
+
const prd = createTestPRD([
|
|
59
|
+
{
|
|
60
|
+
id: "US-001",
|
|
61
|
+
title: "Define core interfaces",
|
|
62
|
+
description: "Create base interfaces for the module",
|
|
63
|
+
acceptanceCriteria: ["Interface exported", "Types documented"],
|
|
64
|
+
dependencies: [],
|
|
65
|
+
status: "passed" as any,
|
|
66
|
+
passes: true,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "US-002",
|
|
70
|
+
title: "Implement health service",
|
|
71
|
+
description: "Service that aggregates indicators",
|
|
72
|
+
acceptanceCriteria: ["Service injectable", "Aggregates results"],
|
|
73
|
+
dependencies: [],
|
|
74
|
+
status: "passed" as any,
|
|
75
|
+
passes: true,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
id: "US-003",
|
|
79
|
+
title: "Add HTTP indicator",
|
|
80
|
+
description: "HTTP health check indicator",
|
|
81
|
+
acceptanceCriteria: ["Pings endpoint", "Returns status"],
|
|
82
|
+
dependencies: ["US-001"],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "US-004",
|
|
86
|
+
title: "Add database indicator",
|
|
87
|
+
description: "Database connectivity check",
|
|
88
|
+
acceptanceCriteria: ["Checks DB connection", "Timeout support"],
|
|
89
|
+
dependencies: ["US-001"],
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "US-005",
|
|
93
|
+
title: "REST endpoint",
|
|
94
|
+
description: "Expose health check via REST API",
|
|
95
|
+
acceptanceCriteria: ["GET /health returns JSON", "Includes all indicators"],
|
|
96
|
+
dependencies: ["US-002", "US-003"],
|
|
97
|
+
},
|
|
98
|
+
]);
|
|
99
|
+
|
|
100
|
+
// Build context for US-003 which depends only on US-001
|
|
101
|
+
const storyContext: StoryContext = {
|
|
102
|
+
prd,
|
|
103
|
+
currentStoryId: "US-003",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const budget: ContextBudget = {
|
|
107
|
+
maxTokens: 50000,
|
|
108
|
+
reservedForInstructions: 5000,
|
|
109
|
+
availableForContext: 45000,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const built = await buildContext(storyContext, budget);
|
|
113
|
+
const markdown = formatContextAsMarkdown(built);
|
|
114
|
+
|
|
115
|
+
// Current story IS present
|
|
116
|
+
expect(markdown).toContain("US-003");
|
|
117
|
+
expect(markdown).toContain("Add HTTP indicator");
|
|
118
|
+
|
|
119
|
+
// Declared dependency IS present
|
|
120
|
+
expect(markdown).toContain("US-001");
|
|
121
|
+
expect(markdown).toContain("Define core interfaces");
|
|
122
|
+
|
|
123
|
+
// Non-dependency stories are NOT present
|
|
124
|
+
expect(markdown).not.toContain("US-002");
|
|
125
|
+
expect(markdown).not.toContain("Implement health service");
|
|
126
|
+
expect(markdown).not.toContain("US-004");
|
|
127
|
+
expect(markdown).not.toContain("Add database indicator");
|
|
128
|
+
expect(markdown).not.toContain("US-005");
|
|
129
|
+
expect(markdown).not.toContain("REST endpoint");
|
|
130
|
+
|
|
131
|
+
// Acceptance criteria from other stories are NOT leaked
|
|
132
|
+
expect(markdown).not.toContain("Aggregates results");
|
|
133
|
+
expect(markdown).not.toContain("Checks DB connection");
|
|
134
|
+
expect(markdown).not.toContain("Includes all indicators");
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test("progress summary contains only aggregate counts, not story titles or IDs", async () => {
|
|
138
|
+
const prd = createTestPRD([
|
|
139
|
+
{
|
|
140
|
+
id: "US-001",
|
|
141
|
+
title: "Secret Story Alpha",
|
|
142
|
+
description: "Should not appear in progress",
|
|
143
|
+
acceptanceCriteria: ["AC1"],
|
|
144
|
+
dependencies: [],
|
|
145
|
+
status: "passed" as any,
|
|
146
|
+
passes: true,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: "US-002",
|
|
150
|
+
title: "Secret Story Beta",
|
|
151
|
+
description: "Also should not appear",
|
|
152
|
+
acceptanceCriteria: ["AC1"],
|
|
153
|
+
dependencies: [],
|
|
154
|
+
status: "failed" as any,
|
|
155
|
+
passes: false,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: "US-003",
|
|
159
|
+
title: "Current Story",
|
|
160
|
+
description: "The one being built",
|
|
161
|
+
acceptanceCriteria: ["AC1"],
|
|
162
|
+
dependencies: [],
|
|
163
|
+
},
|
|
164
|
+
]);
|
|
165
|
+
|
|
166
|
+
const storyContext: StoryContext = {
|
|
167
|
+
prd,
|
|
168
|
+
currentStoryId: "US-003",
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const budget: ContextBudget = {
|
|
172
|
+
maxTokens: 50000,
|
|
173
|
+
reservedForInstructions: 5000,
|
|
174
|
+
availableForContext: 45000,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const built = await buildContext(storyContext, budget);
|
|
178
|
+
const progressElement = built.elements.find((e) => e.type === "progress");
|
|
179
|
+
|
|
180
|
+
expect(progressElement).toBeDefined();
|
|
181
|
+
// Progress shows counts only
|
|
182
|
+
expect(progressElement!.content).toContain("2/3");
|
|
183
|
+
// Does NOT contain other story titles
|
|
184
|
+
expect(progressElement!.content).not.toContain("Secret Story Alpha");
|
|
185
|
+
expect(progressElement!.content).not.toContain("Secret Story Beta");
|
|
186
|
+
expect(progressElement!.content).not.toContain("US-001");
|
|
187
|
+
expect(progressElement!.content).not.toContain("US-002");
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test("prior errors from other stories do not leak into current story context", async () => {
|
|
191
|
+
const prd = createTestPRD([
|
|
192
|
+
{
|
|
193
|
+
id: "US-001",
|
|
194
|
+
title: "Story with errors",
|
|
195
|
+
description: "Has prior errors",
|
|
196
|
+
acceptanceCriteria: ["AC1"],
|
|
197
|
+
dependencies: [],
|
|
198
|
+
priorErrors: ["LEAKED_ERROR: something broke in US-001"],
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: "US-002",
|
|
202
|
+
title: "Clean story",
|
|
203
|
+
description: "No errors here",
|
|
204
|
+
acceptanceCriteria: ["AC1"],
|
|
205
|
+
dependencies: [],
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
|
|
209
|
+
const storyContext: StoryContext = {
|
|
210
|
+
prd,
|
|
211
|
+
currentStoryId: "US-002",
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
const budget: ContextBudget = {
|
|
215
|
+
maxTokens: 50000,
|
|
216
|
+
reservedForInstructions: 5000,
|
|
217
|
+
availableForContext: 45000,
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const built = await buildContext(storyContext, budget);
|
|
221
|
+
const markdown = formatContextAsMarkdown(built);
|
|
222
|
+
|
|
223
|
+
expect(markdown).not.toContain("LEAKED_ERROR");
|
|
224
|
+
expect(markdown).not.toContain("US-001");
|
|
225
|
+
// No error section at all
|
|
226
|
+
expect(markdown).not.toContain("## Prior Errors");
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
test("context elements only contain expected types for a story with no deps/errors", async () => {
|
|
230
|
+
const prd = createTestPRD([
|
|
231
|
+
{
|
|
232
|
+
id: "US-001",
|
|
233
|
+
title: "Solo story",
|
|
234
|
+
description: "No dependencies",
|
|
235
|
+
acceptanceCriteria: ["AC1"],
|
|
236
|
+
dependencies: [],
|
|
237
|
+
},
|
|
238
|
+
]);
|
|
239
|
+
|
|
240
|
+
const storyContext: StoryContext = {
|
|
241
|
+
prd,
|
|
242
|
+
currentStoryId: "US-001",
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
const budget: ContextBudget = {
|
|
246
|
+
maxTokens: 50000,
|
|
247
|
+
reservedForInstructions: 5000,
|
|
248
|
+
availableForContext: 45000,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const built = await buildContext(storyContext, budget);
|
|
252
|
+
|
|
253
|
+
// Should only have progress + current story (no deps, no errors, no files, no test-coverage without workdir)
|
|
254
|
+
const types = built.elements.map((e) => e.type);
|
|
255
|
+
expect(types).toContain("progress");
|
|
256
|
+
expect(types).toContain("story");
|
|
257
|
+
expect(types).not.toContain("dependency");
|
|
258
|
+
expect(types).not.toContain("error");
|
|
259
|
+
|
|
260
|
+
// All story elements reference only US-001
|
|
261
|
+
const storyElements = built.elements.filter((e) => e.storyId);
|
|
262
|
+
for (const el of storyElements) {
|
|
263
|
+
expect(el.storyId).toBe("US-001");
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
});
|
|
@@ -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("sortContextElements", () => {
|
|
57
|
+
test("should sort by priority descending", () => {
|
|
58
|
+
const elements: ContextElement[] = [
|
|
59
|
+
createErrorContext("error", 10),
|
|
60
|
+
createProgressContext("progress", 100),
|
|
61
|
+
createErrorContext("error2", 50),
|
|
62
|
+
];
|
|
63
|
+
|
|
64
|
+
const sorted = sortContextElements(elements);
|
|
65
|
+
|
|
66
|
+
expect(sorted[0].priority).toBe(100);
|
|
67
|
+
expect(sorted[1].priority).toBe(50);
|
|
68
|
+
expect(sorted[2].priority).toBe(10);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test("should sort by tokens ascending for same priority", () => {
|
|
72
|
+
const elements: ContextElement[] = [
|
|
73
|
+
createErrorContext("this is a much longer error message with lots of text", 50),
|
|
74
|
+
createErrorContext("short", 50),
|
|
75
|
+
createErrorContext("medium length message", 50),
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
const sorted = sortContextElements(elements);
|
|
79
|
+
|
|
80
|
+
expect(sorted[0].tokens).toBeLessThan(sorted[1].tokens);
|
|
81
|
+
expect(sorted[1].tokens).toBeLessThan(sorted[2].tokens);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("should not mutate original array", () => {
|
|
85
|
+
const elements: ContextElement[] = [createErrorContext("a", 10), createErrorContext("b", 20)];
|
|
86
|
+
|
|
87
|
+
const original = [...elements];
|
|
88
|
+
sortContextElements(elements);
|
|
89
|
+
|
|
90
|
+
expect(elements).toEqual(original);
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
});
|