prjct-cli 0.6.0 → 0.7.0
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.
- package/CHANGELOG.md +67 -6
- package/CLAUDE.md +442 -36
- package/README.md +47 -54
- package/bin/prjct +170 -240
- package/core/agentic/command-executor.js +113 -0
- package/core/agentic/context-builder.js +85 -0
- package/core/agentic/prompt-builder.js +86 -0
- package/core/agentic/template-loader.js +104 -0
- package/core/agentic/tool-registry.js +117 -0
- package/core/command-registry.js +106 -62
- package/core/commands.js +2030 -2211
- package/core/domain/agent-generator.js +118 -0
- package/core/domain/analyzer.js +211 -0
- package/core/domain/architect-session.js +300 -0
- package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
- package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
- package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
- package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
- package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
- package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
- package/core/{migrator.js → infrastructure/migrator.js} +34 -19
- package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
- package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
- package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
- package/core/{animations-simple.js → utils/animations.js} +3 -23
- package/core/utils/date-helper.js +238 -0
- package/core/utils/file-helper.js +327 -0
- package/core/utils/jsonl-helper.js +206 -0
- package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
- package/core/utils/session-helper.js +277 -0
- package/core/{version.js → utils/version.js} +1 -1
- package/package.json +4 -12
- package/templates/agents/AGENTS.md +101 -27
- package/templates/analysis/analyze.md +84 -0
- package/templates/commands/analyze.md +9 -2
- package/templates/commands/bug.md +79 -0
- package/templates/commands/build.md +5 -2
- package/templates/commands/cleanup.md +5 -2
- package/templates/commands/design.md +5 -2
- package/templates/commands/done.md +4 -2
- package/templates/commands/feature.md +113 -0
- package/templates/commands/fix.md +41 -10
- package/templates/commands/git.md +7 -2
- package/templates/commands/help.md +2 -2
- package/templates/commands/idea.md +14 -5
- package/templates/commands/init.md +62 -7
- package/templates/commands/next.md +4 -2
- package/templates/commands/now.md +4 -2
- package/templates/commands/progress.md +27 -5
- package/templates/commands/recap.md +39 -10
- package/templates/commands/roadmap.md +19 -5
- package/templates/commands/ship.md +118 -16
- package/templates/commands/status.md +4 -2
- package/templates/commands/sync.md +19 -15
- package/templates/commands/task.md +4 -2
- package/templates/commands/test.md +5 -2
- package/templates/commands/workflow.md +4 -2
- package/core/agent-generator.js +0 -525
- package/core/analyzer.js +0 -600
- package/core/animations.js +0 -277
- package/core/ascii-graphics.js +0 -433
- package/core/git-integration.js +0 -401
- package/core/task-schema.js +0 -342
- package/core/workflow-engine.js +0 -213
- package/core/workflow-prompts.js +0 -192
- package/core/workflow-rules.js +0 -147
- package/scripts/post-install.js +0 -121
- package/scripts/preuninstall.js +0 -94
- package/scripts/verify-installation.sh +0 -158
- package/templates/agents/be.template.md +0 -27
- package/templates/agents/coordinator.template.md +0 -34
- package/templates/agents/data.template.md +0 -27
- package/templates/agents/devops.template.md +0 -27
- package/templates/agents/fe.template.md +0 -27
- package/templates/agents/mobile.template.md +0 -27
- package/templates/agents/qa.template.md +0 -27
- package/templates/agents/scribe.template.md +0 -29
- package/templates/agents/security.template.md +0 -27
- package/templates/agents/ux.template.md +0 -27
- package/templates/commands/context.md +0 -36
- package/templates/commands/stuck.md +0 -36
- package/templates/examples/natural-language-examples.md +0 -532
- /package/core/{agent-detector.js → infrastructure/agent-detector.js} +0 -0
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
const path = require('path')
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* File Helper - Centralized file operations with error handling
|
|
6
|
+
*
|
|
7
|
+
* Eliminates duplicated fs operations across:
|
|
8
|
+
* - 101 fs.readFile/writeFile calls in 18 files
|
|
9
|
+
* - Consistent error handling
|
|
10
|
+
* - JSON read/write patterns
|
|
11
|
+
*
|
|
12
|
+
* @module file-helper
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Read JSON file and parse
|
|
17
|
+
*
|
|
18
|
+
* @param {string} filePath - Path to JSON file
|
|
19
|
+
* @param {*} defaultValue - Default value if file doesn't exist (default: null)
|
|
20
|
+
* @returns {Promise<Object|*>} - Parsed JSON or default value
|
|
21
|
+
*/
|
|
22
|
+
async function readJson(filePath, defaultValue = null) {
|
|
23
|
+
try {
|
|
24
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
25
|
+
return JSON.parse(content)
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error.code === 'ENOENT') {
|
|
28
|
+
return defaultValue
|
|
29
|
+
}
|
|
30
|
+
throw error
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Write object to JSON file (pretty-printed)
|
|
36
|
+
*
|
|
37
|
+
* @param {string} filePath - Path to JSON file
|
|
38
|
+
* @param {Object} data - Data to write
|
|
39
|
+
* @param {number} indent - Indentation spaces (default: 2)
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
42
|
+
async function writeJson(filePath, data, indent = 2) {
|
|
43
|
+
const content = JSON.stringify(data, null, indent)
|
|
44
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Read text file
|
|
49
|
+
*
|
|
50
|
+
* @param {string} filePath - Path to file
|
|
51
|
+
* @param {string} defaultValue - Default value if file doesn't exist (default: '')
|
|
52
|
+
* @returns {Promise<string>} - File content or default value
|
|
53
|
+
*/
|
|
54
|
+
async function readFile(filePath, defaultValue = '') {
|
|
55
|
+
try {
|
|
56
|
+
return await fs.readFile(filePath, 'utf-8')
|
|
57
|
+
} catch (error) {
|
|
58
|
+
if (error.code === 'ENOENT') {
|
|
59
|
+
return defaultValue
|
|
60
|
+
}
|
|
61
|
+
throw error
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Write text file
|
|
67
|
+
*
|
|
68
|
+
* @param {string} filePath - Path to file
|
|
69
|
+
* @param {string} content - Content to write
|
|
70
|
+
* @returns {Promise<void>}
|
|
71
|
+
*/
|
|
72
|
+
async function writeFile(filePath, content) {
|
|
73
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Append to text file
|
|
78
|
+
*
|
|
79
|
+
* @param {string} filePath - Path to file
|
|
80
|
+
* @param {string} content - Content to append
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
83
|
+
async function appendToFile(filePath, content) {
|
|
84
|
+
await fs.appendFile(filePath, content, 'utf-8')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Prepend to text file (adds content at beginning)
|
|
89
|
+
*
|
|
90
|
+
* @param {string} filePath - Path to file
|
|
91
|
+
* @param {string} content - Content to prepend
|
|
92
|
+
* @returns {Promise<void>}
|
|
93
|
+
*/
|
|
94
|
+
async function prependToFile(filePath, content) {
|
|
95
|
+
try {
|
|
96
|
+
const existing = await fs.readFile(filePath, 'utf-8')
|
|
97
|
+
await fs.writeFile(filePath, content + existing, 'utf-8')
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT') {
|
|
100
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
101
|
+
} else {
|
|
102
|
+
throw error
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check if file exists
|
|
109
|
+
*
|
|
110
|
+
* @param {string} filePath - Path to file
|
|
111
|
+
* @returns {Promise<boolean>} - True if file exists
|
|
112
|
+
*/
|
|
113
|
+
async function fileExists(filePath) {
|
|
114
|
+
try {
|
|
115
|
+
await fs.access(filePath)
|
|
116
|
+
return true
|
|
117
|
+
} catch {
|
|
118
|
+
return false
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if directory exists
|
|
124
|
+
*
|
|
125
|
+
* @param {string} dirPath - Path to directory
|
|
126
|
+
* @returns {Promise<boolean>} - True if directory exists
|
|
127
|
+
*/
|
|
128
|
+
async function dirExists(dirPath) {
|
|
129
|
+
try {
|
|
130
|
+
const stats = await fs.stat(dirPath)
|
|
131
|
+
return stats.isDirectory()
|
|
132
|
+
} catch {
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Ensure directory exists (create if not)
|
|
139
|
+
*
|
|
140
|
+
* @param {string} dirPath - Path to directory
|
|
141
|
+
* @returns {Promise<void>}
|
|
142
|
+
*/
|
|
143
|
+
async function ensureDir(dirPath) {
|
|
144
|
+
await fs.mkdir(dirPath, { recursive: true })
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Delete file if it exists
|
|
149
|
+
*
|
|
150
|
+
* @param {string} filePath - Path to file
|
|
151
|
+
* @returns {Promise<boolean>} - True if file was deleted
|
|
152
|
+
*/
|
|
153
|
+
async function deleteFile(filePath) {
|
|
154
|
+
try {
|
|
155
|
+
await fs.unlink(filePath)
|
|
156
|
+
return true
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (error.code === 'ENOENT') {
|
|
159
|
+
return false // File didn't exist
|
|
160
|
+
}
|
|
161
|
+
throw error
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Delete directory and all contents
|
|
167
|
+
*
|
|
168
|
+
* @param {string} dirPath - Path to directory
|
|
169
|
+
* @returns {Promise<boolean>} - True if directory was deleted
|
|
170
|
+
*/
|
|
171
|
+
async function deleteDir(dirPath) {
|
|
172
|
+
try {
|
|
173
|
+
await fs.rm(dirPath, { recursive: true, force: true })
|
|
174
|
+
return true
|
|
175
|
+
} catch (error) {
|
|
176
|
+
if (error.code === 'ENOENT') {
|
|
177
|
+
return false
|
|
178
|
+
}
|
|
179
|
+
throw error
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* List files in directory
|
|
185
|
+
*
|
|
186
|
+
* @param {string} dirPath - Path to directory
|
|
187
|
+
* @param {Object} options - Options
|
|
188
|
+
* @param {boolean} options.filesOnly - Only return files (not directories)
|
|
189
|
+
* @param {boolean} options.dirsOnly - Only return directories
|
|
190
|
+
* @param {string} options.extension - Filter by file extension (e.g., '.md')
|
|
191
|
+
* @returns {Promise<Array<string>>} - Array of filenames
|
|
192
|
+
*/
|
|
193
|
+
async function listFiles(dirPath, options = {}) {
|
|
194
|
+
try {
|
|
195
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true })
|
|
196
|
+
let files = entries
|
|
197
|
+
|
|
198
|
+
if (options.filesOnly) {
|
|
199
|
+
files = files.filter((entry) => entry.isFile())
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (options.dirsOnly) {
|
|
203
|
+
files = files.filter((entry) => entry.isDirectory())
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (options.extension) {
|
|
207
|
+
files = files.filter((entry) => entry.name.endsWith(options.extension))
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return files.map((entry) => entry.name)
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error.code === 'ENOENT') {
|
|
213
|
+
return []
|
|
214
|
+
}
|
|
215
|
+
throw error
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Get file size in bytes
|
|
221
|
+
*
|
|
222
|
+
* @param {string} filePath - Path to file
|
|
223
|
+
* @returns {Promise<number>} - File size in bytes
|
|
224
|
+
*/
|
|
225
|
+
async function getFileSize(filePath) {
|
|
226
|
+
const stats = await fs.stat(filePath)
|
|
227
|
+
return stats.size
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get file modification time
|
|
232
|
+
*
|
|
233
|
+
* @param {string} filePath - Path to file
|
|
234
|
+
* @returns {Promise<Date>} - Last modification time
|
|
235
|
+
*/
|
|
236
|
+
async function getFileModifiedTime(filePath) {
|
|
237
|
+
const stats = await fs.stat(filePath)
|
|
238
|
+
return stats.mtime
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Copy file
|
|
243
|
+
*
|
|
244
|
+
* @param {string} sourcePath - Source file path
|
|
245
|
+
* @param {string} destPath - Destination file path
|
|
246
|
+
* @returns {Promise<void>}
|
|
247
|
+
*/
|
|
248
|
+
async function copyFile(sourcePath, destPath) {
|
|
249
|
+
await fs.copyFile(sourcePath, destPath)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Move/rename file
|
|
254
|
+
*
|
|
255
|
+
* @param {string} oldPath - Current file path
|
|
256
|
+
* @param {string} newPath - New file path
|
|
257
|
+
* @returns {Promise<void>}
|
|
258
|
+
*/
|
|
259
|
+
async function moveFile(oldPath, newPath) {
|
|
260
|
+
await fs.rename(oldPath, newPath)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Read file and split into lines
|
|
265
|
+
*
|
|
266
|
+
* @param {string} filePath - Path to file
|
|
267
|
+
* @returns {Promise<Array<string>>} - Array of lines
|
|
268
|
+
*/
|
|
269
|
+
async function readLines(filePath) {
|
|
270
|
+
const content = await readFile(filePath, '')
|
|
271
|
+
return content.split('\n')
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Write lines to file
|
|
276
|
+
*
|
|
277
|
+
* @param {string} filePath - Path to file
|
|
278
|
+
* @param {Array<string>} lines - Array of lines
|
|
279
|
+
* @returns {Promise<void>}
|
|
280
|
+
*/
|
|
281
|
+
async function writeLines(filePath, lines) {
|
|
282
|
+
const content = lines.join('\n')
|
|
283
|
+
await writeFile(filePath, content)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Get file extension
|
|
288
|
+
*
|
|
289
|
+
* @param {string} filePath - Path to file
|
|
290
|
+
* @returns {string} - File extension (e.g., '.md')
|
|
291
|
+
*/
|
|
292
|
+
function getFileExtension(filePath) {
|
|
293
|
+
return path.extname(filePath)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get filename without extension
|
|
298
|
+
*
|
|
299
|
+
* @param {string} filePath - Path to file
|
|
300
|
+
* @returns {string} - Filename without extension
|
|
301
|
+
*/
|
|
302
|
+
function getFileNameWithoutExtension(filePath) {
|
|
303
|
+
return path.basename(filePath, path.extname(filePath))
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
module.exports = {
|
|
307
|
+
readJson,
|
|
308
|
+
writeJson,
|
|
309
|
+
readFile,
|
|
310
|
+
writeFile,
|
|
311
|
+
appendToFile,
|
|
312
|
+
prependToFile,
|
|
313
|
+
fileExists,
|
|
314
|
+
dirExists,
|
|
315
|
+
ensureDir,
|
|
316
|
+
deleteFile,
|
|
317
|
+
deleteDir,
|
|
318
|
+
listFiles,
|
|
319
|
+
getFileSize,
|
|
320
|
+
getFileModifiedTime,
|
|
321
|
+
copyFile,
|
|
322
|
+
moveFile,
|
|
323
|
+
readLines,
|
|
324
|
+
writeLines,
|
|
325
|
+
getFileExtension,
|
|
326
|
+
getFileNameWithoutExtension,
|
|
327
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
const fs = require('fs').promises
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* JSONL Helper - Centralized JSONL parsing and writing
|
|
5
|
+
*
|
|
6
|
+
* Eliminates duplicated JSONL logic across:
|
|
7
|
+
* - session-manager.js (_parseJsonLines)
|
|
8
|
+
* - commands.js (inline parsing)
|
|
9
|
+
* - analyzer.js (inline parsing)
|
|
10
|
+
*
|
|
11
|
+
* JSONL Format: One JSON object per line, newline-separated
|
|
12
|
+
* Example:
|
|
13
|
+
* {"ts":"2025-10-04T14:30:00Z","type":"feature_add","name":"auth"}
|
|
14
|
+
* {"ts":"2025-10-04T15:00:00Z","type":"task_start","task":"JWT"}
|
|
15
|
+
*
|
|
16
|
+
* @module jsonl-helper
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Parse JSONL content into array of objects
|
|
21
|
+
* Handles malformed lines gracefully (skips them)
|
|
22
|
+
*
|
|
23
|
+
* @param {string} content - JSONL content
|
|
24
|
+
* @returns {Array<Object>} - Array of parsed objects
|
|
25
|
+
*/
|
|
26
|
+
function parseJsonLines(content) {
|
|
27
|
+
const lines = content.split('\n').filter((line) => line.trim())
|
|
28
|
+
const entries = []
|
|
29
|
+
|
|
30
|
+
for (const line of lines) {
|
|
31
|
+
try {
|
|
32
|
+
entries.push(JSON.parse(line))
|
|
33
|
+
} catch (error) {
|
|
34
|
+
// Skip malformed lines silently
|
|
35
|
+
// Could optionally log warning
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return entries
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Convert array of objects to JSONL string
|
|
44
|
+
*
|
|
45
|
+
* @param {Array<Object>} objects - Array of objects to stringify
|
|
46
|
+
* @returns {string} - JSONL formatted string
|
|
47
|
+
*/
|
|
48
|
+
function stringifyJsonLines(objects) {
|
|
49
|
+
return objects.map((obj) => JSON.stringify(obj)).join('\n') + '\n'
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Read and parse JSONL file
|
|
54
|
+
*
|
|
55
|
+
* @param {string} filePath - Path to JSONL file
|
|
56
|
+
* @returns {Promise<Array<Object>>} - Array of parsed objects
|
|
57
|
+
*/
|
|
58
|
+
async function readJsonLines(filePath) {
|
|
59
|
+
try {
|
|
60
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
61
|
+
return parseJsonLines(content)
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (error.code === 'ENOENT') {
|
|
64
|
+
return [] // File doesn't exist, return empty array
|
|
65
|
+
}
|
|
66
|
+
throw error
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Write array of objects to JSONL file (overwrites)
|
|
72
|
+
*
|
|
73
|
+
* @param {string} filePath - Path to JSONL file
|
|
74
|
+
* @param {Array<Object>} objects - Array of objects to write
|
|
75
|
+
* @returns {Promise<void>}
|
|
76
|
+
*/
|
|
77
|
+
async function writeJsonLines(filePath, objects) {
|
|
78
|
+
const content = stringifyJsonLines(objects)
|
|
79
|
+
await fs.writeFile(filePath, content, 'utf-8')
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Append a single object to JSONL file
|
|
84
|
+
* Uses append mode for efficiency (no full file read/write)
|
|
85
|
+
*
|
|
86
|
+
* @param {string} filePath - Path to JSONL file
|
|
87
|
+
* @param {Object} object - Object to append
|
|
88
|
+
* @returns {Promise<void>}
|
|
89
|
+
*/
|
|
90
|
+
async function appendJsonLine(filePath, object) {
|
|
91
|
+
const line = JSON.stringify(object) + '\n'
|
|
92
|
+
await fs.appendFile(filePath, line, 'utf-8')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Append multiple objects to JSONL file
|
|
97
|
+
*
|
|
98
|
+
* @param {string} filePath - Path to JSONL file
|
|
99
|
+
* @param {Array<Object>} objects - Objects to append
|
|
100
|
+
* @returns {Promise<void>}
|
|
101
|
+
*/
|
|
102
|
+
async function appendJsonLines(filePath, objects) {
|
|
103
|
+
const content = stringifyJsonLines(objects)
|
|
104
|
+
await fs.appendFile(filePath, content, 'utf-8')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Filter JSONL file entries by predicate
|
|
109
|
+
* Reads all entries, filters, returns matching ones
|
|
110
|
+
*
|
|
111
|
+
* @param {string} filePath - Path to JSONL file
|
|
112
|
+
* @param {Function} predicate - Filter function (entry => boolean)
|
|
113
|
+
* @returns {Promise<Array<Object>>} - Filtered entries
|
|
114
|
+
*/
|
|
115
|
+
async function filterJsonLines(filePath, predicate) {
|
|
116
|
+
const entries = await readJsonLines(filePath)
|
|
117
|
+
return entries.filter(predicate)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Count lines in JSONL file (non-empty, parseable lines)
|
|
122
|
+
*
|
|
123
|
+
* @param {string} filePath - Path to JSONL file
|
|
124
|
+
* @returns {Promise<number>} - Number of valid lines
|
|
125
|
+
*/
|
|
126
|
+
async function countJsonLines(filePath) {
|
|
127
|
+
try {
|
|
128
|
+
const content = await fs.readFile(filePath, 'utf-8')
|
|
129
|
+
const lines = content.split('\n').filter((line) => line.trim())
|
|
130
|
+
return lines.length
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (error.code === 'ENOENT') {
|
|
133
|
+
return 0
|
|
134
|
+
}
|
|
135
|
+
throw error
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get last N entries from JSONL file
|
|
141
|
+
* Efficient for large files (reads whole file but only returns last N)
|
|
142
|
+
*
|
|
143
|
+
* @param {string} filePath - Path to JSONL file
|
|
144
|
+
* @param {number} n - Number of entries to return
|
|
145
|
+
* @returns {Promise<Array<Object>>} - Last N entries
|
|
146
|
+
*/
|
|
147
|
+
async function getLastJsonLines(filePath, n) {
|
|
148
|
+
const entries = await readJsonLines(filePath)
|
|
149
|
+
return entries.slice(-n)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get first N entries from JSONL file
|
|
154
|
+
*
|
|
155
|
+
* @param {string} filePath - Path to JSONL file
|
|
156
|
+
* @param {number} n - Number of entries to return
|
|
157
|
+
* @returns {Promise<Array<Object>>} - First N entries
|
|
158
|
+
*/
|
|
159
|
+
async function getFirstJsonLines(filePath, n) {
|
|
160
|
+
const entries = await readJsonLines(filePath)
|
|
161
|
+
return entries.slice(0, n)
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Merge multiple JSONL files into one array
|
|
166
|
+
* Useful for reading multiple sessions
|
|
167
|
+
*
|
|
168
|
+
* @param {Array<string>} filePaths - Array of JSONL file paths
|
|
169
|
+
* @returns {Promise<Array<Object>>} - Merged entries from all files
|
|
170
|
+
*/
|
|
171
|
+
async function mergeJsonLines(filePaths) {
|
|
172
|
+
const allEntries = []
|
|
173
|
+
|
|
174
|
+
for (const filePath of filePaths) {
|
|
175
|
+
const entries = await readJsonLines(filePath)
|
|
176
|
+
allEntries.push(...entries)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return allEntries
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Check if JSONL file is empty or doesn't exist
|
|
184
|
+
*
|
|
185
|
+
* @param {string} filePath - Path to JSONL file
|
|
186
|
+
* @returns {Promise<boolean>} - True if empty or non-existent
|
|
187
|
+
*/
|
|
188
|
+
async function isJsonLinesEmpty(filePath) {
|
|
189
|
+
const count = await countJsonLines(filePath)
|
|
190
|
+
return count === 0
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = {
|
|
194
|
+
parseJsonLines,
|
|
195
|
+
stringifyJsonLines,
|
|
196
|
+
readJsonLines,
|
|
197
|
+
writeJsonLines,
|
|
198
|
+
appendJsonLine,
|
|
199
|
+
appendJsonLines,
|
|
200
|
+
filterJsonLines,
|
|
201
|
+
countJsonLines,
|
|
202
|
+
getLastJsonLines,
|
|
203
|
+
getFirstJsonLines,
|
|
204
|
+
mergeJsonLines,
|
|
205
|
+
isJsonLinesEmpty,
|
|
206
|
+
}
|
|
@@ -25,12 +25,12 @@ class ProjectCapabilities {
|
|
|
25
25
|
*/
|
|
26
26
|
async hasDesign(projectPath) {
|
|
27
27
|
return (
|
|
28
|
-
await this.hasFolder(projectPath, 'design') ||
|
|
29
|
-
await this.hasFolder(projectPath, 'designs') ||
|
|
30
|
-
await this.hasFolder(projectPath, '.storybook') ||
|
|
31
|
-
await this.hasDep(projectPath, '@storybook/react') ||
|
|
32
|
-
await this.hasDep(projectPath, '@storybook/vue') ||
|
|
33
|
-
await this.hasFiles(projectPath, '**/*.figma')
|
|
28
|
+
(await this.hasFolder(projectPath, 'design')) ||
|
|
29
|
+
(await this.hasFolder(projectPath, 'designs')) ||
|
|
30
|
+
(await this.hasFolder(projectPath, '.storybook')) ||
|
|
31
|
+
(await this.hasDep(projectPath, '@storybook/react')) ||
|
|
32
|
+
(await this.hasDep(projectPath, '@storybook/vue')) ||
|
|
33
|
+
(await this.hasFiles(projectPath, '**/*.figma'))
|
|
34
34
|
)
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -39,13 +39,13 @@ class ProjectCapabilities {
|
|
|
39
39
|
*/
|
|
40
40
|
async hasTest(projectPath) {
|
|
41
41
|
return (
|
|
42
|
-
await this.hasDep(projectPath, 'jest') ||
|
|
43
|
-
await this.hasDep(projectPath, 'vitest') ||
|
|
44
|
-
await this.hasDep(projectPath, 'mocha') ||
|
|
45
|
-
await this.hasDep(projectPath, '@jest/core') ||
|
|
46
|
-
await this.hasFiles(projectPath, '**/*.{test,spec}.{js,ts,jsx,tsx}') ||
|
|
47
|
-
await this.hasFile(projectPath, 'jest.config.js') ||
|
|
48
|
-
await this.hasFile(projectPath, 'vitest.config.js')
|
|
42
|
+
(await this.hasDep(projectPath, 'jest')) ||
|
|
43
|
+
(await this.hasDep(projectPath, 'vitest')) ||
|
|
44
|
+
(await this.hasDep(projectPath, 'mocha')) ||
|
|
45
|
+
(await this.hasDep(projectPath, '@jest/core')) ||
|
|
46
|
+
(await this.hasFiles(projectPath, '**/*.{test,spec}.{js,ts,jsx,tsx}')) ||
|
|
47
|
+
(await this.hasFile(projectPath, 'jest.config.js')) ||
|
|
48
|
+
(await this.hasFile(projectPath, 'vitest.config.js'))
|
|
49
49
|
)
|
|
50
50
|
}
|
|
51
51
|
|
|
@@ -54,11 +54,11 @@ class ProjectCapabilities {
|
|
|
54
54
|
*/
|
|
55
55
|
async hasDocs(projectPath) {
|
|
56
56
|
return (
|
|
57
|
-
await this.hasFolder(projectPath, 'docs') ||
|
|
58
|
-
await this.hasFolder(projectPath, 'documentation') ||
|
|
59
|
-
await this.hasFile(projectPath, 'README.md') ||
|
|
60
|
-
await this.hasDep(projectPath, 'typedoc') ||
|
|
61
|
-
await this.hasDep(projectPath, 'jsdoc')
|
|
57
|
+
(await this.hasFolder(projectPath, 'docs')) ||
|
|
58
|
+
(await this.hasFolder(projectPath, 'documentation')) ||
|
|
59
|
+
(await this.hasFile(projectPath, 'README.md')) ||
|
|
60
|
+
(await this.hasDep(projectPath, 'typedoc')) ||
|
|
61
|
+
(await this.hasDep(projectPath, 'jsdoc'))
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -98,10 +98,9 @@ class ProjectCapabilities {
|
|
|
98
98
|
const files = await fs.readdir(projectPath, { recursive: true })
|
|
99
99
|
|
|
100
100
|
// Filter by pattern and ignore node_modules, dist, build
|
|
101
|
-
return files.some(file => {
|
|
102
|
-
const skip =
|
|
103
|
-
|
|
104
|
-
file.includes('build/')
|
|
101
|
+
return files.some((file) => {
|
|
102
|
+
const skip =
|
|
103
|
+
file.includes('node_modules/') || file.includes('dist/') || file.includes('build/')
|
|
105
104
|
return !skip && regex.test(file)
|
|
106
105
|
})
|
|
107
106
|
} catch {
|