codeep 3.3.3 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/acp/commands.d.ts +50 -1
  2. package/dist/acp/commands.js +545 -109
  3. package/dist/acp/protocol.d.ts +14 -5
  4. package/dist/acp/server.d.ts +36 -1
  5. package/dist/acp/server.js +581 -155
  6. package/dist/acp/serverHandlers.d.ts +2 -1
  7. package/dist/acp/serverHandlers.js +3 -0
  8. package/dist/acp/session.d.ts +28 -2
  9. package/dist/acp/session.js +25 -6
  10. package/dist/acp/transport.d.ts +40 -4
  11. package/dist/acp/transport.js +218 -25
  12. package/dist/acp/turns.d.ts +20 -0
  13. package/dist/acp/turns.js +30 -0
  14. package/dist/api/index.js +2 -0
  15. package/dist/api/ollamaNative.d.ts +3 -0
  16. package/dist/api/ollamaNative.js +35 -3
  17. package/dist/config/index.d.ts +21 -4
  18. package/dist/config/index.js +178 -123
  19. package/dist/renderer/agentExecution.d.ts +30 -2
  20. package/dist/renderer/agentExecution.js +248 -92
  21. package/dist/renderer/commands/helpers.d.ts +18 -2
  22. package/dist/renderer/commands/helpers.js +28 -5
  23. package/dist/renderer/commands.d.ts +2 -0
  24. package/dist/renderer/commands.js +180 -64
  25. package/dist/renderer/main.d.ts +41 -0
  26. package/dist/renderer/main.js +181 -80
  27. package/dist/utils/agent.d.ts +69 -4
  28. package/dist/utils/agent.js +416 -248
  29. package/dist/utils/agentChat.js +82 -10
  30. package/dist/utils/agents.d.ts +2 -1
  31. package/dist/utils/agents.js +100 -29
  32. package/dist/utils/auditLog.d.ts +4 -3
  33. package/dist/utils/auditLog.js +92 -9
  34. package/dist/utils/checkpoints.js +11 -6
  35. package/dist/utils/codeReview.js +28 -23
  36. package/dist/utils/codeepCloud.d.ts +14 -2
  37. package/dist/utils/codeepCloud.js +56 -20
  38. package/dist/utils/customCommands.js +7 -2
  39. package/dist/utils/git.d.ts +262 -4
  40. package/dist/utils/git.js +1928 -61
  41. package/dist/utils/gitHookInstaller.d.ts +32 -1
  42. package/dist/utils/gitHookInstaller.js +76 -8
  43. package/dist/utils/gitignore.d.ts +8 -0
  44. package/dist/utils/gitignore.js +41 -10
  45. package/dist/utils/headlessReview.d.ts +11 -0
  46. package/dist/utils/headlessReview.js +33 -5
  47. package/dist/utils/history.d.ts +22 -6
  48. package/dist/utils/history.js +140 -26
  49. package/dist/utils/logger.js +6 -7
  50. package/dist/utils/mcpConfig.d.ts +24 -0
  51. package/dist/utils/mcpConfig.js +36 -5
  52. package/dist/utils/mentions.d.ts +28 -5
  53. package/dist/utils/mentions.js +253 -45
  54. package/dist/utils/personalities.js +16 -6
  55. package/dist/utils/planMode.d.ts +13 -7
  56. package/dist/utils/planMode.js +32 -12
  57. package/dist/utils/projectIntelligence.d.ts +2 -0
  58. package/dist/utils/projectIntelligence.js +27 -8
  59. package/dist/utils/projectPaths.d.ts +53 -0
  60. package/dist/utils/projectPaths.js +146 -0
  61. package/dist/utils/shell.d.ts +119 -0
  62. package/dist/utils/shell.js +417 -45
  63. package/dist/utils/skillBundles.js +17 -7
  64. package/dist/utils/skillBundlesCloud.js +20 -3
  65. package/dist/utils/skills.d.ts +24 -2
  66. package/dist/utils/skills.js +235 -43
  67. package/dist/utils/smartContext.js +97 -23
  68. package/dist/utils/telegramApproval.d.ts +10 -2
  69. package/dist/utils/telegramApproval.js +22 -4
  70. package/dist/utils/toolExecution.d.ts +50 -2
  71. package/dist/utils/toolExecution.js +418 -16
  72. package/dist/utils/toolParsing.d.ts +7 -1
  73. package/dist/utils/toolParsing.js +12 -3
  74. package/dist/utils/userProfile.js +58 -16
  75. package/dist/utils/verify.d.ts +25 -4
  76. package/dist/utils/verify.js +259 -74
  77. package/dist/version.d.ts +1 -1
  78. package/dist/version.js +1 -1
  79. package/package.json +1 -1
@@ -2,9 +2,9 @@
2
2
  * Self-verification module for agent
3
3
  * Runs build/test and analyzes errors for auto-fixing
4
4
  */
5
- import { existsSync, readFileSync } from 'fs';
6
- import { join } from 'path';
7
- import { executeCommandAsync } from './shell.js';
5
+ import { existsSync, readFileSync, readdirSync } from 'fs';
6
+ import { delimiter, dirname, join, resolve } from 'path';
7
+ import { executeCommandAsync, validateCommandAsync } from './shell.js';
8
8
  const DEFAULT_OPTIONS = {
9
9
  runBuild: true,
10
10
  runTest: true,
@@ -120,45 +120,203 @@ export function detectProjectScripts(projectRoot) {
120
120
  }
121
121
  return result;
122
122
  }
123
+ /** A check that could not be carried out, with the reason. */
124
+ function notRunResult(type, command, reason, output = '', duration = 0) {
125
+ return { success: false, notRun: reason, type, command, output: output || reason, errors: [], duration };
126
+ }
127
+ const STOPPED_REASON = 'Stopped by the user.';
128
+ function timedOutReason(ms) {
129
+ return `Timed out after ${Math.round(ms / 1000)}s. This tool may be too slow for verification.`;
130
+ }
131
+ /** Whether executeCommandAsync stopped the command at its timeout. */
132
+ function commandTimedOut(result) {
133
+ return result.timedOut === true && !result.cancelled;
134
+ }
123
135
  /**
124
- * Run a verification command
136
+ * Why a command that was started produced no exit status of its own, or null
137
+ * when it did. executeCommandAsync reports these cases with exit code -1 and a
138
+ * message of its own in place of the command's stderr.
139
+ */
140
+ function whyCommandDidNotFinish(result, command, duration) {
141
+ if (result.cancelled)
142
+ return STOPPED_REASON;
143
+ if (result.exitCode !== -1)
144
+ return null;
145
+ const stderr = (result.stderr ?? '').trim();
146
+ if (commandTimedOut(result))
147
+ return timedOutReason(duration);
148
+ // Node's own spawn failure, e.g. "spawn php ENOENT" when php is not installed.
149
+ if (/^spawn \S+ E[A-Z]+$/.test(stderr))
150
+ return `Could not start ${command} (${stderr}).`;
151
+ if (stderr.startsWith('Working directory does not exist'))
152
+ return stderr;
153
+ return null;
154
+ }
155
+ /**
156
+ * Run one command for a check. `notRun` is set when the command never ran to
157
+ * completion, in which case its output says nothing about the code.
125
158
  */
126
- async function runVerifyCommand(type, command, args, projectRoot, timeout) {
159
+ async function runCheckCommand(command, args, projectRoot, timeout, signal) {
160
+ // Checks run under the same shell guard as the agent's own commands. A
161
+ // command the guard refuses fails every time, whatever the code looks like,
162
+ // so it is reported as not run instead of as a failing check.
163
+ const validation = await validateCommandAsync(command, args, { cwd: projectRoot, projectRoot });
164
+ if (!validation.valid) {
165
+ return { notRun: `The shell guard refused \`${command}\`: ${validation.reason ?? 'not allowed'}`, duration: 0 };
166
+ }
127
167
  const startTime = Date.now();
128
168
  const result = await executeCommandAsync(command, args, {
129
169
  cwd: projectRoot,
130
170
  projectRoot,
131
171
  timeout,
172
+ signal,
132
173
  });
133
174
  const duration = Date.now() - startTime;
134
- const output = result.stdout + '\n' + result.stderr;
135
- // Parse errors from output
175
+ const notRun = whyCommandDidNotFinish(result, command, duration) ?? undefined;
176
+ return { result, notRun, duration };
177
+ }
178
+ /**
179
+ * Build the result of a check that ran, from its combined output. `reason` is
180
+ * the part of the output that best explains a failure nothing could be parsed
181
+ * from.
182
+ */
183
+ function checkResult(type, command, success, output, duration, reason = output, note) {
136
184
  const errors = parseErrors(output, type);
137
- // If command failed but no errors were parsed, surface the failure reason as warning
138
- // (not error — could be pre-existing build issue unrelated to agent's changes)
139
- if (!result.success && errors.length === 0) {
140
- const reason = result.stderr?.includes('timed out')
141
- ? `Command timed out after ${Math.round(duration / 1000)}s. This build tool may be too slow for verification.`
142
- : result.stderr?.includes('not in the allowed list') || result.stderr?.includes('not allowed')
143
- ? `Command '${command}' is not allowed. Check shell.ts ALLOWED_COMMANDS.`
144
- : result.stderr?.trim() || result.stdout?.trim() || 'Command failed with no output';
145
- errors.push({ severity: 'warning', message: reason });
185
+ // A failed check whose output could not be parsed still failed. Its output
186
+ // is the only account of why, so it goes along as a warning (the failure may
187
+ // predate the agent's change).
188
+ if (!success && errors.length === 0) {
189
+ errors.push({ severity: 'warning', message: reason.trim() || 'Command failed with no output' });
190
+ }
191
+ if (note)
192
+ errors.push({ severity: 'warning', message: note });
193
+ return { success, type, command, output: output.trim(), errors, duration };
194
+ }
195
+ /** The warning on a failed check whose command did not finish. */
196
+ function incompleteNote(notRun) {
197
+ return `Output incomplete: ${notRun.replace(/ This tool may be too slow for verification\.$/, '')} Other errors may be missing.`;
198
+ }
199
+ /**
200
+ * Run a verification command
201
+ */
202
+ async function runVerifyCommand(type, command, args, projectRoot, timeout, signal) {
203
+ const display = `${command} ${args.join(' ')}`;
204
+ const { result, notRun, duration } = await runCheckCommand(command, args, projectRoot, timeout, signal);
205
+ const output = result ? `${result.stdout ?? ''}\n${result.stderr ?? ''}` : '';
206
+ // A check that timed out after printing failures did fail: those failures
207
+ // are real even though the run never finished (a suite that hangs after a
208
+ // FAIL, `jest --watch`).
209
+ if (notRun && result && commandTimedOut(result) && parseErrors(output, type).some(e => e.severity === 'error')) {
210
+ return checkResult(type, display, false, output, duration, output, incompleteNote(notRun));
211
+ }
212
+ if (notRun || !result)
213
+ return notRunResult(type, display, notRun ?? 'The command did not run.', output.trim(), duration);
214
+ return checkResult(type, display, result.success, output, duration, result.stderr?.trim() || result.stdout?.trim() || '');
215
+ }
216
+ /**
217
+ * Whether `name` is installed in a node_modules/.bin directory at or above
218
+ * the project, which is where npx looks for it (a workspace package usually
219
+ * finds its tools hoisted to the repository root).
220
+ */
221
+ function hasLocalBin(projectRoot, name) {
222
+ let dir = resolve(projectRoot);
223
+ for (;;) {
224
+ if (existsSync(join(dir, 'node_modules', '.bin', name)))
225
+ return true;
226
+ const parent = dirname(dir);
227
+ if (parent === dir)
228
+ return false;
229
+ dir = parent;
146
230
  }
147
- return {
148
- success: result.success,
149
- type,
150
- command: `${command} ${args.join(' ')}`,
151
- output: output.trim(),
152
- errors,
153
- duration,
231
+ }
232
+ /** Whether `name` is an executable found on PATH. */
233
+ function isOnPath(name) {
234
+ return (process.env.PATH ?? '').split(delimiter).some(dir => dir !== '' && existsSync(join(dir, name)));
235
+ }
236
+ const PHP_LINT_SKIPPED_DIRS = new Set(['vendor', 'node_modules']);
237
+ const PHP_LINT_CONCURRENCY = 8;
238
+ /** Project PHP files, relative to the root, outside dependency and dot directories. */
239
+ function listPhpFiles(projectRoot) {
240
+ const files = [];
241
+ const walk = (rel) => {
242
+ let entries;
243
+ try {
244
+ entries = readdirSync(rel ? join(projectRoot, rel) : projectRoot, { withFileTypes: true });
245
+ }
246
+ catch {
247
+ return;
248
+ }
249
+ for (const entry of entries) {
250
+ const path = rel ? `${rel}/${entry.name}` : entry.name;
251
+ if (entry.isDirectory()) {
252
+ if (!entry.name.startsWith('.') && !PHP_LINT_SKIPPED_DIRS.has(entry.name))
253
+ walk(path);
254
+ }
255
+ else if (entry.isFile() && entry.name.endsWith('.php')) {
256
+ files.push(path);
257
+ }
258
+ }
154
259
  };
260
+ walk('');
261
+ return files.sort();
262
+ }
263
+ /**
264
+ * PHP syntax check, one `php -l` per file. `find -exec` would hand the file
265
+ * list to php in one command, but the shell guard refuses exec flags, and
266
+ * before PHP 8.3 `php -l` reads only its first file.
267
+ */
268
+ async function runPhpLint(projectRoot, timeout, signal) {
269
+ const files = listPhpFiles(projectRoot);
270
+ if (files.length === 0)
271
+ return null;
272
+ const display = `php -l (${files.length} file${files.length === 1 ? '' : 's'})`;
273
+ const startTime = Date.now();
274
+ const deadline = startTime + timeout;
275
+ const failures = [];
276
+ let notRun;
277
+ let next = 0;
278
+ const worker = async () => {
279
+ while (notRun === undefined && next < files.length) {
280
+ const file = files[next++];
281
+ const remaining = deadline - Date.now();
282
+ if (remaining <= 0) {
283
+ notRun = timedOutReason(timeout);
284
+ return;
285
+ }
286
+ // "./" keeps a file named like an option from being read as one.
287
+ const run = await runCheckCommand('php', ['-l', `./${file}`], projectRoot, remaining, signal);
288
+ if (run.notRun || !run.result) {
289
+ // A call cut short by the deadline stands for the whole lint, which
290
+ // ran for the full timeout, not just this call's share of it.
291
+ notRun ??= run.result && commandTimedOut(run.result)
292
+ ? timedOutReason(timeout)
293
+ : run.notRun ?? 'The command did not run.';
294
+ return;
295
+ }
296
+ if (!run.result.success)
297
+ failures.push(`${run.result.stdout ?? ''}\n${run.result.stderr ?? ''}`);
298
+ }
299
+ };
300
+ await Promise.all(Array.from({ length: Math.min(PHP_LINT_CONCURRENCY, files.length) }, worker));
301
+ const duration = Date.now() - startTime;
302
+ // A file that failed to parse is a failure even if the lint ran out of
303
+ // time before every file was checked. A lint the user stopped is not run,
304
+ // as any other stopped check.
305
+ if (failures.length > 0 && notRun !== STOPPED_REASON) {
306
+ return checkResult('typecheck', display, false, failures.join('\n'), duration, failures.join('\n'), notRun && incompleteNote(notRun));
307
+ }
308
+ if (notRun)
309
+ return notRunResult('typecheck', display, notRun, '', duration);
310
+ return checkResult('typecheck', display, true, '', duration);
155
311
  }
156
312
  /**
157
313
  * Parse errors from command output
158
314
  */
159
315
  function parseErrors(output, type) {
160
316
  const errors = [];
161
- const lines = output.split('\n');
317
+ // Tools that are told to keep their colours (FORCE_COLOR) wrap words in
318
+ // escape codes, which no pattern below would match.
319
+ const lines = output.replace(/\x1b\[[0-9;]*m/g, '').split('\n');
162
320
  for (const line of lines) {
163
321
  // TypeScript/TSC errors: src/file.ts(10,5): error TS2345: ...
164
322
  const tsMatch = line.match(/^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+(TS\d+):\s*(.+)$/);
@@ -185,13 +343,22 @@ function parseErrors(output, type) {
185
343
  });
186
344
  continue;
187
345
  }
188
- // Jest/Vitest: FAIL src/file.test.ts
346
+ // Jest/Vitest: "FAIL src/file.test.ts", with Jest's "(5.1 s)", Vitest's
347
+ // "|project|" label or "[ src/file.test.ts ]" suffix, or Vitest's
348
+ // "FAIL src/file.test.ts > suite > test name" for a single test.
189
349
  const jestFailMatch = line.match(/^\s*FAIL\s+(.+)$/);
190
350
  if (jestFailMatch) {
351
+ const rest = jestFailMatch[1].trim().replace(/^\|[^|]*\|\s+/, '');
352
+ const nameAt = rest.search(/\s+[>›]\s+/);
353
+ const file = (nameAt < 0 ? rest : rest.slice(0, nameAt))
354
+ .replace(/\s+\[.*\]$/, '')
355
+ .replace(/\s+\([\d.]+\s*m?s\)$/, '')
356
+ .trim();
357
+ const testName = nameAt < 0 ? '' : rest.slice(nameAt).replace(/^\s+[>›]\s+/, '').trim();
191
358
  errors.push({
192
- file: jestFailMatch[1],
359
+ file,
193
360
  severity: 'error',
194
- message: 'Test file failed',
361
+ message: testName ? `Test failed: ${testName}` : 'Test file failed',
195
362
  });
196
363
  continue;
197
364
  }
@@ -230,15 +397,20 @@ function parseErrors(output, type) {
230
397
  });
231
398
  continue;
232
399
  }
233
- // PHP errors: PHP Parse error: ... in /path/file.php on line 10
234
- const phpMatch = line.match(/PHP\s+(Parse error|Fatal error|Warning):\s*(.+?)\s+in\s+(.+?)\s+on line\s+(\d+)/i);
400
+ // PHP errors: "PHP Parse error: ... in /path/file.php on line 10" in the
401
+ // error log, "Parse error: ... in ... on line 10" on stdout. php -l often
402
+ // prints both for the same error, so a repeat is dropped.
403
+ const phpMatch = line.match(/^\s*(?:PHP\s+)?(Parse error|Fatal error|Warning):\s*(.+?)\s+in\s+(.+?)\s+on line\s+(\d+)/i);
235
404
  if (phpMatch) {
236
- errors.push({
237
- file: phpMatch[3],
405
+ const error = {
406
+ file: phpMatch[3].replace(/^\.\//, ''),
238
407
  line: parseInt(phpMatch[4]),
239
408
  severity: phpMatch[1].toLowerCase().includes('warning') ? 'warning' : 'error',
240
409
  message: phpMatch[2],
241
- });
410
+ };
411
+ const seen = errors.some(e => e.file === error.file && e.line === error.line && e.message === error.message);
412
+ if (!seen)
413
+ errors.push(error);
242
414
  continue;
243
415
  }
244
416
  // PHPUnit errors: 1) TestClass::testMethod
@@ -256,7 +428,7 @@ function parseErrors(output, type) {
256
428
  /**
257
429
  * Run build verification
258
430
  */
259
- export async function runBuildVerification(projectRoot, timeout = 120000) {
431
+ export async function runBuildVerification(projectRoot, timeout = 120000, signal) {
260
432
  const scripts = detectProjectScripts(projectRoot);
261
433
  if (!scripts.build) {
262
434
  return null;
@@ -277,24 +449,17 @@ export async function runBuildVerification(projectRoot, timeout = 120000) {
277
449
  }
278
450
  else {
279
451
  if (!existsSync(join(projectRoot, 'node_modules'))) {
280
- return {
281
- success: false,
282
- type: 'build',
283
- command: `${scripts.packageManager} run ${scripts.build}`,
284
- output: 'node_modules not found. Run npm install first.',
285
- errors: [{ severity: 'error', message: 'node_modules not found. Run npm install first.' }],
286
- duration: 0,
287
- };
452
+ return notRunResult('build', `${scripts.packageManager} run ${scripts.build}`, 'node_modules not found. Run npm install first.');
288
453
  }
289
454
  command = scripts.packageManager;
290
455
  args = ['run', scripts.build];
291
456
  }
292
- return runVerifyCommand('build', command, args, projectRoot, timeout);
457
+ return runVerifyCommand('build', command, args, projectRoot, timeout, signal);
293
458
  }
294
459
  /**
295
460
  * Run test verification
296
461
  */
297
- export async function runTestVerification(projectRoot, timeout = 120000) {
462
+ export async function runTestVerification(projectRoot, timeout = 120000, signal) {
298
463
  const scripts = detectProjectScripts(projectRoot);
299
464
  if (!scripts.test) {
300
465
  return null;
@@ -314,8 +479,13 @@ export async function runTestVerification(projectRoot, timeout = 120000) {
314
479
  args = ['test'];
315
480
  }
316
481
  else if (scripts.test === '__phpunit__') {
317
- command = './vendor/bin/phpunit';
318
- args = [];
482
+ // Through php: the shell guard runs named commands only, not a path.
483
+ // Composer's vendor/bin/phpunit is a PHP script either way.
484
+ command = 'php';
485
+ args = ['vendor/bin/phpunit'];
486
+ if (!existsSync(join(projectRoot, 'vendor', 'bin', 'phpunit'))) {
487
+ return notRunResult('test', `${command} ${args.join(' ')}`, 'vendor/bin/phpunit not found. Run composer install first.');
488
+ }
319
489
  }
320
490
  else if (scripts.test === '__composer_test__') {
321
491
  command = 'composer';
@@ -327,24 +497,17 @@ export async function runTestVerification(projectRoot, timeout = 120000) {
327
497
  }
328
498
  else {
329
499
  if (!existsSync(join(projectRoot, 'node_modules'))) {
330
- return {
331
- success: false,
332
- type: 'test',
333
- command: `${scripts.packageManager} run ${scripts.test}`,
334
- output: 'node_modules not found. Run npm install first.',
335
- errors: [{ severity: 'error', message: 'node_modules not found. Run npm install first.' }],
336
- duration: 0,
337
- };
500
+ return notRunResult('test', `${scripts.packageManager} run ${scripts.test}`, 'node_modules not found. Run npm install first.');
338
501
  }
339
502
  command = scripts.packageManager;
340
503
  args = ['run', scripts.test];
341
504
  }
342
- return runVerifyCommand('test', command, args, projectRoot, timeout);
505
+ return runVerifyCommand('test', command, args, projectRoot, timeout, signal);
343
506
  }
344
507
  /**
345
508
  * Run TypeScript type checking
346
509
  */
347
- export async function runTypecheckVerification(projectRoot, timeout = 60000) {
510
+ export async function runTypecheckVerification(projectRoot, timeout = 60000, signal) {
348
511
  const scripts = detectProjectScripts(projectRoot);
349
512
  if (!scripts.typecheck) {
350
513
  return null;
@@ -352,38 +515,41 @@ export async function runTypecheckVerification(projectRoot, timeout = 60000) {
352
515
  let command;
353
516
  let args;
354
517
  if (scripts.typecheck === '__tsc_direct__') {
355
- const localTsc = join(projectRoot, 'node_modules', '.bin', 'tsc');
356
- if (existsSync(localTsc)) {
357
- command = localTsc;
518
+ // The shell guard runs named commands only, so a project's own tsc goes
519
+ // through npx, which finds it in node_modules/.bin. --no-install keeps npx
520
+ // from fetching the unrelated "tsc" package from the registry instead.
521
+ if (hasLocalBin(projectRoot, 'tsc')) {
522
+ command = 'npx';
523
+ args = ['--no-install', 'tsc', '--noEmit'];
524
+ }
525
+ else if (isOnPath('tsc')) {
526
+ command = 'tsc';
358
527
  args = ['--noEmit'];
359
528
  }
360
529
  else {
361
- command = 'npx';
362
- args = ['tsc', '--noEmit'];
530
+ return notRunResult('typecheck', 'tsc --noEmit', 'TypeScript is not installed (no tsc in node_modules/.bin or on PATH). Run npm install first.');
363
531
  }
364
532
  }
365
533
  else if (scripts.typecheck === '__php_lint__') {
366
- // PHP syntax check on all PHP files
367
- command = 'find';
368
- args = ['.', '-name', '*.php', '-not', '-path', './vendor/*', '-exec', 'php', '-l', '{}', ';'];
534
+ return runPhpLint(projectRoot, timeout, signal);
369
535
  }
370
536
  else {
371
537
  command = scripts.packageManager;
372
538
  args = ['run', scripts.typecheck];
373
539
  }
374
- return runVerifyCommand('typecheck', command, args, projectRoot, timeout);
540
+ return runVerifyCommand('typecheck', command, args, projectRoot, timeout, signal);
375
541
  }
376
542
  /**
377
543
  * Run lint verification
378
544
  */
379
- export async function runLintVerification(projectRoot, timeout = 60000) {
545
+ export async function runLintVerification(projectRoot, timeout = 60000, signal) {
380
546
  const scripts = detectProjectScripts(projectRoot);
381
547
  if (!scripts.lint) {
382
548
  return null;
383
549
  }
384
550
  const command = scripts.packageManager;
385
551
  const args = ['run', scripts.lint];
386
- return runVerifyCommand('lint', command, args, projectRoot, timeout);
552
+ return runVerifyCommand('lint', command, args, projectRoot, timeout, signal);
387
553
  }
388
554
  /**
389
555
  * Run all verifications
@@ -394,9 +560,9 @@ export async function runAllVerifications(projectRoot, options = {}) {
394
560
  // Run typecheck and lint in parallel (independent checks)
395
561
  const parallel = [];
396
562
  if (opts.runTypecheck)
397
- parallel.push(runTypecheckVerification(projectRoot, opts.timeout));
563
+ parallel.push(runTypecheckVerification(projectRoot, opts.timeout, opts.signal));
398
564
  if (opts.runLint)
399
- parallel.push(runLintVerification(projectRoot, opts.timeout));
565
+ parallel.push(runLintVerification(projectRoot, opts.timeout, opts.signal));
400
566
  if (parallel.length > 0) {
401
567
  const parallelResults = await Promise.all(parallel);
402
568
  for (const r of parallelResults) {
@@ -406,13 +572,13 @@ export async function runAllVerifications(projectRoot, options = {}) {
406
572
  }
407
573
  // Run build after typecheck/lint (may depend on them)
408
574
  if (opts.runBuild) {
409
- const result = await runBuildVerification(projectRoot, opts.timeout);
575
+ const result = await runBuildVerification(projectRoot, opts.timeout, opts.signal);
410
576
  if (result)
411
577
  results.push(result);
412
578
  }
413
579
  // Run tests last (slowest, depends on build)
414
580
  if (opts.runTest) {
415
- const result = await runTestVerification(projectRoot, opts.timeout);
581
+ const result = await runTestVerification(projectRoot, opts.timeout, opts.signal);
416
582
  if (result)
417
583
  results.push(result);
418
584
  }
@@ -424,9 +590,13 @@ export async function runAllVerifications(projectRoot, options = {}) {
424
590
  export function formatVerifyResults(results) {
425
591
  const lines = [];
426
592
  for (const result of results) {
427
- const status = result.success ? '✓' : '✗';
593
+ const status = result.success ? '✓' : result.notRun ? '⚠' : '✗';
428
594
  const duration = `${(result.duration / 1000).toFixed(1)}s`;
429
595
  lines.push(`${status} ${result.type}: ${result.command} (${duration})`);
596
+ if (result.notRun) {
597
+ lines.push(` not run: ${result.notRun}`);
598
+ continue;
599
+ }
430
600
  if (!result.success && result.errors.length > 0) {
431
601
  const errorCount = result.errors.filter(e => e.severity === 'error').length;
432
602
  const warnCount = result.errors.filter(e => e.severity === 'warning').length;
@@ -447,7 +617,8 @@ export function formatVerifyResults(results) {
447
617
  * Format errors for agent to fix
448
618
  */
449
619
  export function formatErrorsForAgent(results) {
450
- const failedResults = results.filter(r => !r.success);
620
+ // A check that did not run has nothing in it to fix.
621
+ const failedResults = failedChecks(results);
451
622
  if (failedResults.length === 0) {
452
623
  return '';
453
624
  }
@@ -480,22 +651,36 @@ export function formatErrorsForAgent(results) {
480
651
  lines.push('Please fix these errors and try again.');
481
652
  return lines.join('\n');
482
653
  }
654
+ /**
655
+ * Checks that ran and failed. A check that could not run is not among them.
656
+ */
657
+ export function failedChecks(results) {
658
+ return results.filter(r => !r.success && !r.notRun);
659
+ }
660
+ /**
661
+ * Checks that could not be carried out (see VerifyResult.notRun).
662
+ */
663
+ export function checksNotRun(results) {
664
+ return results.filter(r => !r.success && r.notRun);
665
+ }
483
666
  /**
484
667
  * Check if any verification failed
485
668
  */
486
669
  export function hasVerificationErrors(results) {
487
- return results.some(r => !r.success);
670
+ return failedChecks(results).length > 0;
488
671
  }
489
672
  /**
490
673
  * Get summary of verification
491
674
  */
492
675
  export function getVerificationSummary(results) {
493
676
  const passed = results.filter(r => r.success).length;
494
- const failed = results.filter(r => !r.success).length;
677
+ const failed = failedChecks(results).length;
678
+ const notRun = checksNotRun(results).length;
495
679
  const errors = results.reduce((sum, r) => sum + r.errors.filter(e => e.severity === 'error').length, 0);
496
680
  return {
497
681
  passed,
498
682
  failed,
683
+ notRun,
499
684
  total: results.length,
500
685
  errors,
501
686
  };
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "3.3.3";
1
+ export declare const VERSION = "3.4.1";
package/dist/version.js CHANGED
@@ -1,4 +1,4 @@
1
1
  // AUTO-GENERATED by scripts/gen-version.js — do not edit by hand.
2
2
  // Baked from package.json at build time so the bun-compiled binary reports
3
3
  // the right version (it has no package.json on disk to read at runtime).
4
- export const VERSION = '3.3.3';
4
+ export const VERSION = '3.4.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "3.3.3",
3
+ "version": "3.4.1",
4
4
  "description": "AI-powered coding assistant built for the terminal. Multiple LLM providers, project-aware context, and a seamless development workflow.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",