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