prjct-cli 0.10.0 → 0.10.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/core/__tests__/agentic/memory-system.test.js +263 -0
  3. package/core/__tests__/agentic/plan-mode.test.js +336 -0
  4. package/core/agentic/chain-of-thought.js +578 -0
  5. package/core/agentic/command-executor.js +238 -4
  6. package/core/agentic/context-builder.js +208 -8
  7. package/core/agentic/ground-truth.js +591 -0
  8. package/core/agentic/loop-detector.js +406 -0
  9. package/core/agentic/memory-system.js +850 -0
  10. package/core/agentic/parallel-tools.js +366 -0
  11. package/core/agentic/plan-mode.js +572 -0
  12. package/core/agentic/prompt-builder.js +76 -1
  13. package/core/agentic/response-templates.js +290 -0
  14. package/core/agentic/semantic-compression.js +517 -0
  15. package/core/agentic/think-blocks.js +657 -0
  16. package/core/agentic/tool-registry.js +32 -0
  17. package/core/agentic/validation-rules.js +380 -0
  18. package/core/command-registry.js +48 -0
  19. package/core/commands.js +43 -1
  20. package/core/context-sync.js +183 -0
  21. package/package.json +7 -15
  22. package/templates/commands/done.md +7 -0
  23. package/templates/commands/feature.md +8 -0
  24. package/templates/commands/ship.md +8 -0
  25. package/templates/commands/spec.md +128 -0
  26. package/templates/global/CLAUDE.md +17 -0
  27. package/core/__tests__/agentic/agent-router.test.js +0 -398
  28. package/core/__tests__/agentic/command-executor.test.js +0 -223
  29. package/core/__tests__/agentic/context-builder.test.js +0 -160
  30. package/core/__tests__/agentic/context-filter.test.js +0 -494
  31. package/core/__tests__/agentic/prompt-builder.test.js +0 -204
  32. package/core/__tests__/agentic/template-loader.test.js +0 -164
  33. package/core/__tests__/agentic/tool-registry.test.js +0 -243
  34. package/core/__tests__/domain/agent-generator.test.js +0 -289
  35. package/core/__tests__/domain/agent-loader.test.js +0 -179
  36. package/core/__tests__/domain/analyzer.test.js +0 -324
  37. package/core/__tests__/infrastructure/author-detector.test.js +0 -103
  38. package/core/__tests__/infrastructure/config-manager.test.js +0 -454
  39. package/core/__tests__/infrastructure/path-manager.test.js +0 -412
  40. package/core/__tests__/setup.test.js +0 -15
  41. package/core/__tests__/utils/date-helper.test.js +0 -169
  42. package/core/__tests__/utils/file-helper.test.js +0 -258
  43. package/core/__tests__/utils/jsonl-helper.test.js +0 -387
@@ -1,258 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import * as fileHelper from '../../utils/file-helper.js'
3
- import fs from 'fs/promises'
4
- import path from 'path'
5
- import os from 'os'
6
-
7
- describe('File Helper', () => {
8
- const testDir = path.join(os.tmpdir(), 'prjct-file-helper-test-' + Date.now())
9
-
10
- beforeEach(async () => {
11
- await fs.mkdir(testDir, { recursive: true })
12
- })
13
-
14
- afterEach(async () => {
15
- try {
16
- await fs.rm(testDir, { recursive: true, force: true })
17
- } catch (error) {
18
- // Ignore cleanup errors
19
- }
20
- })
21
-
22
- describe('readJson()', () => {
23
- it('should read and parse JSON file', async () => {
24
- const testFile = path.join(testDir, 'test.json')
25
- const data = { name: 'test', value: 123 }
26
- await fs.writeFile(testFile, JSON.stringify(data))
27
-
28
- const result = await fileHelper.readJson(testFile)
29
-
30
- expect(result).toEqual(data)
31
- })
32
-
33
- it('should return default value for non-existent file', async () => {
34
- const result = await fileHelper.readJson('/nonexistent/file.json', { default: true })
35
-
36
- expect(result).toEqual({ default: true })
37
- })
38
-
39
- it('should return null by default for non-existent file', async () => {
40
- const result = await fileHelper.readJson('/nonexistent/file.json')
41
-
42
- expect(result).toBeNull()
43
- })
44
-
45
- it('should handle complex JSON structures', async () => {
46
- const testFile = path.join(testDir, 'complex.json')
47
- const data = {
48
- nested: {
49
- array: [1, 2, 3],
50
- object: { key: 'value' },
51
- },
52
- boolean: true,
53
- number: 42,
54
- }
55
- await fs.writeFile(testFile, JSON.stringify(data))
56
-
57
- const result = await fileHelper.readJson(testFile)
58
-
59
- expect(result).toEqual(data)
60
- })
61
- })
62
-
63
- describe('writeJson()', () => {
64
- it('should write JSON file with pretty formatting', async () => {
65
- const testFile = path.join(testDir, 'write.json')
66
- const data = { name: 'test', value: 123 }
67
-
68
- await fileHelper.writeJson(testFile, data)
69
-
70
- const content = await fs.readFile(testFile, 'utf-8')
71
- expect(JSON.parse(content)).toEqual(data)
72
- expect(content).toContain('\n') // Should be pretty-printed
73
- })
74
-
75
- it('should use custom indentation', async () => {
76
- const testFile = path.join(testDir, 'indent.json')
77
- const data = { key: 'value' }
78
-
79
- await fileHelper.writeJson(testFile, data, 4)
80
-
81
- const content = await fs.readFile(testFile, 'utf-8')
82
- expect(content).toContain(' ') // 4 spaces
83
- })
84
-
85
- it('should handle nested objects', async () => {
86
- const testFile = path.join(testDir, 'nested.json')
87
- const data = {
88
- level1: {
89
- level2: {
90
- level3: 'deep',
91
- },
92
- },
93
- }
94
-
95
- await fileHelper.writeJson(testFile, data)
96
-
97
- const result = await fileHelper.readJson(testFile)
98
- expect(result).toEqual(data)
99
- })
100
- })
101
-
102
- describe('readFile()', () => {
103
- it('should read text file', async () => {
104
- const testFile = path.join(testDir, 'text.txt')
105
- const content = 'Hello, World!'
106
- await fs.writeFile(testFile, content)
107
-
108
- const result = await fileHelper.readFile(testFile)
109
-
110
- expect(result).toBe(content)
111
- })
112
-
113
- it('should return default value for non-existent file', async () => {
114
- const result = await fileHelper.readFile('/nonexistent/file.txt', 'default content')
115
-
116
- expect(result).toBe('default content')
117
- })
118
-
119
- it('should return empty string by default', async () => {
120
- const result = await fileHelper.readFile('/nonexistent/file.txt')
121
-
122
- expect(result).toBe('')
123
- })
124
-
125
- it('should handle multi-line content', async () => {
126
- const testFile = path.join(testDir, 'multiline.txt')
127
- const content = 'Line 1\nLine 2\nLine 3'
128
- await fs.writeFile(testFile, content)
129
-
130
- const result = await fileHelper.readFile(testFile)
131
-
132
- expect(result).toBe(content)
133
- })
134
- })
135
-
136
- describe('writeFile()', () => {
137
- it('should write text file', async () => {
138
- const testFile = path.join(testDir, 'write.txt')
139
- const content = 'Test content'
140
-
141
- await fileHelper.writeFile(testFile, content)
142
-
143
- const result = await fs.readFile(testFile, 'utf-8')
144
- expect(result).toBe(content)
145
- })
146
-
147
- it('should overwrite existing file', async () => {
148
- const testFile = path.join(testDir, 'overwrite.txt')
149
-
150
- await fileHelper.writeFile(testFile, 'First')
151
- await fileHelper.writeFile(testFile, 'Second')
152
-
153
- const result = await fs.readFile(testFile, 'utf-8')
154
- expect(result).toBe('Second')
155
- })
156
-
157
- it('should create directory if needed', async () => {
158
- const nestedFile = path.join(testDir, 'nested', 'dir', 'file.txt')
159
-
160
- await fileHelper.writeFile(nestedFile, 'content')
161
-
162
- const exists = await fs
163
- .access(nestedFile)
164
- .then(() => true)
165
- .catch(() => false)
166
- expect(exists).toBe(true)
167
- })
168
- })
169
-
170
- describe('fileExists()', () => {
171
- it('should return true for existing file', async () => {
172
- const testFile = path.join(testDir, 'exists.txt')
173
- await fs.writeFile(testFile, 'content')
174
-
175
- const exists = await fileHelper.fileExists(testFile)
176
-
177
- expect(exists).toBe(true)
178
- })
179
-
180
- it('should return false for non-existent file', async () => {
181
- const exists = await fileHelper.fileExists('/nonexistent/file.txt')
182
-
183
- expect(exists).toBe(false)
184
- })
185
-
186
- it('should work with directories', async () => {
187
- const exists = await fileHelper.fileExists(testDir)
188
-
189
- expect(exists).toBe(true)
190
- })
191
- })
192
-
193
- describe('ensureDir()', () => {
194
- it('should create directory if not exists', async () => {
195
- const newDir = path.join(testDir, 'new', 'nested', 'dir')
196
-
197
- await fileHelper.ensureDir(newDir)
198
-
199
- const exists = await fs
200
- .access(newDir)
201
- .then(() => true)
202
- .catch(() => false)
203
- expect(exists).toBe(true)
204
- })
205
-
206
- it('should not fail if directory exists', async () => {
207
- await fileHelper.ensureDir(testDir)
208
-
209
- // Should not throw
210
- await expect(fileHelper.ensureDir(testDir)).resolves.not.toThrow()
211
- })
212
- })
213
-
214
- describe('Integration', () => {
215
- it('should read and write JSON files', async () => {
216
- const testFile = path.join(testDir, 'integration.json')
217
- const data = { test: 'data', number: 42 }
218
-
219
- await fileHelper.writeJson(testFile, data)
220
- const result = await fileHelper.readJson(testFile)
221
-
222
- expect(result).toEqual(data)
223
- })
224
-
225
- it('should handle file operations pipeline', async () => {
226
- const testFile = path.join(testDir, 'pipeline.txt')
227
-
228
- // Write
229
- await fileHelper.writeFile(testFile, 'original')
230
-
231
- // Check exists
232
- const exists = await fileHelper.fileExists(testFile)
233
- expect(exists).toBe(true)
234
-
235
- // Read
236
- const content = await fileHelper.readFile(testFile)
237
- expect(content).toBe('original')
238
-
239
- // Update
240
- await fileHelper.writeFile(testFile, 'updated')
241
-
242
- // Read again
243
- const updated = await fileHelper.readFile(testFile)
244
- expect(updated).toBe('updated')
245
- })
246
-
247
- it('should create nested structure and write files', async () => {
248
- const nestedDir = path.join(testDir, 'level1', 'level2', 'level3')
249
- const nestedFile = path.join(nestedDir, 'deep.json')
250
-
251
- await fileHelper.ensureDir(nestedDir)
252
- await fileHelper.writeJson(nestedFile, { deep: true })
253
-
254
- const result = await fileHelper.readJson(nestedFile)
255
- expect(result).toEqual({ deep: true })
256
- })
257
- })
258
- })
@@ -1,387 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest'
2
- import { createRequire } from 'module'
3
- import path from 'path'
4
- import fs from 'fs/promises'
5
- import os from 'os'
6
-
7
- const require = createRequire(import.meta.url)
8
-
9
- describe('JSONL Helper', () => {
10
- let jsonlHelper
11
- let testFilePath
12
- let tempDir
13
-
14
- beforeEach(async () => {
15
- jsonlHelper = require('../../utils/jsonl-helper.js')
16
-
17
- // Create temporary test directory
18
- tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'prjct-test-'))
19
- testFilePath = path.join(tempDir, 'test.jsonl')
20
- })
21
-
22
- afterEach(async () => {
23
- if (tempDir) {
24
- try {
25
- await fs.rm(tempDir, { recursive: true, force: true })
26
- } catch (error) {
27
- // Ignore cleanup errors
28
- }
29
- }
30
- })
31
-
32
- describe('parseJsonLines()', () => {
33
- it('should parse valid JSONL content', () => {
34
- const content = '{"ts":"2025-10-04T14:30:00Z","type":"test"}\n{"ts":"2025-10-04T15:00:00Z","type":"test2"}'
35
- const parsed = jsonlHelper.parseJsonLines(content)
36
-
37
- expect(parsed.length).toBe(2)
38
- expect(parsed[0].type).toBe('test')
39
- expect(parsed[1].type).toBe('test2')
40
- })
41
-
42
- it('should skip malformed lines', () => {
43
- const content = '{"valid":true}\ninvalid json\n{"another":true}'
44
- const parsed = jsonlHelper.parseJsonLines(content)
45
-
46
- expect(parsed.length).toBe(2)
47
- expect(parsed[0].valid).toBe(true)
48
- expect(parsed[1].another).toBe(true)
49
- })
50
-
51
- it('should handle empty content', () => {
52
- const parsed = jsonlHelper.parseJsonLines('')
53
- expect(parsed).toEqual([])
54
- })
55
-
56
- it('should filter empty lines', () => {
57
- const content = '{"a":1}\n\n{"b":2}\n \n{"c":3}'
58
- const parsed = jsonlHelper.parseJsonLines(content)
59
-
60
- expect(parsed.length).toBe(3)
61
- })
62
- })
63
-
64
- describe('stringifyJsonLines()', () => {
65
- it('should convert array to JSONL format', () => {
66
- const objects = [
67
- { ts: '2025-10-04T14:30:00Z', type: 'test' },
68
- { ts: '2025-10-04T15:00:00Z', type: 'test2' }
69
- ]
70
-
71
- const jsonl = jsonlHelper.stringifyJsonLines(objects)
72
- const lines = jsonl.trim().split('\n')
73
-
74
- expect(lines.length).toBe(2)
75
- expect(JSON.parse(lines[0]).type).toBe('test')
76
- expect(JSON.parse(lines[1]).type).toBe('test2')
77
- })
78
-
79
- it('should end with newline', () => {
80
- const objects = [{ a: 1 }]
81
- const jsonl = jsonlHelper.stringifyJsonLines(objects)
82
-
83
- expect(jsonl.endsWith('\n')).toBe(true)
84
- })
85
-
86
- it('should handle empty array', () => {
87
- const jsonl = jsonlHelper.stringifyJsonLines([])
88
- expect(jsonl).toBe('\n')
89
- })
90
- })
91
-
92
- describe('readJsonLines()', () => {
93
- it('should read and parse JSONL file', async () => {
94
- const content = '{"a":1}\n{"b":2}'
95
- await fs.writeFile(testFilePath, content)
96
-
97
- const parsed = await jsonlHelper.readJsonLines(testFilePath)
98
-
99
- expect(parsed.length).toBe(2)
100
- expect(parsed[0].a).toBe(1)
101
- expect(parsed[1].b).toBe(2)
102
- })
103
-
104
- it('should return empty array for non-existent file', async () => {
105
- const parsed = await jsonlHelper.readJsonLines(path.join(tempDir, 'nonexistent.jsonl'))
106
-
107
- expect(parsed).toEqual([])
108
- })
109
-
110
- it('should throw for other errors', async () => {
111
- // Create directory with same name to cause error
112
- await fs.mkdir(testFilePath, { recursive: true })
113
-
114
- await expect(jsonlHelper.readJsonLines(testFilePath)).rejects.toThrow()
115
- })
116
- })
117
-
118
- describe('writeJsonLines()', () => {
119
- it('should write objects to JSONL file', async () => {
120
- const objects = [
121
- { ts: '2025-10-04T14:30:00Z', type: 'test' },
122
- { ts: '2025-10-04T15:00:00Z', type: 'test2' }
123
- ]
124
-
125
- await jsonlHelper.writeJsonLines(testFilePath, objects)
126
-
127
- const content = await fs.readFile(testFilePath, 'utf-8')
128
- const parsed = jsonlHelper.parseJsonLines(content)
129
-
130
- expect(parsed.length).toBe(2)
131
- expect(parsed[0].type).toBe('test')
132
- })
133
-
134
- it('should overwrite existing file', async () => {
135
- await fs.writeFile(testFilePath, '{"old":true}')
136
-
137
- await jsonlHelper.writeJsonLines(testFilePath, [{ new: true }])
138
-
139
- const content = await fs.readFile(testFilePath, 'utf-8')
140
- const parsed = jsonlHelper.parseJsonLines(content)
141
-
142
- expect(parsed.length).toBe(1)
143
- expect(parsed[0].old).toBeUndefined()
144
- expect(parsed[0].new).toBe(true)
145
- })
146
- })
147
-
148
- describe('appendJsonLine()', () => {
149
- it('should append single object to file', async () => {
150
- await fs.writeFile(testFilePath, '{"first":true}\n')
151
-
152
- await jsonlHelper.appendJsonLine(testFilePath, { second: true })
153
-
154
- const content = await fs.readFile(testFilePath, 'utf-8')
155
- const parsed = jsonlHelper.parseJsonLines(content)
156
-
157
- expect(parsed.length).toBe(2)
158
- expect(parsed[0].first).toBe(true)
159
- expect(parsed[1].second).toBe(true)
160
- })
161
-
162
- it('should create file if it does not exist', async () => {
163
- await jsonlHelper.appendJsonLine(testFilePath, { new: true })
164
-
165
- const content = await fs.readFile(testFilePath, 'utf-8')
166
- const parsed = jsonlHelper.parseJsonLines(content)
167
-
168
- expect(parsed.length).toBe(1)
169
- expect(parsed[0].new).toBe(true)
170
- })
171
- })
172
-
173
- describe('appendJsonLines()', () => {
174
- it('should append multiple objects', async () => {
175
- await fs.writeFile(testFilePath, '{"first":true}\n')
176
-
177
- await jsonlHelper.appendJsonLines(testFilePath, [
178
- { second: true },
179
- { third: true }
180
- ])
181
-
182
- const content = await fs.readFile(testFilePath, 'utf-8')
183
- const parsed = jsonlHelper.parseJsonLines(content)
184
-
185
- expect(parsed.length).toBe(3)
186
- })
187
- })
188
-
189
- describe('filterJsonLines()', () => {
190
- it('should filter entries by predicate', async () => {
191
- const objects = [
192
- { type: 'test', value: 1 },
193
- { type: 'other', value: 2 },
194
- { type: 'test', value: 3 }
195
- ]
196
- await jsonlHelper.writeJsonLines(testFilePath, objects)
197
-
198
- const filtered = await jsonlHelper.filterJsonLines(testFilePath, entry => entry.type === 'test')
199
-
200
- expect(filtered.length).toBe(2)
201
- expect(filtered.every(e => e.type === 'test')).toBe(true)
202
- })
203
-
204
- it('should return empty array for non-existent file', async () => {
205
- const filtered = await jsonlHelper.filterJsonLines(
206
- path.join(tempDir, 'nonexistent.jsonl'),
207
- () => true
208
- )
209
-
210
- expect(filtered).toEqual([])
211
- })
212
- })
213
-
214
- describe('countJsonLines()', () => {
215
- it('should count valid lines in file', async () => {
216
- await fs.writeFile(testFilePath, '{"a":1}\n{"b":2}\n{"c":3}')
217
-
218
- const count = await jsonlHelper.countJsonLines(testFilePath)
219
-
220
- expect(count).toBe(3)
221
- })
222
-
223
- it('should return 0 for non-existent file', async () => {
224
- const count = await jsonlHelper.countJsonLines(path.join(tempDir, 'nonexistent.jsonl'))
225
-
226
- expect(count).toBe(0)
227
- })
228
-
229
- it('should ignore empty lines', async () => {
230
- await fs.writeFile(testFilePath, '{"a":1}\n\n{"b":2}\n \n')
231
-
232
- const count = await jsonlHelper.countJsonLines(testFilePath)
233
-
234
- expect(count).toBe(2)
235
- })
236
- })
237
-
238
- describe('getLastJsonLines()', () => {
239
- it('should return last N entries', async () => {
240
- const objects = Array.from({ length: 10 }, (_, i) => ({ index: i }))
241
- await jsonlHelper.writeJsonLines(testFilePath, objects)
242
-
243
- const last3 = await jsonlHelper.getLastJsonLines(testFilePath, 3)
244
-
245
- expect(last3.length).toBe(3)
246
- expect(last3[0].index).toBe(7)
247
- expect(last3[2].index).toBe(9)
248
- })
249
-
250
- it('should return all entries if N is larger than file', async () => {
251
- const objects = [{ a: 1 }, { b: 2 }]
252
- await jsonlHelper.writeJsonLines(testFilePath, objects)
253
-
254
- const last = await jsonlHelper.getLastJsonLines(testFilePath, 10)
255
-
256
- expect(last.length).toBe(2)
257
- })
258
- })
259
-
260
- describe('getFirstJsonLines()', () => {
261
- it('should return first N entries', async () => {
262
- const objects = Array.from({ length: 10 }, (_, i) => ({ index: i }))
263
- await jsonlHelper.writeJsonLines(testFilePath, objects)
264
-
265
- const first3 = await jsonlHelper.getFirstJsonLines(testFilePath, 3)
266
-
267
- expect(first3.length).toBe(3)
268
- expect(first3[0].index).toBe(0)
269
- expect(first3[2].index).toBe(2)
270
- })
271
- })
272
-
273
- describe('mergeJsonLines()', () => {
274
- it('should merge multiple files', async () => {
275
- const file1 = path.join(tempDir, 'file1.jsonl')
276
- const file2 = path.join(tempDir, 'file2.jsonl')
277
-
278
- await jsonlHelper.writeJsonLines(file1, [{ file: 1 }])
279
- await jsonlHelper.writeJsonLines(file2, [{ file: 2 }])
280
-
281
- const merged = await jsonlHelper.mergeJsonLines([file1, file2])
282
-
283
- expect(merged.length).toBe(2)
284
- expect(merged[0].file).toBe(1)
285
- expect(merged[1].file).toBe(2)
286
- })
287
-
288
- it('should handle non-existent files gracefully', async () => {
289
- const file1 = path.join(tempDir, 'file1.jsonl')
290
- await jsonlHelper.writeJsonLines(file1, [{ a: 1 }])
291
-
292
- const merged = await jsonlHelper.mergeJsonLines([
293
- file1,
294
- path.join(tempDir, 'nonexistent.jsonl')
295
- ])
296
-
297
- expect(merged.length).toBe(1)
298
- })
299
- })
300
-
301
- describe('isJsonLinesEmpty()', () => {
302
- it('should return true for empty file', async () => {
303
- await fs.writeFile(testFilePath, '')
304
-
305
- const isEmpty = await jsonlHelper.isJsonLinesEmpty(testFilePath)
306
-
307
- expect(isEmpty).toBe(true)
308
- })
309
-
310
- it('should return false for file with content', async () => {
311
- await jsonlHelper.writeJsonLines(testFilePath, [{ a: 1 }])
312
-
313
- const isEmpty = await jsonlHelper.isJsonLinesEmpty(testFilePath)
314
-
315
- expect(isEmpty).toBe(false)
316
- })
317
-
318
- it('should return true for non-existent file', async () => {
319
- const isEmpty = await jsonlHelper.isJsonLinesEmpty(path.join(tempDir, 'nonexistent.jsonl'))
320
-
321
- expect(isEmpty).toBe(true)
322
- })
323
- })
324
-
325
- describe('getFileSizeMB()', () => {
326
- it('should return file size in MB', async () => {
327
- const content = 'x'.repeat(1024 * 1024) // 1MB
328
- await fs.writeFile(testFilePath, content)
329
-
330
- const sizeMB = await jsonlHelper.getFileSizeMB(testFilePath)
331
-
332
- expect(sizeMB).toBeGreaterThan(0.9)
333
- expect(sizeMB).toBeLessThan(1.1)
334
- })
335
-
336
- it('should return 0 for non-existent file', async () => {
337
- const sizeMB = await jsonlHelper.getFileSizeMB(path.join(tempDir, 'nonexistent.jsonl'))
338
-
339
- expect(sizeMB).toBe(0)
340
- })
341
- })
342
-
343
- describe('rotateJsonLinesIfNeeded()', () => {
344
- it('should not rotate if file is small', async () => {
345
- await jsonlHelper.writeJsonLines(testFilePath, [{ a: 1 }])
346
-
347
- const rotated = await jsonlHelper.rotateJsonLinesIfNeeded(testFilePath, 10)
348
-
349
- expect(rotated).toBe(false)
350
- const exists = await fs.access(testFilePath).then(() => true).catch(() => false)
351
- expect(exists).toBe(true)
352
- })
353
-
354
- it('should rotate if file exceeds size limit', async () => {
355
- // Create a large file (simulate with many entries)
356
- const largeContent = Array.from({ length: 10000 }, (_, i) => ({
357
- index: i,
358
- data: 'x'.repeat(100)
359
- }))
360
- await jsonlHelper.writeJsonLines(testFilePath, largeContent)
361
-
362
- // Use very small limit to force rotation
363
- const rotated = await jsonlHelper.rotateJsonLinesIfNeeded(testFilePath, 0.001)
364
-
365
- // File should be rotated (moved to archive)
366
- expect(rotated).toBe(true)
367
- })
368
- })
369
-
370
- describe('appendJsonLineWithRotation()', () => {
371
- it('should append and rotate if needed', async () => {
372
- // Create large file
373
- const largeContent = Array.from({ length: 10000 }, (_, i) => ({
374
- index: i,
375
- data: 'x'.repeat(100)
376
- }))
377
- await jsonlHelper.writeJsonLines(testFilePath, largeContent)
378
-
379
- await jsonlHelper.appendJsonLineWithRotation(testFilePath, { new: true }, 0.001)
380
-
381
- // File should exist (either original or after rotation)
382
- const files = await fs.readdir(tempDir)
383
- expect(files.length).toBeGreaterThan(0)
384
- })
385
- })
386
- })
387
-