@qelos/aidev 0.5.1 → 0.5.3

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 (106) hide show
  1. package/.aidev/assets/86c8yjxrr/9ea11c36-311c-4022-889c-1bb0915122dc.jpg-40e6939e3b68e864260f7ae7e85bd623_exif.jpg +0 -0
  2. package/.env.aidev.example +92 -84
  3. package/CONTRIBUTING.md +78 -78
  4. package/LICENSE +21 -21
  5. package/README.md +735 -622
  6. package/dist/autoCompress.d.ts +54 -0
  7. package/dist/autoCompress.d.ts.map +1 -0
  8. package/dist/autoCompress.js +300 -0
  9. package/dist/autoCompress.js.map +1 -0
  10. package/dist/cli.js +13 -0
  11. package/dist/cli.js.map +1 -1
  12. package/dist/commands/accepted.d.ts +7 -2
  13. package/dist/commands/accepted.d.ts.map +1 -1
  14. package/dist/commands/accepted.js +17 -2
  15. package/dist/commands/accepted.js.map +1 -1
  16. package/dist/commands/help.d.ts.map +1 -1
  17. package/dist/commands/help.js +80 -67
  18. package/dist/commands/help.js.map +1 -1
  19. package/dist/commands/init.js +105 -105
  20. package/dist/commands/run.d.ts +9 -1
  21. package/dist/commands/run.d.ts.map +1 -1
  22. package/dist/commands/run.js +307 -140
  23. package/dist/commands/run.js.map +1 -1
  24. package/dist/commands/tasks.d.ts +1 -0
  25. package/dist/commands/tasks.d.ts.map +1 -1
  26. package/dist/commands/tasks.js +29 -0
  27. package/dist/commands/tasks.js.map +1 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +6 -0
  30. package/dist/config.js.map +1 -1
  31. package/dist/github.js +27 -27
  32. package/dist/hooks.d.ts +1 -1
  33. package/dist/hooks.d.ts.map +1 -1
  34. package/dist/providers/linear.d.ts +4 -1
  35. package/dist/providers/linear.d.ts.map +1 -1
  36. package/dist/providers/linear.js +142 -78
  37. package/dist/providers/linear.js.map +1 -1
  38. package/dist/providers/monday.js +39 -39
  39. package/dist/sessions.d.ts +20 -0
  40. package/dist/sessions.d.ts.map +1 -0
  41. package/dist/sessions.js +171 -0
  42. package/dist/sessions.js.map +1 -0
  43. package/dist/types.d.ts +2 -0
  44. package/dist/types.d.ts.map +1 -1
  45. package/package.json +51 -51
  46. package/scripts/run-tests.cjs +18 -18
  47. package/dist/__tests__/ai-runners.test.d.ts +0 -2
  48. package/dist/__tests__/ai-runners.test.d.ts.map +0 -1
  49. package/dist/__tests__/ai-runners.test.js +0 -367
  50. package/dist/__tests__/ai-runners.test.js.map +0 -1
  51. package/dist/__tests__/clickup-format.test.d.ts +0 -2
  52. package/dist/__tests__/clickup-format.test.d.ts.map +0 -1
  53. package/dist/__tests__/clickup-format.test.js +0 -256
  54. package/dist/__tests__/clickup-format.test.js.map +0 -1
  55. package/dist/__tests__/config.test.d.ts +0 -2
  56. package/dist/__tests__/config.test.d.ts.map +0 -1
  57. package/dist/__tests__/config.test.js +0 -418
  58. package/dist/__tests__/config.test.js.map +0 -1
  59. package/dist/__tests__/git.test.d.ts +0 -2
  60. package/dist/__tests__/git.test.d.ts.map +0 -1
  61. package/dist/__tests__/git.test.js +0 -697
  62. package/dist/__tests__/git.test.js.map +0 -1
  63. package/dist/__tests__/github.test.d.ts +0 -2
  64. package/dist/__tests__/github.test.d.ts.map +0 -1
  65. package/dist/__tests__/github.test.js +0 -273
  66. package/dist/__tests__/github.test.js.map +0 -1
  67. package/dist/__tests__/help.test.d.ts +0 -2
  68. package/dist/__tests__/help.test.d.ts.map +0 -1
  69. package/dist/__tests__/help.test.js +0 -34
  70. package/dist/__tests__/help.test.js.map +0 -1
  71. package/dist/__tests__/hooks.test.d.ts +0 -2
  72. package/dist/__tests__/hooks.test.d.ts.map +0 -1
  73. package/dist/__tests__/hooks.test.js +0 -381
  74. package/dist/__tests__/hooks.test.js.map +0 -1
  75. package/dist/__tests__/init.test.d.ts +0 -2
  76. package/dist/__tests__/init.test.d.ts.map +0 -1
  77. package/dist/__tests__/init.test.js +0 -596
  78. package/dist/__tests__/init.test.js.map +0 -1
  79. package/dist/__tests__/local-provider.test.d.ts +0 -2
  80. package/dist/__tests__/local-provider.test.d.ts.map +0 -1
  81. package/dist/__tests__/local-provider.test.js +0 -631
  82. package/dist/__tests__/local-provider.test.js.map +0 -1
  83. package/dist/__tests__/lockfile.test.d.ts +0 -2
  84. package/dist/__tests__/lockfile.test.d.ts.map +0 -1
  85. package/dist/__tests__/lockfile.test.js +0 -144
  86. package/dist/__tests__/lockfile.test.js.map +0 -1
  87. package/dist/__tests__/permissions.test.d.ts +0 -2
  88. package/dist/__tests__/permissions.test.d.ts.map +0 -1
  89. package/dist/__tests__/permissions.test.js +0 -151
  90. package/dist/__tests__/permissions.test.js.map +0 -1
  91. package/dist/__tests__/platform.test.d.ts +0 -2
  92. package/dist/__tests__/platform.test.d.ts.map +0 -1
  93. package/dist/__tests__/platform.test.js +0 -329
  94. package/dist/__tests__/platform.test.js.map +0 -1
  95. package/dist/__tests__/providers.test.d.ts +0 -2
  96. package/dist/__tests__/providers.test.d.ts.map +0 -1
  97. package/dist/__tests__/providers.test.js +0 -925
  98. package/dist/__tests__/providers.test.js.map +0 -1
  99. package/dist/__tests__/run.test.d.ts +0 -2
  100. package/dist/__tests__/run.test.d.ts.map +0 -1
  101. package/dist/__tests__/run.test.js +0 -767
  102. package/dist/__tests__/run.test.js.map +0 -1
  103. package/dist/__tests__/schedule.test.d.ts +0 -2
  104. package/dist/__tests__/schedule.test.d.ts.map +0 -1
  105. package/dist/__tests__/schedule.test.js +0 -258
  106. package/dist/__tests__/schedule.test.js.map +0 -1
@@ -1,767 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- const node_test_1 = require("node:test");
7
- const strict_1 = __importDefault(require("node:assert/strict"));
8
- const run_1 = require("../commands/run");
9
- const github_1 = require("../github");
10
- const baseConfig = {
11
- provider: 'clickup',
12
- githubRepo: 'owner/repo',
13
- githubBaseBranch: 'main',
14
- clickupInReviewStatus: 'review',
15
- commentPrefix: '[aidev]',
16
- };
17
- // ─── buildPRUrl ───────────────────────────────────────────────────────────────
18
- (0, node_test_1.describe)('buildPRUrl', () => {
19
- (0, node_test_1.it)('returns a GitHub compare URL', () => {
20
- const url = (0, run_1.buildPRUrl)(baseConfig, 'abc123/fix-login');
21
- strict_1.default.ok(url.startsWith('https://github.com/owner/repo/compare/'));
22
- strict_1.default.ok(url.includes('main...'));
23
- strict_1.default.ok(url.includes('expand=1'));
24
- });
25
- (0, node_test_1.it)('URL-encodes the branch name', () => {
26
- const url = (0, run_1.buildPRUrl)(baseConfig, 'abc/fix login');
27
- strict_1.default.ok(url.includes('fix%20login'));
28
- });
29
- (0, node_test_1.it)('returns empty string when githubRepo is not set', () => {
30
- const url = (0, run_1.buildPRUrl)({ ...baseConfig, githubRepo: '' }, 'abc/branch');
31
- strict_1.default.equal(url, '');
32
- });
33
- });
34
- // ─── buildPRBody ─────────────────────────────────────────────────────────────
35
- (0, node_test_1.describe)('buildPRBody', () => {
36
- const task = {
37
- id: 'abc123',
38
- name: 'Fix bug',
39
- description: 'Fix the login bug',
40
- status: 'open',
41
- url: 'https://app.clickup.com/t/abc123',
42
- tags: ['myproject'],
43
- };
44
- (0, node_test_1.it)('includes the task URL', () => {
45
- const body = (0, run_1.buildPRBody)(task);
46
- strict_1.default.ok(body.includes('https://app.clickup.com/t/abc123'));
47
- });
48
- (0, node_test_1.it)('uses default signature when PR_SIGNATURE is not set', () => {
49
- const original = process.env.PR_SIGNATURE;
50
- delete process.env.PR_SIGNATURE;
51
- const body = (0, run_1.buildPRBody)(task);
52
- strict_1.default.ok(body.includes('Automated PR by aidev.'));
53
- if (original !== undefined)
54
- process.env.PR_SIGNATURE = original;
55
- });
56
- (0, node_test_1.it)('uses PR_SIGNATURE env var when set', () => {
57
- const original = process.env.PR_SIGNATURE;
58
- process.env.PR_SIGNATURE = 'Custom signature from CI';
59
- const body = (0, run_1.buildPRBody)(task);
60
- strict_1.default.ok(body.includes('Custom signature from CI'));
61
- strict_1.default.ok(!body.includes('Automated PR by aidev.'));
62
- if (original !== undefined) {
63
- process.env.PR_SIGNATURE = original;
64
- }
65
- else {
66
- delete process.env.PR_SIGNATURE;
67
- }
68
- });
69
- (0, node_test_1.it)('formats as "Implements: <url>\\n\\n<signature>"', () => {
70
- const original = process.env.PR_SIGNATURE;
71
- delete process.env.PR_SIGNATURE;
72
- const body = (0, run_1.buildPRBody)(task);
73
- strict_1.default.equal(body, 'Implements: https://app.clickup.com/t/abc123\n\nAutomated PR by aidev.');
74
- if (original !== undefined)
75
- process.env.PR_SIGNATURE = original;
76
- });
77
- });
78
- // ─── buildCompletionComment ───────────────────────────────────────────────────
79
- (0, node_test_1.describe)('buildCompletionComment', () => {
80
- (0, node_test_1.it)('includes the branch name', () => {
81
- const comment = (0, run_1.buildCompletionComment)('abc123/fix-login', '', baseConfig);
82
- strict_1.default.ok(comment.includes('abc123/fix-login'));
83
- });
84
- (0, node_test_1.it)('includes the in-review status', () => {
85
- const comment = (0, run_1.buildCompletionComment)('abc/branch', '', baseConfig);
86
- strict_1.default.ok(comment.includes('review'));
87
- });
88
- (0, node_test_1.it)('includes PR link when provided', () => {
89
- const comment = (0, run_1.buildCompletionComment)('abc/branch', 'https://github.com/pr/1', baseConfig);
90
- strict_1.default.ok(comment.includes('https://github.com/pr/1'));
91
- });
92
- (0, node_test_1.it)('omits PR link when empty', () => {
93
- const comment = (0, run_1.buildCompletionComment)('abc/branch', '', baseConfig);
94
- strict_1.default.ok(!comment.includes('https://'));
95
- });
96
- (0, node_test_1.it)('uses custom prefix when configured', () => {
97
- const customConfig = { ...baseConfig, commentPrefix: '[mybot]' };
98
- const comment = (0, run_1.buildCompletionComment)('abc/branch', '', customConfig);
99
- strict_1.default.ok(comment.startsWith('[mybot]'));
100
- strict_1.default.ok(!comment.includes('[aidev]'));
101
- });
102
- });
103
- // ─── buildImplementPrompt ─────────────────────────────────────────────────────
104
- (0, node_test_1.describe)('buildImplementPrompt', () => {
105
- const task = {
106
- id: 'abc123',
107
- name: 'Fix login bug',
108
- description: 'Users cannot log in with email.',
109
- status: 'open',
110
- url: 'https://app.clickup.com/t/abc123',
111
- tags: ['myproject'],
112
- };
113
- (0, node_test_1.it)('includes the task name', () => {
114
- const prompt = (0, run_1.buildImplementPrompt)(task, '');
115
- strict_1.default.ok(prompt.includes('Fix login bug'));
116
- });
117
- (0, node_test_1.it)('includes the description', () => {
118
- const prompt = (0, run_1.buildImplementPrompt)(task, '');
119
- strict_1.default.ok(prompt.includes('Users cannot log in with email.'));
120
- });
121
- (0, node_test_1.it)('includes context when provided', () => {
122
- const prompt = (0, run_1.buildImplementPrompt)(task, 'Use JWT tokens.');
123
- strict_1.default.ok(prompt.includes('Use JWT tokens.'));
124
- });
125
- (0, node_test_1.it)('handles missing description gracefully', () => {
126
- const prompt = (0, run_1.buildImplementPrompt)({ ...task, description: '' }, '');
127
- strict_1.default.ok(prompt.includes('no description provided'));
128
- });
129
- });
130
- // ─── hasHumanReply ────────────────────────────────────────────────────────────
131
- function makeComment(text) {
132
- return { id: '1', text, author: 'user', authorId: '100', date: Date.now() };
133
- }
134
- (0, node_test_1.describe)('hasHumanReply', () => {
135
- (0, node_test_1.it)('returns false when fewer than 2 comments', () => {
136
- strict_1.default.equal((0, run_1.hasHumanReply)([]), false);
137
- strict_1.default.equal((0, run_1.hasHumanReply)([makeComment('hello')]), false);
138
- });
139
- (0, node_test_1.it)('returns true when last comment is from a human', () => {
140
- const comments = [
141
- makeComment('[aidev] Starting implementation'),
142
- makeComment('Please also fix the tests'),
143
- ];
144
- strict_1.default.equal((0, run_1.hasHumanReply)(comments), true);
145
- });
146
- (0, node_test_1.it)('returns false when last comment is from aidev', () => {
147
- const comments = [
148
- makeComment('[aidev] Starting implementation'),
149
- makeComment('[aidev] All AI runners failed.'),
150
- ];
151
- strict_1.default.equal((0, run_1.hasHumanReply)(comments), false);
152
- });
153
- (0, node_test_1.it)('uses custom prefix when provided', () => {
154
- const comments = [
155
- makeComment('[mybot] Starting implementation'),
156
- makeComment('Please also fix the tests'),
157
- ];
158
- strict_1.default.equal((0, run_1.hasHumanReply)(comments, '[mybot]'), true);
159
- });
160
- (0, node_test_1.it)('returns false when last comment has custom prefix', () => {
161
- const comments = [
162
- makeComment('[mybot] Starting implementation'),
163
- makeComment('[mybot] All AI runners failed.'),
164
- ];
165
- strict_1.default.equal((0, run_1.hasHumanReply)(comments, '[mybot]'), false);
166
- });
167
- });
168
- // ─── hasTriggerWord ───────────────────────────────────────────────────────────
169
- (0, node_test_1.describe)('hasTriggerWord', () => {
170
- (0, node_test_1.it)('returns false on empty comments', () => {
171
- strict_1.default.equal((0, run_1.hasTriggerWord)([], 'aidev-continue'), false);
172
- });
173
- (0, node_test_1.it)('returns true when last comment contains the trigger word', () => {
174
- const comments = [makeComment('aidev-continue please re-run')];
175
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), true);
176
- });
177
- (0, node_test_1.it)('is case-insensitive', () => {
178
- const comments = [makeComment('AIDEV-CONTINUE')];
179
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), true);
180
- });
181
- (0, node_test_1.it)('returns false when trigger word is absent', () => {
182
- const comments = [makeComment('Please fix the tests')];
183
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), false);
184
- });
185
- (0, node_test_1.it)('returns false when trigger word is empty and no default trigger present', () => {
186
- const comments = [makeComment('any text')];
187
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, ''), false);
188
- });
189
- // ── Default trigger word always works regardless of configured triggerWord ──
190
- (0, node_test_1.it)('DEFAULT_TRIGGER_WORD is "aidev-continue"', () => {
191
- strict_1.default.equal(run_1.DEFAULT_TRIGGER_WORD, 'aidev-continue');
192
- });
193
- (0, node_test_1.it)('aidev-continue works even when triggerWord is empty (no env config)', () => {
194
- const comments = [makeComment('aidev-continue')];
195
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, ''), true);
196
- });
197
- (0, node_test_1.it)('aidev-continue works even when a different triggerWord is configured', () => {
198
- const comments = [makeComment('aidev-continue')];
199
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'my-custom-word'), true);
200
- });
201
- (0, node_test_1.it)('configured custom trigger word also works', () => {
202
- const comments = [makeComment('my-custom-word go ahead')];
203
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'my-custom-word'), true);
204
- });
205
- (0, node_test_1.it)('only checks the last comment — earlier trigger word is ignored', () => {
206
- const comments = [
207
- makeComment('aidev-continue'),
208
- makeComment('Please review my changes'),
209
- ];
210
- // last comment has no trigger word
211
- strict_1.default.equal((0, run_1.hasTriggerWord)(comments, 'aidev-continue'), false);
212
- });
213
- });
214
- // ─── checkNeedsClarification ──────────────────────────────────────────────────
215
- function makeRunner(name, success, output) {
216
- return {
217
- name,
218
- isAvailable: () => true,
219
- run: async (_prompt) => ({ success, output, error: success ? '' : 'error' }),
220
- };
221
- }
222
- const clarificationTask = {
223
- id: 'abc123',
224
- name: 'Add dark mode',
225
- description: 'Support a dark color scheme.',
226
- status: 'open',
227
- url: 'https://app.clickup.com/t/abc123',
228
- tags: ['myproject'],
229
- };
230
- const clarificationConfig = {
231
- devNotesMode: 'smart',
232
- clickupPendingStatus: 'pending',
233
- };
234
- const mockProvider = {
235
- fetchTasks: async () => [],
236
- fetchTasksByStatus: async () => [],
237
- getComments: async () => [],
238
- postComment: async () => { },
239
- updateStatus: async () => { },
240
- createTask: async () => ({ id: '', url: '' }),
241
- };
242
- (0, node_test_1.describe)('checkNeedsClarification', () => {
243
- (0, node_test_1.it)('returns a question when runner says task is not clear', async () => {
244
- const runner = makeRunner('claude', true, JSON.stringify({ clear: false, question: 'Which color scheme?' }));
245
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [runner]);
246
- strict_1.default.equal(q, 'Which color scheme?');
247
- });
248
- (0, node_test_1.it)('returns null when runner says task is clear', async () => {
249
- const runner = makeRunner('claude', true, JSON.stringify({ clear: true, question: null }));
250
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [runner]);
251
- strict_1.default.equal(q, null);
252
- });
253
- (0, node_test_1.it)('falls back to next runner when first runner fails', async () => {
254
- const failing = makeRunner('cursor', false, '');
255
- const working = makeRunner('claude', true, JSON.stringify({ clear: false, question: 'Any preferences?' }));
256
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [failing, working]);
257
- strict_1.default.equal(q, 'Any preferences?');
258
- });
259
- (0, node_test_1.it)('falls back to next runner when first runner returns unparseable JSON', async () => {
260
- const bad = makeRunner('cursor', true, 'not json at all');
261
- const good = makeRunner('claude', true, JSON.stringify({ clear: true, question: null }));
262
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [bad, good]);
263
- strict_1.default.equal(q, null);
264
- });
265
- (0, node_test_1.it)('returns null when all runners fail', async () => {
266
- const a = makeRunner('cursor', false, '');
267
- const b = makeRunner('windsurf', false, '');
268
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [a, b]);
269
- strict_1.default.equal(q, null);
270
- });
271
- (0, node_test_1.it)('returns null when no runners are available', async () => {
272
- const unavailable = { name: 'cursor', isAvailable: () => false, run: async () => ({ success: false, output: '', error: '' }) };
273
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, clarificationConfig, mockProvider, [unavailable]);
274
- strict_1.default.equal(q, null);
275
- });
276
- (0, node_test_1.it)('returns the always-ask clarification prompt when devNotesMode is "always"', async () => {
277
- const config = { ...clarificationConfig, devNotesMode: 'always' };
278
- const q = await (0, run_1.checkNeedsClarification)(clarificationTask, config, mockProvider, []);
279
- strict_1.default.ok(q !== null && q.includes('Add dark mode'));
280
- });
281
- });
282
- // ─── hasAidevComment ──────────────────────────────────────────────────────────
283
- (0, node_test_1.describe)('hasAidevComment', () => {
284
- (0, node_test_1.it)('returns false on empty comments', () => {
285
- strict_1.default.equal((0, run_1.hasAidevComment)([]), false);
286
- });
287
- (0, node_test_1.it)('returns true when a comment contains [aidev]', () => {
288
- const comments = [
289
- makeComment('[aidev] Starting implementation'),
290
- makeComment('Some human reply'),
291
- ];
292
- strict_1.default.equal((0, run_1.hasAidevComment)(comments), true);
293
- });
294
- (0, node_test_1.it)('returns false when no comment contains [aidev]', () => {
295
- const comments = [
296
- makeComment('Please fix the tests'),
297
- makeComment('I agree, this needs work'),
298
- ];
299
- strict_1.default.equal((0, run_1.hasAidevComment)(comments), false);
300
- });
301
- (0, node_test_1.it)('returns true when only the last comment is from aidev', () => {
302
- const comments = [
303
- makeComment('Human wrote this'),
304
- makeComment('[aidev] Non-code task complete!'),
305
- ];
306
- strict_1.default.equal((0, run_1.hasAidevComment)(comments), true);
307
- });
308
- (0, node_test_1.it)('detects custom prefix', () => {
309
- const comments = [
310
- makeComment('[mybot] Starting implementation'),
311
- ];
312
- strict_1.default.equal((0, run_1.hasAidevComment)(comments, '[mybot]'), true);
313
- });
314
- (0, node_test_1.it)('returns false when custom prefix is not present', () => {
315
- const comments = [
316
- makeComment('[aidev] Starting implementation'),
317
- ];
318
- strict_1.default.equal((0, run_1.hasAidevComment)(comments, '[mybot]'), false);
319
- });
320
- });
321
- // ─── filterAutomatedComments ──────────────────────────────────────────────────
322
- (0, node_test_1.describe)('filterAutomatedComments', () => {
323
- (0, node_test_1.it)('returns empty array for empty input', () => {
324
- strict_1.default.deepEqual((0, run_1.filterAutomatedComments)([]), []);
325
- });
326
- (0, node_test_1.it)('removes comments containing [aidev]', () => {
327
- const human = makeComment('Please fix the tests');
328
- const automated = makeComment('[aidev] Starting implementation on branch foo');
329
- const result = (0, run_1.filterAutomatedComments)([human, automated]);
330
- strict_1.default.deepEqual(result, [human]);
331
- });
332
- (0, node_test_1.it)('keeps all comments when none are automated', () => {
333
- const comments = [
334
- makeComment('Please fix the tests'),
335
- makeComment('I agree, this needs work'),
336
- ];
337
- strict_1.default.deepEqual((0, run_1.filterAutomatedComments)(comments), comments);
338
- });
339
- (0, node_test_1.it)('removes all comments when all are automated', () => {
340
- const comments = [
341
- makeComment('[aidev] Starting implementation'),
342
- makeComment('[aidev] Merge conflicts resolved automatically.'),
343
- ];
344
- strict_1.default.deepEqual((0, run_1.filterAutomatedComments)(comments), []);
345
- });
346
- (0, node_test_1.it)('filters by custom prefix', () => {
347
- const human = makeComment('Please fix the tests');
348
- const automated = makeComment('[mybot] Starting implementation on branch foo');
349
- const result = (0, run_1.filterAutomatedComments)([human, automated], '[mybot]');
350
- strict_1.default.deepEqual(result, [human]);
351
- });
352
- });
353
- // ─── buildNonCodeCompletionComment ────────────────────────────────────────────
354
- (0, node_test_1.describe)('buildNonCodeCompletionComment', () => {
355
- (0, node_test_1.it)('includes the in-review status', () => {
356
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
357
- strict_1.default.ok(comment.includes('review'));
358
- });
359
- (0, node_test_1.it)('starts with the [aidev] prefix', () => {
360
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
361
- strict_1.default.ok(comment.startsWith('[aidev]'));
362
- });
363
- (0, node_test_1.it)('mentions non-code task', () => {
364
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
365
- strict_1.default.ok(comment.includes('Non-code task complete'));
366
- });
367
- (0, node_test_1.it)('does not include branch or PR info', () => {
368
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
369
- strict_1.default.ok(!comment.includes('Branch:'));
370
- strict_1.default.ok(!comment.includes('PR'));
371
- });
372
- (0, node_test_1.it)('includes agent response when provided', () => {
373
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig, 'The answer to your question is 42.');
374
- strict_1.default.ok(comment.includes('The answer to your question is 42.'));
375
- strict_1.default.ok(comment.includes('[aidev] Non-code task complete'));
376
- });
377
- (0, node_test_1.it)('omits agent response section when not provided', () => {
378
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig);
379
- strict_1.default.ok(!comment.includes('---'));
380
- });
381
- (0, node_test_1.it)('uses custom prefix when configured', () => {
382
- const customConfig = { ...baseConfig, commentPrefix: '[mybot]' };
383
- const comment = (0, run_1.buildNonCodeCompletionComment)(customConfig);
384
- strict_1.default.ok(comment.startsWith('[mybot]'));
385
- strict_1.default.ok(!comment.includes('[aidev]'));
386
- });
387
- (0, node_test_1.it)('filters out instructional text from agent response', () => {
388
- const agentResponse = `Here's text you can paste as the **task ticket comment** (addresses your latest ask: do it + push):
389
-
390
- ---
391
-
392
- **Done — publish + push**
393
-
394
- I published **one English post** from the LinkedIn drafts, using the **oldest draft by git history** (first commit that added the file): \`qelos-plugins-microfrontends.md\` (committed 2026-03-11 00:51, before \`qelos-netlify-plugin.md\`). The body sent to LinkedIn was the **\`## Post (English)\`** section, **without markdown** (no \`**\`, \`\`\`, or \`#\` headings) so it matches what the text webhook expects.`;
395
- const comment = (0, run_1.buildNonCodeCompletionComment)(baseConfig, agentResponse);
396
- // Should not include the instructional text
397
- strict_1.default.ok(!comment.includes('Here\'s text you can paste as the'));
398
- strict_1.default.ok(!comment.includes('task ticket comment'));
399
- strict_1.default.ok(!comment.includes('addresses your latest ask'));
400
- // Should include the actual content
401
- strict_1.default.ok(comment.includes('Done — publish + push'));
402
- strict_1.default.ok(comment.includes('I published **one English post** from the LinkedIn drafts'));
403
- strict_1.default.ok(comment.includes('qelos-plugins-microfrontends.md'));
404
- });
405
- });
406
- // ─── buildNonCodePrompt ──────────────────────────────────────────────────────
407
- (0, node_test_1.describe)('buildNonCodePrompt', () => {
408
- const task = { id: '1', name: 'Research question', description: 'What is X?', status: 'open', url: 'http://example.com', tags: [] };
409
- (0, node_test_1.it)('includes task name and description', () => {
410
- const prompt = (0, run_1.buildNonCodePrompt)(task, '');
411
- strict_1.default.ok(prompt.includes('Research question'));
412
- strict_1.default.ok(prompt.includes('What is X?'));
413
- });
414
- (0, node_test_1.it)('includes conversation context when provided', () => {
415
- const prompt = (0, run_1.buildNonCodePrompt)(task, '\n\nConversation context:\nAlice: Please clarify');
416
- strict_1.default.ok(prompt.includes('Alice: Please clarify'));
417
- });
418
- (0, node_test_1.it)('instructs AI to focus on latest comment when comments exist', () => {
419
- const prompt = (0, run_1.buildNonCodePrompt)(task, '\n\nConversation context:\nAlice: Change the env file');
420
- strict_1.default.ok(prompt.includes('LATEST comment'));
421
- strict_1.default.ok(prompt.includes('Original description'));
422
- strict_1.default.ok(prompt.includes('CRITICAL'));
423
- strict_1.default.ok(prompt.includes('FOLLOW-UP request'));
424
- strict_1.default.ok(prompt.includes('DO NOT repeat what was already done'));
425
- });
426
- (0, node_test_1.it)('does not mention latest comment when no comments', () => {
427
- const prompt = (0, run_1.buildNonCodePrompt)(task, '');
428
- strict_1.default.ok(!prompt.includes('LATEST comment'));
429
- strict_1.default.ok(!prompt.includes('Original description'));
430
- });
431
- });
432
- // ─── sortTasksByPriority ─────────────────────────────────────────────────────
433
- function makeTask(id, priority) {
434
- return { id, name: `task-${id}`, description: '', status: 'open', url: '', tags: [], priority };
435
- }
436
- (0, node_test_1.describe)('sortTasksByPriority', () => {
437
- (0, node_test_1.it)('sorts tasks by priority ascending (urgent first)', () => {
438
- const tasks = [makeTask('a', 4), makeTask('b', 1), makeTask('c', 2)];
439
- const sorted = (0, run_1.sortTasksByPriority)(tasks);
440
- strict_1.default.deepEqual(sorted.map((t) => t.id), ['b', 'c', 'a']);
441
- });
442
- (0, node_test_1.it)('puts tasks without priority last', () => {
443
- const tasks = [makeTask('a'), makeTask('b', 2), makeTask('c', 1)];
444
- const sorted = (0, run_1.sortTasksByPriority)(tasks);
445
- strict_1.default.deepEqual(sorted.map((t) => t.id), ['c', 'b', 'a']);
446
- });
447
- (0, node_test_1.it)('preserves relative order among tasks with the same priority', () => {
448
- const tasks = [makeTask('a', 2), makeTask('b', 2), makeTask('c', 2)];
449
- const sorted = (0, run_1.sortTasksByPriority)(tasks);
450
- strict_1.default.deepEqual(sorted.map((t) => t.id), ['a', 'b', 'c']);
451
- });
452
- (0, node_test_1.it)('preserves relative order among tasks without priority', () => {
453
- const tasks = [makeTask('a'), makeTask('b'), makeTask('c')];
454
- const sorted = (0, run_1.sortTasksByPriority)(tasks);
455
- strict_1.default.deepEqual(sorted.map((t) => t.id), ['a', 'b', 'c']);
456
- });
457
- (0, node_test_1.it)('does not mutate the original array', () => {
458
- const tasks = [makeTask('a', 3), makeTask('b', 1)];
459
- const sorted = (0, run_1.sortTasksByPriority)(tasks);
460
- strict_1.default.equal(tasks[0].id, 'a');
461
- strict_1.default.equal(sorted[0].id, 'b');
462
- });
463
- (0, node_test_1.it)('handles empty array', () => {
464
- strict_1.default.deepEqual((0, run_1.sortTasksByPriority)([]), []);
465
- });
466
- (0, node_test_1.it)('handles single task', () => {
467
- const sorted = (0, run_1.sortTasksByPriority)([makeTask('a', 1)]);
468
- strict_1.default.equal(sorted.length, 1);
469
- strict_1.default.equal(sorted[0].id, 'a');
470
- });
471
- });
472
- // ─── getRunSkipReason ─────────────────────────────────────────────────────────
473
- (0, node_test_1.describe)('getRunSkipReason', () => {
474
- (0, node_test_1.it)('allows open tasks for the default all filter', () => {
475
- strict_1.default.equal((0, run_1.getRunSkipReason)('open', 'all', 'pending'), null);
476
- });
477
- (0, node_test_1.it)('allows configured pending tasks for the default all filter', () => {
478
- strict_1.default.equal((0, run_1.getRunSkipReason)('Pending Review', 'all', 'pending review'), null);
479
- });
480
- (0, node_test_1.it)('skips statuses that are neither open nor pending', () => {
481
- strict_1.default.equal((0, run_1.getRunSkipReason)('failed', 'all', 'pending'), 'status "failed" is not open or pending');
482
- });
483
- (0, node_test_1.it)('skips pending tasks for the open filter', () => {
484
- strict_1.default.equal((0, run_1.getRunSkipReason)('pending', 'open', 'pending'), 'filter=open but task is pending');
485
- });
486
- (0, node_test_1.it)('skips open tasks for the pending filter', () => {
487
- strict_1.default.equal((0, run_1.getRunSkipReason)('open', 'pending', 'pending'), 'filter=pending but task is not pending');
488
- });
489
- });
490
- // ─── buildConflictResolutionPrompt ───────────────────────────────────────────
491
- (0, node_test_1.describe)('buildConflictResolutionPrompt', () => {
492
- const task = {
493
- id: 'abc123',
494
- name: 'Add user settings page',
495
- description: 'Create a settings page where users can update their profile.',
496
- status: 'pending',
497
- url: 'https://app.clickup.com/t/abc123',
498
- tags: ['myproject'],
499
- };
500
- (0, node_test_1.it)('includes the task name and description so the agent understands the task', () => {
501
- const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '');
502
- strict_1.default.ok(prompt.includes('Add user settings page'));
503
- strict_1.default.ok(prompt.includes('Create a settings page'));
504
- });
505
- (0, node_test_1.it)('lists all conflicting files', () => {
506
- const files = ['src/app.ts', 'src/config.ts', 'package.json'];
507
- const prompt = (0, run_1.buildConflictResolutionPrompt)(task, files, '');
508
- for (const f of files) {
509
- strict_1.default.ok(prompt.includes(f));
510
- }
511
- });
512
- (0, node_test_1.it)('includes conversation context when provided', () => {
513
- const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '\n\nConversation context:\nAlice: Use tabs not spaces');
514
- strict_1.default.ok(prompt.includes('Alice: Use tabs not spaces'));
515
- });
516
- (0, node_test_1.it)('instructs to preserve the task intent', () => {
517
- const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '');
518
- strict_1.default.ok(prompt.includes("task's intent") || prompt.includes("task's changes"));
519
- });
520
- (0, node_test_1.it)('instructs to remove conflict markers', () => {
521
- const prompt = (0, run_1.buildConflictResolutionPrompt)(task, ['src/app.ts'], '');
522
- strict_1.default.ok(prompt.includes('<<<<<<'));
523
- strict_1.default.ok(prompt.includes('>>>>>>>'));
524
- });
525
- (0, node_test_1.it)('handles missing description gracefully', () => {
526
- const prompt = (0, run_1.buildConflictResolutionPrompt)({ ...task, description: '' }, ['a.ts'], '');
527
- strict_1.default.ok(prompt.includes('no description provided'));
528
- });
529
- });
530
- // ─── buildReviewPrompt ──────────────────────────────────────────────────────
531
- (0, node_test_1.describe)('buildReviewPrompt', () => {
532
- const reviewTask = {
533
- id: 'rev1',
534
- name: 'Add caching layer',
535
- description: 'Add Redis caching to the API endpoints.',
536
- status: 'review',
537
- url: 'https://app.clickup.com/t/rev1',
538
- tags: ['myproject'],
539
- };
540
- const sampleThreads = [
541
- {
542
- id: 'thread_1',
543
- path: 'src/cache.ts',
544
- line: 42,
545
- comments: [
546
- { author: 'alice', body: 'This TTL should be configurable' },
547
- { author: 'bob', body: 'Agreed, hardcoded values are fragile' },
548
- ],
549
- },
550
- {
551
- id: 'thread_2',
552
- path: 'src/api.ts',
553
- line: 10,
554
- comments: [
555
- { author: 'alice', body: 'Missing error handling for cache miss' },
556
- ],
557
- },
558
- ];
559
- (0, node_test_1.it)('includes task name and description', () => {
560
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
561
- strict_1.default.ok(prompt.includes('Add caching layer'));
562
- strict_1.default.ok(prompt.includes('Add Redis caching to the API endpoints.'));
563
- });
564
- (0, node_test_1.it)('includes all thread file paths and line numbers', () => {
565
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
566
- strict_1.default.ok(prompt.includes('`src/cache.ts` (line 42)'));
567
- strict_1.default.ok(prompt.includes('`src/api.ts` (line 10)'));
568
- });
569
- (0, node_test_1.it)('includes all comment bodies', () => {
570
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
571
- strict_1.default.ok(prompt.includes('This TTL should be configurable'));
572
- strict_1.default.ok(prompt.includes('Agreed, hardcoded values are fragile'));
573
- strict_1.default.ok(prompt.includes('Missing error handling for cache miss'));
574
- });
575
- (0, node_test_1.it)('includes comment authors', () => {
576
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
577
- strict_1.default.ok(prompt.includes('**alice**'));
578
- strict_1.default.ok(prompt.includes('**bob**'));
579
- });
580
- (0, node_test_1.it)('includes thread IDs', () => {
581
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
582
- strict_1.default.ok(prompt.includes('Thread thread_1'));
583
- strict_1.default.ok(prompt.includes('Thread thread_2'));
584
- });
585
- (0, node_test_1.it)('handles threads with no line number', () => {
586
- const threads = [
587
- {
588
- id: 'thread_no_line',
589
- path: 'README.md',
590
- line: null,
591
- comments: [{ author: 'alice', body: 'Update the docs' }],
592
- },
593
- ];
594
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, threads);
595
- strict_1.default.ok(prompt.includes('`README.md`'));
596
- strict_1.default.ok(!prompt.includes('(line'));
597
- });
598
- (0, node_test_1.it)('handles empty threads array', () => {
599
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, []);
600
- strict_1.default.ok(prompt.includes('Add caching layer'));
601
- // Should still produce a valid prompt, just with no thread sections
602
- strict_1.default.ok(!prompt.includes('Thread '));
603
- });
604
- (0, node_test_1.it)('handles missing description gracefully', () => {
605
- const prompt = (0, run_1.buildReviewPrompt)({ ...reviewTask, description: '' }, sampleThreads);
606
- strict_1.default.ok(prompt.includes('no description provided'));
607
- });
608
- (0, node_test_1.it)('includes AIDEV-REPLY instruction', () => {
609
- const prompt = (0, run_1.buildReviewPrompt)(reviewTask, sampleThreads);
610
- strict_1.default.ok(prompt.includes('AIDEV-REPLY'));
611
- });
612
- });
613
- // ─── buildReviewCompletionComment ───────────────────────────────────────────
614
- (0, node_test_1.describe)('buildReviewCompletionComment', () => {
615
- (0, node_test_1.it)('includes resolved count', () => {
616
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 3, 0);
617
- strict_1.default.ok(comment.includes('Resolved 3 thread(s)'));
618
- });
619
- (0, node_test_1.it)('includes replied count', () => {
620
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 0, 2);
621
- strict_1.default.ok(comment.includes('Replied to 2 thread(s)'));
622
- });
623
- (0, node_test_1.it)('includes both resolved and replied counts', () => {
624
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 2, 1);
625
- strict_1.default.ok(comment.includes('Resolved 2 thread(s)'));
626
- strict_1.default.ok(comment.includes('Replied to 1 thread(s)'));
627
- });
628
- (0, node_test_1.it)('uses correct commentPrefix', () => {
629
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 1, 0);
630
- strict_1.default.ok(comment.startsWith('[aidev]'));
631
- });
632
- (0, node_test_1.it)('uses custom commentPrefix', () => {
633
- const customConfig = { ...baseConfig, commentPrefix: '[mybot]' };
634
- const comment = (0, run_1.buildReviewCompletionComment)(customConfig, 1, 1);
635
- strict_1.default.ok(comment.startsWith('[mybot]'));
636
- strict_1.default.ok(!comment.includes('[aidev]'));
637
- });
638
- (0, node_test_1.it)('mentions code review in the message', () => {
639
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 1, 0);
640
- strict_1.default.ok(comment.includes('Code review comments addressed'));
641
- });
642
- (0, node_test_1.it)('omits resolved line when count is zero', () => {
643
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 0, 2);
644
- strict_1.default.ok(!comment.includes('Resolved'));
645
- });
646
- (0, node_test_1.it)('omits replied line when count is zero', () => {
647
- const comment = (0, run_1.buildReviewCompletionComment)(baseConfig, 3, 0);
648
- strict_1.default.ok(!comment.includes('Replied'));
649
- });
650
- });
651
- // ─── parseReplyDirectives ───────────────────────────────────────────────────
652
- (0, node_test_1.describe)('parseReplyDirectives', () => {
653
- (0, node_test_1.it)('extracts a single AIDEV-REPLY block', () => {
654
- const output = 'Some text\n<!-- AIDEV-REPLY thread_abc -->This is my reply<!-- /AIDEV-REPLY -->\nMore text';
655
- const replies = (0, run_1.parseReplyDirectives)(output);
656
- strict_1.default.equal(replies.length, 1);
657
- strict_1.default.equal(replies[0].threadId, 'thread_abc');
658
- strict_1.default.equal(replies[0].body, 'This is my reply');
659
- });
660
- (0, node_test_1.it)('extracts multiple AIDEV-REPLY blocks', () => {
661
- const output = `<!-- AIDEV-REPLY id1 -->Reply one<!-- /AIDEV-REPLY -->
662
- Some middle text
663
- <!-- AIDEV-REPLY id2 -->Reply two<!-- /AIDEV-REPLY -->`;
664
- const replies = (0, run_1.parseReplyDirectives)(output);
665
- strict_1.default.equal(replies.length, 2);
666
- strict_1.default.equal(replies[0].threadId, 'id1');
667
- strict_1.default.equal(replies[0].body, 'Reply one');
668
- strict_1.default.equal(replies[1].threadId, 'id2');
669
- strict_1.default.equal(replies[1].body, 'Reply two');
670
- });
671
- (0, node_test_1.it)('returns empty array when no AIDEV-REPLY blocks present', () => {
672
- const output = 'Just regular agent output with no reply directives';
673
- const replies = (0, run_1.parseReplyDirectives)(output);
674
- strict_1.default.equal(replies.length, 0);
675
- });
676
- (0, node_test_1.it)('trims whitespace from reply body', () => {
677
- const output = '<!-- AIDEV-REPLY thread_x -->\n This has whitespace \n<!-- /AIDEV-REPLY -->';
678
- const replies = (0, run_1.parseReplyDirectives)(output);
679
- strict_1.default.equal(replies.length, 1);
680
- strict_1.default.equal(replies[0].body, 'This has whitespace');
681
- });
682
- (0, node_test_1.it)('handles multiline reply bodies', () => {
683
- const output = '<!-- AIDEV-REPLY thread_y -->Line one\nLine two\nLine three<!-- /AIDEV-REPLY -->';
684
- const replies = (0, run_1.parseReplyDirectives)(output);
685
- strict_1.default.equal(replies.length, 1);
686
- strict_1.default.ok(replies[0].body.includes('Line one'));
687
- strict_1.default.ok(replies[0].body.includes('Line three'));
688
- });
689
- (0, node_test_1.it)('handles thread IDs with base64 characters', () => {
690
- const output = '<!-- AIDEV-REPLY abc+/def= -->Reply<!-- /AIDEV-REPLY -->';
691
- const replies = (0, run_1.parseReplyDirectives)(output);
692
- strict_1.default.equal(replies.length, 1);
693
- strict_1.default.equal(replies[0].threadId, 'abc+/def=');
694
- });
695
- });
696
- // ─── filterUnresolvedByNonAidev ─────────────────────────────────────────────
697
- (0, node_test_1.describe)('filterUnresolvedByNonAidev', () => {
698
- function makeThread(id, comments) {
699
- return { id, path: 'src/file.ts', line: 1, comments };
700
- }
701
- (0, node_test_1.it)('includes thread where last comment is from a human', () => {
702
- const threads = [
703
- makeThread('t1', [
704
- { author: 'alice', body: 'Fix this bug' },
705
- ]),
706
- ];
707
- const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
708
- strict_1.default.equal(result.length, 1);
709
- strict_1.default.equal(result[0].id, 't1');
710
- });
711
- (0, node_test_1.it)('filters out thread where last comment is from aidev', () => {
712
- const threads = [
713
- makeThread('t1', [
714
- { author: 'alice', body: 'Fix this bug' },
715
- { author: 'bot', body: '[aidev] I have addressed this issue' },
716
- ]),
717
- ];
718
- const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
719
- strict_1.default.equal(result.length, 0);
720
- });
721
- (0, node_test_1.it)('handles mixed threads — keeps human-last, filters aidev-last', () => {
722
- const threads = [
723
- makeThread('t1', [
724
- { author: 'alice', body: 'Please refactor this' },
725
- ]),
726
- makeThread('t2', [
727
- { author: 'alice', body: 'Fix the typo' },
728
- { author: 'bot', body: '[aidev] Fixed the typo' },
729
- ]),
730
- makeThread('t3', [
731
- { author: 'bot', body: '[aidev] Done' },
732
- { author: 'bob', body: 'Actually, this is still wrong' },
733
- ]),
734
- ];
735
- const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
736
- strict_1.default.equal(result.length, 2);
737
- strict_1.default.deepEqual(result.map((t) => t.id), ['t1', 't3']);
738
- });
739
- (0, node_test_1.it)('includes thread with empty comments array', () => {
740
- const threads = [makeThread('t1', [])];
741
- const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
742
- strict_1.default.equal(result.length, 1);
743
- });
744
- (0, node_test_1.it)('works with custom comment prefix', () => {
745
- const threads = [
746
- makeThread('t1', [
747
- { author: 'bot', body: '[mybot] I fixed this' },
748
- ]),
749
- makeThread('t2', [
750
- { author: 'alice', body: 'Needs work' },
751
- ]),
752
- ];
753
- const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[mybot]');
754
- strict_1.default.equal(result.length, 1);
755
- strict_1.default.equal(result[0].id, 't2');
756
- });
757
- (0, node_test_1.it)('does not filter when aidev prefix appears mid-comment (not at start)', () => {
758
- const threads = [
759
- makeThread('t1', [
760
- { author: 'alice', body: 'The [aidev] tool did something wrong' },
761
- ]),
762
- ];
763
- const result = (0, github_1.filterUnresolvedByNonAidev)(threads, '[aidev]');
764
- strict_1.default.equal(result.length, 1);
765
- });
766
- });
767
- //# sourceMappingURL=run.test.js.map