edsger 0.44.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 (105) hide show
  1. package/dist/api/run-sheets.d.ts +22 -0
  2. package/dist/api/run-sheets.js +13 -0
  3. package/dist/commands/run-sheet/index.d.ts +6 -0
  4. package/dist/commands/run-sheet/index.js +48 -0
  5. package/dist/index.js +22 -0
  6. package/dist/phases/run-sheet/index.d.ts +39 -0
  7. package/dist/phases/run-sheet/index.js +297 -0
  8. package/dist/phases/run-sheet/render.d.ts +42 -0
  9. package/dist/phases/run-sheet/render.js +133 -0
  10. package/package.json +11 -4
  11. package/tsconfig.build.json +4 -0
  12. package/tsconfig.json +3 -8
  13. package/vitest.config.ts +12 -0
  14. package/dist/api/__tests__/app-store.test.d.ts +0 -7
  15. package/dist/api/__tests__/app-store.test.js +0 -60
  16. package/dist/api/__tests__/intelligence.test.d.ts +0 -11
  17. package/dist/api/__tests__/intelligence.test.js +0 -315
  18. package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
  19. package/dist/api/features/__tests__/feature-utils.test.js +0 -370
  20. package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
  21. package/dist/api/features/__tests__/status-updater.test.js +0 -88
  22. package/dist/commands/build/__tests__/build.test.d.ts +0 -5
  23. package/dist/commands/build/__tests__/build.test.js +0 -206
  24. package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
  25. package/dist/commands/build/__tests__/detect-project.test.js +0 -160
  26. package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
  27. package/dist/commands/build/__tests__/run-build.test.js +0 -433
  28. package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
  29. package/dist/commands/intelligence/__tests__/command.test.js +0 -48
  30. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
  31. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
  32. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
  33. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
  34. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
  35. package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
  36. package/dist/config/__tests__/config.test.d.ts +0 -4
  37. package/dist/config/__tests__/config.test.js +0 -286
  38. package/dist/config/__tests__/feature-status.test.d.ts +0 -4
  39. package/dist/config/__tests__/feature-status.test.js +0 -111
  40. package/dist/errors/__tests__/index.test.d.ts +0 -4
  41. package/dist/errors/__tests__/index.test.js +0 -349
  42. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
  43. package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
  44. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
  45. package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
  46. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
  47. package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
  48. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
  49. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
  50. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
  51. package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
  52. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
  53. package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
  54. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
  55. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
  56. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
  57. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
  58. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
  59. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
  60. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
  61. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
  62. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
  63. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
  64. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
  65. package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
  66. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
  67. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
  68. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
  69. package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
  70. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
  71. package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
  72. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
  73. package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
  74. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
  75. package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
  76. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
  77. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
  78. package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
  79. package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
  80. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
  81. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
  82. package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
  83. package/dist/phases/release-sync/__tests__/github.test.js +0 -123
  84. package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
  85. package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
  86. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
  87. package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
  88. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
  89. package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
  90. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
  91. package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
  92. package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
  93. package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
  94. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
  95. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
  96. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
  97. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
  98. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
  99. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
  100. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
  101. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
  102. package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
  103. package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
  104. package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
  105. 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
- });