codeep 1.2.16 → 1.2.18

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 (63) 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/renderer/App.d.ts +1 -5
  5. package/dist/renderer/App.js +106 -486
  6. package/dist/renderer/Input.js +8 -1
  7. package/dist/renderer/agentExecution.d.ts +36 -0
  8. package/dist/renderer/agentExecution.js +394 -0
  9. package/dist/renderer/commands.d.ts +16 -0
  10. package/dist/renderer/commands.js +838 -0
  11. package/dist/renderer/handlers.d.ts +87 -0
  12. package/dist/renderer/handlers.js +260 -0
  13. package/dist/renderer/highlight.d.ts +18 -0
  14. package/dist/renderer/highlight.js +130 -0
  15. package/dist/renderer/main.d.ts +4 -2
  16. package/dist/renderer/main.js +103 -1550
  17. package/dist/utils/agent.d.ts +5 -15
  18. package/dist/utils/agent.js +9 -693
  19. package/dist/utils/agentChat.d.ts +46 -0
  20. package/dist/utils/agentChat.js +343 -0
  21. package/dist/utils/agentStream.d.ts +23 -0
  22. package/dist/utils/agentStream.js +216 -0
  23. package/dist/utils/keychain.js +3 -2
  24. package/dist/utils/learning.js +9 -3
  25. package/dist/utils/mcpIntegration.d.ts +61 -0
  26. package/dist/utils/mcpIntegration.js +154 -0
  27. package/dist/utils/project.js +8 -3
  28. package/dist/utils/skills.js +21 -11
  29. package/dist/utils/smartContext.d.ts +4 -0
  30. package/dist/utils/smartContext.js +51 -14
  31. package/dist/utils/toolExecution.d.ts +27 -0
  32. package/dist/utils/toolExecution.js +525 -0
  33. package/dist/utils/toolParsing.d.ts +18 -0
  34. package/dist/utils/toolParsing.js +302 -0
  35. package/dist/utils/tools.d.ts +27 -24
  36. package/dist/utils/tools.js +30 -1169
  37. package/package.json +3 -1
  38. package/dist/config/config.test.d.ts +0 -1
  39. package/dist/config/config.test.js +0 -157
  40. package/dist/config/providers.test.d.ts +0 -1
  41. package/dist/config/providers.test.js +0 -187
  42. package/dist/hooks/index.d.ts +0 -4
  43. package/dist/hooks/index.js +0 -4
  44. package/dist/hooks/useAgent.d.ts +0 -29
  45. package/dist/hooks/useAgent.js +0 -148
  46. package/dist/utils/agent.test.d.ts +0 -1
  47. package/dist/utils/agent.test.js +0 -315
  48. package/dist/utils/git.test.d.ts +0 -1
  49. package/dist/utils/git.test.js +0 -193
  50. package/dist/utils/gitignore.test.d.ts +0 -1
  51. package/dist/utils/gitignore.test.js +0 -167
  52. package/dist/utils/project.test.d.ts +0 -1
  53. package/dist/utils/project.test.js +0 -212
  54. package/dist/utils/ratelimit.test.d.ts +0 -1
  55. package/dist/utils/ratelimit.test.js +0 -131
  56. package/dist/utils/retry.test.d.ts +0 -1
  57. package/dist/utils/retry.test.js +0 -163
  58. package/dist/utils/smartContext.test.d.ts +0 -1
  59. package/dist/utils/smartContext.test.js +0 -382
  60. package/dist/utils/tools.test.d.ts +0 -1
  61. package/dist/utils/tools.test.js +0 -676
  62. package/dist/utils/validation.test.d.ts +0 -1
  63. package/dist/utils/validation.test.js +0 -164
@@ -1,163 +0,0 @@
1
- import { describe, it, expect, vi } from 'vitest';
2
- import { withRetry, isNetworkError, isTimeoutError, fetchWithTimeout, } from './retry.js';
3
- describe('retry utilities', () => {
4
- describe('isNetworkError', () => {
5
- it('should detect fetch TypeError', () => {
6
- const error = new TypeError('Failed to fetch');
7
- expect(isNetworkError(error)).toBe(true);
8
- });
9
- it('should detect network TypeError', () => {
10
- const error = new TypeError('Network request failed');
11
- expect(isNetworkError(error)).toBe(true);
12
- });
13
- it('should detect ECONNREFUSED', () => {
14
- const error = { code: 'ECONNREFUSED' };
15
- expect(isNetworkError(error)).toBe(true);
16
- });
17
- it('should detect ENOTFOUND', () => {
18
- const error = { code: 'ENOTFOUND' };
19
- expect(isNetworkError(error)).toBe(true);
20
- });
21
- it('should detect ETIMEDOUT', () => {
22
- const error = { code: 'ETIMEDOUT' };
23
- expect(isNetworkError(error)).toBe(true);
24
- });
25
- it('should detect ENETUNREACH', () => {
26
- const error = { code: 'ENETUNREACH' };
27
- expect(isNetworkError(error)).toBe(true);
28
- });
29
- it('should detect ECONNRESET', () => {
30
- const error = { code: 'ECONNRESET' };
31
- expect(isNetworkError(error)).toBe(true);
32
- });
33
- it('should return false for non-network errors', () => {
34
- expect(isNetworkError(new Error('Some other error'))).toBe(false);
35
- expect(isNetworkError({ status: 400 })).toBe(false);
36
- expect(isNetworkError({ code: 'EPERM' })).toBe(false);
37
- });
38
- });
39
- describe('isTimeoutError', () => {
40
- it('should detect AbortError', () => {
41
- const error = new Error('Aborted');
42
- error.name = 'AbortError';
43
- expect(isTimeoutError(error)).toBe(true);
44
- });
45
- it('should detect ETIMEDOUT', () => {
46
- const error = { code: 'ETIMEDOUT' };
47
- expect(isTimeoutError(error)).toBe(true);
48
- });
49
- it('should return false for non-timeout errors', () => {
50
- expect(isTimeoutError(new Error('Some error'))).toBe(false);
51
- expect(isTimeoutError({ code: 'ECONNREFUSED' })).toBe(false);
52
- });
53
- });
54
- describe('withRetry', () => {
55
- it('should return result on first success', async () => {
56
- const fn = vi.fn().mockResolvedValue('success');
57
- const result = await withRetry(fn);
58
- expect(result).toBe('success');
59
- expect(fn).toHaveBeenCalledTimes(1);
60
- });
61
- it('should retry on failure and succeed', async () => {
62
- const fn = vi.fn()
63
- .mockRejectedValueOnce(new Error('fail 1'))
64
- .mockRejectedValueOnce(new Error('fail 2'))
65
- .mockResolvedValue('success');
66
- const result = await withRetry(fn, { baseDelay: 10 });
67
- expect(result).toBe('success');
68
- expect(fn).toHaveBeenCalledTimes(3);
69
- });
70
- it('should throw after max attempts', async () => {
71
- const fn = vi.fn().mockRejectedValue(new Error('always fails'));
72
- await expect(withRetry(fn, { maxAttempts: 3, baseDelay: 10 }))
73
- .rejects.toThrow('always fails');
74
- expect(fn).toHaveBeenCalledTimes(3);
75
- });
76
- it('should not retry on AbortError', async () => {
77
- const abortError = new Error('Aborted');
78
- abortError.name = 'AbortError';
79
- const fn = vi.fn().mockRejectedValue(abortError);
80
- await expect(withRetry(fn, { maxAttempts: 3, baseDelay: 10 }))
81
- .rejects.toThrow('Aborted');
82
- expect(fn).toHaveBeenCalledTimes(1);
83
- });
84
- it('should call onRetry callback', async () => {
85
- const onRetry = vi.fn();
86
- const fn = vi.fn()
87
- .mockRejectedValueOnce(new Error('fail'))
88
- .mockResolvedValue('success');
89
- await withRetry(fn, { baseDelay: 10, onRetry });
90
- expect(onRetry).toHaveBeenCalledTimes(1);
91
- expect(onRetry).toHaveBeenCalledWith(1, expect.any(Error), expect.any(Number));
92
- });
93
- it('should respect shouldRetry option', async () => {
94
- const shouldRetry = vi.fn().mockReturnValue(false);
95
- const fn = vi.fn().mockRejectedValue(new Error('fail'));
96
- await expect(withRetry(fn, { shouldRetry, maxAttempts: 3, baseDelay: 10 }))
97
- .rejects.toThrow('fail');
98
- expect(fn).toHaveBeenCalledTimes(1);
99
- });
100
- it('should not retry on 4xx errors by default', async () => {
101
- const error = { status: 400, message: 'Bad Request' };
102
- const fn = vi.fn().mockRejectedValue(error);
103
- await expect(withRetry(fn, { maxAttempts: 3, baseDelay: 10 }))
104
- .rejects.toEqual(error);
105
- expect(fn).toHaveBeenCalledTimes(1);
106
- });
107
- it('should retry on 5xx errors by default', async () => {
108
- const error = { status: 500, message: 'Server Error' };
109
- const fn = vi.fn()
110
- .mockRejectedValueOnce(error)
111
- .mockResolvedValue('success');
112
- const result = await withRetry(fn, { baseDelay: 10 });
113
- expect(result).toBe('success');
114
- expect(fn).toHaveBeenCalledTimes(2);
115
- });
116
- it('should respect maxDelay', async () => {
117
- const onRetry = vi.fn();
118
- const fn = vi.fn()
119
- .mockRejectedValueOnce(new Error('fail 1'))
120
- .mockRejectedValueOnce(new Error('fail 2'))
121
- .mockResolvedValue('success');
122
- await withRetry(fn, { baseDelay: 1000, maxDelay: 100, onRetry });
123
- // All delays should be capped at maxDelay
124
- for (const call of onRetry.mock.calls) {
125
- expect(call[2]).toBeLessThanOrEqual(100);
126
- }
127
- });
128
- });
129
- describe('fetchWithTimeout', () => {
130
- it('should make fetch request', async () => {
131
- const mockResponse = new Response('ok', { status: 200 });
132
- global.fetch = vi.fn().mockResolvedValue(mockResponse);
133
- const response = await fetchWithTimeout('https://example.com');
134
- expect(response.status).toBe(200);
135
- expect(global.fetch).toHaveBeenCalledWith('https://example.com', expect.objectContaining({ signal: expect.any(AbortSignal) }));
136
- });
137
- it('should abort on timeout', async () => {
138
- global.fetch = vi.fn().mockImplementation(() => new Promise((_, reject) => {
139
- setTimeout(() => {
140
- const error = new Error('Aborted');
141
- error.name = 'AbortError';
142
- reject(error);
143
- }, 100);
144
- }));
145
- await expect(fetchWithTimeout('https://example.com', { timeout: 50 }))
146
- .rejects.toThrow();
147
- });
148
- it('should pass through fetch options', async () => {
149
- const mockResponse = new Response('ok');
150
- global.fetch = vi.fn().mockResolvedValue(mockResponse);
151
- await fetchWithTimeout('https://example.com', {
152
- method: 'POST',
153
- headers: { 'Content-Type': 'application/json' },
154
- body: JSON.stringify({ test: true }),
155
- });
156
- expect(global.fetch).toHaveBeenCalledWith('https://example.com', expect.objectContaining({
157
- method: 'POST',
158
- headers: { 'Content-Type': 'application/json' },
159
- body: JSON.stringify({ test: true }),
160
- }));
161
- });
162
- });
163
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,382 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { extractTargetFile, formatSmartContext, } from './smartContext.js';
3
- // ---------------------------------------------------------------------------
4
- // Helper to build a SmartContextResult quickly
5
- // ---------------------------------------------------------------------------
6
- function makeContext(files, overrides) {
7
- const fullFiles = files.map((f) => ({
8
- path: f.path ?? `/project/${f.relativePath}`,
9
- relativePath: f.relativePath,
10
- reason: f.reason ?? 'test reason',
11
- priority: f.priority ?? 5,
12
- content: f.content, // may be undefined
13
- size: f.size ?? (f.content ? f.content.length : 0),
14
- }));
15
- return {
16
- files: fullFiles,
17
- totalSize: overrides?.totalSize ?? fullFiles.reduce((s, f) => s + (f.content?.length ?? 0), 0),
18
- truncated: overrides?.truncated ?? false,
19
- };
20
- }
21
- // ===========================================================================
22
- // extractTargetFile
23
- // ===========================================================================
24
- describe('extractTargetFile', () => {
25
- // ---- Action-verb patterns (edit, modify, update, change, fix) ----------
26
- describe('action verb patterns', () => {
27
- it('should extract file from "edit <file>"', () => {
28
- expect(extractTargetFile('edit src/utils/helper.ts')).toBe('src/utils/helper.ts');
29
- });
30
- it('should extract file from "modify the file <file>"', () => {
31
- expect(extractTargetFile('modify the file src/index.js')).toBe('src/index.js');
32
- });
33
- it('should extract file from "update <file>"', () => {
34
- expect(extractTargetFile('update config/settings.json')).toBe('config/settings.json');
35
- });
36
- it('should extract file from "change <file>"', () => {
37
- expect(extractTargetFile('change lib/core.ts')).toBe('lib/core.ts');
38
- });
39
- it('should extract file from "fix <file>"', () => {
40
- expect(extractTargetFile('fix src/api/endpoint.ts')).toBe('src/api/endpoint.ts');
41
- });
42
- it('should be case-insensitive for action verbs', () => {
43
- expect(extractTargetFile('Edit src/utils/helper.ts')).toBe('src/utils/helper.ts');
44
- expect(extractTargetFile('MODIFY src/utils/helper.ts')).toBe('src/utils/helper.ts');
45
- });
46
- it('should handle "the file" preamble', () => {
47
- expect(extractTargetFile('edit the file src/utils/helper.ts')).toBe('src/utils/helper.ts');
48
- });
49
- it('should handle "the" without "file"', () => {
50
- expect(extractTargetFile('fix the src/bug.ts')).toBe('src/bug.ts');
51
- });
52
- });
53
- // ---- "in/to" pattern ---------------------------------------------------
54
- describe('"in" and "to" patterns', () => {
55
- it('should extract file from "in <file>"', () => {
56
- expect(extractTargetFile('add a function in src/utils/helper.ts')).toBe('src/utils/helper.ts');
57
- });
58
- it('should extract file from "to <file>"', () => {
59
- expect(extractTargetFile('add a method to src/utils/helper.ts')).toBe('src/utils/helper.ts');
60
- });
61
- it('should extract file from "in the file <file>"', () => {
62
- expect(extractTargetFile('refactor code in the file src/app.tsx')).toBe('src/app.tsx');
63
- });
64
- });
65
- // ---- Quoted and backtick-quoted paths ----------------------------------
66
- describe('quoted file paths', () => {
67
- it('should extract file from single quotes', () => {
68
- expect(extractTargetFile("look at 'src/utils/helper.ts'")).toBe('src/utils/helper.ts');
69
- });
70
- it('should extract file from double quotes', () => {
71
- expect(extractTargetFile('look at "src/utils/helper.ts"')).toBe('src/utils/helper.ts');
72
- });
73
- it('should extract file from backticks', () => {
74
- expect(extractTargetFile('look at `src/utils/helper.ts`')).toBe('src/utils/helper.ts');
75
- });
76
- it('should extract quoted file after action verb', () => {
77
- expect(extractTargetFile('edit "src/utils/helper.ts"')).toBe('src/utils/helper.ts');
78
- });
79
- });
80
- // ---- Bare file path pattern (last resort) ------------------------------
81
- describe('bare file paths', () => {
82
- it('should extract a bare file path in the middle of text', () => {
83
- expect(extractTargetFile('please review src/utils/helper.ts soon')).toBe('src/utils/helper.ts');
84
- });
85
- it('should extract a bare file at the start of text', () => {
86
- expect(extractTargetFile('src/utils/helper.ts needs work')).toBe('src/utils/helper.ts');
87
- });
88
- it('should extract a bare file at the end of text', () => {
89
- expect(extractTargetFile('please look at src/utils/helper.ts')).toBe('src/utils/helper.ts');
90
- });
91
- });
92
- // ---- Various file extensions -------------------------------------------
93
- describe('various file extensions', () => {
94
- it('should match .ts files', () => {
95
- expect(extractTargetFile('edit app.ts')).toBe('app.ts');
96
- });
97
- it('should match .tsx files', () => {
98
- expect(extractTargetFile('edit App.tsx')).toBe('App.tsx');
99
- });
100
- it('should match .js files', () => {
101
- expect(extractTargetFile('edit index.js')).toBe('index.js');
102
- });
103
- it('should match .jsx files', () => {
104
- expect(extractTargetFile('edit Component.jsx')).toBe('Component.jsx');
105
- });
106
- it('should match .py files', () => {
107
- expect(extractTargetFile('edit main.py')).toBe('main.py');
108
- });
109
- it('should match .go files', () => {
110
- expect(extractTargetFile('edit main.go')).toBe('main.go');
111
- });
112
- it('should match .json files', () => {
113
- expect(extractTargetFile('edit package.json')).toBe('package.json');
114
- });
115
- it('should match .css files', () => {
116
- expect(extractTargetFile('edit styles.css')).toBe('styles.css');
117
- });
118
- it('should match .yaml files', () => {
119
- expect(extractTargetFile('edit config.yaml')).toBe('config.yaml');
120
- });
121
- it('should match .rs files', () => {
122
- expect(extractTargetFile('edit src/main.rs')).toBe('src/main.rs');
123
- });
124
- it('should match .html files', () => {
125
- expect(extractTargetFile('edit index.html')).toBe('index.html');
126
- });
127
- });
128
- // ---- Dotfiles, relative paths, and deeply nested paths -----------------
129
- describe('path variations', () => {
130
- it('should match relative paths with dot prefix', () => {
131
- expect(extractTargetFile('edit ./src/helper.ts')).toBe('./src/helper.ts');
132
- });
133
- it('should match deeply nested paths', () => {
134
- expect(extractTargetFile('edit src/components/ui/buttons/Primary.tsx')).toBe('src/components/ui/buttons/Primary.tsx');
135
- });
136
- it('should match filenames without directory', () => {
137
- expect(extractTargetFile('edit index.ts')).toBe('index.ts');
138
- });
139
- });
140
- // ---- No match cases ----------------------------------------------------
141
- describe('no match / edge cases', () => {
142
- it('should return null for empty string', () => {
143
- expect(extractTargetFile('')).toBeNull();
144
- });
145
- it('should return null for text with no file paths', () => {
146
- expect(extractTargetFile('add a new feature to the app')).toBeNull();
147
- });
148
- it('should return null for text with no file extension', () => {
149
- expect(extractTargetFile('edit the README')).toBeNull();
150
- });
151
- it('should return null when only directories are mentioned', () => {
152
- expect(extractTargetFile('look inside src/utils/')).toBeNull();
153
- });
154
- });
155
- // ---- Priority of patterns (first match wins) --------------------------
156
- describe('pattern priority', () => {
157
- it('should prefer the action-verb pattern when task starts with an action', () => {
158
- // "edit src/a.ts something in src/b.ts" should pick src/a.ts (first pattern)
159
- const result = extractTargetFile('edit src/a.ts something in src/b.ts');
160
- expect(result).toBe('src/a.ts');
161
- });
162
- it('should fall through to the quoted pattern when no action verb', () => {
163
- const result = extractTargetFile('look at "config.json" please');
164
- expect(result).toBe('config.json');
165
- });
166
- it('should fall through to the bare path pattern when nothing else matches', () => {
167
- // No action verb, no "in/to", no quotes — bare path is last resort
168
- const result = extractTargetFile('check src/helper.ts');
169
- expect(result).toBe('src/helper.ts');
170
- });
171
- });
172
- });
173
- // ===========================================================================
174
- // formatSmartContext
175
- // ===========================================================================
176
- describe('formatSmartContext', () => {
177
- // ---- Empty context -----------------------------------------------------
178
- describe('empty context', () => {
179
- it('should return empty string when no files', () => {
180
- const ctx = makeContext([]);
181
- expect(formatSmartContext(ctx)).toBe('');
182
- });
183
- it('should return empty string when files array is empty and truncated is true', () => {
184
- const ctx = makeContext([], { truncated: true });
185
- // No files => early return ''
186
- expect(formatSmartContext(ctx)).toBe('');
187
- });
188
- });
189
- // ---- Single file -------------------------------------------------------
190
- describe('single file with content', () => {
191
- it('should format one file with header, reason, and code block', () => {
192
- const ctx = makeContext([
193
- {
194
- relativePath: 'src/index.ts',
195
- reason: 'target file',
196
- content: 'console.log("hello");',
197
- },
198
- ]);
199
- const result = formatSmartContext(ctx);
200
- expect(result).toContain('## Related Files (Smart Context)');
201
- expect(result).toContain('### src/index.ts');
202
- expect(result).toContain('> Reason: target file');
203
- expect(result).toContain('```');
204
- expect(result).toContain('console.log("hello");');
205
- });
206
- it('should not include truncation note when truncated is false', () => {
207
- const ctx = makeContext([
208
- {
209
- relativePath: 'src/index.ts',
210
- reason: 'target file',
211
- content: 'code',
212
- },
213
- ]);
214
- const result = formatSmartContext(ctx);
215
- expect(result).not.toContain('truncated due to size limits');
216
- });
217
- });
218
- // ---- File without content (skipped) ------------------------------------
219
- describe('file without content', () => {
220
- it('should skip files that have no content', () => {
221
- const ctx = makeContext([
222
- {
223
- relativePath: 'src/big.ts',
224
- reason: 'imported module',
225
- content: undefined,
226
- },
227
- ]);
228
- const result = formatSmartContext(ctx);
229
- // Header is still produced because files.length > 0, but the file
230
- // itself should not appear as a section
231
- expect(result).toContain('## Related Files (Smart Context)');
232
- expect(result).not.toContain('### src/big.ts');
233
- });
234
- });
235
- // ---- Multiple files ----------------------------------------------------
236
- describe('multiple files', () => {
237
- it('should list multiple files in order', () => {
238
- const ctx = makeContext([
239
- {
240
- relativePath: 'src/a.ts',
241
- reason: 'target file',
242
- priority: 10,
243
- content: 'const a = 1;',
244
- },
245
- {
246
- relativePath: 'src/b.ts',
247
- reason: 'imported module',
248
- priority: 8,
249
- content: 'const b = 2;',
250
- },
251
- {
252
- relativePath: 'src/c.ts',
253
- reason: 'type definitions',
254
- priority: 7,
255
- content: 'export type C = string;',
256
- },
257
- ]);
258
- const result = formatSmartContext(ctx);
259
- expect(result).toContain('### src/a.ts');
260
- expect(result).toContain('### src/b.ts');
261
- expect(result).toContain('### src/c.ts');
262
- expect(result).toContain('> Reason: target file');
263
- expect(result).toContain('> Reason: imported module');
264
- expect(result).toContain('> Reason: type definitions');
265
- // Verify order: a.ts appears before b.ts, b.ts before c.ts
266
- const posA = result.indexOf('### src/a.ts');
267
- const posB = result.indexOf('### src/b.ts');
268
- const posC = result.indexOf('### src/c.ts');
269
- expect(posA).toBeLessThan(posB);
270
- expect(posB).toBeLessThan(posC);
271
- });
272
- it('should include content from all files that have it', () => {
273
- const ctx = makeContext([
274
- { relativePath: 'src/a.ts', content: 'AAA' },
275
- { relativePath: 'src/b.ts', content: undefined },
276
- { relativePath: 'src/c.ts', content: 'CCC' },
277
- ]);
278
- const result = formatSmartContext(ctx);
279
- expect(result).toContain('AAA');
280
- expect(result).not.toContain('### src/b.ts');
281
- expect(result).toContain('CCC');
282
- });
283
- });
284
- // ---- Truncation warning ------------------------------------------------
285
- describe('truncation warning', () => {
286
- it('should include truncation note when truncated is true', () => {
287
- const ctx = makeContext([
288
- {
289
- relativePath: 'src/index.ts',
290
- reason: 'target file',
291
- content: 'code',
292
- },
293
- ], { truncated: true });
294
- const result = formatSmartContext(ctx);
295
- expect(result).toContain('> Note: Some files were truncated due to size limits.');
296
- });
297
- it('should not include truncation note when truncated is false', () => {
298
- const ctx = makeContext([
299
- {
300
- relativePath: 'src/index.ts',
301
- reason: 'target file',
302
- content: 'code',
303
- },
304
- ], { truncated: false });
305
- const result = formatSmartContext(ctx);
306
- expect(result).not.toContain('truncated');
307
- });
308
- });
309
- // ---- Output structure --------------------------------------------------
310
- describe('output structure', () => {
311
- it('should start with the smart context header', () => {
312
- const ctx = makeContext([
313
- { relativePath: 'src/index.ts', content: 'x' },
314
- ]);
315
- const result = formatSmartContext(ctx);
316
- const lines = result.split('\n');
317
- expect(lines[0]).toBe('## Related Files (Smart Context)');
318
- expect(lines[1]).toBe('');
319
- });
320
- it('should wrap file content in fenced code blocks', () => {
321
- const ctx = makeContext([
322
- { relativePath: 'src/index.ts', content: 'const x = 1;' },
323
- ]);
324
- const result = formatSmartContext(ctx);
325
- // Find the code fences surrounding the content
326
- const codeBlockStart = result.indexOf('```\nconst x = 1;');
327
- const codeBlockEnd = result.indexOf('```', codeBlockStart + 3);
328
- expect(codeBlockStart).toBeGreaterThan(-1);
329
- expect(codeBlockEnd).toBeGreaterThan(codeBlockStart);
330
- });
331
- it('should separate file sections with blank lines', () => {
332
- const ctx = makeContext([
333
- { relativePath: 'src/a.ts', content: 'a' },
334
- { relativePath: 'src/b.ts', content: 'b' },
335
- ]);
336
- const result = formatSmartContext(ctx);
337
- // After the closing ``` of a file, there should be a blank line
338
- // before the next ### header
339
- const closingFenceA = result.indexOf('```\n\n### src/b.ts');
340
- expect(closingFenceA).toBeGreaterThan(-1);
341
- });
342
- it('should include reason as a blockquote', () => {
343
- const ctx = makeContext([
344
- { relativePath: 'x.ts', reason: 'imported module', content: 'y' },
345
- ]);
346
- const result = formatSmartContext(ctx);
347
- expect(result).toContain('> Reason: imported module');
348
- });
349
- });
350
- // ---- Content with special characters -----------------------------------
351
- describe('special content', () => {
352
- it('should handle content with backticks inside code blocks', () => {
353
- const ctx = makeContext([
354
- {
355
- relativePath: 'src/template.ts',
356
- content: 'const s = `hello ${name}`;',
357
- },
358
- ]);
359
- const result = formatSmartContext(ctx);
360
- expect(result).toContain('const s = `hello ${name}`;');
361
- });
362
- it('should handle empty string content', () => {
363
- const ctx = makeContext([
364
- {
365
- relativePath: 'src/empty.ts',
366
- content: '',
367
- },
368
- ]);
369
- // Empty content is falsy, so the file section is skipped
370
- const result = formatSmartContext(ctx);
371
- expect(result).not.toContain('### src/empty.ts');
372
- });
373
- it('should handle multiline content', () => {
374
- const multiline = 'line1\nline2\nline3';
375
- const ctx = makeContext([
376
- { relativePath: 'src/multi.ts', content: multiline },
377
- ]);
378
- const result = formatSmartContext(ctx);
379
- expect(result).toContain('line1\nline2\nline3');
380
- });
381
- });
382
- });
@@ -1 +0,0 @@
1
- export {};