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.
Files changed (83) hide show
  1. package/CHANGELOG.md +67 -6
  2. package/CLAUDE.md +442 -36
  3. package/README.md +47 -54
  4. package/bin/prjct +170 -240
  5. package/core/agentic/command-executor.js +113 -0
  6. package/core/agentic/context-builder.js +85 -0
  7. package/core/agentic/prompt-builder.js +86 -0
  8. package/core/agentic/template-loader.js +104 -0
  9. package/core/agentic/tool-registry.js +117 -0
  10. package/core/command-registry.js +106 -62
  11. package/core/commands.js +2030 -2211
  12. package/core/domain/agent-generator.js +118 -0
  13. package/core/domain/analyzer.js +211 -0
  14. package/core/domain/architect-session.js +300 -0
  15. package/core/{agents → infrastructure/agents}/claude-agent.js +16 -13
  16. package/core/{author-detector.js → infrastructure/author-detector.js} +3 -1
  17. package/core/{capability-installer.js → infrastructure/capability-installer.js} +3 -6
  18. package/core/{command-installer.js → infrastructure/command-installer.js} +4 -2
  19. package/core/{config-manager.js → infrastructure/config-manager.js} +4 -4
  20. package/core/{editors-config.js → infrastructure/editors-config.js} +2 -10
  21. package/core/{migrator.js → infrastructure/migrator.js} +34 -19
  22. package/core/{path-manager.js → infrastructure/path-manager.js} +20 -44
  23. package/core/{session-manager.js → infrastructure/session-manager.js} +45 -105
  24. package/core/{update-checker.js → infrastructure/update-checker.js} +67 -67
  25. package/core/{animations-simple.js → utils/animations.js} +3 -23
  26. package/core/utils/date-helper.js +238 -0
  27. package/core/utils/file-helper.js +327 -0
  28. package/core/utils/jsonl-helper.js +206 -0
  29. package/core/{project-capabilities.js → utils/project-capabilities.js} +21 -22
  30. package/core/utils/session-helper.js +277 -0
  31. package/core/{version.js → utils/version.js} +1 -1
  32. package/package.json +4 -12
  33. package/templates/agents/AGENTS.md +101 -27
  34. package/templates/analysis/analyze.md +84 -0
  35. package/templates/commands/analyze.md +9 -2
  36. package/templates/commands/bug.md +79 -0
  37. package/templates/commands/build.md +5 -2
  38. package/templates/commands/cleanup.md +5 -2
  39. package/templates/commands/design.md +5 -2
  40. package/templates/commands/done.md +4 -2
  41. package/templates/commands/feature.md +113 -0
  42. package/templates/commands/fix.md +41 -10
  43. package/templates/commands/git.md +7 -2
  44. package/templates/commands/help.md +2 -2
  45. package/templates/commands/idea.md +14 -5
  46. package/templates/commands/init.md +62 -7
  47. package/templates/commands/next.md +4 -2
  48. package/templates/commands/now.md +4 -2
  49. package/templates/commands/progress.md +27 -5
  50. package/templates/commands/recap.md +39 -10
  51. package/templates/commands/roadmap.md +19 -5
  52. package/templates/commands/ship.md +118 -16
  53. package/templates/commands/status.md +4 -2
  54. package/templates/commands/sync.md +19 -15
  55. package/templates/commands/task.md +4 -2
  56. package/templates/commands/test.md +5 -2
  57. package/templates/commands/workflow.md +4 -2
  58. package/core/agent-generator.js +0 -525
  59. package/core/analyzer.js +0 -600
  60. package/core/animations.js +0 -277
  61. package/core/ascii-graphics.js +0 -433
  62. package/core/git-integration.js +0 -401
  63. package/core/task-schema.js +0 -342
  64. package/core/workflow-engine.js +0 -213
  65. package/core/workflow-prompts.js +0 -192
  66. package/core/workflow-rules.js +0 -147
  67. package/scripts/post-install.js +0 -121
  68. package/scripts/preuninstall.js +0 -94
  69. package/scripts/verify-installation.sh +0 -158
  70. package/templates/agents/be.template.md +0 -27
  71. package/templates/agents/coordinator.template.md +0 -34
  72. package/templates/agents/data.template.md +0 -27
  73. package/templates/agents/devops.template.md +0 -27
  74. package/templates/agents/fe.template.md +0 -27
  75. package/templates/agents/mobile.template.md +0 -27
  76. package/templates/agents/qa.template.md +0 -27
  77. package/templates/agents/scribe.template.md +0 -29
  78. package/templates/agents/security.template.md +0 -27
  79. package/templates/agents/ux.template.md +0 -27
  80. package/templates/commands/context.md +0 -36
  81. package/templates/commands/stuck.md +0 -36
  82. package/templates/examples/natural-language-examples.md +0 -532
  83. /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 = file.includes('node_modules/') ||
103
- file.includes('dist/') ||
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 {