codeep 1.2.66 → 1.2.68

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.
@@ -44,6 +44,7 @@ export declare class App {
44
44
  private agentMaxIterations;
45
45
  private agentActions;
46
46
  private agentThinking;
47
+ private agentWaitingForAI;
47
48
  private pasteInfo;
48
49
  private pasteInfoOpen;
49
50
  private codeBlockCounter;
@@ -179,6 +180,7 @@ export declare class App {
179
180
  * Set agent thinking text
180
181
  */
181
182
  setAgentThinking(text: string): void;
183
+ setAgentWaitingForAI(waiting: boolean): void;
182
184
  /**
183
185
  * Paste from system clipboard (Ctrl+V)
184
186
  */
@@ -102,6 +102,7 @@ export class App {
102
102
  agentMaxIterations = 0;
103
103
  agentActions = [];
104
104
  agentThinking = '';
105
+ agentWaitingForAI = false;
105
106
  // Paste detection state
106
107
  pasteInfo = null;
107
108
  pasteInfoOpen = false;
@@ -365,6 +366,7 @@ export class App {
365
366
  this.agentMaxIterations = 0;
366
367
  this.agentActions = [];
367
368
  this.agentThinking = '';
369
+ this.agentWaitingForAI = true;
368
370
  this.isLoading = false; // Clear loading state when agent takes over
369
371
  this.startSpinner();
370
372
  }
@@ -394,6 +396,10 @@ export class App {
394
396
  this.agentThinking = text;
395
397
  this.render();
396
398
  }
399
+ setAgentWaitingForAI(waiting) {
400
+ this.agentWaitingForAI = waiting;
401
+ this.render();
402
+ }
397
403
  /**
398
404
  * Paste from system clipboard (Ctrl+V)
399
405
  */
@@ -1892,8 +1898,12 @@ export class App {
1892
1898
  this.screen.write(titlePadLeft, y, title, PRIMARY_COLOR + style.bold);
1893
1899
  this.screen.write(titlePadLeft + title.length, y, '─'.repeat(Math.max(0, titlePadRight)), PRIMARY_COLOR);
1894
1900
  y++;
1895
- // Current action line (no side borders)
1896
- if (this.agentActions.length > 0) {
1901
+ // Current action line (clear first to avoid stale text from longer previous paths)
1902
+ this.screen.writeLine(y, '');
1903
+ if (this.agentWaitingForAI) {
1904
+ this.screen.write(1, y, 'Thinking...', fg.gray);
1905
+ }
1906
+ else if (this.agentActions.length > 0) {
1897
1907
  const lastAction = this.agentActions[this.agentActions.length - 1];
1898
1908
  const actionLabel = this.getActionLabel(lastAction.type);
1899
1909
  const actionColor = this.getActionColor(lastAction.type);
@@ -1906,7 +1916,8 @@ export class App {
1906
1916
  this.screen.write(1, y, 'Starting...', fg.gray);
1907
1917
  }
1908
1918
  y++;
1909
- // Stats line: Files and step info
1919
+ // Stats line: Files and step info (clear line first to avoid stale text)
1920
+ this.screen.writeLine(y, '');
1910
1921
  let x = 1;
1911
1922
  // File changes
1912
1923
  if (stats.writes > 0) {
@@ -119,6 +119,7 @@ export class Screen {
119
119
  this.buffer[y][x] = { char: ' ', style: '' };
120
120
  }
121
121
  // Parse text character by character, tracking ANSI escape sequences
122
+ // Use index-based loop to handle ANSI sequences, but read full Unicode code points
122
123
  let col = 0;
123
124
  let i = 0;
124
125
  let currentStyle = prefixStyle;
@@ -132,8 +133,8 @@ export class Screen {
132
133
  }
133
134
  if (escEnd < text.length) {
134
135
  const escSeq = text.slice(i, escEnd + 1);
135
- // Reset code clears style, otherwise accumulate
136
- if (escSeq === '\x1b[0m') {
136
+ // Reset code clears accumulated style back to prefix
137
+ if (escSeq === '\x1b[0m' || escSeq === '\x1b[m') {
137
138
  currentStyle = prefixStyle;
138
139
  }
139
140
  else {
@@ -146,17 +147,20 @@ export class Screen {
146
147
  }
147
148
  }
148
149
  else {
149
- // Regular character
150
- const w = charWidth(text[i]);
150
+ // Read full Unicode code point (handles surrogate pairs for emoji)
151
+ const codePoint = text.codePointAt(i) ?? text.charCodeAt(i);
152
+ const char = String.fromCodePoint(codePoint);
153
+ const charLen = char.length; // surrogate pair = 2, BMP char = 1
154
+ const w = charWidth(char);
151
155
  if (col < this.width) {
152
- this.buffer[y][col] = { char: text[i], style: currentStyle };
156
+ this.buffer[y][col] = { char, style: currentStyle };
153
157
  // Wide char: fill next cell with empty placeholder
154
158
  if (w === 2 && col + 1 < this.width) {
155
159
  this.buffer[y][col + 1] = { char: '', style: currentStyle };
156
160
  }
157
161
  }
158
162
  col += w;
159
- i++;
163
+ i += charLen;
160
164
  }
161
165
  }
162
166
  }
@@ -192,8 +196,14 @@ export class Screen {
192
196
  lineLength = wordLength;
193
197
  }
194
198
  else {
195
- line += (line ? ' ' : '') + word;
196
- lineLength += wordLength + (line.length > wordLength ? 1 : 0);
199
+ if (line) {
200
+ line += ' ' + word;
201
+ lineLength += 1 + wordLength;
202
+ }
203
+ else {
204
+ line = word;
205
+ lineLength = wordLength;
206
+ }
197
207
  }
198
208
  }
199
209
  if (line && currentY < this.height) {
@@ -236,8 +246,17 @@ export class Screen {
236
246
  if (cell.char === renderedCell.char && cell.style === renderedCell.style) {
237
247
  continue;
238
248
  }
239
- // Skip wide-char placeholder cells
249
+ // Wide-char placeholder cell (right half of emoji/CJK):
250
+ // If previously rendered something here, clear it with a space so there's no ghost.
251
+ // The wide char itself (left cell) already moved the terminal cursor past this cell.
240
252
  if (cell.char === '') {
253
+ if (renderedCell.char !== '') {
254
+ // Previous char here was something visible — overwrite with space to clear ghost
255
+ output += cursor.to(y + 1, x + 1);
256
+ output += style.reset;
257
+ lastStyle = '';
258
+ output += ' ';
259
+ }
241
260
  this.rendered[y][x] = { ...cell };
242
261
  continue;
243
262
  }
@@ -142,12 +142,14 @@ export async function executeAgentTask(task, dryRun, ctx) {
142
142
  chatHistory: app.getChatHistory(),
143
143
  onIteration: (iteration, message) => {
144
144
  app.updateAgentProgress(iteration);
145
+ app.setAgentWaitingForAI(true); // Waiting for AI response between tool calls
145
146
  // Show special status messages (timeout retries, verification) but not generic iteration messages
146
147
  if (message && !message.startsWith('Iteration ')) {
147
148
  app.addMessage({ role: 'system', content: `_${message}_` });
148
149
  }
149
150
  },
150
151
  onToolCall: (tool) => {
152
+ app.setAgentWaitingForAI(false); // AI responded, executing tool
151
153
  const toolName = tool.tool.toLowerCase();
152
154
  const target = tool.parameters.path ||
153
155
  tool.parameters.command ||
@@ -283,12 +283,16 @@ export async function runAgent(prompt, projectContext, options = {}) {
283
283
  await new Promise(resolve => setTimeout(resolve, 1000 * retryCount));
284
284
  continue;
285
285
  }
286
- // Handle 429 rate-limit / server overload with retry + backoff
287
- if (err.message.includes('429')) {
286
+ // Retry on transient errors: rate-limit, server errors, network failures
287
+ const isRateLimit = err.message.includes('429');
288
+ const isServerError = err.message.includes('500') || err.message.includes('502') || err.message.includes('503') || err.message.includes('529');
289
+ const isNetworkError = ['ECONNRESET', 'ECONNREFUSED', 'ETIMEDOUT', 'ENOTFOUND', 'EPIPE', 'EAI_AGAIN'].some(code => err.message.includes(code));
290
+ if (isRateLimit || isServerError || isNetworkError) {
288
291
  retryCount++;
289
292
  const waitSec = Math.min(5 * retryCount, 30); // 5s, 10s, 15s … max 30s
290
- debug(`429 rate limit (retry ${retryCount}/${maxTimeoutRetries}), waiting ${waitSec}s`);
291
- opts.onIteration?.(iteration, `Server busy (429), retrying in ${waitSec}s... (${retryCount}/${maxTimeoutRetries})`);
293
+ const code = isRateLimit ? '429' : isServerError ? '5xx' : 'network';
294
+ debug(`${code} error (retry ${retryCount}/${maxTimeoutRetries}), waiting ${waitSec}s`);
295
+ opts.onIteration?.(iteration, `Server error (${code}), retrying in ${waitSec}s... (${retryCount}/${maxTimeoutRetries})`);
292
296
  if (retryCount >= maxTimeoutRetries) {
293
297
  throw error; // Give up after max retries
294
298
  }
@@ -468,7 +472,7 @@ export async function runAgent(prompt, projectContext, options = {}) {
468
472
  }
469
473
  opts.onIteration?.(iteration, `Verification attempt ${fixAttempt + 1}/${maxFixAttempts}`);
470
474
  // Run verifications
471
- const verifyResults = runAllVerifications(projectContext.root || process.cwd(), {
475
+ const verifyResults = await runAllVerifications(projectContext.root || process.cwd(), {
472
476
  runBuild: true,
473
477
  runTest: true,
474
478
  runTypecheck: true,
@@ -27,10 +27,18 @@ export declare function validateCommand(command: string, args: string[], options
27
27
  * Execute a shell command with safety checks
28
28
  */
29
29
  export declare function executeCommand(command: string, args?: string[], options?: CommandOptions): CommandResult;
30
+ /**
31
+ * Execute a shell command asynchronously (non-blocking)
32
+ */
33
+ export declare function executeCommandAsync(command: string, args?: string[], options?: CommandOptions): Promise<CommandResult>;
30
34
  /**
31
35
  * Execute a command and return only stdout if successful
32
36
  */
33
37
  export declare function execSimple(command: string, args?: string[], options?: CommandOptions): string | null;
38
+ /**
39
+ * Execute a command asynchronously and return only stdout if successful
40
+ */
41
+ export declare function execSimpleAsync(command: string, args?: string[], options?: CommandOptions): Promise<string | null>;
34
42
  /**
35
43
  * Check if a command exists in PATH
36
44
  */
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Shell command execution utilities with safety checks
3
3
  */
4
- import { spawnSync } from 'child_process';
4
+ import { spawnSync, spawn } from 'child_process';
5
5
  import { resolve, relative, isAbsolute } from 'path';
6
6
  import { existsSync } from 'fs';
7
7
  // Dangerous command patterns that should never be executed
@@ -68,7 +68,7 @@ const ALLOWED_COMMANDS = new Set([
68
68
  // Linting/Formatting
69
69
  'eslint', 'prettier', 'black', 'rustfmt',
70
70
  // Other common tools
71
- 'echo', 'pwd', 'which', 'env', 'date',
71
+ 'echo', 'pwd', 'which', 'env', 'date', 'sleep',
72
72
  'curl', 'wget', // allowed but patterns checked
73
73
  'tar', 'unzip', 'zip',
74
74
  // HTTP tools
@@ -205,6 +205,100 @@ export function executeCommand(command, args = [], options) {
205
205
  };
206
206
  }
207
207
  }
208
+ /**
209
+ * Execute a shell command asynchronously (non-blocking)
210
+ */
211
+ export function executeCommandAsync(command, args = [], options) {
212
+ return new Promise((resolve) => {
213
+ const startTime = Date.now();
214
+ const cwd = options?.cwd || process.cwd();
215
+ const timeout = options?.timeout || 60000;
216
+ // Validate command first (synchronous, fast)
217
+ const validation = validateCommand(command, args, options);
218
+ if (!validation.valid) {
219
+ resolve({
220
+ success: false,
221
+ stdout: '',
222
+ stderr: validation.reason || 'Command validation failed',
223
+ exitCode: -1,
224
+ duration: 0,
225
+ command,
226
+ args,
227
+ });
228
+ return;
229
+ }
230
+ // Ensure cwd exists
231
+ if (!existsSync(cwd)) {
232
+ resolve({
233
+ success: false,
234
+ stdout: '',
235
+ stderr: `Working directory does not exist: ${cwd}`,
236
+ exitCode: -1,
237
+ duration: 0,
238
+ command,
239
+ args,
240
+ });
241
+ return;
242
+ }
243
+ const child = spawn(command, args, {
244
+ cwd,
245
+ env: { ...process.env, ...options?.env },
246
+ });
247
+ let stdout = '';
248
+ let stderr = '';
249
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
250
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
251
+ let settled = false;
252
+ const timer = setTimeout(() => {
253
+ if (settled)
254
+ return;
255
+ settled = true;
256
+ child.kill('SIGTERM');
257
+ const duration = Date.now() - startTime;
258
+ resolve({
259
+ success: false,
260
+ stdout,
261
+ stderr: `Command timed out after ${timeout}ms`,
262
+ exitCode: -1,
263
+ duration,
264
+ command,
265
+ args,
266
+ });
267
+ }, timeout);
268
+ child.on('close', (code) => {
269
+ if (settled)
270
+ return;
271
+ settled = true;
272
+ clearTimeout(timer);
273
+ const duration = Date.now() - startTime;
274
+ resolve({
275
+ success: code === 0,
276
+ stdout,
277
+ stderr,
278
+ exitCode: code ?? -1,
279
+ duration,
280
+ command,
281
+ args,
282
+ });
283
+ });
284
+ child.on('error', (err) => {
285
+ if (settled)
286
+ return;
287
+ settled = true;
288
+ clearTimeout(timer);
289
+ const duration = Date.now() - startTime;
290
+ resolve({
291
+ success: false,
292
+ stdout: '',
293
+ stderr: err.message,
294
+ exitCode: -1,
295
+ duration,
296
+ command,
297
+ args,
298
+ });
299
+ });
300
+ });
301
+ }
208
302
  /**
209
303
  * Execute a command and return only stdout if successful
210
304
  */
@@ -212,6 +306,13 @@ export function execSimple(command, args = [], options) {
212
306
  const result = executeCommand(command, args, options);
213
307
  return result.success ? result.stdout.trim() : null;
214
308
  }
309
+ /**
310
+ * Execute a command asynchronously and return only stdout if successful
311
+ */
312
+ export async function execSimpleAsync(command, args = [], options) {
313
+ const result = await executeCommandAsync(command, args, options);
314
+ return result.success ? result.stdout.trim() : null;
315
+ }
215
316
  /**
216
317
  * Check if a command exists in PATH
217
318
  */
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { existsSync, readdirSync, statSync, readFileSync, writeFileSync, unlinkSync, mkdirSync, rmSync, realpathSync } from 'fs';
10
10
  import { join, dirname, relative, resolve, isAbsolute } from 'path';
11
- import { executeCommand } from './shell.js';
11
+ import { executeCommandAsync } from './shell.js';
12
12
  import { recordWrite, recordEdit, recordDelete, recordMkdir, recordCommand } from './history.js';
13
13
  import { loadIgnoreRules, isIgnored } from './gitignore.js';
14
14
  import { normalizeToolName } from './toolParsing.js';
@@ -276,7 +276,7 @@ export async function executeTool(toolCall, projectRoot) {
276
276
  if (!command)
277
277
  return { success: false, output: '', error: 'Missing required parameter: command', tool, parameters };
278
278
  recordCommand(command, args);
279
- const result = executeCommand(command, args, {
279
+ const result = await executeCommandAsync(command, args, {
280
280
  cwd: projectRoot,
281
281
  projectRoot,
282
282
  timeout: 120000,
@@ -293,7 +293,7 @@ export async function executeTool(toolCall, projectRoot) {
293
293
  const validation = validatePath(searchPath, projectRoot);
294
294
  if (!validation.valid)
295
295
  return { success: false, output: '', error: validation.error, tool, parameters };
296
- const result = executeCommand('grep', ['-rn', '--include=*.{ts,tsx,js,jsx,json,md,css,html,py,go,rs,rb,kt,kts,swift,php,java,cs,c,cpp,h,hpp,vue,svelte,yaml,yml,toml,sh,sql,xml,scss,less}', pattern, validation.absolutePath], {
296
+ const result = await executeCommandAsync('grep', ['-rn', '--include=*.{ts,tsx,js,jsx,json,md,css,html,py,go,rs,rb,kt,kts,swift,php,java,cs,c,cpp,h,hpp,vue,svelte,yaml,yml,toml,sh,sql,xml,scss,less}', pattern, validation.absolutePath], {
297
297
  cwd: projectRoot,
298
298
  projectRoot,
299
299
  timeout: 30000,
@@ -322,7 +322,7 @@ export async function executeTool(toolCall, projectRoot) {
322
322
  else {
323
323
  findArgs.push('-name', pattern, '-print');
324
324
  }
325
- const result = executeCommand('find', findArgs, { cwd: projectRoot, projectRoot, timeout: 15000 });
325
+ const result = await executeCommandAsync('find', findArgs, { cwd: projectRoot, projectRoot, timeout: 15000 });
326
326
  if (result.exitCode === 0 || result.stdout) {
327
327
  const files = result.stdout.split('\n').filter(Boolean);
328
328
  const relativePaths = files.map(f => relative(projectRoot, f) || f).slice(0, 100);
@@ -342,7 +342,7 @@ export async function executeTool(toolCall, projectRoot) {
342
342
  catch {
343
343
  return { success: false, output: '', error: 'Invalid URL format', tool, parameters };
344
344
  }
345
- const result = executeCommand('curl', ['-s', '-L', '-m', '30', '-A', 'Codeep/1.0', '--max-filesize', '1000000', url], {
345
+ const result = await executeCommandAsync('curl', ['-s', '-L', '-m', '30', '-A', 'Codeep/1.0', '--max-filesize', '1000000', url], {
346
346
  cwd: projectRoot,
347
347
  projectRoot,
348
348
  timeout: 35000,
@@ -38,23 +38,23 @@ export declare function detectProjectScripts(projectRoot: string): {
38
38
  /**
39
39
  * Run build verification
40
40
  */
41
- export declare function runBuildVerification(projectRoot: string, timeout?: number): VerifyResult | null;
41
+ export declare function runBuildVerification(projectRoot: string, timeout?: number): Promise<VerifyResult | null>;
42
42
  /**
43
43
  * Run test verification
44
44
  */
45
- export declare function runTestVerification(projectRoot: string, timeout?: number): VerifyResult | null;
45
+ export declare function runTestVerification(projectRoot: string, timeout?: number): Promise<VerifyResult | null>;
46
46
  /**
47
47
  * Run TypeScript type checking
48
48
  */
49
- export declare function runTypecheckVerification(projectRoot: string, timeout?: number): VerifyResult | null;
49
+ export declare function runTypecheckVerification(projectRoot: string, timeout?: number): Promise<VerifyResult | null>;
50
50
  /**
51
51
  * Run lint verification
52
52
  */
53
- export declare function runLintVerification(projectRoot: string, timeout?: number): VerifyResult | null;
53
+ export declare function runLintVerification(projectRoot: string, timeout?: number): Promise<VerifyResult | null>;
54
54
  /**
55
55
  * Run all verifications
56
56
  */
57
- export declare function runAllVerifications(projectRoot: string, options?: Partial<VerifyOptions>): VerifyResult[];
57
+ export declare function runAllVerifications(projectRoot: string, options?: Partial<VerifyOptions>): Promise<VerifyResult[]>;
58
58
  /**
59
59
  * Format verification results for display
60
60
  */
@@ -4,7 +4,7 @@
4
4
  */
5
5
  import { existsSync, readFileSync } from 'fs';
6
6
  import { join } from 'path';
7
- import { executeCommand } from './shell.js';
7
+ import { executeCommandAsync } from './shell.js';
8
8
  const DEFAULT_OPTIONS = {
9
9
  runBuild: true,
10
10
  runTest: true,
@@ -123,9 +123,9 @@ export function detectProjectScripts(projectRoot) {
123
123
  /**
124
124
  * Run a verification command
125
125
  */
126
- function runVerifyCommand(type, command, args, projectRoot, timeout) {
126
+ async function runVerifyCommand(type, command, args, projectRoot, timeout) {
127
127
  const startTime = Date.now();
128
- const result = executeCommand(command, args, {
128
+ const result = await executeCommandAsync(command, args, {
129
129
  cwd: projectRoot,
130
130
  projectRoot,
131
131
  timeout,
@@ -134,6 +134,15 @@ function runVerifyCommand(type, command, args, projectRoot, timeout) {
134
134
  const output = result.stdout + '\n' + result.stderr;
135
135
  // Parse errors from output
136
136
  const errors = parseErrors(output, type);
137
+ // If command failed but no errors were parsed, surface the failure reason explicitly
138
+ if (!result.success && errors.length === 0) {
139
+ const reason = result.stderr?.includes('timed out')
140
+ ? `Command timed out after ${Math.round(duration / 1000)}s. This build tool may be too slow for verification. Consider adding a faster typecheck script to package.json.`
141
+ : result.stderr?.includes('not in the allowed list') || result.stderr?.includes('not allowed')
142
+ ? `Command '${command}' is not allowed. Check shell.ts ALLOWED_COMMANDS.`
143
+ : result.stderr?.trim() || result.stdout?.trim() || 'Command failed with no output';
144
+ errors.push({ severity: 'error', message: reason });
145
+ }
137
146
  return {
138
147
  success: result.success,
139
148
  type,
@@ -246,7 +255,7 @@ function parseErrors(output, type) {
246
255
  /**
247
256
  * Run build verification
248
257
  */
249
- export function runBuildVerification(projectRoot, timeout = 120000) {
258
+ export async function runBuildVerification(projectRoot, timeout = 120000) {
250
259
  const scripts = detectProjectScripts(projectRoot);
251
260
  if (!scripts.build) {
252
261
  return null;
@@ -266,6 +275,16 @@ export function runBuildVerification(projectRoot, timeout = 120000) {
266
275
  args = ['run', 'build'];
267
276
  }
268
277
  else {
278
+ if (!existsSync(join(projectRoot, 'node_modules'))) {
279
+ return {
280
+ success: false,
281
+ type: 'build',
282
+ command: `${scripts.packageManager} run ${scripts.build}`,
283
+ output: 'node_modules not found. Run npm install first.',
284
+ errors: [{ severity: 'error', message: 'node_modules not found. Run npm install first.' }],
285
+ duration: 0,
286
+ };
287
+ }
269
288
  command = scripts.packageManager;
270
289
  args = ['run', scripts.build];
271
290
  }
@@ -274,7 +293,7 @@ export function runBuildVerification(projectRoot, timeout = 120000) {
274
293
  /**
275
294
  * Run test verification
276
295
  */
277
- export function runTestVerification(projectRoot, timeout = 120000) {
296
+ export async function runTestVerification(projectRoot, timeout = 120000) {
278
297
  const scripts = detectProjectScripts(projectRoot);
279
298
  if (!scripts.test) {
280
299
  return null;
@@ -306,6 +325,16 @@ export function runTestVerification(projectRoot, timeout = 120000) {
306
325
  args = ['artisan', 'test'];
307
326
  }
308
327
  else {
328
+ if (!existsSync(join(projectRoot, 'node_modules'))) {
329
+ return {
330
+ success: false,
331
+ type: 'test',
332
+ command: `${scripts.packageManager} run ${scripts.test}`,
333
+ output: 'node_modules not found. Run npm install first.',
334
+ errors: [{ severity: 'error', message: 'node_modules not found. Run npm install first.' }],
335
+ duration: 0,
336
+ };
337
+ }
309
338
  command = scripts.packageManager;
310
339
  args = ['run', scripts.test];
311
340
  }
@@ -314,7 +343,7 @@ export function runTestVerification(projectRoot, timeout = 120000) {
314
343
  /**
315
344
  * Run TypeScript type checking
316
345
  */
317
- export function runTypecheckVerification(projectRoot, timeout = 60000) {
346
+ export async function runTypecheckVerification(projectRoot, timeout = 60000) {
318
347
  const scripts = detectProjectScripts(projectRoot);
319
348
  if (!scripts.typecheck) {
320
349
  return null;
@@ -322,8 +351,15 @@ export function runTypecheckVerification(projectRoot, timeout = 60000) {
322
351
  let command;
323
352
  let args;
324
353
  if (scripts.typecheck === '__tsc_direct__') {
325
- command = 'npx';
326
- args = ['tsc', '--noEmit'];
354
+ const localTsc = join(projectRoot, 'node_modules', '.bin', 'tsc');
355
+ if (existsSync(localTsc)) {
356
+ command = localTsc;
357
+ args = ['--noEmit'];
358
+ }
359
+ else {
360
+ command = 'npx';
361
+ args = ['tsc', '--noEmit'];
362
+ }
327
363
  }
328
364
  else if (scripts.typecheck === '__php_lint__') {
329
365
  // PHP syntax check on all PHP files
@@ -339,7 +375,7 @@ export function runTypecheckVerification(projectRoot, timeout = 60000) {
339
375
  /**
340
376
  * Run lint verification
341
377
  */
342
- export function runLintVerification(projectRoot, timeout = 60000) {
378
+ export async function runLintVerification(projectRoot, timeout = 60000) {
343
379
  const scripts = detectProjectScripts(projectRoot);
344
380
  if (!scripts.lint) {
345
381
  return null;
@@ -351,30 +387,30 @@ export function runLintVerification(projectRoot, timeout = 60000) {
351
387
  /**
352
388
  * Run all verifications
353
389
  */
354
- export function runAllVerifications(projectRoot, options = {}) {
390
+ export async function runAllVerifications(projectRoot, options = {}) {
355
391
  const opts = { ...DEFAULT_OPTIONS, ...options };
356
392
  const results = [];
357
393
  // Run typecheck first (fastest feedback)
358
394
  if (opts.runTypecheck) {
359
- const result = runTypecheckVerification(projectRoot, opts.timeout);
395
+ const result = await runTypecheckVerification(projectRoot, opts.timeout);
360
396
  if (result)
361
397
  results.push(result);
362
398
  }
363
399
  // Run build
364
400
  if (opts.runBuild) {
365
- const result = runBuildVerification(projectRoot, opts.timeout);
401
+ const result = await runBuildVerification(projectRoot, opts.timeout);
366
402
  if (result)
367
403
  results.push(result);
368
404
  }
369
405
  // Run lint
370
406
  if (opts.runLint) {
371
- const result = runLintVerification(projectRoot, opts.timeout);
407
+ const result = await runLintVerification(projectRoot, opts.timeout);
372
408
  if (result)
373
409
  results.push(result);
374
410
  }
375
411
  // Run tests last (slowest)
376
412
  if (opts.runTest) {
377
- const result = runTestVerification(projectRoot, opts.timeout);
413
+ const result = await runTestVerification(projectRoot, opts.timeout);
378
414
  if (result)
379
415
  results.push(result);
380
416
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeep",
3
- "version": "1.2.66",
3
+ "version": "1.2.68",
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",