codeep 1.1.36 → 1.2.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 (36) hide show
  1. package/README.md +90 -4
  2. package/dist/api/index.js +64 -2
  3. package/dist/renderer/App.d.ts +5 -0
  4. package/dist/renderer/App.js +164 -227
  5. package/dist/renderer/components/Export.d.ts +22 -0
  6. package/dist/renderer/components/Export.js +64 -0
  7. package/dist/renderer/components/Help.js +5 -1
  8. package/dist/renderer/components/Logout.d.ts +29 -0
  9. package/dist/renderer/components/Logout.js +91 -0
  10. package/dist/renderer/components/Search.d.ts +30 -0
  11. package/dist/renderer/components/Search.js +83 -0
  12. package/dist/renderer/components/Settings.js +20 -0
  13. package/dist/renderer/components/Status.d.ts +6 -0
  14. package/dist/renderer/components/Status.js +20 -1
  15. package/dist/renderer/main.js +316 -141
  16. package/dist/utils/agent.d.ts +5 -0
  17. package/dist/utils/agent.js +238 -3
  18. package/dist/utils/agent.test.d.ts +1 -0
  19. package/dist/utils/agent.test.js +250 -0
  20. package/dist/utils/diffPreview.js +104 -35
  21. package/dist/utils/gitignore.d.ts +24 -0
  22. package/dist/utils/gitignore.js +161 -0
  23. package/dist/utils/gitignore.test.d.ts +1 -0
  24. package/dist/utils/gitignore.test.js +167 -0
  25. package/dist/utils/skills.d.ts +21 -0
  26. package/dist/utils/skills.js +51 -0
  27. package/dist/utils/smartContext.js +8 -0
  28. package/dist/utils/smartContext.test.d.ts +1 -0
  29. package/dist/utils/smartContext.test.js +382 -0
  30. package/dist/utils/tokenTracker.d.ts +52 -0
  31. package/dist/utils/tokenTracker.js +86 -0
  32. package/dist/utils/tools.d.ts +16 -0
  33. package/dist/utils/tools.js +146 -19
  34. package/dist/utils/tools.test.d.ts +1 -0
  35. package/dist/utils/tools.test.js +664 -0
  36. package/package.json +1 -1
@@ -15,12 +15,15 @@ import { config, loadApiKey, loadAllApiKeys, getCurrentProvider, getModelsForCur
15
15
  import { isProjectDirectory, getProjectContext } from '../utils/project.js';
16
16
  import { getCurrentVersion } from '../utils/update.js';
17
17
  import { getProviderList } from '../config/providers.js';
18
+ import { getSessionStats } from '../utils/tokenTracker.js';
18
19
  // State
19
20
  let projectPath = process.cwd();
20
21
  let projectContext = null;
21
22
  let hasWriteAccess = false;
22
23
  let sessionId = getCurrentSessionId();
23
24
  let app;
25
+ // Added file context (/add, /drop)
26
+ const addedFiles = new Map();
24
27
  /**
25
28
  * Get current status
26
29
  */
@@ -28,6 +31,7 @@ function getStatus() {
28
31
  const provider = getCurrentProvider();
29
32
  const providers = getProviderList();
30
33
  const providerInfo = providers.find(p => p.id === provider.id);
34
+ const stats = getSessionStats();
31
35
  return {
32
36
  version: getCurrentVersion(),
33
37
  provider: providerInfo?.name || 'Unknown',
@@ -37,14 +41,29 @@ function getStatus() {
37
41
  hasWriteAccess,
38
42
  sessionId,
39
43
  messageCount: 0, // Will be updated
44
+ tokenStats: {
45
+ totalTokens: stats.totalTokens,
46
+ promptTokens: stats.totalPromptTokens,
47
+ completionTokens: stats.totalCompletionTokens,
48
+ requestCount: stats.requestCount,
49
+ },
40
50
  };
41
51
  }
42
52
  // Agent state
43
53
  let isAgentRunning = false;
44
54
  let agentAbortController = null;
45
55
  /**
46
- * Handle chat submission
56
+ * Format added files as context to prepend to user messages
47
57
  */
58
+ function formatAddedFilesContext() {
59
+ if (addedFiles.size === 0)
60
+ return '';
61
+ const parts = ['[Attached files]'];
62
+ for (const [, file] of addedFiles) {
63
+ parts.push(`\nFile: ${file.relativePath}\n\`\`\`\n${file.content}\n\`\`\``);
64
+ }
65
+ return parts.join('\n') + '\n\n';
66
+ }
48
67
  async function handleSubmit(message) {
49
68
  // Check if we're waiting for interactive mode answers
50
69
  if (pendingInteractiveContext) {
@@ -112,7 +131,10 @@ async function handleSubmit(message) {
112
131
  app.startStreaming();
113
132
  // Get conversation history for context
114
133
  const history = app.getChatHistory();
115
- const response = await chat(message, history, (chunk) => {
134
+ // Prepend added file context if any
135
+ const fileContext = formatAddedFilesContext();
136
+ const enrichedMessage = fileContext ? fileContext + message : message;
137
+ const response = await chat(enrichedMessage, history, (chunk) => {
116
138
  app.addStreamChunk(chunk);
117
139
  }, undefined, projectContext, undefined);
118
140
  app.endStreaming();
@@ -290,7 +312,10 @@ async function executeAgentTask(task, dryRun = false) {
290
312
  // Store context in local variable for TypeScript narrowing
291
313
  const context = projectContext;
292
314
  try {
293
- const result = await runAgent(task, context, {
315
+ // Enrich task with added file context if any
316
+ const fileContext = formatAddedFilesContext();
317
+ const enrichedTask = fileContext ? fileContext + task : task;
318
+ const result = await runAgent(enrichedTask, context, {
294
319
  dryRun,
295
320
  onIteration: (iteration) => {
296
321
  app.updateAgentProgress(iteration);
@@ -312,21 +337,62 @@ async function executeAgentTask(task, dryRun = false) {
312
337
  // Update agent thinking
313
338
  const shortTarget = target.length > 50 ? '...' + target.slice(-47) : target;
314
339
  app.setAgentThinking(`${actionType}: ${shortTarget}`);
315
- // Add chat message for write/edit operations
340
+ // Add chat message with diff preview for write/edit operations
316
341
  if (actionType === 'write' && tool.parameters.content) {
317
342
  const filePath = tool.parameters.path;
318
- const ext = filePath.split('.').pop() || '';
319
- app.addMessage({
320
- role: 'system',
321
- content: `**Write** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.content}\n\`\`\``,
322
- });
343
+ try {
344
+ const { createFileDiff, formatDiffForDisplay } = require('../utils/diffPreview');
345
+ const diff = createFileDiff(filePath, tool.parameters.content, context.root);
346
+ const diffText = formatDiffForDisplay(diff);
347
+ const additions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'add').length, 0);
348
+ const deletions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'remove').length, 0);
349
+ app.addMessage({
350
+ role: 'system',
351
+ content: `**${diff.type === 'create' ? 'Create' : 'Write'}** \`${filePath}\` (+${additions} -${deletions})\n\n\`\`\`diff\n${diffText}\n\`\`\``,
352
+ });
353
+ }
354
+ catch {
355
+ const ext = filePath.split('.').pop() || '';
356
+ app.addMessage({
357
+ role: 'system',
358
+ content: `**Write** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.content}\n\`\`\``,
359
+ });
360
+ }
323
361
  }
324
362
  else if (actionType === 'edit' && tool.parameters.new_text) {
325
363
  const filePath = tool.parameters.path;
326
- const ext = filePath.split('.').pop() || '';
364
+ try {
365
+ const { createEditDiff, formatDiffForDisplay } = require('../utils/diffPreview');
366
+ const diff = createEditDiff(filePath, tool.parameters.old_text, tool.parameters.new_text, context.root);
367
+ if (diff) {
368
+ const additions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'add').length, 0);
369
+ const deletions = diff.hunks.reduce((sum, h) => sum + h.lines.filter((l) => l.type === 'remove').length, 0);
370
+ app.addMessage({
371
+ role: 'system',
372
+ content: `**Edit** \`${filePath}\` (+${additions} -${deletions})\n\n\`\`\`diff\n${formatDiffForDisplay(diff)}\n\`\`\``,
373
+ });
374
+ }
375
+ else {
376
+ const ext = filePath.split('.').pop() || '';
377
+ app.addMessage({
378
+ role: 'system',
379
+ content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
380
+ });
381
+ }
382
+ }
383
+ catch {
384
+ const ext = filePath.split('.').pop() || '';
385
+ app.addMessage({
386
+ role: 'system',
387
+ content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
388
+ });
389
+ }
390
+ }
391
+ else if (actionType === 'delete') {
392
+ const filePath = tool.parameters.path;
327
393
  app.addMessage({
328
394
  role: 'system',
329
- content: `**Edit** \`${filePath}\`\n\n\`\`\`${ext}\n${tool.parameters.new_text}\n\`\`\``,
395
+ content: `**Delete** \`${filePath}\``,
330
396
  });
331
397
  }
332
398
  },
@@ -360,6 +426,34 @@ async function executeAgentTask(task, dryRun = false) {
360
426
  const summary = result.finalResponse || `Completed ${result.actions.length} actions in ${result.iterations} steps.`;
361
427
  app.addMessage({ role: 'assistant', content: summary });
362
428
  app.notify(`Agent completed: ${result.actions.length} actions`);
429
+ // Auto-commit if enabled and there were file changes
430
+ if (!dryRun && config.get('agentAutoCommit') && result.actions.length > 0) {
431
+ try {
432
+ const { autoCommitAgentChanges, createBranchAndCommit } = await import('../utils/git.js');
433
+ const useBranch = config.get('agentAutoCommitBranch');
434
+ if (useBranch) {
435
+ const commitResult = createBranchAndCommit(task, result.actions, context.root);
436
+ if (commitResult.success) {
437
+ app.addMessage({ role: 'system', content: `Auto-committed on branch \`${commitResult.branch}\` (${commitResult.hash?.slice(0, 7)})` });
438
+ }
439
+ else if (commitResult.error !== 'No changes detected by git') {
440
+ app.addMessage({ role: 'system', content: `Auto-commit failed: ${commitResult.error}` });
441
+ }
442
+ }
443
+ else {
444
+ const commitResult = autoCommitAgentChanges(task, result.actions, context.root);
445
+ if (commitResult.success) {
446
+ app.addMessage({ role: 'system', content: `Auto-committed: ${commitResult.hash?.slice(0, 7)}` });
447
+ }
448
+ else if (commitResult.error !== 'No changes detected by git') {
449
+ app.addMessage({ role: 'system', content: `Auto-commit failed: ${commitResult.error}` });
450
+ }
451
+ }
452
+ }
453
+ catch {
454
+ // Silently ignore commit errors
455
+ }
456
+ }
363
457
  }
364
458
  else if (result.aborted) {
365
459
  app.addMessage({ role: 'assistant', content: 'Agent stopped by user.' });
@@ -383,6 +477,110 @@ async function executeAgentTask(task, dryRun = false) {
383
477
  app.setAgentRunning(false);
384
478
  }
385
479
  }
480
+ /**
481
+ * Run a skill by name or shortcut with the given args.
482
+ * Wires the skill execution engine to App's UI.
483
+ */
484
+ async function runSkill(nameOrShortcut, args) {
485
+ const { findSkill, parseSkillArgs, executeSkill, trackSkillUsage } = await import('../utils/skills.js');
486
+ const skill = findSkill(nameOrShortcut);
487
+ if (!skill)
488
+ return false;
489
+ // Pre-flight checks
490
+ if (skill.requiresGit) {
491
+ const { getGitStatus } = await import('../utils/git.js');
492
+ if (!projectPath || !getGitStatus(projectPath).isRepo) {
493
+ app.notify('This skill requires a git repository');
494
+ return true;
495
+ }
496
+ }
497
+ if (skill.requiresWriteAccess && !hasWriteAccess) {
498
+ app.notify('This skill requires write access. Use /grant first.');
499
+ return true;
500
+ }
501
+ const params = parseSkillArgs(args.join(' '), skill);
502
+ app.addMessage({ role: 'user', content: `/${skill.name}${args.length ? ' ' + args.join(' ') : ''}` });
503
+ trackSkillUsage(skill.name);
504
+ const { execSync } = await import('child_process');
505
+ try {
506
+ const result = await executeSkill(skill, params, {
507
+ onCommand: async (cmd) => {
508
+ try {
509
+ const output = execSync(cmd, {
510
+ cwd: projectPath || process.cwd(),
511
+ encoding: 'utf-8',
512
+ timeout: 60000,
513
+ stdio: ['pipe', 'pipe', 'pipe'],
514
+ });
515
+ const result = output.trim();
516
+ if (result) {
517
+ app.addMessage({ role: 'system', content: `\`${cmd}\`\n\`\`\`\n${result}\n\`\`\`` });
518
+ }
519
+ return result;
520
+ }
521
+ catch (err) {
522
+ const error = err;
523
+ const stderr = (error.stderr || '').trim();
524
+ const stdout = (error.stdout || '').trim();
525
+ // Git commands output progress/info to stderr even on success
526
+ if (error.status === 0 || stderr.includes('up-to-date') || stderr.includes('up to date') || stderr.includes('Already up to date')) {
527
+ const result = stdout || stderr;
528
+ if (result) {
529
+ app.addMessage({ role: 'system', content: `\`${cmd}\`\n\`\`\`\n${result}\n\`\`\`` });
530
+ }
531
+ return result;
532
+ }
533
+ const errOutput = stderr || stdout;
534
+ if (errOutput) {
535
+ app.addMessage({ role: 'system', content: `\`${cmd}\` failed:\n\`\`\`\n${errOutput}\n\`\`\`` });
536
+ }
537
+ throw new Error(errOutput || error.message || 'Command failed');
538
+ }
539
+ },
540
+ onPrompt: (prompt) => {
541
+ return new Promise((resolve, reject) => {
542
+ handleSubmit(prompt).then(() => {
543
+ // The AI response will be displayed in chat.
544
+ // We resolve with an empty string since the response is already shown.
545
+ resolve('');
546
+ }).catch(reject);
547
+ });
548
+ },
549
+ onAgent: (task) => {
550
+ return new Promise((resolve, reject) => {
551
+ if (!projectContext) {
552
+ reject(new Error('Agent requires project context'));
553
+ return;
554
+ }
555
+ runAgentTask(task).then(() => resolve('Agent completed')).catch(reject);
556
+ });
557
+ },
558
+ onConfirm: (message) => {
559
+ return new Promise((resolve) => {
560
+ app.showConfirm({
561
+ title: 'Confirm',
562
+ message: [message],
563
+ confirmLabel: 'Yes',
564
+ cancelLabel: 'No',
565
+ onConfirm: () => resolve(true),
566
+ onCancel: () => resolve(false),
567
+ });
568
+ });
569
+ },
570
+ onNotify: (message) => {
571
+ app.notify(message);
572
+ },
573
+ });
574
+ if (!result.success && result.output !== 'Cancelled by user') {
575
+ app.notify(`Skill failed: ${result.output}`);
576
+ }
577
+ }
578
+ catch (err) {
579
+ app.notify(`Skill error: ${err.message}`);
580
+ trackSkillUsage(skill.name, false);
581
+ }
582
+ return true;
583
+ }
386
584
  /**
387
585
  * Run a chain of commands sequentially
388
586
  */
@@ -546,28 +744,6 @@ function handleCommand(command, args) {
546
744
  });
547
745
  break;
548
746
  }
549
- case 'commit': {
550
- if (!projectContext) {
551
- app.notify('No project context');
552
- return;
553
- }
554
- import('../utils/git.js').then(({ getGitDiff, getGitStatus, suggestCommitMessage }) => {
555
- const status = getGitStatus(projectPath);
556
- if (!status.isRepo) {
557
- app.notify('Not a git repository');
558
- return;
559
- }
560
- const diff = getGitDiff(true, projectPath);
561
- if (!diff.success || !diff.diff) {
562
- app.notify('No staged changes. Use git add first.');
563
- return;
564
- }
565
- const suggestion = suggestCommitMessage(diff.diff);
566
- app.addMessage({ role: 'user', content: '/commit' });
567
- handleSubmit(`Generate a commit message for these staged changes. Suggestion: "${suggestion}"\n\nDiff:\n\`\`\`diff\n${diff.diff.slice(0, 2000)}\n\`\`\``);
568
- });
569
- break;
570
- }
571
747
  case 'undo': {
572
748
  import('../utils/agent.js').then(({ undoLastAction }) => {
573
749
  const result = undoLastAction();
@@ -966,6 +1142,81 @@ function handleCommand(command, args) {
966
1142
  });
967
1143
  break;
968
1144
  }
1145
+ // File context commands
1146
+ case 'add': {
1147
+ if (!args.length) {
1148
+ if (addedFiles.size === 0) {
1149
+ app.notify('Usage: /add <file-path> [file2] ... | No files added');
1150
+ }
1151
+ else {
1152
+ const fileList = Array.from(addedFiles.values()).map(f => f.relativePath).join(', ');
1153
+ app.notify(`Added files (${addedFiles.size}): ${fileList}`);
1154
+ }
1155
+ return;
1156
+ }
1157
+ const path = require('path');
1158
+ const fs = require('fs');
1159
+ const root = projectContext?.root || projectPath;
1160
+ let added = 0;
1161
+ const errors = [];
1162
+ for (const filePath of args) {
1163
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath);
1164
+ const relativePath = path.isAbsolute(filePath) ? path.relative(root, filePath) : filePath;
1165
+ try {
1166
+ const stat = fs.statSync(fullPath);
1167
+ if (!stat.isFile()) {
1168
+ errors.push(`${filePath}: not a file`);
1169
+ continue;
1170
+ }
1171
+ if (stat.size > 100000) {
1172
+ errors.push(`${filePath}: too large (${Math.round(stat.size / 1024)}KB, max 100KB)`);
1173
+ continue;
1174
+ }
1175
+ const content = fs.readFileSync(fullPath, 'utf-8');
1176
+ addedFiles.set(fullPath, { relativePath, content });
1177
+ added++;
1178
+ }
1179
+ catch {
1180
+ errors.push(`${filePath}: file not found`);
1181
+ }
1182
+ }
1183
+ if (added > 0) {
1184
+ app.notify(`Added ${added} file(s) to context (${addedFiles.size} total)`);
1185
+ }
1186
+ if (errors.length > 0) {
1187
+ app.notify(errors.join(', '));
1188
+ }
1189
+ break;
1190
+ }
1191
+ case 'drop': {
1192
+ if (!args.length) {
1193
+ if (addedFiles.size === 0) {
1194
+ app.notify('No files in context');
1195
+ }
1196
+ else {
1197
+ const count = addedFiles.size;
1198
+ addedFiles.clear();
1199
+ app.notify(`Dropped all ${count} file(s) from context`);
1200
+ }
1201
+ return;
1202
+ }
1203
+ const path = require('path');
1204
+ const root = projectContext?.root || projectPath;
1205
+ let dropped = 0;
1206
+ for (const filePath of args) {
1207
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(root, filePath);
1208
+ if (addedFiles.delete(fullPath)) {
1209
+ dropped++;
1210
+ }
1211
+ }
1212
+ if (dropped > 0) {
1213
+ app.notify(`Dropped ${dropped} file(s) (${addedFiles.size} remaining)`);
1214
+ }
1215
+ else {
1216
+ app.notify('File not found in context. Use /add to see added files.');
1217
+ }
1218
+ break;
1219
+ }
969
1220
  // Agent history and changes
970
1221
  case 'history': {
971
1222
  import('../utils/agent.js').then(({ getAgentHistory }) => {
@@ -1107,119 +1358,38 @@ function handleCommand(command, args) {
1107
1358
  break;
1108
1359
  }
1109
1360
  // Skills shortcuts
1110
- case 'c': {
1111
- handleCommand('commit', []);
1112
- break;
1113
- }
1114
- case 't': {
1115
- if (!projectContext) {
1116
- app.notify('No project context');
1117
- return;
1118
- }
1119
- app.addMessage({ role: 'user', content: '/test' });
1120
- handleSubmit('Generate and run tests for the current project. Focus on untested code.');
1121
- break;
1122
- }
1123
- case 'd': {
1124
- if (!projectContext) {
1125
- app.notify('No project context');
1126
- return;
1127
- }
1128
- app.addMessage({ role: 'user', content: '/docs' });
1129
- handleSubmit('Add documentation to the code. Focus on functions and classes that lack proper documentation.');
1130
- break;
1131
- }
1132
- case 'r': {
1133
- if (!projectContext) {
1134
- app.notify('No project context');
1135
- return;
1136
- }
1137
- app.addMessage({ role: 'user', content: '/refactor' });
1138
- handleSubmit('Refactor the code to improve quality, readability, and maintainability.');
1139
- break;
1140
- }
1141
- case 'f': {
1142
- if (!projectContext) {
1143
- app.notify('No project context');
1144
- return;
1145
- }
1146
- app.addMessage({ role: 'user', content: '/fix' });
1147
- handleSubmit('Debug and fix any issues in the current code. Look for bugs, errors, and potential problems.');
1148
- break;
1149
- }
1150
- case 'e': {
1151
- if (!args.length) {
1152
- app.notify('Usage: /e <file or code to explain>');
1153
- return;
1154
- }
1155
- app.addMessage({ role: 'user', content: `/explain ${args.join(' ')}` });
1156
- handleSubmit(`Explain this code or concept: ${args.join(' ')}`);
1157
- break;
1158
- }
1159
- case 'o': {
1160
- if (!projectContext) {
1161
- app.notify('No project context');
1162
- return;
1163
- }
1164
- app.addMessage({ role: 'user', content: '/optimize' });
1165
- handleSubmit('Optimize the code for better performance. Focus on efficiency and speed improvements.');
1166
- break;
1167
- }
1168
- case 'b': {
1169
- if (!projectContext) {
1170
- app.notify('No project context');
1171
- return;
1172
- }
1173
- app.addMessage({ role: 'user', content: '/debug' });
1174
- handleSubmit('Help debug the current issue. Analyze the code and identify the root cause of problems.');
1175
- break;
1176
- }
1177
- case 'p': {
1178
- // Push shortcut
1179
- import('child_process').then(({ execSync }) => {
1180
- try {
1181
- execSync('git push', { cwd: projectPath, encoding: 'utf-8' });
1182
- app.notify('Pushed successfully');
1183
- }
1184
- catch (err) {
1185
- app.notify(`Push failed: ${err.message}`);
1186
- }
1187
- });
1188
- break;
1189
- }
1190
- // Full skill names
1361
+ // Skill shortcuts and full names — delegated to skill execution engine
1362
+ case 'c':
1363
+ case 'commit':
1364
+ case 't':
1191
1365
  case 'test':
1366
+ case 'd':
1192
1367
  case 'docs':
1368
+ case 'r':
1193
1369
  case 'refactor':
1370
+ case 'f':
1194
1371
  case 'fix':
1372
+ case 'e':
1195
1373
  case 'explain':
1374
+ case 'o':
1196
1375
  case 'optimize':
1197
- case 'debug': {
1198
- const skillMap = {
1199
- test: 't',
1200
- docs: 'd',
1201
- refactor: 'r',
1202
- fix: 'f',
1203
- explain: 'e',
1204
- optimize: 'o',
1205
- debug: 'b',
1206
- };
1207
- handleCommand(skillMap[command], args);
1208
- break;
1209
- }
1210
- case 'push': {
1211
- handleCommand('p', args);
1212
- break;
1213
- }
1214
- case 'pull': {
1215
- import('child_process').then(({ execSync }) => {
1216
- try {
1217
- execSync('git pull', { cwd: projectPath, encoding: 'utf-8' });
1218
- app.notify('Pulled successfully');
1219
- }
1220
- catch (err) {
1221
- app.notify(`Pull failed: ${err.message}`);
1222
- }
1376
+ case 'b':
1377
+ case 'debug':
1378
+ case 'p':
1379
+ case 'push':
1380
+ case 'pull':
1381
+ case 'amend':
1382
+ case 'pr':
1383
+ case 'changelog':
1384
+ case 'branch':
1385
+ case 'stash':
1386
+ case 'unstash':
1387
+ case 'build':
1388
+ case 'deploy':
1389
+ case 'release':
1390
+ case 'publish': {
1391
+ runSkill(command, args).catch((err) => {
1392
+ app.notify(`Skill error: ${err.message}`);
1223
1393
  });
1224
1394
  break;
1225
1395
  }
@@ -1322,7 +1492,12 @@ function handleCommand(command, args) {
1322
1492
  break;
1323
1493
  }
1324
1494
  default:
1325
- app.notify(`Unknown command: /${command}`);
1495
+ // Try to run as a skill (handles custom skills and any built-in not in the switch)
1496
+ runSkill(command, args).then(handled => {
1497
+ if (!handled) {
1498
+ app.notify(`Unknown command: /${command}`);
1499
+ }
1500
+ });
1326
1501
  }
1327
1502
  }
1328
1503
  /**
@@ -30,6 +30,11 @@ export interface AgentResult {
30
30
  error?: string;
31
31
  aborted?: boolean;
32
32
  }
33
+ /**
34
+ * Load project rules from .codeep/rules.md or CODEEP.md
35
+ * Returns the rules content formatted for system prompt, or empty string if no rules found
36
+ */
37
+ export declare function loadProjectRules(projectRoot: string): string;
33
38
  /**
34
39
  * Run the agent loop
35
40
  */