codehere 0.2.1 → 0.2.2

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.
@@ -230,21 +230,11 @@ export function getMetrics() {
230
230
  * Format health status for display
231
231
  */
232
232
  export function formatHealthStatus(health) {
233
- const statusIcon = {
234
- healthy: '✓',
235
- degraded: '⚠',
236
- unhealthy: '✗',
237
- }[health.status];
238
- const statusColor = {
239
- healthy: 'green',
240
- degraded: 'yellow',
241
- unhealthy: 'red',
242
- }[health.status];
243
- let output = `${statusIcon} System Status: ${health.status.toUpperCase()}\n\n`;
244
- output += 'Component Checks:\n';
233
+ const statusIcon = health.status === 'healthy' ? '✓' : health.status === 'degraded' ? '!' : '✗';
234
+ let output = ` ${statusIcon} ${health.status.toUpperCase()}\n\n`;
245
235
  for (const [component, check] of Object.entries(health.checks)) {
246
- const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '⚠' : '✗';
247
- output += ` ${icon} ${component}: ${check.message}\n`;
236
+ const icon = check.status === 'ok' ? '✓' : check.status === 'warning' ? '!' : '✗';
237
+ output += ` ${icon} ${component.padEnd(12)} ${check.message}\n`;
248
238
  }
249
239
  return output;
250
240
  }
@@ -9,14 +9,14 @@ import { encoding_for_model } from 'tiktoken';
9
9
  // Load .env
10
10
  config();
11
11
  const enc = encoding_for_model('gpt-3.5-turbo');
12
- const CHUNK_SIZE = 300;
12
+ const CHUNK_SIZE = 2000; // ~500 tokens. OpenAI handles 8191 tokens per embedding.
13
13
  const OVERLAP = 50;
14
14
  // Rate limiting state
15
15
  let lastApiCallTime = 0;
16
16
  let apiCallCount = 0;
17
17
  let monthlyApiCallCount = 0;
18
- const MIN_DELAY_MS = 1500; // 1.5s between calls (40 calls/min = 1.5s)
19
- const MONTHLY_LIMIT = 1000; // Trial key limit
18
+ const MIN_DELAY_MS = 200; // 200ms between calls (OpenAI allows 3000 RPM for embeddings)
19
+ const MONTHLY_LIMIT = 50000; // OpenAI production limit is much higher
20
20
  /**
21
21
  * Rate-limited API call wrapper
22
22
  */
@@ -42,8 +42,8 @@ async function rateLimitedCall(fn) {
42
42
  catch (error) {
43
43
  // If rate limit error, wait longer and retry once
44
44
  if (error instanceof Error && error.message.includes('429')) {
45
- console.log('Rate limit hit, waiting 60 seconds...');
46
- await new Promise(resolve => setTimeout(resolve, 60000)); // Wait 1 minute
45
+ console.log('Rate limit hit, waiting 5 seconds...');
46
+ await new Promise(resolve => setTimeout(resolve, 5000));
47
47
  lastApiCallTime = Date.now();
48
48
  monthlyApiCallCount++;
49
49
  return await fn();
@@ -363,8 +363,8 @@ function findCodeFiles(dir) {
363
363
  const entries = readdirSync(dir, { withFileTypes: true });
364
364
  for (const entry of entries) {
365
365
  const fullPath = join(dir, entry.name);
366
- // Skip node_modules, .git, etc.
367
- if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build') {
366
+ // Skip build artifacts, caches, and hidden dirs
367
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' || entry.name === 'dist' || entry.name === 'build' || entry.name === '.codehere-cache' || entry.name === '.codehere') {
368
368
  continue;
369
369
  }
370
370
  if (entry.isDirectory()) {
@@ -444,6 +444,8 @@ async function embedTextLocal(text, retries = 3) {
444
444
  for (let attempt = 0; attempt < retries; attempt++) {
445
445
  try {
446
446
  if (process.env.OPENAI_API_KEY) {
447
+ const controller = new AbortController();
448
+ const timeout = setTimeout(() => controller.abort(), 10000);
447
449
  const response = await fetch('https://api.openai.com/v1/embeddings', {
448
450
  method: 'POST',
449
451
  headers: {
@@ -451,7 +453,9 @@ async function embedTextLocal(text, retries = 3) {
451
453
  'Content-Type': 'application/json',
452
454
  },
453
455
  body: JSON.stringify({ input: text, model: 'text-embedding-3-small' }),
456
+ signal: controller.signal,
454
457
  });
458
+ clearTimeout(timeout);
455
459
  if (!response.ok)
456
460
  throw new Error(`OpenAI embedding error: ${response.status}`);
457
461
  const data = await response.json();
@@ -239,27 +239,26 @@ export async function executeStatusCommand(options = {}) {
239
239
  }
240
240
  else {
241
241
  // Activity table
242
- const col = (n, color) => {
243
- const s = String(n);
244
- return color && n > 0 ? color(s) : colors.dim(s);
242
+ // Pad number BEFORE applying color, so ANSI codes don't break alignment
243
+ const fmtNum = (n, color) => {
244
+ const padded = String(n).padStart(10);
245
+ return color && n > 0 ? color(padded) : colors.dim(padded);
245
246
  };
246
247
  const labelW = 12;
247
- const numW = 10;
248
248
  const label = (s) => s.padEnd(labelW);
249
- const num = (s) => s.padStart(numW);
250
- const header = ` ${label('')}${num('Last 24h')}${num('This week')}${num('All time')}`;
249
+ const header = ` ${label('')}${'Last 24h'.padStart(10)}${'This week'.padStart(10)}${'All time'.padStart(10)}`;
251
250
  console.log(colors.dim(header));
252
- console.log(colors.dim(` ${'-'.repeat(labelW + numW * 3)}`));
253
- console.log(` ${label('Scans')}${num(col(s24.total))}${num(col(s7d.total))}${num(col(sAll.total))}`);
254
- console.log(` ${label('Blocked')}${num(col(s24.blocked, colors.red))}${num(col(s7d.blocked, colors.red))}${num(col(sAll.blocked, colors.red))}`);
251
+ console.log(colors.dim(` ${'-'.repeat(labelW + 30)}`));
252
+ console.log(` ${label('Scans')}${fmtNum(s24.total)}${fmtNum(s7d.total)}${fmtNum(sAll.total)}`);
253
+ console.log(` ${label('Blocked')}${fmtNum(s24.blocked, colors.red)}${fmtNum(s7d.blocked, colors.red)}${fmtNum(sAll.blocked, colors.red)}`);
255
254
  if (sAll.critical > 0) {
256
- console.log(` ${label('Critical')}${num(col(s24.critical, colors.red))}${num(col(s7d.critical, colors.red))}${num(col(sAll.critical, colors.red))}`);
255
+ console.log(` ${label('Critical')}${fmtNum(s24.critical, colors.red)}${fmtNum(s7d.critical, colors.red)}${fmtNum(sAll.critical, colors.red)}`);
257
256
  }
258
257
  newline();
259
258
  // Recent blocks
260
259
  const recentBlocked = [...entries].reverse().filter(e => e.blocked).slice(0, 5);
261
260
  if (recentBlocked.length > 0) {
262
- console.log(colors.bold('Recent blocks:'));
261
+ console.log(colors.dim(' RECENT BLOCKS'));
263
262
  for (const entry of recentBlocked) {
264
263
  const tool = colors.cyan(entry.toolName.padEnd(10));
265
264
  const time = colors.dim(timeAgo(entry.timestamp).padEnd(8));
@@ -275,11 +274,12 @@ export async function executeStatusCommand(options = {}) {
275
274
  }
276
275
  }
277
276
  // Skills status
277
+ console.log(colors.dim(' ─────'));
278
278
  if (hasSkills) {
279
- console.log(colors.dim(' Skills: /codehere:trust /codehere:report /codehere:audit /codehere:hook-status'));
279
+ console.log(colors.dim(' /codehere:trust /codehere:report /codehere:audit /codehere:hook-status'));
280
280
  }
281
281
  else {
282
- console.log(colors.dim(' Skills not installed. Run `codehere hook install` to add them.'));
282
+ console.log(colors.dim(' Run codehere hook install to add slash commands'));
283
283
  }
284
284
  newline();
285
285
  // Multi-agent session analysis
@@ -206,59 +206,58 @@ export async function executeTrustCommand(target, options = {}) {
206
206
  newline();
207
207
  sectionHeader('codehere Trust Report');
208
208
  newline();
209
+ // Score
209
210
  console.log(` Score ${scoreBar(overallScore)} ${colors.bold(String(overallScore))}/100 ${gradeColor(grade)}`);
210
211
  newline();
211
- // Findings summary
212
- const rows = [
213
- criticalCount > 0 ? ` ${colors.red('●')} ${colors.red(String(criticalCount) + ' critical')}` : null,
214
- highCount > 0 ? ` ${colors.yellow('●')} ${colors.yellow(String(highCount) + ' high')}` : null,
215
- mediumCount > 0 ? ` ${colors.dim('●')} ${mediumCount} medium` : null,
216
- lowCount > 0 ? ` ${colors.dim('●')} ${lowCount} low` : null,
212
+ // Findings summary (inline, compact)
213
+ const parts = [
214
+ criticalCount > 0 ? colors.red(`${criticalCount} critical`) : null,
215
+ highCount > 0 ? colors.yellow(`${highCount} high`) : null,
216
+ mediumCount > 0 ? colors.dim(`${mediumCount} medium`) : null,
217
+ lowCount > 0 ? colors.dim(`${lowCount} low`) : null,
217
218
  ].filter(Boolean);
218
- if (rows.length === 0) {
219
+ if (parts.length === 0) {
219
220
  console.log(` ${colors.green('✓')} ${randomCleanScan()}`);
220
221
  }
221
222
  else {
222
- rows.forEach(r => console.log(r));
223
+ console.log(` ${parts.join(colors.dim(' · '))}`);
223
224
  }
224
225
  newline();
225
226
  // Per-file results (show worst first)
226
227
  if (results.length > 1) {
227
228
  const sorted = [...results].sort((a, b) => a.score - b.score);
228
- console.log(colors.bold('Files:'));
229
+ console.log(colors.dim(' FILES'));
229
230
  for (const r of sorted.slice(0, 10)) {
230
- const icon = r.score >= 75 ? colors.green('✓') : r.score >= 60 ? colors.yellow('⚠') : colors.red('✗');
231
- console.log(` ${icon} ${gradeColor(r.grade)} ${String(r.score).padStart(3)}/100 ${colors.dim(r.filepath)}`);
231
+ const icon = r.score >= 75 ? colors.green('✓') : r.score >= 60 ? colors.yellow('!') : colors.red('✗');
232
+ console.log(` ${icon} ${gradeColor(r.grade)} ${String(r.score).padStart(3)}/100 ${r.filepath}`);
232
233
  }
233
234
  if (sorted.length > 10) {
234
- console.log(colors.dim(` ... and ${sorted.length - 10} more files`));
235
+ console.log(colors.dim(` + ${sorted.length - 10} more files`));
235
236
  }
236
237
  newline();
237
238
  }
238
- // Verbose: show all findings
239
+ // Findings detail
239
240
  if (options.verbose) {
241
+ console.log(colors.dim(' FINDINGS'));
240
242
  for (const r of results) {
241
243
  if (r.findings.length === 0)
242
244
  continue;
243
- console.log(colors.bold(r.filepath) + ':');
245
+ console.log(` ${colors.bold(r.filepath)}`);
244
246
  for (const f of r.findings) {
245
- const sev = f.severity === 'critical' ? colors.red(f.severity)
246
- : f.severity === 'high' ? colors.yellow(f.severity)
247
- : colors.dim(f.severity);
248
- const line = f.line ? colors.dim(`:${f.line}`) : '';
249
- const cwe = f.cwe ? colors.dim(` [${f.cwe}]`) : '';
250
- console.log(` ${sev} ${f.category}${cwe}${line} — ${f.description}`);
247
+ const sev = f.severity === 'critical' ? colors.red('crit')
248
+ : f.severity === 'high' ? colors.yellow('high')
249
+ : colors.dim(f.severity.slice(0, 4));
250
+ const ln = f.line ? colors.dim(`L${f.line}`) : '';
251
+ console.log(` ${sev} ${ln.padEnd(6)} ${f.description}`);
251
252
  }
252
- newline();
253
253
  }
254
+ newline();
254
255
  }
255
256
  else if (criticalCount + highCount > 0) {
256
- // Show only critical/high findings without verbose flag
257
- console.log(colors.bold('Top findings:'));
257
+ console.log(colors.dim(' FINDINGS'));
258
258
  const allFindings = results.flatMap(r => r.findings
259
259
  .filter(f => f.severity === 'critical' || f.severity === 'high')
260
260
  .map(f => ({ ...f, filepath: r.filepath })));
261
- // Show critical first, then high
262
261
  const sorted = allFindings.sort((a, b) => {
263
262
  if (a.severity === 'critical' && b.severity !== 'critical')
264
263
  return -1;
@@ -267,9 +266,13 @@ export async function executeTrustCommand(target, options = {}) {
267
266
  return 0;
268
267
  });
269
268
  for (const f of sorted.slice(0, 5)) {
270
- const sev = f.severity === 'critical' ? colors.red(f.severity) : colors.yellow(f.severity);
271
- const line = f.line ? `:${f.line}` : '';
272
- console.log(` ${sev} ${f.filepath}${line} — ${f.description}`);
269
+ const sev = f.severity === 'critical' ? colors.red('crit') : colors.yellow('high');
270
+ const ln = f.line ? `:${f.line}` : '';
271
+ console.log(` ${sev} ${f.filepath}${ln}`);
272
+ console.log(` ${colors.dim(f.description)}`);
273
+ }
274
+ if (sorted.length > 5) {
275
+ console.log(colors.dim(` + ${sorted.length - 5} more findings (use --verbose to see all)`));
273
276
  }
274
277
  newline();
275
278
  }
@@ -277,10 +280,11 @@ export async function executeTrustCommand(target, options = {}) {
277
280
  const recColor = criticalCount > 0 ? colors.red : highCount > 0 ? colors.yellow : colors.green;
278
281
  console.log(recColor(recommendation));
279
282
  newline();
280
- // Helpful tips
283
+ // Tips footer (quiet, separated)
281
284
  if (results.length > 0 && !options.ci) {
282
- console.log(colors.dim(' codehere trust --badge') + colors.dim(' Add a badge to your README'));
283
- console.log(colors.dim(' codehere trust --ci') + colors.dim(' Exit 1 on critical findings (for CI)'));
285
+ console.log(colors.dim(' ─────'));
286
+ console.log(colors.dim(' trust --badge Add a badge to your README'));
287
+ console.log(colors.dim(' trust --ci Exit 1 on critical findings'));
284
288
  newline();
285
289
  }
286
290
  if (ciFailed) {
package/dist/session.js CHANGED
@@ -254,6 +254,14 @@ export async function startSession() {
254
254
  }
255
255
  // Enter
256
256
  if (key === '\r' || key === '\n') {
257
+ // If palette is visible, complete the selected command first
258
+ if (pLines > 0 && line.startsWith('/') && !line.includes(' ')) {
259
+ const filter = line.slice(1).toLowerCase();
260
+ const filtered = PALETTE_CMDS.filter(c => c.name.startsWith(filter)).slice(0, maxPalette());
261
+ if (filtered[selected]) {
262
+ line = `/${filtered[selected].name}`;
263
+ }
264
+ }
257
265
  stdout.write('\r\x1b[J\n');
258
266
  const trimmed = line.trim();
259
267
  if (trimmed) {
@@ -302,29 +310,42 @@ export async function startSession() {
302
310
  // ── Initial render ──
303
311
  render();
304
312
  // ── Stdin with escape buffering ──
313
+ // CRITICAL: raw mode can deliver multiple chars in one data event (fast typing, paste).
314
+ // We must process each character individually, except escape sequences which are multi-byte.
305
315
  stdin.on('data', (raw) => {
306
316
  if (processing)
307
317
  return;
308
- if (escBuf.length > 0) {
309
- escBuf += raw;
310
- if (escTimer)
311
- clearTimeout(escTimer);
312
- if (escBuf.length >= 3) {
313
- const seq = escBuf;
314
- escBuf = '';
315
- handle(seq);
318
+ // If the whole chunk is a known escape sequence, handle it directly
319
+ if (raw.startsWith('\x1b') && raw.length >= 2) {
320
+ handle(raw);
321
+ return;
322
+ }
323
+ // Otherwise iterate character by character
324
+ for (let i = 0; i < raw.length; i++) {
325
+ if (processing)
326
+ return; // stop if a command started executing mid-chunk
327
+ const ch = raw[i];
328
+ if (escBuf.length > 0) {
329
+ escBuf += ch;
330
+ if (escTimer)
331
+ clearTimeout(escTimer);
332
+ if (escBuf.length >= 3) {
333
+ const seq = escBuf;
334
+ escBuf = '';
335
+ handle(seq);
336
+ }
337
+ else {
338
+ escTimer = setTimeout(() => { const seq = escBuf; escBuf = ''; handle(seq); }, 50);
339
+ }
340
+ continue;
316
341
  }
317
- else {
342
+ if (ch === '\x1b') {
343
+ escBuf = ch;
318
344
  escTimer = setTimeout(() => { const seq = escBuf; escBuf = ''; handle(seq); }, 50);
345
+ continue;
319
346
  }
320
- return;
321
- }
322
- if (raw === '\x1b') {
323
- escBuf = raw;
324
- escTimer = setTimeout(() => { const seq = escBuf; escBuf = ''; handle(seq); }, 50);
325
- return;
347
+ handle(ch);
326
348
  }
327
- handle(raw);
328
349
  });
329
350
  }
330
351
  // ── Command list (for bare / or help) ──────────────────────────────────────
@@ -363,13 +384,14 @@ async function executeInput(input, state) {
363
384
  stdout.write('\x1b[2J\x1b[H');
364
385
  return;
365
386
  }
366
- // Bare command names: "explain src/auth.js" → same as "/explain src/auth.js"
367
- const BARE_CMDS = PALETTE_CMDS.map(c => c.name);
387
+ // Bare command names: "trust ." or "status" → same as "/trust ." or "/status"
388
+ // Only match unambiguous commands — skip words that are common English ("help", "fix", "run", "plan", "report", "test")
389
+ const BARE_SAFE = ['trust', 'hook', 'status', 'health', 'setup', 'serve', 'models', 'config', 'index', 'explain', 'review', 'cost', 'undo'];
368
390
  const lower = input.toLowerCase();
369
391
  const firstWord = lower.split(/\s+/)[0];
370
- if (BARE_CMDS.includes(firstWord)) {
371
- const args = input.slice(firstWord.length).trim();
372
- await handleSlash(firstWord, args, state);
392
+ const restArgs = input.slice(firstWord.length).trim();
393
+ if (BARE_SAFE.includes(firstWord)) {
394
+ await handleSlash(firstWord, restArgs, state);
373
395
  return;
374
396
  }
375
397
  // Everything else → AI query (the router decides what to do)
@@ -448,7 +470,13 @@ async function handleSlash(cmd, args, state) {
448
470
  break;
449
471
  }
450
472
  case 'test': {
451
- console.log(colors.dim(' Run from terminal: ') + colors.cyan(`codehere test ${args || '<file>'}`));
473
+ if (!args) {
474
+ console.log(colors.dim(' Usage: /test <file>'));
475
+ return;
476
+ }
477
+ if (!(await requireKey()))
478
+ return;
479
+ await handleQuery(`Generate tests for ${args}`, state);
452
480
  break;
453
481
  }
454
482
  case 'docs': {
@@ -457,7 +485,13 @@ async function handleSlash(cmd, args, state) {
457
485
  break;
458
486
  }
459
487
  case 'refactor': {
460
- console.log(colors.dim(' Run from terminal: ') + colors.cyan(`codehere refactor ${args || '<pattern> <instruction>'}`));
488
+ if (!args) {
489
+ console.log(colors.dim(' Usage: /refactor <goal>'));
490
+ return;
491
+ }
492
+ if (!(await requireKey()))
493
+ return;
494
+ await handleQuery('Refactor: ' + args, state);
461
495
  break;
462
496
  }
463
497
  case 'orchestrate': {
@@ -479,12 +513,25 @@ async function handleSlash(cmd, args, state) {
479
513
  break;
480
514
  }
481
515
  case 'git-review': {
482
- console.log(colors.dim(' Run from terminal: ') + colors.cyan('codehere git-review'));
516
+ if (!(await requireKey()))
517
+ return;
518
+ await handleQuery('Review the recent git changes in this project', state);
483
519
  break;
484
520
  }
485
- case 'index':
486
- console.log(colors.dim(' Run from terminal: ') + colors.cyan('codehere index'));
521
+ case 'index': {
522
+ if (!(await requireKey()))
523
+ return;
524
+ const { indexRepositoryParallel } = await import('./embed.js');
525
+ const spinner = startSpinner('Indexing codebase...');
526
+ try {
527
+ await indexRepositoryParallel(process.cwd(), { useCache: true, maxConcurrency: 5 });
528
+ spinner.succeed(colors.green('Index built'));
529
+ }
530
+ catch (err) {
531
+ spinner.fail(error('Index failed: ' + (err instanceof Error ? err.message : String(err))));
532
+ }
487
533
  break;
534
+ }
488
535
  case 'health': {
489
536
  const { performHealthCheck, formatHealthStatus } = await import('./monitoring.js');
490
537
  console.log(formatHealthStatus(performHealthCheck()));
@@ -496,7 +543,8 @@ async function handleSlash(cmd, args, state) {
496
543
  break;
497
544
  }
498
545
  case 'serve': {
499
- console.log(colors.dim(' Run from terminal: ') + colors.cyan('codehere serve'));
546
+ console.log(colors.dim(' MCP server must run in its own process:'));
547
+ console.log(colors.cyan(' codehere serve'));
500
548
  break;
501
549
  }
502
550
  case 'config': {
@@ -516,7 +564,8 @@ async function handleSlash(cmd, args, state) {
516
564
  break;
517
565
  }
518
566
  case 'undo': {
519
- console.log(colors.dim(' Run from terminal: ') + colors.cyan(`codehere undo ${args || '<file>'}`));
567
+ const { executeUndoCommand } = await import('./presentation/cli/commands/undo-command.js');
568
+ await executeUndoCommand(args ? 'undo' : 'list', args || undefined);
520
569
  break;
521
570
  }
522
571
  case 'clear':
@@ -584,6 +633,9 @@ async function handleQuery(query, state) {
584
633
  else if (result.error) {
585
634
  console.log(error(' ' + result.error));
586
635
  }
636
+ else {
637
+ console.log(colors.dim(' No response. Try rephrasing, or run "codehere index" first.'));
638
+ }
587
639
  state.history.push({ command: query, response: result.message || result.error || '', timestamp: new Date().toISOString() });
588
640
  const entities = conversationContextManager.extractEntities(query);
589
641
  conversationContextManager.addTurn(sessionId, query, result.message || '', entities, result.intent);
package/dist/ui.js CHANGED
@@ -19,7 +19,12 @@ export function isMinimalStyle() {
19
19
  }
20
20
  // Minimal header for commands (used in minimal style)
21
21
  export function printMinimalHeader(command, query) {
22
- console.log(colors.cyan(`codehere ${command}`) + colors.dim(' ▸ ') + query);
22
+ if (query) {
23
+ console.log(colors.cyan(`codehere ${command}`) + colors.dim(' ▸ ') + query);
24
+ }
25
+ else {
26
+ console.log(colors.cyan(`codehere ${command}`));
27
+ }
23
28
  console.log();
24
29
  }
25
30
  // Minimal safety note (used at end of AI responses)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codehere",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Know what your AI agent actually did — scans every AI write before it touches your codebase",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -19,12 +19,14 @@
19
19
  "start:api": "npm run build && node dist/api/server.js",
20
20
  "dev": "tsx src/index.ts",
21
21
  "dev:api": "tsx api/server.ts",
22
- "test": "npm run build && tsx ./test/run-tests.ts",
23
- "test:comprehensive": "npm run build && RUN_COMPREHENSIVE_TESTS=true tsx ./test/comprehensive-use-cases.test.ts",
24
- "test:execution": "npm run build && tsx ./test/run-comprehensive-test-execution.ts",
25
- "test:suite": "npm run build && tsx test/test-suite.ts",
26
- "test:integration": "npm run build && tsx test/integration/run-integration-tests.ts",
22
+ "test": "npm run build && vitest run",
23
+ "test:unit": "npm run build && tsx test/unit/run-unit-tests.ts",
27
24
  "test:cli": "npm run build && tsx test/integration/run-cli-tests.ts",
25
+ "test:watch": "vitest",
26
+ "lint": "eslint src/ --ext .ts",
27
+ "format": "prettier --write 'src/**/*.ts' 'test/**/*.ts'",
28
+ "format:check": "prettier --check 'src/**/*.ts' 'test/**/*.ts'",
29
+ "test:integration": "npm run build && tsx test/integration/run-integration-tests.ts",
28
30
  "test:scenarios": "npm run build && tsx test/scenarios/real-world-scenarios.ts",
29
31
  "test:security": "npm run build && tsx test/security/sast-scanner.test.ts",
30
32
  "test:security-gate": "npm run build && tsx test/security/security-gate.test.ts",
@@ -36,7 +38,6 @@
36
38
  "test:compound-instruction": "npm run build && tsx test/compound-instruction/compound-instruction.test.ts",
37
39
  "check-api": "npm run build && tsx -e \"import('./dist/utils/check-api-status.js').then(m => m.checkAPIStatus().then(s => { console.log(s.canProceed ? '✅ ' + s.message : '❌ ' + s.message); process.exit(s.canProceed ? 0 : 1); }))\"",
38
40
  "test:all": "npm run build && npm run test:integration && npm run test:scenarios && npm run test:security && npm run test:security-gate && npm run test:git-workflow && npm run test:memory && npm run test:performance",
39
- "test:unit": "npm run build && tsx test/unit/run-unit-tests.ts",
40
41
  "test:coverage": "c8 npm run test:unit && c8 report --reporter=text --reporter=html",
41
42
  "test:rerank": "tsx test/rerank-integration.test.ts",
42
43
  "eval:command-r": "tsx test/command-a-evaluation.test.ts",
@@ -97,6 +98,7 @@
97
98
  "wrap-ansi": "^9.0.2"
98
99
  },
99
100
  "devDependencies": {
101
+ "@eslint/js": "^10.0.1",
100
102
  "@types/cli-progress": "^3.11.5",
101
103
  "@types/cors": "^2.8.19",
102
104
  "@types/diff": "^5.0.8",
@@ -105,8 +107,12 @@
105
107
  "@types/sql.js": "^1.4.8",
106
108
  "@types/supertest": "^6.0.3",
107
109
  "c8": "^10.1.3",
110
+ "eslint": "^10.2.0",
111
+ "prettier": "^3.8.3",
108
112
  "supertest": "^7.1.4",
109
113
  "tsx": "^4.21.0",
110
- "typescript": "^5.9.3"
114
+ "typescript": "^5.9.3",
115
+ "typescript-eslint": "^8.58.2",
116
+ "vitest": "^4.1.4"
111
117
  }
112
118
  }