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.
- package/dist/acp/commands.d.ts +50 -1
- package/dist/acp/commands.js +545 -109
- package/dist/acp/protocol.d.ts +14 -5
- package/dist/acp/server.d.ts +36 -1
- package/dist/acp/server.js +581 -155
- package/dist/acp/serverHandlers.d.ts +2 -1
- package/dist/acp/serverHandlers.js +3 -0
- package/dist/acp/session.d.ts +28 -2
- package/dist/acp/session.js +25 -6
- package/dist/acp/transport.d.ts +40 -4
- package/dist/acp/transport.js +218 -25
- package/dist/acp/turns.d.ts +20 -0
- package/dist/acp/turns.js +30 -0
- package/dist/api/index.js +2 -0
- package/dist/api/ollamaNative.d.ts +3 -0
- package/dist/api/ollamaNative.js +35 -3
- package/dist/config/index.d.ts +21 -4
- package/dist/config/index.js +178 -123
- package/dist/renderer/agentExecution.d.ts +30 -2
- package/dist/renderer/agentExecution.js +248 -92
- package/dist/renderer/commands/helpers.d.ts +18 -2
- package/dist/renderer/commands/helpers.js +28 -5
- package/dist/renderer/commands.d.ts +2 -0
- package/dist/renderer/commands.js +180 -64
- package/dist/renderer/main.d.ts +41 -0
- package/dist/renderer/main.js +181 -80
- package/dist/utils/agent.d.ts +69 -4
- package/dist/utils/agent.js +416 -248
- package/dist/utils/agentChat.js +82 -10
- package/dist/utils/agents.d.ts +2 -1
- package/dist/utils/agents.js +100 -29
- package/dist/utils/auditLog.d.ts +4 -3
- package/dist/utils/auditLog.js +92 -9
- package/dist/utils/checkpoints.js +11 -6
- package/dist/utils/codeReview.js +28 -23
- package/dist/utils/codeepCloud.d.ts +14 -2
- package/dist/utils/codeepCloud.js +56 -20
- package/dist/utils/customCommands.js +7 -2
- package/dist/utils/git.d.ts +262 -4
- package/dist/utils/git.js +1928 -61
- package/dist/utils/gitHookInstaller.d.ts +32 -1
- package/dist/utils/gitHookInstaller.js +76 -8
- package/dist/utils/gitignore.d.ts +8 -0
- package/dist/utils/gitignore.js +41 -10
- package/dist/utils/headlessReview.d.ts +11 -0
- package/dist/utils/headlessReview.js +33 -5
- package/dist/utils/history.d.ts +22 -6
- package/dist/utils/history.js +140 -26
- package/dist/utils/logger.js +6 -7
- package/dist/utils/mcpConfig.d.ts +24 -0
- package/dist/utils/mcpConfig.js +36 -5
- package/dist/utils/mentions.d.ts +28 -5
- package/dist/utils/mentions.js +253 -45
- package/dist/utils/personalities.js +16 -6
- package/dist/utils/planMode.d.ts +13 -7
- package/dist/utils/planMode.js +32 -12
- package/dist/utils/projectIntelligence.d.ts +2 -0
- package/dist/utils/projectIntelligence.js +27 -8
- package/dist/utils/projectPaths.d.ts +53 -0
- package/dist/utils/projectPaths.js +146 -0
- package/dist/utils/shell.d.ts +119 -0
- package/dist/utils/shell.js +417 -45
- package/dist/utils/skillBundles.js +17 -7
- package/dist/utils/skillBundlesCloud.js +20 -3
- package/dist/utils/skills.d.ts +24 -2
- package/dist/utils/skills.js +235 -43
- package/dist/utils/smartContext.js +97 -23
- package/dist/utils/telegramApproval.d.ts +10 -2
- package/dist/utils/telegramApproval.js +22 -4
- package/dist/utils/toolExecution.d.ts +50 -2
- package/dist/utils/toolExecution.js +418 -16
- package/dist/utils/toolParsing.d.ts +7 -1
- package/dist/utils/toolParsing.js +12 -3
- package/dist/utils/userProfile.js +58 -16
- package/dist/utils/verify.d.ts +25 -4
- package/dist/utils/verify.js +259 -74
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/utils/verify.js
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
135
|
-
|
|
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
|
-
//
|
|
138
|
-
//
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
318
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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.
|
|
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.
|
|
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
|
+
"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",
|