edsger 0.45.0 → 0.45.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/package.json +3 -3
  2. package/tsconfig.build.json +4 -0
  3. package/tsconfig.json +3 -9
  4. package/dist/api/__tests__/app-store.test.d.ts +0 -7
  5. package/dist/api/__tests__/app-store.test.js +0 -60
  6. package/dist/api/__tests__/intelligence.test.d.ts +0 -11
  7. package/dist/api/__tests__/intelligence.test.js +0 -315
  8. package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
  9. package/dist/api/features/__tests__/feature-utils.test.js +0 -370
  10. package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
  11. package/dist/api/features/__tests__/status-updater.test.js +0 -88
  12. package/dist/commands/build/__tests__/build.test.d.ts +0 -5
  13. package/dist/commands/build/__tests__/build.test.js +0 -206
  14. package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
  15. package/dist/commands/build/__tests__/detect-project.test.js +0 -160
  16. package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
  17. package/dist/commands/build/__tests__/run-build.test.js +0 -433
  18. package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
  19. package/dist/commands/intelligence/__tests__/command.test.js +0 -48
  20. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
  21. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
  22. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
  23. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
  24. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
  25. package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
  26. package/dist/config/__tests__/config.test.d.ts +0 -4
  27. package/dist/config/__tests__/config.test.js +0 -286
  28. package/dist/config/__tests__/feature-status.test.d.ts +0 -4
  29. package/dist/config/__tests__/feature-status.test.js +0 -111
  30. package/dist/errors/__tests__/index.test.d.ts +0 -4
  31. package/dist/errors/__tests__/index.test.js +0 -349
  32. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
  33. package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
  34. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
  35. package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
  36. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
  37. package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
  38. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
  39. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
  40. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
  41. package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
  42. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
  43. package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
  44. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
  45. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
  46. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
  47. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
  48. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
  49. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
  50. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
  51. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
  52. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
  53. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
  54. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
  55. package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
  56. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
  57. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
  58. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
  59. package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
  60. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
  61. package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
  62. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
  63. package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
  64. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
  65. package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
  66. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
  67. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
  68. package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
  69. package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
  70. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
  71. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
  72. package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
  73. package/dist/phases/release-sync/__tests__/github.test.js +0 -123
  74. package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
  75. package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
  76. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
  77. package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
  78. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
  79. package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
  80. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
  81. package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
  82. package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
  83. package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
  84. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
  85. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
  86. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
  87. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
  88. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
  89. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
  90. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
  91. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
  92. package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
  93. package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
  94. package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
  95. package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
@@ -1,74 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { buildImprovementPrompt, } from '../coaching-agent.js';
4
- void describe('buildImprovementPrompt', () => {
5
- const decision = {
6
- shouldContinue: true,
7
- reasoning: 'Completeness is weak, needs more coverage',
8
- focusAreas: ['completeness', 'accuracy'],
9
- suggestions: [
10
- 'Add error handling section',
11
- 'Include database migration plan',
12
- ],
13
- };
14
- const rating = {
15
- score: 72,
16
- summary: 'Decent but incomplete',
17
- criteria_scores: {
18
- completeness: { score: 60, reason: 'Missing sections' },
19
- accuracy: { score: 75, reason: 'Minor issues' },
20
- quality: { score: 82, reason: 'Good structure' },
21
- },
22
- strengths: ['Clean structure'],
23
- weaknesses: ['Missing error handling', 'No migration plan'],
24
- };
25
- void it('includes current score and target', () => {
26
- const prompt = buildImprovementPrompt(decision, rating);
27
- assert.ok(prompt.includes('72/100'));
28
- assert.ok(prompt.includes('100/100'));
29
- });
30
- void it('includes coaching reasoning', () => {
31
- const prompt = buildImprovementPrompt(decision, rating);
32
- assert.ok(prompt.includes('Completeness is weak, needs more coverage'));
33
- });
34
- void it('includes focus areas with scores', () => {
35
- const prompt = buildImprovementPrompt(decision, rating);
36
- assert.ok(prompt.includes('completeness'));
37
- assert.ok(prompt.includes('60/100'));
38
- assert.ok(prompt.includes('accuracy'));
39
- assert.ok(prompt.includes('75/100'));
40
- });
41
- void it('includes specific suggestions', () => {
42
- const prompt = buildImprovementPrompt(decision, rating);
43
- assert.ok(prompt.includes('Add error handling section'));
44
- assert.ok(prompt.includes('Include database migration plan'));
45
- });
46
- void it('includes weaknesses to address', () => {
47
- const prompt = buildImprovementPrompt(decision, rating);
48
- assert.ok(prompt.includes('Missing error handling'));
49
- assert.ok(prompt.includes('No migration plan'));
50
- });
51
- void it('handles empty focus areas gracefully', () => {
52
- const emptyDecision = {
53
- shouldContinue: true,
54
- reasoning: 'General improvement needed',
55
- focusAreas: [],
56
- suggestions: ['Improve overall'],
57
- };
58
- const prompt = buildImprovementPrompt(emptyDecision, rating);
59
- assert.ok(prompt.includes('General improvement needed'));
60
- assert.ok(prompt.includes('Improve overall'));
61
- });
62
- void it('handles focus areas not present in criteria_scores', () => {
63
- const decisionWithUnknown = {
64
- shouldContinue: true,
65
- reasoning: 'test',
66
- focusAreas: ['nonexistent_criterion'],
67
- suggestions: ['test'],
68
- };
69
- const prompt = buildImprovementPrompt(decisionWithUnknown, rating);
70
- // Should not crash, should show '?' for missing score
71
- assert.ok(prompt.includes('nonexistent_criterion'));
72
- assert.ok(prompt.includes('?/100'));
73
- });
74
- });
@@ -1,59 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { isStagnant } from '../coaching-loop.js';
4
- function makeRating(score) {
5
- return {
6
- score,
7
- summary: `Score: ${score}`,
8
- criteria_scores: {},
9
- strengths: [],
10
- weaknesses: [],
11
- };
12
- }
13
- void describe('isStagnant', () => {
14
- void it('returns false when fewer than 3 ratings', () => {
15
- assert.strictEqual(isStagnant([]), false);
16
- assert.strictEqual(isStagnant([makeRating(50)]), false);
17
- assert.strictEqual(isStagnant([makeRating(50), makeRating(52)]), false);
18
- });
19
- void it('returns true when 3 ratings with < 3 point improvement', () => {
20
- const ratings = [makeRating(80), makeRating(81), makeRating(82)];
21
- assert.strictEqual(isStagnant(ratings), true);
22
- });
23
- void it('returns true when 3 ratings with zero improvement', () => {
24
- const ratings = [makeRating(85), makeRating(85), makeRating(85)];
25
- assert.strictEqual(isStagnant(ratings), true);
26
- });
27
- void it('returns false when improvement >= 3 points', () => {
28
- // 73 - 70 = 3, and MIN_IMPROVEMENT = 3, so 3 < 3 is false → not stagnant
29
- const ratings = [makeRating(70), makeRating(72), makeRating(73)];
30
- assert.strictEqual(isStagnant(ratings), false);
31
- });
32
- void it('returns false when improvement exactly equals threshold', () => {
33
- const ratings = [makeRating(70), makeRating(72), makeRating(73)];
34
- // improvement = 73 - 70 = 3, MIN_IMPROVEMENT = 3
35
- // 3 < 3 is false → not stagnant
36
- assert.strictEqual(isStagnant(ratings), false);
37
- });
38
- void it('returns false when improvement exceeds threshold', () => {
39
- const ratings = [makeRating(60), makeRating(65), makeRating(70)];
40
- assert.strictEqual(isStagnant(ratings), false);
41
- });
42
- void it('only looks at last STAGNATION_WINDOW ratings', () => {
43
- // Early ratings had big jumps, but last 3 are flat
44
- const ratings = [
45
- makeRating(30),
46
- makeRating(50),
47
- makeRating(70),
48
- makeRating(71),
49
- makeRating(72),
50
- ];
51
- // Last 3: 70, 71, 72 → improvement = 2 < 3 → stagnant
52
- assert.strictEqual(isStagnant(ratings), true);
53
- });
54
- void it('handles decreasing scores as stagnant', () => {
55
- const ratings = [makeRating(80), makeRating(78), makeRating(79)];
56
- // improvement = 79 - 80 = -1 < 3 → stagnant
57
- assert.strictEqual(isStagnant(ratings), true);
58
- });
59
- });
@@ -1,188 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { buildSelfRatingPrompt, getCriteriaForPhase, parseSelfRating, } from '../self-rating.js';
4
- void describe('getCriteriaForPhase', () => {
5
- void it('returns analysis criteria for feature_analysis', () => {
6
- const criteria = getCriteriaForPhase('feature_analysis');
7
- assert.ok(criteria.includes('coverage'));
8
- assert.ok(criteria.includes('clarity'));
9
- assert.ok(criteria.includes('completeness'));
10
- });
11
- void it('returns analysis criteria for user_stories_analysis', () => {
12
- const criteria = getCriteriaForPhase('user_stories_analysis');
13
- assert.ok(criteria.includes('coverage'));
14
- });
15
- void it('returns analysis criteria for test_cases_analysis', () => {
16
- const criteria = getCriteriaForPhase('test_cases_analysis');
17
- assert.ok(criteria.includes('clarity'));
18
- });
19
- void it('returns design criteria for technical_design', () => {
20
- const criteria = getCriteriaForPhase('technical_design');
21
- assert.ok(criteria.includes('feasibility'));
22
- assert.ok(criteria.includes('scalability'));
23
- assert.ok(criteria.includes('maintainability'));
24
- });
25
- void it('returns design criteria for branch_planning', () => {
26
- const criteria = getCriteriaForPhase('branch_planning');
27
- assert.ok(criteria.includes('feasibility'));
28
- });
29
- void it('returns code criteria for code_implementation', () => {
30
- const criteria = getCriteriaForPhase('code_implementation');
31
- assert.ok(criteria.includes('readability'));
32
- assert.ok(criteria.includes('testability'));
33
- assert.ok(criteria.includes('performance'));
34
- assert.ok(criteria.includes('security'));
35
- });
36
- void it('returns code criteria for code_refine', () => {
37
- const criteria = getCriteriaForPhase('code_refine');
38
- assert.ok(criteria.includes('readability'));
39
- });
40
- void it('returns common criteria for autonomous', () => {
41
- const criteria = getCriteriaForPhase('autonomous');
42
- assert.ok(criteria.includes('completeness'));
43
- assert.ok(criteria.includes('accuracy'));
44
- assert.ok(criteria.includes('quality'));
45
- assert.ok(criteria.includes('consistency'));
46
- });
47
- void it('all criteria sets include common criteria', () => {
48
- const phases = [
49
- 'feature_analysis',
50
- 'technical_design',
51
- 'code_implementation',
52
- 'autonomous',
53
- ];
54
- for (const phase of phases) {
55
- const criteria = getCriteriaForPhase(phase);
56
- assert.ok(criteria.includes('completeness'), `${phase} missing completeness`);
57
- assert.ok(criteria.includes('accuracy'), `${phase} missing accuracy`);
58
- assert.ok(criteria.includes('quality'), `${phase} missing quality`);
59
- assert.ok(criteria.includes('consistency'), `${phase} missing consistency`);
60
- }
61
- });
62
- });
63
- void describe('buildSelfRatingPrompt', () => {
64
- void it('includes phase-specific criteria', () => {
65
- const prompt = buildSelfRatingPrompt('technical_design');
66
- assert.ok(prompt.includes('"feasibility"'));
67
- assert.ok(prompt.includes('"scalability"'));
68
- assert.ok(prompt.includes('"maintainability"'));
69
- });
70
- void it('includes JSON structure template', () => {
71
- const prompt = buildSelfRatingPrompt('code_implementation');
72
- assert.ok(prompt.includes('"self_rating"'));
73
- assert.ok(prompt.includes('"score"'));
74
- assert.ok(prompt.includes('"criteria_scores"'));
75
- assert.ok(prompt.includes('"strengths"'));
76
- assert.ok(prompt.includes('"weaknesses"'));
77
- });
78
- void it('asks for 0-100 scores', () => {
79
- const prompt = buildSelfRatingPrompt('feature_analysis');
80
- assert.ok(prompt.includes('0-100'));
81
- });
82
- });
83
- void describe('parseSelfRating', () => {
84
- void it('parses valid JSON in markdown code block', () => {
85
- const response = `Here is my self-rating:
86
-
87
- \`\`\`json
88
- {
89
- "self_rating": {
90
- "score": 85,
91
- "summary": "Good coverage but some gaps",
92
- "criteria_scores": {
93
- "completeness": { "score": 90, "reason": "Covered all cases" },
94
- "accuracy": { "score": 80, "reason": "Minor issues" }
95
- },
96
- "strengths": ["Good structure", "Clear naming"],
97
- "weaknesses": ["Missing edge cases"]
98
- }
99
- }
100
- \`\`\``;
101
- const rating = parseSelfRating(response, 'technical_design');
102
- assert.ok(rating);
103
- assert.strictEqual(rating.score, 85);
104
- assert.strictEqual(rating.summary, 'Good coverage but some gaps');
105
- assert.strictEqual(rating.criteria_scores.completeness.score, 90);
106
- assert.strictEqual(rating.criteria_scores.accuracy.score, 80);
107
- assert.deepStrictEqual(rating.strengths, ['Good structure', 'Clear naming']);
108
- assert.deepStrictEqual(rating.weaknesses, ['Missing edge cases']);
109
- });
110
- void it('parses raw JSON without code block', () => {
111
- const response = JSON.stringify({
112
- self_rating: {
113
- score: 72,
114
- summary: 'Needs work',
115
- criteria_scores: {},
116
- strengths: [],
117
- weaknesses: ['Poor coverage'],
118
- },
119
- });
120
- const rating = parseSelfRating(response, 'feature_analysis');
121
- assert.ok(rating);
122
- assert.strictEqual(rating.score, 72);
123
- assert.strictEqual(rating.summary, 'Needs work');
124
- });
125
- void it('rounds score to integer', () => {
126
- const response = JSON.stringify({
127
- self_rating: {
128
- score: 85.7,
129
- summary: 'test',
130
- criteria_scores: {},
131
- strengths: [],
132
- weaknesses: [],
133
- },
134
- });
135
- const rating = parseSelfRating(response, 'technical_design');
136
- assert.ok(rating);
137
- assert.strictEqual(rating.score, 86);
138
- });
139
- void it('returns null for invalid score above 100', () => {
140
- const response = JSON.stringify({
141
- self_rating: {
142
- score: 150,
143
- summary: 'test',
144
- criteria_scores: {},
145
- strengths: [],
146
- weaknesses: [],
147
- },
148
- });
149
- const rating = parseSelfRating(response, 'technical_design');
150
- assert.strictEqual(rating, null);
151
- });
152
- void it('returns null for negative score', () => {
153
- const response = JSON.stringify({
154
- self_rating: {
155
- score: -5,
156
- summary: 'test',
157
- criteria_scores: {},
158
- strengths: [],
159
- weaknesses: [],
160
- },
161
- });
162
- const rating = parseSelfRating(response, 'technical_design');
163
- assert.strictEqual(rating, null);
164
- });
165
- void it('returns null for non-JSON response', () => {
166
- const rating = parseSelfRating('This is not JSON at all', 'technical_design');
167
- assert.strictEqual(rating, null);
168
- });
169
- void it('returns null for empty response', () => {
170
- const rating = parseSelfRating('', 'technical_design');
171
- assert.strictEqual(rating, null);
172
- });
173
- void it('handles missing optional fields gracefully', () => {
174
- const response = JSON.stringify({
175
- self_rating: {
176
- score: 60,
177
- criteria_scores: { completeness: { score: 60, reason: 'ok' } },
178
- },
179
- });
180
- const rating = parseSelfRating(response, 'technical_design');
181
- assert.ok(rating);
182
- assert.strictEqual(rating.score, 60);
183
- assert.strictEqual(rating.summary, '');
184
- assert.deepStrictEqual(rating.strengths, []);
185
- assert.deepStrictEqual(rating.weaknesses, []);
186
- assert.strictEqual(rating.criteria_scores.completeness.score, 60);
187
- });
188
- });
@@ -1,122 +0,0 @@
1
- import assert from 'node:assert';
2
- import { afterEach, describe, it } from 'node:test';
3
- import { cacheBindings, clearBindingsCache, getBindingsForPhase, getCachedBindings, } from '../bindings-fetcher.js';
4
- function makeBinding(overrides = {}) {
5
- return {
6
- id: 'hook-1',
7
- product_id: 'prod-1',
8
- phase: 'technical-design',
9
- hook_point: 'after',
10
- plugin_name: 'payload-cms',
11
- skill_name: 'validate-schema',
12
- on_failure: 'block',
13
- config: {},
14
- sort_order: 0,
15
- enabled: true,
16
- ...overrides,
17
- };
18
- }
19
- // ---- getBindingsForPhase ----
20
- void describe('getBindingsForPhase', () => {
21
- void it('filters by exact phase match', () => {
22
- const bindings = [
23
- makeBinding({ phase: 'technical-design', hook_point: 'after' }),
24
- makeBinding({
25
- id: '2',
26
- phase: 'code-implementation',
27
- hook_point: 'after',
28
- }),
29
- ];
30
- const result = getBindingsForPhase(bindings, 'technical-design', 'after');
31
- assert.strictEqual(result.length, 1);
32
- assert.strictEqual(result[0].phase, 'technical-design');
33
- });
34
- void it('matches wildcard phase "*"', () => {
35
- const bindings = [
36
- makeBinding({ phase: '*', hook_point: 'after', skill_name: 'notify' }),
37
- ];
38
- const result = getBindingsForPhase(bindings, 'code-review', 'after');
39
- assert.strictEqual(result.length, 1);
40
- assert.strictEqual(result[0].skill_name, 'notify');
41
- });
42
- void it('combines exact match and wildcard', () => {
43
- const bindings = [
44
- makeBinding({
45
- id: '1',
46
- phase: 'technical-design',
47
- hook_point: 'after',
48
- sort_order: 1,
49
- }),
50
- makeBinding({ id: '2', phase: '*', hook_point: 'after', sort_order: 0 }),
51
- ];
52
- const result = getBindingsForPhase(bindings, 'technical-design', 'after');
53
- assert.strictEqual(result.length, 2);
54
- // Wildcard (sort_order 0) comes first
55
- assert.strictEqual(result[0].id, '2');
56
- assert.strictEqual(result[1].id, '1');
57
- });
58
- void it('filters by hook_point', () => {
59
- const bindings = [
60
- makeBinding({ hook_point: 'before' }),
61
- makeBinding({ id: '2', hook_point: 'after' }),
62
- ];
63
- const result = getBindingsForPhase(bindings, 'technical-design', 'before');
64
- assert.strictEqual(result.length, 1);
65
- assert.strictEqual(result[0].hook_point, 'before');
66
- });
67
- void it('excludes disabled bindings', () => {
68
- const bindings = [makeBinding({ enabled: false })];
69
- const result = getBindingsForPhase(bindings, 'technical-design', 'after');
70
- assert.strictEqual(result.length, 0);
71
- });
72
- void it('sorts by sort_order ascending', () => {
73
- const bindings = [
74
- makeBinding({ id: 'c', sort_order: 10 }),
75
- makeBinding({ id: 'a', sort_order: 1 }),
76
- makeBinding({ id: 'b', sort_order: 5 }),
77
- ];
78
- const result = getBindingsForPhase(bindings, 'technical-design', 'after');
79
- assert.deepStrictEqual(result.map((b) => b.id), ['a', 'b', 'c']);
80
- });
81
- void it('returns empty array when no bindings match', () => {
82
- const bindings = [
83
- makeBinding({ phase: 'code-review', hook_point: 'before' }),
84
- ];
85
- const result = getBindingsForPhase(bindings, 'technical-design', 'after');
86
- assert.strictEqual(result.length, 0);
87
- });
88
- void it('handles on_error hook point', () => {
89
- const bindings = [makeBinding({ hook_point: 'on_error' })];
90
- const result = getBindingsForPhase(bindings, 'technical-design', 'on_error');
91
- assert.strictEqual(result.length, 1);
92
- });
93
- });
94
- // ---- Cache management ----
95
- void describe('bindings cache', () => {
96
- afterEach(() => {
97
- clearBindingsCache('test-feature-1');
98
- clearBindingsCache('test-feature-2');
99
- });
100
- void it('returns null for uncached feature', () => {
101
- assert.strictEqual(getCachedBindings('nonexistent'), null);
102
- });
103
- void it('round-trips cache/get/clear', () => {
104
- const bindings = [makeBinding()];
105
- cacheBindings('test-feature-1', 'prod-1', bindings);
106
- const cached = getCachedBindings('test-feature-1');
107
- assert.ok(cached);
108
- assert.strictEqual(cached.productId, 'prod-1');
109
- assert.strictEqual(cached.bindings.length, 1);
110
- assert.ok(cached.fetchedAt > 0);
111
- clearBindingsCache('test-feature-1');
112
- assert.strictEqual(getCachedBindings('test-feature-1'), null);
113
- });
114
- void it('isolates cache by featureId', () => {
115
- cacheBindings('test-feature-1', 'prod-1', [makeBinding({ id: 'a' })]);
116
- cacheBindings('test-feature-2', 'prod-2', [makeBinding({ id: 'b' })]);
117
- const cached1 = getCachedBindings('test-feature-1');
118
- const cached2 = getCachedBindings('test-feature-2');
119
- assert.strictEqual(cached1?.bindings[0].id, 'a');
120
- assert.strictEqual(cached2?.bindings[0].id, 'b');
121
- });
122
- });