prjct-cli 0.45.0 → 0.45.3

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 (207) hide show
  1. package/CHANGELOG.md +75 -0
  2. package/bin/prjct.ts +117 -10
  3. package/core/__tests__/agentic/memory-system.test.ts +39 -26
  4. package/core/__tests__/agentic/plan-mode.test.ts +64 -46
  5. package/core/__tests__/agentic/prompt-builder.test.ts +14 -14
  6. package/core/__tests__/services/project-index.test.ts +353 -0
  7. package/core/__tests__/types/fs.test.ts +3 -3
  8. package/core/__tests__/utils/date-helper.test.ts +10 -10
  9. package/core/__tests__/utils/output.test.ts +9 -6
  10. package/core/__tests__/utils/project-commands.test.ts +5 -6
  11. package/core/agentic/agent-router.ts +9 -10
  12. package/core/agentic/chain-of-thought.ts +16 -4
  13. package/core/agentic/command-executor.ts +66 -40
  14. package/core/agentic/context-builder.ts +8 -5
  15. package/core/agentic/ground-truth.ts +15 -9
  16. package/core/agentic/index.ts +145 -152
  17. package/core/agentic/loop-detector.ts +40 -11
  18. package/core/agentic/memory-system.ts +98 -35
  19. package/core/agentic/orchestrator-executor.ts +135 -71
  20. package/core/agentic/plan-mode.ts +46 -16
  21. package/core/agentic/prompt-builder.ts +108 -42
  22. package/core/agentic/services.ts +10 -9
  23. package/core/agentic/skill-loader.ts +9 -15
  24. package/core/agentic/smart-context.ts +129 -79
  25. package/core/agentic/template-executor.ts +13 -12
  26. package/core/agentic/template-loader.ts +7 -4
  27. package/core/agentic/tool-registry.ts +16 -13
  28. package/core/agents/index.ts +1 -1
  29. package/core/agents/performance.ts +10 -27
  30. package/core/ai-tools/formatters.ts +8 -6
  31. package/core/ai-tools/generator.ts +4 -4
  32. package/core/ai-tools/index.ts +1 -1
  33. package/core/ai-tools/registry.ts +21 -11
  34. package/core/bus/bus.ts +23 -16
  35. package/core/bus/index.ts +2 -2
  36. package/core/cli/linear.ts +3 -5
  37. package/core/cli/start.ts +28 -25
  38. package/core/commands/analysis.ts +58 -39
  39. package/core/commands/analytics.ts +52 -44
  40. package/core/commands/base.ts +15 -13
  41. package/core/commands/cleanup.ts +6 -13
  42. package/core/commands/command-data.ts +28 -4
  43. package/core/commands/commands.ts +57 -24
  44. package/core/commands/context.ts +4 -4
  45. package/core/commands/design.ts +3 -10
  46. package/core/commands/index.ts +5 -8
  47. package/core/commands/maintenance.ts +7 -4
  48. package/core/commands/planning.ts +179 -56
  49. package/core/commands/register.ts +13 -9
  50. package/core/commands/registry.ts +15 -14
  51. package/core/commands/setup.ts +26 -14
  52. package/core/commands/shipping.ts +11 -16
  53. package/core/commands/snapshots.ts +16 -32
  54. package/core/commands/uninstall.ts +541 -0
  55. package/core/commands/workflow.ts +24 -28
  56. package/core/constants/index.ts +10 -22
  57. package/core/context/generator.ts +82 -33
  58. package/core/context-tools/files-tool.ts +18 -19
  59. package/core/context-tools/imports-tool.ts +13 -33
  60. package/core/context-tools/index.ts +29 -54
  61. package/core/context-tools/recent-tool.ts +16 -22
  62. package/core/context-tools/signatures-tool.ts +17 -26
  63. package/core/context-tools/summary-tool.ts +20 -22
  64. package/core/context-tools/token-counter.ts +25 -20
  65. package/core/context-tools/types.ts +5 -5
  66. package/core/domain/agent-generator.ts +7 -5
  67. package/core/domain/agent-loader.ts +2 -2
  68. package/core/domain/analyzer.ts +19 -16
  69. package/core/domain/architecture-generator.ts +6 -3
  70. package/core/domain/context-estimator.ts +3 -4
  71. package/core/domain/snapshot-manager.ts +25 -22
  72. package/core/domain/task-stack.ts +24 -14
  73. package/core/errors.ts +1 -1
  74. package/core/events/events.ts +2 -4
  75. package/core/events/index.ts +1 -2
  76. package/core/index.ts +28 -16
  77. package/core/infrastructure/agent-detector.ts +3 -3
  78. package/core/infrastructure/ai-provider.ts +23 -20
  79. package/core/infrastructure/author-detector.ts +16 -10
  80. package/core/infrastructure/capability-installer.ts +2 -2
  81. package/core/infrastructure/claude-agent.ts +6 -6
  82. package/core/infrastructure/command-installer.ts +22 -17
  83. package/core/infrastructure/config-manager.ts +18 -14
  84. package/core/infrastructure/editors-config.ts +8 -4
  85. package/core/infrastructure/path-manager.ts +8 -6
  86. package/core/infrastructure/permission-manager.ts +20 -17
  87. package/core/infrastructure/setup.ts +42 -38
  88. package/core/infrastructure/update-checker.ts +5 -5
  89. package/core/integrations/issue-tracker/enricher.ts +8 -19
  90. package/core/integrations/issue-tracker/index.ts +2 -2
  91. package/core/integrations/issue-tracker/manager.ts +15 -15
  92. package/core/integrations/issue-tracker/types.ts +5 -22
  93. package/core/integrations/jira/client.ts +67 -59
  94. package/core/integrations/jira/index.ts +11 -14
  95. package/core/integrations/jira/mcp-adapter.ts +5 -10
  96. package/core/integrations/jira/service.ts +10 -10
  97. package/core/integrations/linear/client.ts +27 -18
  98. package/core/integrations/linear/index.ts +9 -12
  99. package/core/integrations/linear/service.ts +11 -11
  100. package/core/integrations/linear/sync.ts +8 -8
  101. package/core/outcomes/analyzer.ts +5 -18
  102. package/core/outcomes/index.ts +2 -2
  103. package/core/outcomes/recorder.ts +3 -3
  104. package/core/plugin/builtin/webhook.ts +19 -15
  105. package/core/plugin/hooks.ts +29 -21
  106. package/core/plugin/index.ts +7 -7
  107. package/core/plugin/loader.ts +19 -19
  108. package/core/plugin/registry.ts +12 -23
  109. package/core/schemas/agents.ts +1 -1
  110. package/core/schemas/analysis.ts +1 -1
  111. package/core/schemas/enriched-task.ts +62 -49
  112. package/core/schemas/ideas.ts +13 -13
  113. package/core/schemas/index.ts +17 -27
  114. package/core/schemas/issues.ts +40 -25
  115. package/core/schemas/metrics.ts +25 -25
  116. package/core/schemas/outcomes.ts +70 -62
  117. package/core/schemas/permissions.ts +15 -12
  118. package/core/schemas/prd.ts +27 -14
  119. package/core/schemas/project.ts +3 -3
  120. package/core/schemas/roadmap.ts +47 -34
  121. package/core/schemas/schemas.ts +3 -4
  122. package/core/schemas/shipped.ts +3 -3
  123. package/core/schemas/state.ts +43 -29
  124. package/core/server/index.ts +5 -6
  125. package/core/server/routes-extended.ts +68 -72
  126. package/core/server/routes.ts +3 -3
  127. package/core/server/server.ts +31 -26
  128. package/core/services/agent-generator.ts +237 -0
  129. package/core/services/agent-service.ts +2 -2
  130. package/core/services/breakdown-service.ts +2 -4
  131. package/core/services/context-generator.ts +299 -0
  132. package/core/services/context-selector.ts +420 -0
  133. package/core/services/doctor-service.ts +426 -0
  134. package/core/services/file-categorizer.ts +448 -0
  135. package/core/services/file-scorer.ts +270 -0
  136. package/core/services/git-analyzer.ts +267 -0
  137. package/core/services/index.ts +27 -10
  138. package/core/services/memory-service.ts +3 -4
  139. package/core/services/project-index.ts +911 -0
  140. package/core/services/project-service.ts +4 -4
  141. package/core/services/skill-installer.ts +14 -17
  142. package/core/services/skill-lock.ts +3 -3
  143. package/core/services/skill-service.ts +12 -6
  144. package/core/services/stack-detector.ts +245 -0
  145. package/core/services/sync-service.ts +87 -345
  146. package/core/services/watch-service.ts +294 -0
  147. package/core/session/compaction.ts +23 -31
  148. package/core/session/index.ts +11 -5
  149. package/core/session/log-migration.ts +3 -3
  150. package/core/session/metrics.ts +19 -14
  151. package/core/session/session-log-manager.ts +12 -17
  152. package/core/session/task-session-manager.ts +25 -25
  153. package/core/session/utils.ts +1 -1
  154. package/core/storage/ideas-storage.ts +41 -57
  155. package/core/storage/index-storage.ts +514 -0
  156. package/core/storage/index.ts +41 -17
  157. package/core/storage/metrics-storage.ts +39 -34
  158. package/core/storage/queue-storage.ts +35 -45
  159. package/core/storage/shipped-storage.ts +17 -20
  160. package/core/storage/state-storage.ts +50 -30
  161. package/core/storage/storage-manager.ts +6 -6
  162. package/core/storage/storage.ts +18 -15
  163. package/core/sync/auth-config.ts +3 -3
  164. package/core/sync/index.ts +13 -19
  165. package/core/sync/oauth-handler.ts +3 -3
  166. package/core/sync/sync-client.ts +4 -9
  167. package/core/sync/sync-manager.ts +12 -14
  168. package/core/types/commands.ts +42 -7
  169. package/core/types/index.ts +284 -305
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +14 -14
  172. package/core/types/utils.ts +3 -3
  173. package/core/utils/agent-stream.ts +3 -1
  174. package/core/utils/animations.ts +14 -11
  175. package/core/utils/branding.ts +7 -7
  176. package/core/utils/cache.ts +1 -3
  177. package/core/utils/collection-filters.ts +3 -15
  178. package/core/utils/date-helper.ts +2 -7
  179. package/core/utils/file-helper.ts +13 -8
  180. package/core/utils/jsonl-helper.ts +13 -10
  181. package/core/utils/keychain.ts +4 -8
  182. package/core/utils/logger.ts +1 -1
  183. package/core/utils/next-steps.ts +3 -3
  184. package/core/utils/output.ts +58 -11
  185. package/core/utils/project-commands.ts +6 -6
  186. package/core/utils/project-credentials.ts +5 -12
  187. package/core/utils/runtime.ts +2 -2
  188. package/core/utils/session-helper.ts +3 -4
  189. package/core/utils/version.ts +3 -3
  190. package/core/wizard/index.ts +13 -0
  191. package/core/wizard/onboarding.ts +633 -0
  192. package/core/workflow/state-machine.ts +7 -7
  193. package/dist/bin/prjct.mjs +18755 -15574
  194. package/dist/core/infrastructure/command-installer.js +86 -79
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +246 -225
  197. package/dist/core/utils/version.js +9 -9
  198. package/package.json +11 -12
  199. package/scripts/build.js +3 -3
  200. package/scripts/postinstall.js +2 -2
  201. package/templates/mcp-config.json +6 -1
  202. package/templates/permissions/permissive.jsonc +1 -1
  203. package/templates/permissions/strict.jsonc +5 -9
  204. package/templates/global/docs/agents.md +0 -88
  205. package/templates/global/docs/architecture.md +0 -103
  206. package/templates/global/docs/commands.md +0 -96
  207. package/templates/global/docs/validation.md +0 -95
@@ -15,16 +15,15 @@
15
15
  * @version 1.0.0
16
16
  */
17
17
 
18
- import type { ContextToolOutput, ContextToolUsage } from './types'
18
+ import { metricsStorage } from '../storage/metrics-storage'
19
+ import { getTimestamp } from '../utils/date-helper'
19
20
  import { findRelevantFiles } from './files-tool'
20
- import { extractSignatures, extractDirectorySignatures } from './signatures-tool'
21
21
  import { analyzeImports } from './imports-tool'
22
22
  import { getRecentFiles } from './recent-tool'
23
- import { summarizeFile, summarizeDirectory } from './summary-tool'
23
+ import { extractDirectorySignatures, extractSignatures } from './signatures-tool'
24
+ import { summarizeDirectory, summarizeFile } from './summary-tool'
24
25
  import { combineMetrics } from './token-counter'
25
- import { metricsStorage } from '../storage/metrics-storage'
26
- import configManager from '../infrastructure/config-manager'
27
- import { getTimestamp } from '../utils/date-helper'
26
+ import type { ContextToolOutput, ContextToolUsage } from './types'
28
27
 
29
28
  // =============================================================================
30
29
  // CLI Dispatcher
@@ -127,17 +126,14 @@ export async function runContextTool(
127
126
  // Tool Runners
128
127
  // =============================================================================
129
128
 
130
- async function runFilesTool(
131
- args: string[],
132
- projectPath: string
133
- ): Promise<ContextToolOutput> {
129
+ async function runFilesTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
134
130
  // Parse options
135
131
  const options: { maxFiles?: number; minScore?: number; includeTests?: boolean } = {}
136
132
  const taskParts: string[] = []
137
133
 
138
134
  for (let i = 0; i < args.length; i++) {
139
135
  if (args[i] === '--max' && args[i + 1]) {
140
- options.maxFiles = parseInt(args[++i])
136
+ options.maxFiles = parseInt(args[++i], 10)
141
137
  } else if (args[i] === '--min-score' && args[i + 1]) {
142
138
  options.minScore = parseFloat(args[++i])
143
139
  } else if (args[i] === '--include-tests') {
@@ -162,10 +158,7 @@ async function runFilesTool(
162
158
  return { tool: 'files', result }
163
159
  }
164
160
 
165
- async function runSignaturesTool(
166
- args: string[],
167
- projectPath: string
168
- ): Promise<ContextToolOutput> {
161
+ async function runSignaturesTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
169
162
  const filePath = args[0]
170
163
  if (!filePath) {
171
164
  return {
@@ -178,11 +171,9 @@ async function runSignaturesTool(
178
171
  }
179
172
 
180
173
  // Check if it's a directory
181
- const fs = await import('fs/promises')
182
- const path = await import('path')
183
- const fullPath = path.isAbsolute(filePath)
184
- ? filePath
185
- : path.join(projectPath, filePath)
174
+ const fs = await import('node:fs/promises')
175
+ const path = await import('node:path')
176
+ const fullPath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
186
177
 
187
178
  try {
188
179
  const stat = await fs.stat(fullPath)
@@ -192,13 +183,11 @@ async function runSignaturesTool(
192
183
  recursive: args.includes('--recursive') || args.includes('-r'),
193
184
  })
194
185
  // Combine into single output with cost savings
195
- const combinedMetrics = combineMetrics(results.map(r => r.metrics))
186
+ const combinedMetrics = combineMetrics(results.map((r) => r.metrics))
196
187
  const combined = {
197
188
  file: filePath,
198
189
  language: 'multiple',
199
- signatures: results.flatMap((r) =>
200
- r.signatures.map((s) => ({ ...s, file: r.file }))
201
- ),
190
+ signatures: results.flatMap((r) => r.signatures.map((s) => ({ ...s, file: r.file }))),
202
191
  fallback: false,
203
192
  metrics: combinedMetrics,
204
193
  }
@@ -212,10 +201,7 @@ async function runSignaturesTool(
212
201
  return { tool: 'signatures', result }
213
202
  }
214
203
 
215
- async function runImportsTool(
216
- args: string[],
217
- projectPath: string
218
- ): Promise<ContextToolOutput> {
204
+ async function runImportsTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
219
205
  const filePath = args[0]
220
206
  if (!filePath) {
221
207
  return {
@@ -233,7 +219,7 @@ async function runImportsTool(
233
219
  if (args[i] === '--reverse' || args[i] === '-r') {
234
220
  options.reverse = true
235
221
  } else if ((args[i] === '--depth' || args[i] === '-d') && args[i + 1]) {
236
- options.depth = parseInt(args[++i])
222
+ options.depth = parseInt(args[++i], 10)
237
223
  }
238
224
  }
239
225
 
@@ -241,19 +227,16 @@ async function runImportsTool(
241
227
  return { tool: 'imports', result }
242
228
  }
243
229
 
244
- async function runRecentTool(
245
- args: string[],
246
- projectPath: string
247
- ): Promise<ContextToolOutput> {
230
+ async function runRecentTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
248
231
  const options: { commits?: number; branch?: boolean; maxFiles?: number } = {}
249
232
 
250
233
  for (let i = 0; i < args.length; i++) {
251
234
  if (args[i] === '--branch' || args[i] === '-b') {
252
235
  options.branch = true
253
236
  } else if (args[i] === '--max' && args[i + 1]) {
254
- options.maxFiles = parseInt(args[++i])
237
+ options.maxFiles = parseInt(args[++i], 10)
255
238
  } else if (/^\d+$/.test(args[i])) {
256
- options.commits = parseInt(args[i])
239
+ options.commits = parseInt(args[i], 10)
257
240
  }
258
241
  }
259
242
 
@@ -261,10 +244,7 @@ async function runRecentTool(
261
244
  return { tool: 'recent', result }
262
245
  }
263
246
 
264
- async function runSummaryTool(
265
- args: string[],
266
- projectPath: string
267
- ): Promise<ContextToolOutput> {
247
+ async function runSummaryTool(args: string[], projectPath: string): Promise<ContextToolOutput> {
268
248
  const targetPath = args[0]
269
249
  if (!targetPath) {
270
250
  return {
@@ -277,11 +257,9 @@ async function runSummaryTool(
277
257
  }
278
258
 
279
259
  // Check if it's a directory
280
- const fs = await import('fs/promises')
281
- const path = await import('path')
282
- const fullPath = path.isAbsolute(targetPath)
283
- ? targetPath
284
- : path.join(projectPath, targetPath)
260
+ const fs = await import('node:fs/promises')
261
+ const path = await import('node:path')
262
+ const fullPath = path.isAbsolute(targetPath) ? targetPath : path.join(projectPath, targetPath)
285
263
 
286
264
  try {
287
265
  const stat = await fs.stat(fullPath)
@@ -293,11 +271,9 @@ async function runSummaryTool(
293
271
  const combined = {
294
272
  file: targetPath,
295
273
  purpose: `Directory with ${results.length} files`,
296
- publicAPI: results.flatMap((r) =>
297
- r.publicAPI.map((api) => ({ ...api, file: r.file }))
298
- ),
274
+ publicAPI: results.flatMap((r) => r.publicAPI.map((api) => ({ ...api, file: r.file }))),
299
275
  dependencies: [...new Set(results.flatMap((r) => r.dependencies))],
300
- metrics: combineMetrics(results.map(r => r.metrics)),
276
+ metrics: combineMetrics(results.map((r) => r.metrics)),
301
277
  }
302
278
  return { tool: 'summary', result: combined }
303
279
  }
@@ -341,19 +317,17 @@ function getCompressionRate(result: ContextToolOutput): number {
341
317
  case 'signatures':
342
318
  case 'summary':
343
319
  return result.result.metrics.compression
344
- case 'files':
320
+ case 'files': {
345
321
  const scanned = result.result.metrics.filesScanned
346
322
  const returned = result.result.metrics.filesReturned
347
323
  return scanned > 0 ? (scanned - returned) / scanned : 0
324
+ }
348
325
  default:
349
326
  return 0
350
327
  }
351
328
  }
352
329
 
353
- async function recordToolUsage(
354
- projectId: string,
355
- usage: ContextToolUsage
356
- ): Promise<void> {
330
+ async function recordToolUsage(projectId: string, usage: ContextToolUsage): Promise<void> {
357
331
  try {
358
332
  // Record to metrics storage
359
333
  await metricsStorage.recordSync(projectId, {
@@ -451,8 +425,9 @@ export {
451
425
  summarizeFile,
452
426
  summarizeDirectory,
453
427
  }
428
+
454
429
  // Note: runContextTool is already exported at its definition (line 48)
455
430
 
431
+ export * from './token-counter'
456
432
  // Re-export types
457
433
  export * from './types'
458
- export * from './token-counter'
@@ -8,9 +8,9 @@
8
8
  * @version 1.0.0
9
9
  */
10
10
 
11
- import { exec as execCallback } from 'child_process'
12
- import { promisify } from 'util'
13
- import type { RecentToolOutput, HotFile } from './types'
11
+ import { exec as execCallback } from 'node:child_process'
12
+ import { promisify } from 'node:util'
13
+ import type { HotFile, RecentToolOutput } from './types'
14
14
 
15
15
  const exec = promisify(execCallback)
16
16
 
@@ -74,9 +74,7 @@ export async function getRecentFiles(
74
74
  }
75
75
 
76
76
  // Filter and limit
77
- hotFiles = hotFiles
78
- .filter((f) => !shouldIgnore(f.path))
79
- .slice(0, maxFiles)
77
+ hotFiles = hotFiles.filter((f) => !shouldIgnore(f.path)).slice(0, maxFiles)
80
78
 
81
79
  return {
82
80
  hotFiles,
@@ -88,7 +86,7 @@ export async function getRecentFiles(
88
86
  analysisWindow,
89
87
  },
90
88
  }
91
- } catch (error) {
89
+ } catch (_error) {
92
90
  // Git not available or not a repo
93
91
  return {
94
92
  hotFiles: [],
@@ -110,10 +108,7 @@ export async function getRecentFiles(
110
108
  /**
111
109
  * Get hot files from recent commits
112
110
  */
113
- async function getHotFilesFromCommits(
114
- projectPath: string,
115
- commits: number
116
- ): Promise<HotFile[]> {
111
+ async function getHotFilesFromCommits(projectPath: string, commits: number): Promise<HotFile[]> {
117
112
  // Get file change counts and last modified times
118
113
  const { stdout } = await exec(
119
114
  `git log -${commits} --pretty=format:"%ct" --name-only | awk '
@@ -138,7 +133,7 @@ async function getHotFilesFromCommits(
138
133
  for (const line of lines) {
139
134
  const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
140
135
  if (match) {
141
- const changes = parseInt(match[1])
136
+ const changes = parseInt(match[1], 10)
142
137
  if (changes > maxChanges) maxChanges = changes
143
138
  }
144
139
  }
@@ -147,8 +142,8 @@ async function getHotFilesFromCommits(
147
142
  const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
148
143
  if (!match) continue
149
144
 
150
- const changes = parseInt(match[1])
151
- const timestamp = parseInt(match[2])
145
+ const changes = parseInt(match[1], 10)
146
+ const timestamp = parseInt(match[2], 10)
152
147
  const filePath = match[3]
153
148
 
154
149
  const secondsAgo = now - timestamp
@@ -199,7 +194,7 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
199
194
  const { stdout: branchOutput } = await exec('git branch --show-current', {
200
195
  cwd: projectPath,
201
196
  })
202
- const currentBranch = branchOutput.trim()
197
+ const _currentBranch = branchOutput.trim()
203
198
 
204
199
  // Determine base branch (main or master)
205
200
  let baseBranch = 'main'
@@ -210,10 +205,9 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
210
205
  }
211
206
 
212
207
  // Get files changed in this branch
213
- const { stdout: diffOutput } = await exec(
214
- `git diff --name-only ${baseBranch}...HEAD`,
215
- { cwd: projectPath }
216
- )
208
+ const { stdout: diffOutput } = await exec(`git diff --name-only ${baseBranch}...HEAD`, {
209
+ cwd: projectPath,
210
+ })
217
211
 
218
212
  const branchOnlyFiles = diffOutput.trim().split('\n').filter(Boolean)
219
213
 
@@ -241,7 +235,7 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
241
235
  for (const line of lines) {
242
236
  const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
243
237
  if (match) {
244
- const changes = parseInt(match[1])
238
+ const changes = parseInt(match[1], 10)
245
239
  if (changes > maxChanges) maxChanges = changes
246
240
  }
247
241
  }
@@ -250,8 +244,8 @@ async function getBranchOnlyFiles(projectPath: string): Promise<{
250
244
  const match = line.match(/^(\d+)\s+(\d+)\s+(.+)$/)
251
245
  if (!match) continue
252
246
 
253
- const changes = parseInt(match[1])
254
- const timestamp = parseInt(match[2])
247
+ const changes = parseInt(match[1], 10)
248
+ const timestamp = parseInt(match[2], 10)
255
249
  const filePath = match[3]
256
250
 
257
251
  const secondsAgo = now - timestamp
@@ -16,11 +16,11 @@
16
16
  * @version 1.0.0
17
17
  */
18
18
 
19
- import fs from 'fs/promises'
20
- import path from 'path'
21
- import type { SignaturesToolOutput, CodeSignature, SignatureType } from './types'
22
- import { measureCompression, noCompression } from './token-counter'
19
+ import fs from 'node:fs/promises'
20
+ import path from 'node:path'
23
21
  import { isNotFoundError } from '../types/fs'
22
+ import { measureCompression, noCompression } from './token-counter'
23
+ import type { CodeSignature, SignaturesToolOutput, SignatureType } from './types'
24
24
 
25
25
  // =============================================================================
26
26
  // Language Support
@@ -92,15 +92,13 @@ const TS_PATTERNS: ExtractionPattern[] = [
92
92
  // Regular function declarations
93
93
  {
94
94
  type: 'function',
95
- pattern:
96
- /^(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/gm,
95
+ pattern: /^(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/gm,
97
96
  nameIndex: 1,
98
97
  },
99
98
  // Const arrow functions
100
99
  {
101
100
  type: 'function',
102
- pattern:
103
- /^const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/gm,
101
+ pattern: /^const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/gm,
104
102
  nameIndex: 1,
105
103
  },
106
104
  // Interface declarations
@@ -131,14 +129,14 @@ const TS_PATTERNS: ExtractionPattern[] = [
131
129
  {
132
130
  type: 'class',
133
131
  pattern:
134
- /^export\s+(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^\{]+)?(?:\s+implements\s+[^\{]+)?\s*\{/gm,
132
+ /^export\s+(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
135
133
  nameIndex: 1,
136
134
  exported: true,
137
135
  },
138
136
  {
139
137
  type: 'class',
140
138
  pattern:
141
- /^(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^\{]+)?(?:\s+implements\s+[^\{]+)?\s*\{/gm,
139
+ /^(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
142
140
  nameIndex: 1,
143
141
  },
144
142
  // Enum declarations
@@ -325,9 +323,7 @@ export async function extractSignatures(
325
323
  projectPath: string = process.cwd()
326
324
  ): Promise<SignaturesToolOutput> {
327
325
  // Resolve to absolute path
328
- const absolutePath = path.isAbsolute(filePath)
329
- ? filePath
330
- : path.join(projectPath, filePath)
326
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
331
327
 
332
328
  // Read file content
333
329
  let content: string
@@ -392,9 +388,7 @@ export async function extractDirectorySignatures(
392
388
  projectPath: string = process.cwd(),
393
389
  options: { recursive?: boolean } = {}
394
390
  ): Promise<SignaturesToolOutput[]> {
395
- const absolutePath = path.isAbsolute(dirPath)
396
- ? dirPath
397
- : path.join(projectPath, dirPath)
391
+ const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.join(projectPath, dirPath)
398
392
 
399
393
  const results: SignaturesToolOutput[] = []
400
394
 
@@ -407,11 +401,7 @@ export async function extractDirectorySignatures(
407
401
 
408
402
  if (entry.isDirectory()) {
409
403
  // Skip common ignore patterns
410
- if (
411
- entry.name === 'node_modules' ||
412
- entry.name === '.git' ||
413
- entry.name.startsWith('.')
414
- ) {
404
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
415
405
  continue
416
406
  }
417
407
  if (options.recursive) {
@@ -438,10 +428,7 @@ export async function extractDirectorySignatures(
438
428
  /**
439
429
  * Extract signatures from content using patterns
440
430
  */
441
- function extractFromContent(
442
- content: string,
443
- patterns: ExtractionPattern[]
444
- ): CodeSignature[] {
431
+ function extractFromContent(content: string, patterns: ExtractionPattern[]): CodeSignature[] {
445
432
  const signatures: CodeSignature[] = []
446
433
  const lines = content.split('\n')
447
434
 
@@ -473,7 +460,11 @@ function extractFromContent(
473
460
  let docstring: string | undefined
474
461
  if (lineNumber > 1) {
475
462
  const prevLine = lines[lineNumber - 2]?.trim()
476
- if (prevLine?.startsWith('/**') || prevLine?.startsWith('///') || prevLine?.startsWith('#')) {
463
+ if (
464
+ prevLine?.startsWith('/**') ||
465
+ prevLine?.startsWith('///') ||
466
+ prevLine?.startsWith('#')
467
+ ) {
477
468
  docstring = prevLine
478
469
  }
479
470
  }
@@ -12,13 +12,13 @@
12
12
  * @version 1.0.0
13
13
  */
14
14
 
15
- import fs from 'fs/promises'
16
- import path from 'path'
17
- import type { SummaryToolOutput, PublicAPIEntry, SignatureType } from './types'
18
- import { measureCompression, noCompression, combineMetrics } from './token-counter'
19
- import { extractSignatures } from './signatures-tool'
20
- import { analyzeImports } from './imports-tool'
15
+ import fs from 'node:fs/promises'
16
+ import path from 'node:path'
21
17
  import { isNotFoundError } from '../types/fs'
18
+ import { analyzeImports } from './imports-tool'
19
+ import { extractSignatures } from './signatures-tool'
20
+ import { measureCompression, noCompression } from './token-counter'
21
+ import type { PublicAPIEntry, SummaryToolOutput } from './types'
22
22
 
23
23
  // =============================================================================
24
24
  // Docstring Patterns
@@ -83,9 +83,7 @@ export async function summarizeFile(
83
83
  filePath: string,
84
84
  projectPath: string = process.cwd()
85
85
  ): Promise<SummaryToolOutput> {
86
- const absolutePath = path.isAbsolute(filePath)
87
- ? filePath
88
- : path.join(projectPath, filePath)
86
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
89
87
 
90
88
  // Read file content
91
89
  let content: string
@@ -124,9 +122,7 @@ export async function summarizeFile(
124
122
  name: sig.name,
125
123
  type: sig.type,
126
124
  signature: sig.signature,
127
- description: sig.docstring
128
- ? extractDescriptionFromDocstring(sig.docstring)
129
- : undefined,
125
+ description: sig.docstring ? extractDescriptionFromDocstring(sig.docstring) : undefined,
130
126
  }))
131
127
 
132
128
  // Get key dependencies (internal only, external are obvious from package.json)
@@ -155,9 +151,7 @@ export async function summarizeDirectory(
155
151
  projectPath: string = process.cwd(),
156
152
  options: { recursive?: boolean } = {}
157
153
  ): Promise<SummaryToolOutput[]> {
158
- const absolutePath = path.isAbsolute(dirPath)
159
- ? dirPath
160
- : path.join(projectPath, dirPath)
154
+ const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.join(projectPath, dirPath)
161
155
 
162
156
  const results: SummaryToolOutput[] = []
163
157
 
@@ -170,11 +164,7 @@ export async function summarizeDirectory(
170
164
 
171
165
  if (entry.isDirectory()) {
172
166
  // Skip common ignore patterns
173
- if (
174
- entry.name === 'node_modules' ||
175
- entry.name === '.git' ||
176
- entry.name.startsWith('.')
177
- ) {
167
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
178
168
  continue
179
169
  }
180
170
  if (options.recursive) {
@@ -227,7 +217,7 @@ function extractFilePurpose(content: string, language: string): string {
227
217
  let comment = ''
228
218
  let j = i
229
219
  while (j < lines.length) {
230
- comment += lines[j] + '\n'
220
+ comment += `${lines[j]}\n`
231
221
  if (pattern.end.test(lines[j])) break
232
222
  j++
233
223
  }
@@ -246,7 +236,15 @@ function extractFilePurpose(content: string, language: string): string {
246
236
  }
247
237
 
248
238
  // Stop if we hit code (not comments or empty lines)
249
- if (line.length > 0 && !line.startsWith('//') && !line.startsWith('#') && !line.startsWith('/*') && !line.startsWith('*') && !line.startsWith("'") && !line.startsWith('"')) {
239
+ if (
240
+ line.length > 0 &&
241
+ !line.startsWith('//') &&
242
+ !line.startsWith('#') &&
243
+ !line.startsWith('/*') &&
244
+ !line.startsWith('*') &&
245
+ !line.startsWith("'") &&
246
+ !line.startsWith('"')
247
+ ) {
250
248
  break
251
249
  }
252
250
  }
@@ -36,17 +36,17 @@ const CHARS_PER_TOKEN = 4
36
36
  */
37
37
  const MODEL_PRICING = {
38
38
  // Anthropic Claude (2026)
39
- 'claude-opus-4.5': { input: 0.005, output: 0.025 }, // $5/$25 per M
40
- 'claude-sonnet-4.5': { input: 0.003, output: 0.015 }, // $3/$15 per M
41
- 'claude-haiku-4.5': { input: 0.001, output: 0.005 }, // $1/$5 per M
42
- 'claude-opus-4': { input: 0.015, output: 0.075 }, // $15/$75 per M (legacy)
39
+ 'claude-opus-4.5': { input: 0.005, output: 0.025 }, // $5/$25 per M
40
+ 'claude-sonnet-4.5': { input: 0.003, output: 0.015 }, // $3/$15 per M
41
+ 'claude-haiku-4.5': { input: 0.001, output: 0.005 }, // $1/$5 per M
42
+ 'claude-opus-4': { input: 0.015, output: 0.075 }, // $15/$75 per M (legacy)
43
43
  // OpenAI
44
- 'gpt-4o': { input: 0.0025, output: 0.01 }, // $2.50/$10 per M
45
- 'gpt-4-turbo': { input: 0.01, output: 0.03 }, // $10/$30 per M
46
- 'gpt-4o-mini': { input: 0.00015, output: 0.0006 }, // $0.15/$0.60 per M
44
+ 'gpt-4o': { input: 0.0025, output: 0.01 }, // $2.50/$10 per M
45
+ 'gpt-4-turbo': { input: 0.01, output: 0.03 }, // $10/$30 per M
46
+ 'gpt-4o-mini': { input: 0.00015, output: 0.0006 }, // $0.15/$0.60 per M
47
47
  // Google
48
- 'gemini-1.5-pro': { input: 0.00125, output: 0.005 }, // $1.25/$5 per M
49
- 'gemini-1.5-flash': { input: 0.000075, output: 0.0003 }, // $0.075/$0.30 per M
48
+ 'gemini-1.5-pro': { input: 0.00125, output: 0.005 }, // $1.25/$5 per M
49
+ 'gemini-1.5-flash': { input: 0.000075, output: 0.0003 }, // $0.075/$0.30 per M
50
50
  } as const
51
51
 
52
52
  type ModelName = keyof typeof MODEL_PRICING
@@ -86,7 +86,10 @@ const BREAKDOWN_MODELS: ModelName[] = [
86
86
  * Calculate cost breakdown for a model
87
87
  * Output potential = estimated savings if response is proportionally shorter
88
88
  */
89
- function calculateModelCost(tokensSaved: number, model: ModelName): {
89
+ function calculateModelCost(
90
+ tokensSaved: number,
91
+ model: ModelName
92
+ ): {
90
93
  inputSaved: number
91
94
  outputPotential: number
92
95
  total: number
@@ -128,14 +131,13 @@ export function measureCompression(original: string, filtered: string): TokenMet
128
131
  const filteredTokens = countTokens(filtered)
129
132
  const tokensSaved = Math.max(0, originalTokens - filteredTokens)
130
133
 
131
- const compression =
132
- originalTokens > 0 ? (originalTokens - filteredTokens) / originalTokens : 0
134
+ const compression = originalTokens > 0 ? (originalTokens - filteredTokens) / originalTokens : 0
133
135
 
134
136
  // Calculate cost for default model
135
137
  const defaultCost = calculateModelCost(tokensSaved, DEFAULT_MODEL)
136
138
 
137
139
  // Calculate breakdown for popular models
138
- const byModel = BREAKDOWN_MODELS.map(model => ({
140
+ const byModel = BREAKDOWN_MODELS.map((model) => ({
139
141
  model,
140
142
  ...calculateModelCost(tokensSaved, model),
141
143
  }))
@@ -170,7 +172,7 @@ export function noCompression(content: string): TokenMetrics {
170
172
  cost: {
171
173
  saved: 0,
172
174
  formatted: '$0.00',
173
- byModel: BREAKDOWN_MODELS.map(model => ({
175
+ byModel: BREAKDOWN_MODELS.map((model) => ({
174
176
  model,
175
177
  inputSaved: 0,
176
178
  outputPotential: 0,
@@ -199,14 +201,17 @@ export function combineMetrics(metrics: TokenMetrics[]): TokenMetrics {
199
201
  const totalSaved = metrics.reduce((sum, m) => sum + m.tokens.saved, 0)
200
202
 
201
203
  // Calculate overall compression
202
- const compression = totalOriginal > 0
203
- ? (totalOriginal - totalFiltered) / totalOriginal
204
- : 0
204
+ const compression = totalOriginal > 0 ? (totalOriginal - totalFiltered) / totalOriginal : 0
205
205
 
206
206
  // Sum costs by model
207
- const byModel = BREAKDOWN_MODELS.map(model => {
208
- const modelMetrics = metrics.map(m =>
209
- m.cost.byModel.find(b => b.model === model) || { inputSaved: 0, outputPotential: 0, total: 0 }
207
+ const byModel = BREAKDOWN_MODELS.map((model) => {
208
+ const modelMetrics = metrics.map(
209
+ (m) =>
210
+ m.cost.byModel.find((b) => b.model === model) || {
211
+ inputSaved: 0,
212
+ outputPotential: 0,
213
+ total: 0,
214
+ }
210
215
  )
211
216
  return {
212
217
  model,
@@ -18,9 +18,9 @@
18
18
  */
19
19
  export interface CostBreakdown {
20
20
  model: string
21
- inputSaved: number // $ saved on input tokens
21
+ inputSaved: number // $ saved on input tokens
22
22
  outputPotential: number // $ potential savings on output (estimated)
23
- total: number // Combined savings
23
+ total: number // Combined savings
24
24
  }
25
25
 
26
26
  /**
@@ -32,10 +32,10 @@ export interface TokenMetrics {
32
32
  filtered: number
33
33
  saved: number
34
34
  }
35
- compression: number // 0-1 (e.g., 0.90 = 90% reduction)
35
+ compression: number // 0-1 (e.g., 0.90 = 90% reduction)
36
36
  cost: {
37
- saved: number // $ saved (using default model)
38
- formatted: string // Human-readable (e.g., "$0.02")
37
+ saved: number // $ saved (using default model)
38
+ formatted: string // Human-readable (e.g., "$0.02")
39
39
  byModel: CostBreakdown[] // Breakdown by popular models
40
40
  }
41
41
  }
@@ -4,11 +4,11 @@
4
4
  * @version 1.0.0
5
5
  */
6
6
 
7
- import fs from 'fs/promises'
8
- import path from 'path'
9
- import os from 'os'
10
- import AgentLoader from './agent-loader'
7
+ import fs from 'node:fs/promises'
8
+ import os from 'node:os'
9
+ import path from 'node:path'
11
10
  import log from '../utils/logger'
11
+ import AgentLoader from './agent-loader'
12
12
 
13
13
  interface AgentConfig {
14
14
  role?: string
@@ -152,7 +152,9 @@ ${config.contextFilter || 'Only relevant files'}
152
152
  async listAgents(): Promise<string[]> {
153
153
  try {
154
154
  const files = await fs.readdir(this.outputDir)
155
- return files.filter((f) => f.endsWith('.md') && !f.startsWith('.')).map((f) => f.replace('.md', ''))
155
+ return files
156
+ .filter((f) => f.endsWith('.md') && !f.startsWith('.'))
157
+ .map((f) => f.replace('.md', ''))
156
158
  } catch (_error) {
157
159
  return []
158
160
  }
@@ -6,8 +6,8 @@
6
6
  * @version 1.0.0
7
7
  */
8
8
 
9
- import fs from 'fs/promises'
10
- import path from 'path'
9
+ import fs from 'node:fs/promises'
10
+ import path from 'node:path'
11
11
  import pathManager from '../infrastructure/path-manager'
12
12
  import { isNotFoundError } from '../types/fs'
13
13