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.
- package/package.json +3 -3
- package/tsconfig.build.json +4 -0
- package/tsconfig.json +3 -9
- package/dist/api/__tests__/app-store.test.d.ts +0 -7
- package/dist/api/__tests__/app-store.test.js +0 -60
- package/dist/api/__tests__/intelligence.test.d.ts +0 -11
- package/dist/api/__tests__/intelligence.test.js +0 -315
- package/dist/api/features/__tests__/feature-utils.test.d.ts +0 -4
- package/dist/api/features/__tests__/feature-utils.test.js +0 -370
- package/dist/api/features/__tests__/status-updater.test.d.ts +0 -4
- package/dist/api/features/__tests__/status-updater.test.js +0 -88
- package/dist/commands/build/__tests__/build.test.d.ts +0 -5
- package/dist/commands/build/__tests__/build.test.js +0 -206
- package/dist/commands/build/__tests__/detect-project.test.d.ts +0 -6
- package/dist/commands/build/__tests__/detect-project.test.js +0 -160
- package/dist/commands/build/__tests__/run-build.test.d.ts +0 -6
- package/dist/commands/build/__tests__/run-build.test.js +0 -433
- package/dist/commands/intelligence/__tests__/command.test.d.ts +0 -4
- package/dist/commands/intelligence/__tests__/command.test.js +0 -48
- package/dist/commands/workflow/core/__tests__/feature-filter.test.d.ts +0 -5
- package/dist/commands/workflow/core/__tests__/feature-filter.test.js +0 -316
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/pipeline-evaluator.test.js +0 -397
- package/dist/commands/workflow/core/__tests__/state-manager.test.d.ts +0 -4
- package/dist/commands/workflow/core/__tests__/state-manager.test.js +0 -384
- package/dist/config/__tests__/config.test.d.ts +0 -4
- package/dist/config/__tests__/config.test.js +0 -286
- package/dist/config/__tests__/feature-status.test.d.ts +0 -4
- package/dist/config/__tests__/feature-status.test.js +0 -111
- package/dist/errors/__tests__/index.test.d.ts +0 -4
- package/dist/errors/__tests__/index.test.js +0 -349
- package/dist/phases/app-store-generation/__tests__/agent.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/agent.test.js +0 -142
- package/dist/phases/app-store-generation/__tests__/context.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/context.test.js +0 -284
- package/dist/phases/app-store-generation/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/app-store-generation/__tests__/prompts.test.js +0 -122
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.d.ts +0 -5
- package/dist/phases/app-store-generation/__tests__/screenshot-composer.test.js +0 -826
- package/dist/phases/code-review/__tests__/diff-utils.test.d.ts +0 -1
- package/dist/phases/code-review/__tests__/diff-utils.test.js +0 -101
- package/dist/phases/intelligence-analysis/__tests__/context.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/context.test.js +0 -192
- package/dist/phases/intelligence-analysis/__tests__/matching.test.d.ts +0 -13
- package/dist/phases/intelligence-analysis/__tests__/matching.test.js +0 -154
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.d.ts +0 -5
- package/dist/phases/intelligence-analysis/__tests__/orchestration.test.js +0 -378
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.d.ts +0 -4
- package/dist/phases/intelligence-analysis/__tests__/prompts.test.js +0 -33
- package/dist/phases/pr-execution/__tests__/file-assigner.test.d.ts +0 -1
- package/dist/phases/pr-execution/__tests__/file-assigner.test.js +0 -303
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/checklist-learner.test.js +0 -157
- package/dist/phases/pr-resolve/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/prompts.test.js +0 -116
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/resolve-mapping.test.js +0 -138
- package/dist/phases/pr-resolve/__tests__/types.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/types.test.js +0 -43
- package/dist/phases/pr-resolve/__tests__/workspace.test.d.ts +0 -1
- package/dist/phases/pr-resolve/__tests__/workspace.test.js +0 -111
- package/dist/phases/pr-review/__tests__/prompts.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/prompts.test.js +0 -49
- package/dist/phases/pr-review/__tests__/review-comments.test.d.ts +0 -1
- package/dist/phases/pr-review/__tests__/review-comments.test.js +0 -110
- package/dist/phases/pr-shared/__tests__/agent-utils.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/agent-utils.test.js +0 -91
- package/dist/phases/pr-shared/__tests__/context.test.d.ts +0 -1
- package/dist/phases/pr-shared/__tests__/context.test.js +0 -94
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.d.ts +0 -1
- package/dist/phases/pr-splitting/__tests__/import-dep-validator.test.js +0 -331
- package/dist/phases/release-sync/__tests__/github.test.d.ts +0 -9
- package/dist/phases/release-sync/__tests__/github.test.js +0 -123
- package/dist/phases/release-sync/__tests__/snapshot.test.d.ts +0 -8
- package/dist/phases/release-sync/__tests__/snapshot.test.js +0 -93
- package/dist/phases/smoke-test/__tests__/agent.test.d.ts +0 -4
- package/dist/phases/smoke-test/__tests__/agent.test.js +0 -85
- package/dist/services/coaching/__tests__/coaching-agent.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-agent.test.js +0 -74
- package/dist/services/coaching/__tests__/coaching-loop.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/coaching-loop.test.js +0 -59
- package/dist/services/coaching/__tests__/self-rating.test.d.ts +0 -1
- package/dist/services/coaching/__tests__/self-rating.test.js +0 -188
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/bindings-fetcher.test.js +0 -122
- package/dist/services/phase-hooks/__tests__/hook-executor.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-executor.test.js +0 -321
- package/dist/services/phase-hooks/__tests__/hook-runner.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/hook-runner.test.js +0 -261
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.d.ts +0 -1
- package/dist/services/phase-hooks/__tests__/plugin-loader.test.js +0 -158
- package/dist/services/video/__tests__/video-pipeline.test.d.ts +0 -6
- package/dist/services/video/__tests__/video-pipeline.test.js +0 -249
- package/dist/workspace/__tests__/workspace-manager.test.d.ts +0 -7
- 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 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|