edsger 0.51.0 → 0.52.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 (184) hide show
  1. package/.claude/settings.local.json +23 -3
  2. package/.env.local +12 -0
  3. package/dist/commands/find-smells/index.d.ts +21 -0
  4. package/dist/commands/find-smells/index.js +65 -0
  5. package/dist/index.js +29 -0
  6. package/dist/phases/find-bugs/index.js +7 -92
  7. package/dist/phases/find-bugs/state.d.ts +10 -35
  8. package/dist/phases/find-bugs/state.js +12 -120
  9. package/dist/phases/find-features/index.js +16 -83
  10. package/dist/phases/find-features/prompts.d.ts +7 -1
  11. package/dist/phases/find-features/prompts.js +31 -11
  12. package/dist/phases/find-features/state.d.ts +15 -19
  13. package/dist/phases/find-features/state.js +17 -89
  14. package/dist/phases/find-features/types.d.ts +1 -1
  15. package/dist/phases/find-shared/git.d.ts +24 -0
  16. package/dist/phases/find-shared/git.js +60 -0
  17. package/dist/phases/find-shared/mcp.d.ts +33 -0
  18. package/dist/phases/find-shared/mcp.js +69 -0
  19. package/dist/phases/find-shared/scan-state.d.ts +33 -0
  20. package/dist/phases/find-shared/scan-state.js +112 -0
  21. package/dist/phases/find-smells/index.d.ts +47 -0
  22. package/dist/phases/find-smells/index.js +278 -0
  23. package/dist/phases/find-smells/prompts.d.ts +30 -0
  24. package/dist/phases/find-smells/prompts.js +129 -0
  25. package/dist/phases/find-smells/state.d.ts +21 -0
  26. package/dist/phases/find-smells/state.js +17 -0
  27. package/dist/phases/find-smells/types.d.ts +51 -0
  28. package/dist/phases/find-smells/types.js +64 -0
  29. package/package.json +1 -1
  30. package/vitest.config.ts +2 -0
  31. package/dist/api/__tests__/app-store.test.d.ts +0 -7
  32. package/dist/api/__tests__/app-store.test.js +0 -60
  33. package/dist/api/__tests__/intelligence.test.d.ts +0 -11
  34. package/dist/api/__tests__/intelligence.test.js +0 -315
  35. package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
  36. package/dist/api/features/__tests__/feature-utils.test.js +0 -370
  37. package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
  38. package/dist/api/features/__tests__/status-updater.test.js +0 -88
  39. package/dist/api/features/approval-checker.d.ts +0 -20
  40. package/dist/api/features/approval-checker.js +0 -152
  41. package/dist/api/features/batch-operations.d.ts +0 -17
  42. package/dist/api/features/batch-operations.js +0 -100
  43. package/dist/api/features/feature-utils.d.ts +0 -23
  44. package/dist/api/features/feature-utils.js +0 -80
  45. package/dist/api/features/get-feature.d.ts +0 -5
  46. package/dist/api/features/get-feature.js +0 -21
  47. package/dist/api/features/index.d.ts +0 -8
  48. package/dist/api/features/index.js +0 -10
  49. package/dist/api/features/status-updater.d.ts +0 -41
  50. package/dist/api/features/status-updater.js +0 -122
  51. package/dist/api/features/test-cases.d.ts +0 -29
  52. package/dist/api/features/test-cases.js +0 -110
  53. package/dist/api/features/update-feature.d.ts +0 -20
  54. package/dist/api/features/update-feature.js +0 -83
  55. package/dist/api/features/user-stories.d.ts +0 -21
  56. package/dist/api/features/user-stories.js +0 -88
  57. package/dist/commands/agent-workflow/feature-worker.d.ts +0 -14
  58. package/dist/commands/agent-workflow/feature-worker.js +0 -65
  59. package/dist/commands/build/__tests__/build.test.d.ts +0 -5
  60. package/dist/commands/build/__tests__/build.test.js +0 -206
  61. package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
  62. package/dist/commands/build/__tests__/detect-project.test.js +0 -160
  63. package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
  64. package/dist/commands/build/__tests__/run-build.test.js +0 -433
  65. package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
  66. package/dist/commands/intelligence/__tests__/command.test.js +0 -48
  67. package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
  68. package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
  69. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
  70. package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
  71. package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
  72. package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
  73. package/dist/commands/workflow/core/feature-filter.d.ts +0 -16
  74. package/dist/commands/workflow/core/feature-filter.js +0 -47
  75. package/dist/commands/workflow/feature-coordinator.d.ts +0 -18
  76. package/dist/commands/workflow/feature-coordinator.js +0 -161
  77. package/dist/config/__tests__/config.test.d.ts +0 -4
  78. package/dist/config/__tests__/config.test.js +0 -286
  79. package/dist/config/__tests__/feature-status.test.d.ts +0 -4
  80. package/dist/config/__tests__/feature-status.test.js +0 -111
  81. package/dist/config/feature-status.d.ts +0 -56
  82. package/dist/config/feature-status.js +0 -130
  83. package/dist/errors/__tests__/index.test.d.ts +0 -4
  84. package/dist/errors/__tests__/index.test.js +0 -349
  85. package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
  86. package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
  87. package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
  88. package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
  89. package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
  90. package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
  91. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
  92. package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
  93. package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
  94. package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
  95. package/dist/phases/feature-analysis/agent.d.ts +0 -13
  96. package/dist/phases/feature-analysis/agent.js +0 -112
  97. package/dist/phases/feature-analysis/context.d.ts +0 -24
  98. package/dist/phases/feature-analysis/context.js +0 -138
  99. package/dist/phases/feature-analysis/index.d.ts +0 -8
  100. package/dist/phases/feature-analysis/index.js +0 -199
  101. package/dist/phases/feature-analysis/outcome.d.ts +0 -40
  102. package/dist/phases/feature-analysis/outcome.js +0 -280
  103. package/dist/phases/feature-analysis/prompts.d.ts +0 -10
  104. package/dist/phases/feature-analysis/prompts.js +0 -212
  105. package/dist/phases/feature-analysis-verification/agent.d.ts +0 -33
  106. package/dist/phases/feature-analysis-verification/agent.js +0 -124
  107. package/dist/phases/feature-analysis-verification/index.d.ts +0 -25
  108. package/dist/phases/feature-analysis-verification/index.js +0 -92
  109. package/dist/phases/feature-analysis-verification/prompts.d.ts +0 -10
  110. package/dist/phases/feature-analysis-verification/prompts.js +0 -100
  111. package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
  112. package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
  113. package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
  114. package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
  115. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
  116. package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
  117. package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
  118. package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
  119. package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
  120. package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
  121. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
  122. package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
  123. package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
  124. package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
  125. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
  126. package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
  127. package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
  128. package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
  129. package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
  130. package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
  131. package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
  132. package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
  133. package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
  134. package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
  135. package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
  136. package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
  137. package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
  138. package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
  139. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
  140. package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
  141. package/dist/phases/run-sheet/render.d.ts +0 -60
  142. package/dist/phases/run-sheet/render.js +0 -297
  143. package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
  144. package/dist/phases/smoke-test/__tests__/agent.test.js +0 -84
  145. package/dist/phases/smoke-test/__tests__/github.test.d.ts +0 -9
  146. package/dist/phases/smoke-test/__tests__/github.test.js +0 -120
  147. package/dist/phases/smoke-test/__tests__/snapshot.test.d.ts +0 -8
  148. package/dist/phases/smoke-test/__tests__/snapshot.test.js +0 -93
  149. package/dist/phases/smoke-test/github.d.ts +0 -54
  150. package/dist/phases/smoke-test/github.js +0 -101
  151. package/dist/phases/smoke-test/snapshot.d.ts +0 -27
  152. package/dist/phases/smoke-test/snapshot.js +0 -157
  153. package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
  154. package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
  155. package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
  156. package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
  157. package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
  158. package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
  159. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.d.ts +0 -4
  160. package/dist/services/lifecycle-agent/__tests__/phase-criteria.test.js +0 -133
  161. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.d.ts +0 -4
  162. package/dist/services/lifecycle-agent/__tests__/transition-rules.test.js +0 -336
  163. package/dist/services/lifecycle-agent/index.d.ts +0 -24
  164. package/dist/services/lifecycle-agent/index.js +0 -25
  165. package/dist/services/lifecycle-agent/phase-criteria.d.ts +0 -57
  166. package/dist/services/lifecycle-agent/phase-criteria.js +0 -335
  167. package/dist/services/lifecycle-agent/transition-rules.d.ts +0 -60
  168. package/dist/services/lifecycle-agent/transition-rules.js +0 -184
  169. package/dist/services/lifecycle-agent/types.d.ts +0 -190
  170. package/dist/services/lifecycle-agent/types.js +0 -12
  171. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
  172. package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
  173. package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
  174. package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
  175. package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
  176. package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
  177. package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
  178. package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
  179. package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
  180. package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
  181. package/dist/types/features.d.ts +0 -35
  182. package/dist/types/features.js +0 -1
  183. package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
  184. package/dist/workspace/__tests__/workspace-manager.test.js +0 -52
@@ -1,303 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { applyAssignments, findUnassignedFiles, removeDeletedFilesFromPRs, } from '../file-assigner.js';
4
- function makePR(overrides) {
5
- return {
6
- feature_id: 'feat-1',
7
- name: `PR ${overrides.sequence}`,
8
- description: null,
9
- branch_name: null,
10
- base_pr_id: null,
11
- pull_request_url: null,
12
- pull_request_number: null,
13
- compare_url: null,
14
- status: 'pending',
15
- last_synced_commit: null,
16
- files: null,
17
- created_by: 'user',
18
- created_at: '2026-01-01',
19
- updated_at: '2026-01-01',
20
- ...overrides,
21
- };
22
- }
23
- function cf(path, changeType = 'modified') {
24
- return { path, change_type: changeType };
25
- }
26
- // ============================================================
27
- // findUnassignedFiles
28
- // ============================================================
29
- void describe('findUnassignedFiles', () => {
30
- void it('returns all files when no PR has files', () => {
31
- const prs = [makePR({ id: '1', sequence: 1, files: null })];
32
- const result = findUnassignedFiles([cf('a.ts'), cf('b.ts')], prs);
33
- assert.deepStrictEqual(result, [cf('a.ts'), cf('b.ts')]);
34
- });
35
- void it('returns empty when all files are assigned', () => {
36
- const prs = [
37
- makePR({
38
- id: '1',
39
- sequence: 1,
40
- files: [
41
- { path: 'a.ts', change_type: 'modified' },
42
- { path: 'b.ts', change_type: 'added' },
43
- ],
44
- }),
45
- ];
46
- const result = findUnassignedFiles([cf('a.ts'), cf('b.ts', 'added')], prs);
47
- assert.deepStrictEqual(result, []);
48
- });
49
- void it('returns only unassigned files', () => {
50
- const prs = [
51
- makePR({
52
- id: '1',
53
- sequence: 1,
54
- files: [{ path: 'a.ts', change_type: 'modified' }],
55
- }),
56
- makePR({
57
- id: '2',
58
- sequence: 2,
59
- files: [{ path: 'b.ts', change_type: 'added' }],
60
- }),
61
- ];
62
- const result = findUnassignedFiles([cf('a.ts'), cf('b.ts'), cf('c.ts'), cf('d.ts', 'added')], prs);
63
- assert.deepStrictEqual(result, [cf('c.ts'), cf('d.ts', 'added')]);
64
- });
65
- void it('handles PRs with empty files array', () => {
66
- const prs = [
67
- makePR({ id: '1', sequence: 1, files: [] }),
68
- makePR({
69
- id: '2',
70
- sequence: 2,
71
- files: [{ path: 'a.ts', change_type: 'modified' }],
72
- }),
73
- ];
74
- const result = findUnassignedFiles([cf('a.ts'), cf('b.ts')], prs);
75
- assert.deepStrictEqual(result, [cf('b.ts')]);
76
- });
77
- void it('returns empty for empty changed files', () => {
78
- const prs = [
79
- makePR({
80
- id: '1',
81
- sequence: 1,
82
- files: [{ path: 'a.ts', change_type: 'modified' }],
83
- }),
84
- ];
85
- const result = findUnassignedFiles([], prs);
86
- assert.deepStrictEqual(result, []);
87
- });
88
- void it('handles files spread across multiple PRs', () => {
89
- const prs = [
90
- makePR({
91
- id: '1',
92
- sequence: 1,
93
- files: [
94
- { path: 'src/auth/login.ts', change_type: 'modified' },
95
- { path: 'src/auth/session.ts', change_type: 'modified' },
96
- ],
97
- }),
98
- makePR({
99
- id: '2',
100
- sequence: 2,
101
- files: [
102
- { path: 'src/api/users.ts', change_type: 'added' },
103
- { path: 'src/api/roles.ts', change_type: 'added' },
104
- ],
105
- }),
106
- ];
107
- const changedFiles = [
108
- cf('src/auth/login.ts'),
109
- cf('src/auth/session.ts'),
110
- cf('src/api/users.ts'),
111
- cf('src/api/roles.ts'),
112
- cf('src/auth/oauth.ts', 'added'),
113
- cf('README.md'),
114
- ];
115
- const result = findUnassignedFiles(changedFiles, prs);
116
- assert.deepStrictEqual(result, [
117
- cf('src/auth/oauth.ts', 'added'),
118
- cf('README.md'),
119
- ]);
120
- });
121
- });
122
- // ============================================================
123
- // applyAssignments
124
- // ============================================================
125
- void describe('applyAssignments', () => {
126
- void it('merges new files into existing PR files', async () => {
127
- const prs = [
128
- makePR({
129
- id: 'pr-1',
130
- sequence: 1,
131
- files: [{ path: 'a.ts', change_type: 'modified' }],
132
- }),
133
- ];
134
- const unassigned = [cf('b.ts', 'added')];
135
- const updates = [];
136
- const mockUpdater = (prId, u) => {
137
- updates.push({ prId, files: u.files });
138
- return Promise.resolve();
139
- };
140
- const count = await applyAssignments([{ file: 'b.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
141
- assert.strictEqual(count, 1);
142
- assert.strictEqual(updates.length, 1);
143
- assert.strictEqual(updates[0].prId, 'pr-1');
144
- assert.deepStrictEqual(updates[0].files, [
145
- { path: 'a.ts', change_type: 'modified' },
146
- { path: 'b.ts', change_type: 'added' },
147
- ]);
148
- });
149
- void it('deduplicates files already in PR', async () => {
150
- const prs = [
151
- makePR({
152
- id: 'pr-1',
153
- sequence: 1,
154
- files: [{ path: 'a.ts', change_type: 'modified' }],
155
- }),
156
- ];
157
- const unassigned = [cf('a.ts')];
158
- const updates = [];
159
- const mockUpdater = (_prId, u) => {
160
- updates.push({ files: u.files });
161
- return Promise.resolve();
162
- };
163
- await applyAssignments([{ file: 'a.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
164
- // File list should not have duplicates
165
- assert.deepStrictEqual(updates[0].files, [
166
- { path: 'a.ts', change_type: 'modified' },
167
- ]);
168
- });
169
- void it('skips unknown PR ids', async () => {
170
- const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
171
- const unassigned = [cf('x.ts', 'added')];
172
- let called = false;
173
- const mockUpdater = () => {
174
- called = true;
175
- return Promise.resolve();
176
- };
177
- const count = await applyAssignments([{ file: 'x.ts', pr_id: 'unknown-id' }], unassigned, prs, mockUpdater);
178
- assert.strictEqual(count, 0);
179
- assert.strictEqual(called, false);
180
- });
181
- void it('uses change_type from git, not a hardcoded default', async () => {
182
- const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
183
- const unassigned = [cf('new.ts', 'added')];
184
- const updates = [];
185
- const mockUpdater = (_prId, u) => {
186
- updates.push({ files: u.files });
187
- return Promise.resolve();
188
- };
189
- await applyAssignments([{ file: 'new.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
190
- assert.strictEqual(updates[0].files[0].change_type, 'added');
191
- });
192
- void it('distributes files to multiple PRs', async () => {
193
- const prs = [
194
- makePR({ id: 'pr-1', sequence: 1, files: [] }),
195
- makePR({ id: 'pr-2', sequence: 2, files: [] }),
196
- ];
197
- const unassigned = [
198
- cf('a.ts', 'added'),
199
- cf('b.ts', 'modified'),
200
- ];
201
- const updates = [];
202
- const mockUpdater = (prId) => {
203
- updates.push({ prId });
204
- return Promise.resolve();
205
- };
206
- const count = await applyAssignments([
207
- { file: 'a.ts', pr_id: 'pr-1' },
208
- { file: 'b.ts', pr_id: 'pr-2' },
209
- ], unassigned, prs, mockUpdater);
210
- assert.strictEqual(count, 2);
211
- assert.deepStrictEqual(updates.map((u) => u.prId), ['pr-1', 'pr-2']);
212
- });
213
- void it('handles updater failure gracefully', async () => {
214
- const prs = [makePR({ id: 'pr-1', sequence: 1, files: [] })];
215
- const unassigned = [cf('a.ts')];
216
- const mockUpdater = () => {
217
- return Promise.reject(new Error('DB error'));
218
- };
219
- const count = await applyAssignments([{ file: 'a.ts', pr_id: 'pr-1' }], unassigned, prs, mockUpdater);
220
- assert.strictEqual(count, 0);
221
- });
222
- });
223
- // ============================================================
224
- // removeDeletedFilesFromPRs
225
- // ============================================================
226
- void describe('removeDeletedFilesFromPRs', () => {
227
- void it('removes deleted files from PR file lists', async () => {
228
- const prs = [
229
- makePR({
230
- id: 'pr-1',
231
- sequence: 1,
232
- files: [
233
- { path: 'keep.ts', change_type: 'modified' },
234
- { path: 'gone.ts', change_type: 'added' },
235
- ],
236
- }),
237
- ];
238
- const changedFiles = [
239
- cf('keep.ts'),
240
- cf('gone.ts', 'deleted'),
241
- ];
242
- const updates = [];
243
- const mockUpdater = (prId, u) => {
244
- updates.push({ prId, files: u.files });
245
- return Promise.resolve();
246
- };
247
- const count = await removeDeletedFilesFromPRs(changedFiles, prs, mockUpdater);
248
- assert.strictEqual(count, 1);
249
- assert.deepStrictEqual(updates[0].files, [
250
- { path: 'keep.ts', change_type: 'modified' },
251
- ]);
252
- });
253
- void it('returns 0 when no files are deleted', async () => {
254
- const prs = [
255
- makePR({
256
- id: 'pr-1',
257
- sequence: 1,
258
- files: [{ path: 'a.ts', change_type: 'modified' }],
259
- }),
260
- ];
261
- let called = false;
262
- const mockUpdater = () => {
263
- called = true;
264
- return Promise.resolve();
265
- };
266
- const count = await removeDeletedFilesFromPRs([cf('a.ts')], prs, mockUpdater);
267
- assert.strictEqual(count, 0);
268
- assert.strictEqual(called, false);
269
- });
270
- void it('skips PRs with no matching deleted files', async () => {
271
- const prs = [
272
- makePR({
273
- id: 'pr-1',
274
- sequence: 1,
275
- files: [{ path: 'a.ts', change_type: 'modified' }],
276
- }),
277
- makePR({
278
- id: 'pr-2',
279
- sequence: 2,
280
- files: [{ path: 'b.ts', change_type: 'added' }],
281
- }),
282
- ];
283
- const updates = [];
284
- const mockUpdater = (prId) => {
285
- updates.push(prId);
286
- return Promise.resolve();
287
- };
288
- await removeDeletedFilesFromPRs([cf('b.ts', 'deleted')], prs, mockUpdater);
289
- // Only pr-2 should be updated
290
- assert.deepStrictEqual(updates, ['pr-2']);
291
- });
292
- void it('handles PRs with null files', async () => {
293
- const prs = [makePR({ id: 'pr-1', sequence: 1, files: null })];
294
- let called = false;
295
- const mockUpdater = () => {
296
- called = true;
297
- return Promise.resolve();
298
- };
299
- const count = await removeDeletedFilesFromPRs([cf('a.ts', 'deleted')], prs, mockUpdater);
300
- assert.strictEqual(count, 0);
301
- assert.strictEqual(called, false);
302
- });
303
- });
@@ -1,157 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { buildLearnerPrompt } from '../checklist-learner.js';
4
- // ── helpers ──────────────────────────────────────────────────
5
- function makeThread(id, overrides) {
6
- return {
7
- id,
8
- isResolved: false,
9
- isOutdated: false,
10
- comments: {
11
- totalCount: 1,
12
- nodes: [
13
- {
14
- id: `${id}-c1`,
15
- author: { login: overrides?.author || 'reviewer' },
16
- body: overrides?.body || 'Please fix this',
17
- path: overrides?.path || 'src/index.ts',
18
- line: overrides && 'line' in overrides ? (overrides.line ?? null) : 10,
19
- url: `https://github.com/o/r/pull/1#${id}`,
20
- },
21
- ],
22
- },
23
- };
24
- }
25
- function makeMap(entries) {
26
- return new Map(entries);
27
- }
28
- // ── buildLearnerPrompt ──────────────────────────────────────
29
- void describe('buildLearnerPrompt', () => {
30
- void it('includes addressed comment count in header', () => {
31
- const comments = [
32
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
33
- { comment_id: 'comment_2', action: 'changed', reply: 'Done' },
34
- ];
35
- const threads = [makeThread('t1'), makeThread('t2')];
36
- const map = makeMap([
37
- ['comment_1', 't1'],
38
- ['comment_2', 't2'],
39
- ]);
40
- const prompt = buildLearnerPrompt(comments, threads, map);
41
- assert.ok(prompt.includes('2 review comment(s)'));
42
- });
43
- void it('includes reviewer, file path, line, and comment body', () => {
44
- const comments = [
45
- { comment_id: 'comment_1', action: 'changed', reply: 'Added null check' },
46
- ];
47
- const threads = [
48
- makeThread('t1', {
49
- path: 'src/auth.ts',
50
- line: 42,
51
- body: 'Missing null check here',
52
- author: 'alice',
53
- }),
54
- ];
55
- const map = makeMap([['comment_1', 't1']]);
56
- const prompt = buildLearnerPrompt(comments, threads, map);
57
- assert.ok(prompt.includes('src/auth.ts'));
58
- assert.ok(prompt.includes('42'));
59
- assert.ok(prompt.includes('@alice'));
60
- assert.ok(prompt.includes('Missing null check here'));
61
- assert.ok(prompt.includes('Added null check'));
62
- });
63
- void it('includes resolution text for each comment', () => {
64
- const comments = [
65
- {
66
- comment_id: 'comment_1',
67
- action: 'changed',
68
- reply: 'Refactored to use try/catch',
69
- },
70
- ];
71
- const threads = [makeThread('t1')];
72
- const map = makeMap([['comment_1', 't1']]);
73
- const prompt = buildLearnerPrompt(comments, threads, map);
74
- assert.ok(prompt.includes('**Resolution**: Refactored to use try/catch'));
75
- });
76
- void it('includes summary when provided', () => {
77
- const comments = [
78
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
79
- ];
80
- const threads = [makeThread('t1')];
81
- const map = makeMap([['comment_1', 't1']]);
82
- const prompt = buildLearnerPrompt(comments, threads, map, 'Improved error handling across 3 files');
83
- assert.ok(prompt.includes('## Overall Summary'));
84
- assert.ok(prompt.includes('Improved error handling across 3 files'));
85
- });
86
- void it('omits summary section when not provided', () => {
87
- const comments = [
88
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
89
- ];
90
- const threads = [makeThread('t1')];
91
- const map = makeMap([['comment_1', 't1']]);
92
- const prompt = buildLearnerPrompt(comments, threads, map);
93
- assert.ok(!prompt.includes('## Overall Summary'));
94
- });
95
- void it('handles comment with no matching thread gracefully', () => {
96
- const comments = [
97
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
98
- ];
99
- // Empty threads — no match for comment_1
100
- const map = makeMap([['comment_1', 'nonexistent']]);
101
- const prompt = buildLearnerPrompt(comments, [], map);
102
- // Should still include the comment section with resolution
103
- assert.ok(prompt.includes('## comment_1'));
104
- assert.ok(prompt.includes('**Resolution**: Fixed'));
105
- // Should NOT include reviewer info since thread was not found
106
- assert.ok(!prompt.includes('**Reviewer**'));
107
- });
108
- void it('handles comment_id not in map gracefully', () => {
109
- const comments = [
110
- { comment_id: 'comment_99', action: 'changed', reply: 'Fixed' },
111
- ];
112
- const prompt = buildLearnerPrompt(comments, [], new Map());
113
- assert.ok(prompt.includes('## comment_99'));
114
- assert.ok(prompt.includes('**Resolution**: Fixed'));
115
- });
116
- void it('omits line when null', () => {
117
- const comments = [
118
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
119
- ];
120
- const threads = [makeThread('t1', { line: null })];
121
- const map = makeMap([['comment_1', 't1']]);
122
- const prompt = buildLearnerPrompt(comments, threads, map);
123
- assert.ok(prompt.includes('**File**: src/index.ts'));
124
- assert.ok(!prompt.includes('**Line**'));
125
- });
126
- void it('uses threadById map correctly for multiple comments', () => {
127
- const comments = [
128
- { comment_id: 'comment_1', action: 'changed', reply: 'Fix A' },
129
- { comment_id: 'comment_3', action: 'changed', reply: 'Fix C' },
130
- ];
131
- const threads = [
132
- makeThread('t1', { body: 'Issue A', path: 'a.ts' }),
133
- makeThread('t2', { body: 'Issue B', path: 'b.ts' }),
134
- makeThread('t3', { body: 'Issue C', path: 'c.ts' }),
135
- ];
136
- const map = makeMap([
137
- ['comment_1', 't1'],
138
- ['comment_2', 't2'],
139
- ['comment_3', 't3'],
140
- ]);
141
- const prompt = buildLearnerPrompt(comments, threads, map);
142
- assert.ok(prompt.includes('Issue A'));
143
- assert.ok(prompt.includes('a.ts'));
144
- assert.ok(prompt.includes('Issue C'));
145
- assert.ok(prompt.includes('c.ts'));
146
- // comment_2 was not in addressedComments, should not appear
147
- assert.ok(!prompt.includes('Issue B'));
148
- });
149
- void it('includes instructions section', () => {
150
- const comments = [
151
- { comment_id: 'comment_1', action: 'changed', reply: 'Fixed' },
152
- ];
153
- const prompt = buildLearnerPrompt(comments, [makeThread('t1')], makeMap([['comment_1', 't1']]));
154
- assert.ok(prompt.includes('## Instructions'));
155
- assert.ok(prompt.includes('code_review checklists'));
156
- });
157
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,116 +0,0 @@
1
- import assert from 'node:assert';
2
- import { describe, it } from 'node:test';
3
- import { createResolveSystemPrompt, createResolveUserPrompt, } from '../prompts.js';
4
- function makeThread(id, overrides) {
5
- const nodes = [
6
- {
7
- id: `${id}-comment-1`,
8
- author: { login: overrides?.author || 'reviewer' },
9
- body: overrides?.body || 'Please fix this',
10
- path: overrides?.path || 'src/index.ts',
11
- line: overrides?.line ?? 10,
12
- url: `https://github.com/owner/repo/pull/1#discussion_${id}`,
13
- },
14
- ];
15
- if (overrides?.followUps) {
16
- for (const fu of overrides.followUps) {
17
- nodes.push({
18
- id: `${id}-followup`,
19
- author: { login: fu.author },
20
- body: fu.body,
21
- path: overrides?.path || 'src/index.ts',
22
- line: overrides?.line ?? 10,
23
- url: `https://github.com/owner/repo/pull/1#discussion_${id}_fu`,
24
- });
25
- }
26
- }
27
- return {
28
- id,
29
- isResolved: false,
30
- isOutdated: false,
31
- comments: {
32
- totalCount: nodes.length,
33
- nodes,
34
- },
35
- };
36
- }
37
- void describe('createResolveSystemPrompt', () => {
38
- void it('includes decision criteria', () => {
39
- const prompt = createResolveSystemPrompt();
40
- assert.ok(prompt.includes('Make the change when'));
41
- assert.ok(prompt.includes('Skip the change when'));
42
- });
43
- void it('specifies comment_id format', () => {
44
- const prompt = createResolveSystemPrompt();
45
- assert.ok(prompt.includes('comment_id'));
46
- assert.ok(prompt.includes('comment_1'));
47
- });
48
- void it('includes result format', () => {
49
- const prompt = createResolveSystemPrompt();
50
- assert.ok(prompt.includes('resolve_result'));
51
- assert.ok(prompt.includes('"action"'));
52
- assert.ok(prompt.includes('"reply"'));
53
- });
54
- });
55
- void describe('createResolveUserPrompt', () => {
56
- void it('uses sequential comment IDs not thread IDs', () => {
57
- const threads = [makeThread('PRRT_kwDOxx_1'), makeThread('PRRT_kwDOxx_2')];
58
- const { prompt, commentIdToThreadId } = createResolveUserPrompt(threads);
59
- // Should use comment_1, comment_2 in the prompt
60
- assert.ok(prompt.includes('## comment_1'));
61
- assert.ok(prompt.includes('## comment_2'));
62
- // Should NOT include opaque GraphQL IDs
63
- assert.ok(!prompt.includes('PRRT_kwDOxx_1'));
64
- assert.ok(!prompt.includes('PRRT_kwDOxx_2'));
65
- // Mapping should be correct
66
- assert.strictEqual(commentIdToThreadId.get('comment_1'), 'PRRT_kwDOxx_1');
67
- assert.strictEqual(commentIdToThreadId.get('comment_2'), 'PRRT_kwDOxx_2');
68
- assert.strictEqual(commentIdToThreadId.size, 2);
69
- });
70
- void it('includes file path and line number', () => {
71
- const threads = [makeThread('t1', { path: 'src/auth.ts', line: 42 })];
72
- const { prompt } = createResolveUserPrompt(threads);
73
- assert.ok(prompt.includes('src/auth.ts'));
74
- assert.ok(prompt.includes('42'));
75
- });
76
- void it('includes comment body', () => {
77
- const threads = [
78
- makeThread('t1', { body: 'This should use a const instead of let' }),
79
- ];
80
- const { prompt } = createResolveUserPrompt(threads);
81
- assert.ok(prompt.includes('This should use a const instead of let'));
82
- });
83
- void it('includes follow-up comments', () => {
84
- const threads = [
85
- makeThread('t1', {
86
- body: 'Main comment',
87
- followUps: [{ author: 'dev', body: 'I disagree because...' }],
88
- }),
89
- ];
90
- const { prompt } = createResolveUserPrompt(threads);
91
- assert.ok(prompt.includes('Main comment'));
92
- assert.ok(prompt.includes('I disagree because...'));
93
- assert.ok(prompt.includes('@dev'));
94
- });
95
- void it('includes instruction to use exact comment IDs', () => {
96
- const threads = [makeThread('t1'), makeThread('t2'), makeThread('t3')];
97
- const { prompt } = createResolveUserPrompt(threads);
98
- assert.ok(prompt.includes('comment_1, comment_2, comment_3'));
99
- });
100
- void it('handles threads with no comments gracefully', () => {
101
- const emptyThread = {
102
- id: 'empty',
103
- isResolved: false,
104
- isOutdated: false,
105
- comments: { totalCount: 0, nodes: [] },
106
- };
107
- const { commentIdToThreadId } = createResolveUserPrompt([emptyThread]);
108
- // Empty thread should be skipped (no comment nodes to index)
109
- assert.strictEqual(commentIdToThreadId.size, 0);
110
- });
111
- void it('returns correct count in header', () => {
112
- const threads = [makeThread('t1'), makeThread('t2')];
113
- const { prompt } = createResolveUserPrompt(threads);
114
- assert.ok(prompt.includes('2 unresolved review comment(s)'));
115
- });
116
- });