codeep 1.2.17 → 1.2.19

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 (64) hide show
  1. package/README.md +20 -7
  2. package/dist/api/index.d.ts +7 -0
  3. package/dist/api/index.js +21 -17
  4. package/dist/config/providers.d.ts +6 -0
  5. package/dist/config/providers.js +11 -0
  6. package/dist/renderer/App.d.ts +1 -5
  7. package/dist/renderer/App.js +106 -486
  8. package/dist/renderer/agentExecution.d.ts +36 -0
  9. package/dist/renderer/agentExecution.js +394 -0
  10. package/dist/renderer/commands.d.ts +16 -0
  11. package/dist/renderer/commands.js +838 -0
  12. package/dist/renderer/handlers.d.ts +87 -0
  13. package/dist/renderer/handlers.js +260 -0
  14. package/dist/renderer/highlight.d.ts +18 -0
  15. package/dist/renderer/highlight.js +130 -0
  16. package/dist/renderer/main.d.ts +4 -2
  17. package/dist/renderer/main.js +103 -1550
  18. package/dist/utils/agent.d.ts +5 -15
  19. package/dist/utils/agent.js +9 -693
  20. package/dist/utils/agentChat.d.ts +46 -0
  21. package/dist/utils/agentChat.js +343 -0
  22. package/dist/utils/agentStream.d.ts +23 -0
  23. package/dist/utils/agentStream.js +216 -0
  24. package/dist/utils/keychain.js +3 -2
  25. package/dist/utils/learning.js +9 -3
  26. package/dist/utils/mcpIntegration.d.ts +61 -0
  27. package/dist/utils/mcpIntegration.js +154 -0
  28. package/dist/utils/project.js +8 -3
  29. package/dist/utils/skills.js +21 -11
  30. package/dist/utils/smartContext.d.ts +4 -0
  31. package/dist/utils/smartContext.js +51 -14
  32. package/dist/utils/toolExecution.d.ts +27 -0
  33. package/dist/utils/toolExecution.js +525 -0
  34. package/dist/utils/toolParsing.d.ts +18 -0
  35. package/dist/utils/toolParsing.js +302 -0
  36. package/dist/utils/tools.d.ts +11 -24
  37. package/dist/utils/tools.js +22 -1187
  38. package/package.json +3 -1
  39. package/dist/config/config.test.d.ts +0 -1
  40. package/dist/config/config.test.js +0 -157
  41. package/dist/config/providers.test.d.ts +0 -1
  42. package/dist/config/providers.test.js +0 -187
  43. package/dist/hooks/index.d.ts +0 -4
  44. package/dist/hooks/index.js +0 -4
  45. package/dist/hooks/useAgent.d.ts +0 -29
  46. package/dist/hooks/useAgent.js +0 -148
  47. package/dist/utils/agent.test.d.ts +0 -1
  48. package/dist/utils/agent.test.js +0 -315
  49. package/dist/utils/git.test.d.ts +0 -1
  50. package/dist/utils/git.test.js +0 -193
  51. package/dist/utils/gitignore.test.d.ts +0 -1
  52. package/dist/utils/gitignore.test.js +0 -167
  53. package/dist/utils/project.test.d.ts +0 -1
  54. package/dist/utils/project.test.js +0 -212
  55. package/dist/utils/ratelimit.test.d.ts +0 -1
  56. package/dist/utils/ratelimit.test.js +0 -131
  57. package/dist/utils/retry.test.d.ts +0 -1
  58. package/dist/utils/retry.test.js +0 -163
  59. package/dist/utils/smartContext.test.d.ts +0 -1
  60. package/dist/utils/smartContext.test.js +0 -382
  61. package/dist/utils/tools.test.d.ts +0 -1
  62. package/dist/utils/tools.test.js +0 -681
  63. package/dist/utils/validation.test.d.ts +0 -1
  64. package/dist/utils/validation.test.js +0 -164
@@ -1,315 +0,0 @@
1
- import { describe, it, expect, beforeEach, vi } from 'vitest';
2
- // Mock 'fs' before importing the module under test - use importOriginal to
3
- // preserve all fs exports that transitive dependencies need (e.g. mkdirSync).
4
- vi.mock('fs', async (importOriginal) => {
5
- const actual = await importOriginal();
6
- return {
7
- ...actual,
8
- existsSync: vi.fn(),
9
- readFileSync: vi.fn(),
10
- };
11
- });
12
- import { existsSync, readFileSync } from 'fs';
13
- import { join } from 'path';
14
- import { loadProjectRules, formatAgentResult, formatChatHistoryForAgent } from './agent.js';
15
- // Cast mocked functions for convenience
16
- const mockExistsSync = existsSync;
17
- const mockReadFileSync = readFileSync;
18
- describe('loadProjectRules', () => {
19
- beforeEach(() => {
20
- vi.clearAllMocks();
21
- });
22
- it('should return formatted rules when .codeep/rules.md exists', () => {
23
- const projectRoot = '/my/project';
24
- const rulesContent = 'Always use TypeScript.\nNo console.log in production.';
25
- mockExistsSync.mockImplementation((filePath) => {
26
- return filePath === join(projectRoot, '.codeep', 'rules.md');
27
- });
28
- mockReadFileSync.mockReturnValue(rulesContent);
29
- const result = loadProjectRules(projectRoot);
30
- expect(result).toContain('## Project Rules');
31
- expect(result).toContain('Always use TypeScript.');
32
- expect(result).toContain('No console.log in production.');
33
- expect(mockExistsSync).toHaveBeenCalledWith(join(projectRoot, '.codeep', 'rules.md'));
34
- expect(mockReadFileSync).toHaveBeenCalledWith(join(projectRoot, '.codeep', 'rules.md'), 'utf-8');
35
- });
36
- it('should fall back to CODEEP.md when .codeep/rules.md does not exist', () => {
37
- const projectRoot = '/my/project';
38
- const rulesContent = 'Follow the style guide.';
39
- mockExistsSync.mockImplementation((filePath) => {
40
- return filePath === join(projectRoot, 'CODEEP.md');
41
- });
42
- mockReadFileSync.mockReturnValue(rulesContent);
43
- const result = loadProjectRules(projectRoot);
44
- expect(result).toContain('## Project Rules');
45
- expect(result).toContain('Follow the style guide.');
46
- // Should have checked .codeep/rules.md first
47
- expect(mockExistsSync).toHaveBeenCalledWith(join(projectRoot, '.codeep', 'rules.md'));
48
- // Then checked CODEEP.md
49
- expect(mockExistsSync).toHaveBeenCalledWith(join(projectRoot, 'CODEEP.md'));
50
- expect(mockReadFileSync).toHaveBeenCalledWith(join(projectRoot, 'CODEEP.md'), 'utf-8');
51
- });
52
- it('should return empty string when neither rules file exists', () => {
53
- const projectRoot = '/my/project';
54
- mockExistsSync.mockReturnValue(false);
55
- const result = loadProjectRules(projectRoot);
56
- expect(result).toBe('');
57
- expect(mockExistsSync).toHaveBeenCalledTimes(2);
58
- expect(mockReadFileSync).not.toHaveBeenCalled();
59
- });
60
- it('should return empty string when rules file exists but is empty', () => {
61
- const projectRoot = '/my/project';
62
- mockExistsSync.mockImplementation((filePath) => {
63
- return filePath === join(projectRoot, '.codeep', 'rules.md');
64
- });
65
- mockReadFileSync.mockReturnValue(' \n \n ');
66
- const result = loadProjectRules(projectRoot);
67
- // Empty after trim, so should skip and check next candidate
68
- expect(mockExistsSync).toHaveBeenCalledWith(join(projectRoot, '.codeep', 'rules.md'));
69
- // Since the first file was empty (whitespace-only), it checks the second
70
- expect(mockExistsSync).toHaveBeenCalledWith(join(projectRoot, 'CODEEP.md'));
71
- });
72
- it('should return empty string when both files exist but are empty', () => {
73
- const projectRoot = '/my/project';
74
- mockExistsSync.mockReturnValue(true);
75
- mockReadFileSync.mockReturnValue(' ');
76
- const result = loadProjectRules(projectRoot);
77
- expect(result).toBe('');
78
- });
79
- it('should return empty string when readFileSync throws an error', () => {
80
- const projectRoot = '/my/project';
81
- mockExistsSync.mockReturnValue(true);
82
- mockReadFileSync.mockImplementation(() => {
83
- throw new Error('EACCES: permission denied');
84
- });
85
- const result = loadProjectRules(projectRoot);
86
- // Both candidates exist but both throw on read, so should return ''
87
- expect(result).toBe('');
88
- // Should have attempted to read both files
89
- expect(mockReadFileSync).toHaveBeenCalledTimes(2);
90
- });
91
- it('should fall back to CODEEP.md when .codeep/rules.md read throws', () => {
92
- const projectRoot = '/my/project';
93
- mockExistsSync.mockReturnValue(true);
94
- mockReadFileSync.mockImplementation((filePath) => {
95
- if (filePath === join(projectRoot, '.codeep', 'rules.md')) {
96
- throw new Error('EACCES: permission denied');
97
- }
98
- return 'Fallback rules content';
99
- });
100
- const result = loadProjectRules(projectRoot);
101
- expect(result).toContain('## Project Rules');
102
- expect(result).toContain('Fallback rules content');
103
- expect(mockReadFileSync).toHaveBeenCalledTimes(2);
104
- });
105
- it('should prefer .codeep/rules.md over CODEEP.md when both exist', () => {
106
- const projectRoot = '/my/project';
107
- mockExistsSync.mockReturnValue(true);
108
- mockReadFileSync.mockImplementation((filePath) => {
109
- if (filePath === join(projectRoot, '.codeep', 'rules.md')) {
110
- return 'Primary rules';
111
- }
112
- return 'Secondary rules';
113
- });
114
- const result = loadProjectRules(projectRoot);
115
- expect(result).toContain('Primary rules');
116
- expect(result).not.toContain('Secondary rules');
117
- // Should only read the first file since it had content
118
- expect(mockReadFileSync).toHaveBeenCalledTimes(1);
119
- });
120
- it('should include the MUST follow preamble in the returned string', () => {
121
- const projectRoot = '/my/project';
122
- mockExistsSync.mockImplementation((filePath) => {
123
- return filePath === join(projectRoot, '.codeep', 'rules.md');
124
- });
125
- mockReadFileSync.mockReturnValue('Some rules');
126
- const result = loadProjectRules(projectRoot);
127
- expect(result).toContain('You MUST follow these rules');
128
- });
129
- });
130
- describe('formatAgentResult', () => {
131
- it('should format a successful result with iterations', () => {
132
- const result = {
133
- success: true,
134
- iterations: 3,
135
- actions: [],
136
- finalResponse: 'All done',
137
- };
138
- const formatted = formatAgentResult(result);
139
- expect(formatted).toContain('Agent completed in 3 iteration(s)');
140
- });
141
- it('should format a failed result with error message', () => {
142
- const result = {
143
- success: false,
144
- iterations: 5,
145
- actions: [],
146
- finalResponse: '',
147
- error: 'Exceeded maximum duration',
148
- };
149
- const formatted = formatAgentResult(result);
150
- expect(formatted).toContain('Agent failed: Exceeded maximum duration');
151
- });
152
- it('should format an aborted result', () => {
153
- const result = {
154
- success: false,
155
- iterations: 2,
156
- actions: [],
157
- finalResponse: 'Agent was stopped by user',
158
- aborted: true,
159
- };
160
- const formatted = formatAgentResult(result);
161
- expect(formatted).toContain('Agent was stopped by user');
162
- });
163
- it('should list actions when present', () => {
164
- const result = {
165
- success: true,
166
- iterations: 2,
167
- actions: [
168
- { type: 'read', target: 'src/index.ts', result: 'success', timestamp: Date.now() },
169
- { type: 'write', target: 'src/new-file.ts', result: 'success', timestamp: Date.now() },
170
- { type: 'command', target: 'npm install', result: 'error', timestamp: Date.now() },
171
- ],
172
- finalResponse: 'Done',
173
- };
174
- const formatted = formatAgentResult(result);
175
- expect(formatted).toContain('Actions performed:');
176
- expect(formatted).toContain('read: src/index.ts');
177
- expect(formatted).toContain('write: src/new-file.ts');
178
- expect(formatted).toContain('command: npm install');
179
- });
180
- it('should show check mark for successful actions and cross for errors', () => {
181
- const result = {
182
- success: true,
183
- iterations: 1,
184
- actions: [
185
- { type: 'write', target: 'file.ts', result: 'success', timestamp: Date.now() },
186
- { type: 'edit', target: 'other.ts', result: 'error', timestamp: Date.now() },
187
- ],
188
- finalResponse: 'Done',
189
- };
190
- const formatted = formatAgentResult(result);
191
- const lines = formatted.split('\n');
192
- const successLine = lines.find(l => l.includes('write: file.ts'));
193
- const errorLine = lines.find(l => l.includes('edit: other.ts'));
194
- expect(successLine).toMatch(/✓/);
195
- expect(errorLine).toMatch(/✗/);
196
- });
197
- it('should not show actions section when there are no actions', () => {
198
- const result = {
199
- success: true,
200
- iterations: 1,
201
- actions: [],
202
- finalResponse: 'Nothing to do',
203
- };
204
- const formatted = formatAgentResult(result);
205
- expect(formatted).not.toContain('Actions performed:');
206
- });
207
- it('should format a failed result without aborted flag', () => {
208
- const result = {
209
- success: false,
210
- iterations: 10,
211
- actions: [
212
- { type: 'read', target: 'config.json', result: 'success', timestamp: Date.now() },
213
- ],
214
- finalResponse: '',
215
- error: 'Exceeded maximum of 10 iterations',
216
- };
217
- const formatted = formatAgentResult(result);
218
- expect(formatted).toContain('Agent failed: Exceeded maximum of 10 iterations');
219
- expect(formatted).toContain('Actions performed:');
220
- expect(formatted).toContain('read: config.json');
221
- });
222
- it('should format result with single iteration correctly', () => {
223
- const result = {
224
- success: true,
225
- iterations: 1,
226
- actions: [],
227
- finalResponse: 'Quick task',
228
- };
229
- const formatted = formatAgentResult(result);
230
- expect(formatted).toContain('Agent completed in 1 iteration(s)');
231
- });
232
- it('should handle all action types', () => {
233
- const actionTypes = ['read', 'write', 'edit', 'delete', 'command', 'search', 'list', 'mkdir', 'fetch'];
234
- const result = {
235
- success: true,
236
- iterations: 5,
237
- actions: actionTypes.map(type => ({
238
- type,
239
- target: `target-for-${type}`,
240
- result: 'success',
241
- timestamp: Date.now(),
242
- })),
243
- finalResponse: 'Done',
244
- };
245
- const formatted = formatAgentResult(result);
246
- for (const type of actionTypes) {
247
- expect(formatted).toContain(`${type}: target-for-${type}`);
248
- }
249
- });
250
- });
251
- describe('formatChatHistoryForAgent', () => {
252
- it('should return empty string for undefined input', () => {
253
- expect(formatChatHistoryForAgent(undefined)).toBe('');
254
- });
255
- it('should return empty string for empty array', () => {
256
- expect(formatChatHistoryForAgent([])).toBe('');
257
- });
258
- it('should format simple chat history', () => {
259
- const history = [
260
- { role: 'user', content: 'How do I fix the login bug?' },
261
- { role: 'assistant', content: 'Check the auth middleware in src/auth.ts' },
262
- ];
263
- const result = formatChatHistoryForAgent(history);
264
- expect(result).toContain('## Prior Conversation Context');
265
- expect(result).toContain('**User:** How do I fix the login bug?');
266
- expect(result).toContain('**Assistant:** Check the auth middleware in src/auth.ts');
267
- });
268
- it('should filter out [AGENT] messages', () => {
269
- const history = [
270
- { role: 'user', content: 'Hello' },
271
- { role: 'user', content: '[AGENT] fix the bug' },
272
- { role: 'assistant', content: 'Agent completed in 3 iteration(s)' },
273
- { role: 'user', content: 'Thanks' },
274
- ];
275
- const result = formatChatHistoryForAgent(history);
276
- expect(result).toContain('**User:** Hello');
277
- expect(result).toContain('**User:** Thanks');
278
- expect(result).not.toContain('[AGENT]');
279
- expect(result).not.toContain('Agent completed');
280
- });
281
- it('should filter out [DRY RUN] messages', () => {
282
- const history = [
283
- { role: 'user', content: '[DRY RUN] test task' },
284
- ];
285
- const result = formatChatHistoryForAgent(history);
286
- expect(result).toBe('');
287
- });
288
- it('should filter out Agent failed/stopped messages', () => {
289
- const history = [
290
- { role: 'assistant', content: 'Agent failed: timeout' },
291
- { role: 'assistant', content: 'Agent stopped by user' },
292
- ];
293
- const result = formatChatHistoryForAgent(history);
294
- expect(result).toBe('');
295
- });
296
- it('should respect character budget and keep newest messages', () => {
297
- const history = [
298
- { role: 'user', content: 'A'.repeat(5000) },
299
- { role: 'assistant', content: 'B'.repeat(5000) },
300
- { role: 'user', content: 'Most recent message' },
301
- ];
302
- const result = formatChatHistoryForAgent(history, 6000);
303
- expect(result).toContain('Most recent message');
304
- // The first 5000-char message should be dropped due to budget
305
- expect(result).not.toContain('AAAAA');
306
- });
307
- it('should truncate a single very long message', () => {
308
- const history = [
309
- { role: 'user', content: 'X'.repeat(20000) },
310
- ];
311
- const result = formatChatHistoryForAgent(history, 8000);
312
- expect(result).toContain('[truncated]');
313
- expect(result.length).toBeLessThan(9000);
314
- });
315
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,193 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { execSync } from 'child_process';
3
- import { mkdirSync, rmSync, writeFileSync } from 'fs';
4
- import { join } from 'path';
5
- import { tmpdir } from 'os';
6
- import { isGitRepository, getGitStatus, getGitDiff, getChangedFiles, suggestCommitMessage, createCommit, stageAll, formatDiffForDisplay, } from './git.js';
7
- // Create a temp directory for git tests
8
- const TEST_DIR = join(tmpdir(), 'codeep-git-test-' + Date.now());
9
- const NON_GIT_DIR = join(tmpdir(), 'codeep-non-git-test-' + Date.now());
10
- describe('git utilities', () => {
11
- beforeEach(() => {
12
- // Create test directories
13
- mkdirSync(TEST_DIR, { recursive: true });
14
- mkdirSync(NON_GIT_DIR, { recursive: true });
15
- // Initialize git repo in TEST_DIR
16
- execSync('git init', { cwd: TEST_DIR, stdio: 'ignore' });
17
- execSync('git config user.email "test@test.com"', { cwd: TEST_DIR, stdio: 'ignore' });
18
- execSync('git config user.name "Test User"', { cwd: TEST_DIR, stdio: 'ignore' });
19
- });
20
- afterEach(() => {
21
- // Cleanup
22
- try {
23
- rmSync(TEST_DIR, { recursive: true, force: true });
24
- rmSync(NON_GIT_DIR, { recursive: true, force: true });
25
- }
26
- catch { }
27
- });
28
- describe('isGitRepository', () => {
29
- it('should return true for git repository', () => {
30
- expect(isGitRepository(TEST_DIR)).toBe(true);
31
- });
32
- it('should return false for non-git directory', () => {
33
- expect(isGitRepository(NON_GIT_DIR)).toBe(false);
34
- });
35
- it('should return false for non-existent directory', () => {
36
- expect(isGitRepository('/non/existent/path')).toBe(false);
37
- });
38
- });
39
- describe('getGitStatus', () => {
40
- it('should return isRepo: false for non-git directory', () => {
41
- const status = getGitStatus(NON_GIT_DIR);
42
- expect(status.isRepo).toBe(false);
43
- });
44
- it('should return correct status for git repo', () => {
45
- // Create initial commit so we have a branch
46
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
47
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
48
- execSync('git commit -m "initial"', { cwd: TEST_DIR, stdio: 'ignore' });
49
- const status = getGitStatus(TEST_DIR);
50
- expect(status.isRepo).toBe(true);
51
- expect(status.branch).toBeDefined();
52
- expect(status.hasChanges).toBe(false);
53
- });
54
- it('should detect changes', () => {
55
- // Create initial commit
56
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
57
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
58
- execSync('git commit -m "initial"', { cwd: TEST_DIR, stdio: 'ignore' });
59
- // Make a change
60
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello world');
61
- const status = getGitStatus(TEST_DIR);
62
- expect(status.hasChanges).toBe(true);
63
- });
64
- });
65
- describe('getGitDiff', () => {
66
- it('should return error for non-git directory', () => {
67
- const result = getGitDiff(false, NON_GIT_DIR);
68
- expect(result.success).toBe(false);
69
- expect(result.error).toBe('Not a git repository');
70
- });
71
- it('should return empty diff when no changes', () => {
72
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
73
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
74
- execSync('git commit -m "initial"', { cwd: TEST_DIR, stdio: 'ignore' });
75
- const result = getGitDiff(false, TEST_DIR);
76
- expect(result.success).toBe(true);
77
- expect(result.diff).toBe('');
78
- });
79
- it('should return diff for unstaged changes', () => {
80
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
81
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
82
- execSync('git commit -m "initial"', { cwd: TEST_DIR, stdio: 'ignore' });
83
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello world');
84
- const result = getGitDiff(false, TEST_DIR);
85
- expect(result.success).toBe(true);
86
- expect(result.diff).toContain('hello world');
87
- });
88
- it('should return diff for staged changes', () => {
89
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
90
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
91
- execSync('git commit -m "initial"', { cwd: TEST_DIR, stdio: 'ignore' });
92
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello world');
93
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
94
- const result = getGitDiff(true, TEST_DIR);
95
- expect(result.success).toBe(true);
96
- expect(result.diff).toContain('hello world');
97
- });
98
- });
99
- describe('getChangedFiles', () => {
100
- it('should return empty array for non-git directory', () => {
101
- expect(getChangedFiles(NON_GIT_DIR)).toEqual([]);
102
- });
103
- it('should return changed files', () => {
104
- writeFileSync(join(TEST_DIR, 'file1.txt'), 'content1');
105
- writeFileSync(join(TEST_DIR, 'file2.txt'), 'content2');
106
- const files = getChangedFiles(TEST_DIR);
107
- expect(files).toContain('file1.txt');
108
- expect(files).toContain('file2.txt');
109
- });
110
- });
111
- describe('suggestCommitMessage', () => {
112
- it('should suggest feat for new files', () => {
113
- const diff = 'new file mode 100644\n+++ b/newfile.ts';
114
- expect(suggestCommitMessage(diff)).toBe('feat: add new files');
115
- });
116
- it('should suggest chore for deleted files', () => {
117
- const diff = 'deleted file mode 100644\n--- a/oldfile.ts';
118
- expect(suggestCommitMessage(diff)).toBe('chore: remove files');
119
- });
120
- it('should suggest chore for package.json changes', () => {
121
- const diff = '+++ b/package.json\n+ "new-dep": "1.0.0"';
122
- expect(suggestCommitMessage(diff)).toBe('chore: update dependencies');
123
- });
124
- it('should suggest docs for README changes', () => {
125
- const diff = '+++ b/README.md\n+ New documentation';
126
- expect(suggestCommitMessage(diff)).toBe('docs: update documentation');
127
- });
128
- it('should suggest test for test file changes', () => {
129
- const diff = '+++ b/utils.test.ts\n+ test case';
130
- expect(suggestCommitMessage(diff)).toBe('test: update tests');
131
- });
132
- });
133
- describe('createCommit', () => {
134
- it('should return error for non-git directory', () => {
135
- const result = createCommit('test message', NON_GIT_DIR);
136
- expect(result.success).toBe(false);
137
- expect(result.error).toBe('Not a git repository');
138
- });
139
- it('should return error when no staged changes', () => {
140
- const result = createCommit('test message', TEST_DIR);
141
- expect(result.success).toBe(false);
142
- expect(result.error).toBe('No staged changes to commit');
143
- });
144
- it('should create commit successfully', () => {
145
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
146
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
147
- const result = createCommit('test commit message', TEST_DIR);
148
- expect(result.success).toBe(true);
149
- expect(result.hash).toBeDefined();
150
- expect(result.hash.length).toBeGreaterThan(0);
151
- });
152
- it('should handle special characters in commit message safely', () => {
153
- writeFileSync(join(TEST_DIR, 'test.txt'), 'hello');
154
- execSync('git add .', { cwd: TEST_DIR, stdio: 'ignore' });
155
- // Test with potentially dangerous characters that could cause shell injection
156
- const dangerousMessage = 'test `whoami` $(echo dangerous) ; rm -rf /';
157
- const result = createCommit(dangerousMessage, TEST_DIR);
158
- expect(result.success).toBe(true);
159
- // Verify the commit message was stored correctly (not executed)
160
- const log = execSync('git log -1 --format=%s', { cwd: TEST_DIR, encoding: 'utf-8' });
161
- expect(log.trim()).toBe(dangerousMessage);
162
- });
163
- });
164
- describe('stageAll', () => {
165
- it('should return false for non-git directory', () => {
166
- expect(stageAll(NON_GIT_DIR)).toBe(false);
167
- });
168
- it('should stage all files', () => {
169
- writeFileSync(join(TEST_DIR, 'file1.txt'), 'content1');
170
- writeFileSync(join(TEST_DIR, 'file2.txt'), 'content2');
171
- expect(stageAll(TEST_DIR)).toBe(true);
172
- // Verify files are staged
173
- const staged = execSync('git diff --cached --name-only', { cwd: TEST_DIR, encoding: 'utf-8' });
174
- expect(staged).toContain('file1.txt');
175
- expect(staged).toContain('file2.txt');
176
- });
177
- });
178
- describe('formatDiffForDisplay', () => {
179
- it('should return full diff if under limit', () => {
180
- const diff = 'line1\nline2\nline3';
181
- expect(formatDiffForDisplay(diff, 10)).toBe(diff);
182
- });
183
- it('should truncate long diffs', () => {
184
- const lines = Array.from({ length: 100 }, (_, i) => `line${i}`);
185
- const diff = lines.join('\n');
186
- const result = formatDiffForDisplay(diff, 10);
187
- expect(result).toContain('line0');
188
- expect(result).toContain('line9');
189
- expect(result).not.toContain('line99');
190
- expect(result).toContain('90 more lines');
191
- });
192
- });
193
- });
@@ -1 +0,0 @@
1
- export {};