prjct-cli 0.44.1 → 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 +114 -0
  2. package/bin/prjct.ts +131 -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 +287 -29
  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 +49 -8
  43. package/core/commands/commands.ts +60 -23
  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 +14 -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 +583 -0
  59. package/core/context-tools/imports-tool.ts +403 -0
  60. package/core/context-tools/index.ts +433 -0
  61. package/core/context-tools/recent-tool.ts +307 -0
  62. package/core/context-tools/signatures-tool.ts +501 -0
  63. package/core/context-tools/summary-tool.ts +307 -0
  64. package/core/context-tools/token-counter.ts +284 -0
  65. package/core/context-tools/types.ts +253 -0
  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 -12
  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 +143 -0
  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 +170 -329
  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 -13
  157. package/core/storage/metrics-storage.ts +320 -0
  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 -302
  170. package/core/types/integrations.ts +3 -3
  171. package/core/types/storage.ts +49 -0
  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 +18907 -13189
  194. package/dist/core/infrastructure/command-installer.js +96 -111
  195. package/dist/core/infrastructure/editors-config.js +6 -6
  196. package/dist/core/infrastructure/setup.js +256 -257
  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
@@ -0,0 +1,501 @@
1
+ /**
2
+ * Signatures Tool - Extract code signatures without full content
3
+ *
4
+ * Extracts:
5
+ * - Function names + params + return types
6
+ * - Interface/type definitions
7
+ * - Class names + methods
8
+ * - Export lists
9
+ *
10
+ * Achieves ~90% token reduction by returning structure only.
11
+ *
12
+ * Uses regex patterns for broad language support.
13
+ * Falls back to full file if language not supported.
14
+ *
15
+ * @module context-tools/signatures-tool
16
+ * @version 1.0.0
17
+ */
18
+
19
+ import fs from 'node:fs/promises'
20
+ import path from 'node:path'
21
+ import { isNotFoundError } from '../types/fs'
22
+ import { measureCompression, noCompression } from './token-counter'
23
+ import type { CodeSignature, SignaturesToolOutput, SignatureType } from './types'
24
+
25
+ // =============================================================================
26
+ // Language Support
27
+ // =============================================================================
28
+
29
+ type LanguageId =
30
+ | 'typescript'
31
+ | 'javascript'
32
+ | 'python'
33
+ | 'go'
34
+ | 'rust'
35
+ | 'java'
36
+ | 'csharp'
37
+ | 'php'
38
+ | 'ruby'
39
+ | 'unknown'
40
+
41
+ /**
42
+ * Map file extensions to language identifiers
43
+ */
44
+ const EXTENSION_TO_LANGUAGE: Record<string, LanguageId> = {
45
+ '.ts': 'typescript',
46
+ '.tsx': 'typescript',
47
+ '.js': 'javascript',
48
+ '.jsx': 'javascript',
49
+ '.mjs': 'javascript',
50
+ '.cjs': 'javascript',
51
+ '.py': 'python',
52
+ '.go': 'go',
53
+ '.rs': 'rust',
54
+ '.java': 'java',
55
+ '.cs': 'csharp',
56
+ '.php': 'php',
57
+ '.rb': 'ruby',
58
+ }
59
+
60
+ // =============================================================================
61
+ // Extraction Patterns
62
+ // =============================================================================
63
+
64
+ interface ExtractionPattern {
65
+ type: SignatureType
66
+ pattern: RegExp
67
+ nameIndex: number
68
+ signatureIndex?: number
69
+ exported?: boolean
70
+ }
71
+
72
+ /**
73
+ * TypeScript/JavaScript extraction patterns
74
+ */
75
+ const TS_PATTERNS: ExtractionPattern[] = [
76
+ // Exported function declarations
77
+ {
78
+ type: 'function',
79
+ pattern:
80
+ /^export\s+(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/gm,
81
+ nameIndex: 1,
82
+ exported: true,
83
+ },
84
+ // Exported const arrow functions
85
+ {
86
+ type: 'function',
87
+ pattern:
88
+ /^export\s+const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/gm,
89
+ nameIndex: 1,
90
+ exported: true,
91
+ },
92
+ // Regular function declarations
93
+ {
94
+ type: 'function',
95
+ pattern: /^(?:async\s+)?function\s+(\w+)\s*(<[^>]*>)?\s*\(([^)]*)\)\s*(?::\s*([^{;]+))?/gm,
96
+ nameIndex: 1,
97
+ },
98
+ // Const arrow functions
99
+ {
100
+ type: 'function',
101
+ pattern: /^const\s+(\w+)\s*(?::\s*[^=]+)?\s*=\s*(?:async\s+)?\([^)]*\)\s*(?::\s*[^=]+)?\s*=>/gm,
102
+ nameIndex: 1,
103
+ },
104
+ // Interface declarations
105
+ {
106
+ type: 'interface',
107
+ pattern: /^export\s+interface\s+(\w+)(?:<[^>]+>)?\s*(?:extends\s+[^{]+)?\s*\{/gm,
108
+ nameIndex: 1,
109
+ exported: true,
110
+ },
111
+ {
112
+ type: 'interface',
113
+ pattern: /^interface\s+(\w+)(?:<[^>]+>)?\s*(?:extends\s+[^{]+)?\s*\{/gm,
114
+ nameIndex: 1,
115
+ },
116
+ // Type aliases
117
+ {
118
+ type: 'type',
119
+ pattern: /^export\s+type\s+(\w+)(?:<[^>]+>)?\s*=/gm,
120
+ nameIndex: 1,
121
+ exported: true,
122
+ },
123
+ {
124
+ type: 'type',
125
+ pattern: /^type\s+(\w+)(?:<[^>]+>)?\s*=/gm,
126
+ nameIndex: 1,
127
+ },
128
+ // Class declarations
129
+ {
130
+ type: 'class',
131
+ pattern:
132
+ /^export\s+(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
133
+ nameIndex: 1,
134
+ exported: true,
135
+ },
136
+ {
137
+ type: 'class',
138
+ pattern:
139
+ /^(?:abstract\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
140
+ nameIndex: 1,
141
+ },
142
+ // Enum declarations
143
+ {
144
+ type: 'enum',
145
+ pattern: /^export\s+enum\s+(\w+)\s*\{/gm,
146
+ nameIndex: 1,
147
+ exported: true,
148
+ },
149
+ {
150
+ type: 'enum',
151
+ pattern: /^enum\s+(\w+)\s*\{/gm,
152
+ nameIndex: 1,
153
+ },
154
+ // Exported constants
155
+ {
156
+ type: 'const',
157
+ pattern: /^export\s+const\s+(\w+)\s*(?::\s*([^=]+))?\s*=/gm,
158
+ nameIndex: 1,
159
+ exported: true,
160
+ },
161
+ ]
162
+
163
+ /**
164
+ * Python extraction patterns
165
+ */
166
+ const PYTHON_PATTERNS: ExtractionPattern[] = [
167
+ // Function definitions
168
+ {
169
+ type: 'function',
170
+ pattern: /^def\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^:]+))?\s*:/gm,
171
+ nameIndex: 1,
172
+ },
173
+ // Async function definitions
174
+ {
175
+ type: 'function',
176
+ pattern: /^async\s+def\s+(\w+)\s*\(([^)]*)\)\s*(?:->\s*([^:]+))?\s*:/gm,
177
+ nameIndex: 1,
178
+ },
179
+ // Class definitions
180
+ {
181
+ type: 'class',
182
+ pattern: /^class\s+(\w+)(?:\(([^)]*)\))?\s*:/gm,
183
+ nameIndex: 1,
184
+ },
185
+ ]
186
+
187
+ /**
188
+ * Go extraction patterns
189
+ */
190
+ const GO_PATTERNS: ExtractionPattern[] = [
191
+ // Function declarations
192
+ {
193
+ type: 'function',
194
+ pattern: /^func\s+(\w+)\s*\(([^)]*)\)\s*(?:\(([^)]*)\)|([^\s{]+))?\s*\{/gm,
195
+ nameIndex: 1,
196
+ },
197
+ // Method declarations
198
+ {
199
+ type: 'method',
200
+ pattern: /^func\s+\([^)]+\)\s+(\w+)\s*\(([^)]*)\)\s*(?:\(([^)]*)\)|([^\s{]+))?\s*\{/gm,
201
+ nameIndex: 1,
202
+ },
203
+ // Type definitions
204
+ {
205
+ type: 'type',
206
+ pattern: /^type\s+(\w+)\s+(?:struct|interface)\s*\{/gm,
207
+ nameIndex: 1,
208
+ },
209
+ ]
210
+
211
+ /**
212
+ * Rust extraction patterns
213
+ */
214
+ const RUST_PATTERNS: ExtractionPattern[] = [
215
+ // Public function declarations
216
+ {
217
+ type: 'function',
218
+ pattern: /^pub\s+(?:async\s+)?fn\s+(\w+)(?:<[^>]+>)?\s*\(([^)]*)\)\s*(?:->\s*([^{]+))?\s*\{/gm,
219
+ nameIndex: 1,
220
+ exported: true,
221
+ },
222
+ // Private function declarations
223
+ {
224
+ type: 'function',
225
+ pattern: /^(?:async\s+)?fn\s+(\w+)(?:<[^>]+>)?\s*\(([^)]*)\)\s*(?:->\s*([^{]+))?\s*\{/gm,
226
+ nameIndex: 1,
227
+ },
228
+ // Struct definitions
229
+ {
230
+ type: 'class',
231
+ pattern: /^pub\s+struct\s+(\w+)(?:<[^>]+>)?\s*(?:\{|;)/gm,
232
+ nameIndex: 1,
233
+ exported: true,
234
+ },
235
+ {
236
+ type: 'class',
237
+ pattern: /^struct\s+(\w+)(?:<[^>]+>)?\s*(?:\{|;)/gm,
238
+ nameIndex: 1,
239
+ },
240
+ // Trait definitions
241
+ {
242
+ type: 'interface',
243
+ pattern: /^pub\s+trait\s+(\w+)(?:<[^>]+>)?\s*(?:\{|:)/gm,
244
+ nameIndex: 1,
245
+ exported: true,
246
+ },
247
+ {
248
+ type: 'interface',
249
+ pattern: /^trait\s+(\w+)(?:<[^>]+>)?\s*(?:\{|:)/gm,
250
+ nameIndex: 1,
251
+ },
252
+ // Enum definitions
253
+ {
254
+ type: 'enum',
255
+ pattern: /^pub\s+enum\s+(\w+)(?:<[^>]+>)?\s*\{/gm,
256
+ nameIndex: 1,
257
+ exported: true,
258
+ },
259
+ {
260
+ type: 'enum',
261
+ pattern: /^enum\s+(\w+)(?:<[^>]+>)?\s*\{/gm,
262
+ nameIndex: 1,
263
+ },
264
+ ]
265
+
266
+ /**
267
+ * Java extraction patterns
268
+ */
269
+ const JAVA_PATTERNS: ExtractionPattern[] = [
270
+ // Class declarations
271
+ {
272
+ type: 'class',
273
+ pattern:
274
+ /^(?:public\s+)?(?:abstract\s+)?(?:final\s+)?class\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+\w+)?(?:\s+implements\s+[^{]+)?\s*\{/gm,
275
+ nameIndex: 1,
276
+ exported: true,
277
+ },
278
+ // Interface declarations
279
+ {
280
+ type: 'interface',
281
+ pattern: /^(?:public\s+)?interface\s+(\w+)(?:<[^>]+>)?(?:\s+extends\s+[^{]+)?\s*\{/gm,
282
+ nameIndex: 1,
283
+ exported: true,
284
+ },
285
+ // Method declarations
286
+ {
287
+ type: 'method',
288
+ pattern:
289
+ /^\s+(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?(?:synchronized\s+)?(?:<[^>]+>\s+)?(\w+(?:<[^>]+>)?)\s+(\w+)\s*\([^)]*\)\s*(?:throws\s+[^{]+)?\s*\{/gm,
290
+ nameIndex: 2,
291
+ },
292
+ ]
293
+
294
+ /**
295
+ * Language to patterns mapping
296
+ */
297
+ const LANGUAGE_PATTERNS: Record<LanguageId, ExtractionPattern[]> = {
298
+ typescript: TS_PATTERNS,
299
+ javascript: TS_PATTERNS,
300
+ python: PYTHON_PATTERNS,
301
+ go: GO_PATTERNS,
302
+ rust: RUST_PATTERNS,
303
+ java: JAVA_PATTERNS,
304
+ csharp: JAVA_PATTERNS, // Similar enough for basic extraction
305
+ php: [], // Fallback to full file
306
+ ruby: [], // Fallback to full file
307
+ unknown: [],
308
+ }
309
+
310
+ // =============================================================================
311
+ // Main Function
312
+ // =============================================================================
313
+
314
+ /**
315
+ * Extract code signatures from a file
316
+ *
317
+ * @param filePath - Path to the file (absolute or relative to cwd)
318
+ * @param projectPath - Project root path (for resolving relative paths)
319
+ * @returns Extracted signatures with compression metrics
320
+ */
321
+ export async function extractSignatures(
322
+ filePath: string,
323
+ projectPath: string = process.cwd()
324
+ ): Promise<SignaturesToolOutput> {
325
+ // Resolve to absolute path
326
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
327
+
328
+ // Read file content
329
+ let content: string
330
+ try {
331
+ content = await fs.readFile(absolutePath, 'utf-8')
332
+ } catch (error) {
333
+ if (isNotFoundError(error)) {
334
+ return {
335
+ file: filePath,
336
+ language: 'unknown',
337
+ signatures: [],
338
+ fallback: true,
339
+ fallbackReason: 'File not found',
340
+ metrics: noCompression(''),
341
+ }
342
+ }
343
+ throw error
344
+ }
345
+
346
+ // Detect language
347
+ const ext = path.extname(filePath).toLowerCase()
348
+ const language = EXTENSION_TO_LANGUAGE[ext] || 'unknown'
349
+ const patterns = LANGUAGE_PATTERNS[language]
350
+
351
+ // No patterns = fallback to full file
352
+ if (!patterns || patterns.length === 0) {
353
+ return {
354
+ file: filePath,
355
+ language,
356
+ signatures: [],
357
+ fallback: true,
358
+ fallbackReason: `No extraction patterns for ${language}`,
359
+ metrics: noCompression(content),
360
+ }
361
+ }
362
+
363
+ // Extract signatures
364
+ const signatures = extractFromContent(content, patterns)
365
+
366
+ // Build filtered output (just signatures)
367
+ const filteredContent = signatures
368
+ .map((sig) => {
369
+ const exportPrefix = sig.exported ? 'export ' : ''
370
+ return `${exportPrefix}${sig.type} ${sig.name}: ${sig.signature}`
371
+ })
372
+ .join('\n')
373
+
374
+ return {
375
+ file: filePath,
376
+ language,
377
+ signatures,
378
+ fallback: false,
379
+ metrics: measureCompression(content, filteredContent),
380
+ }
381
+ }
382
+
383
+ /**
384
+ * Extract signatures from multiple files in a directory
385
+ */
386
+ export async function extractDirectorySignatures(
387
+ dirPath: string,
388
+ projectPath: string = process.cwd(),
389
+ options: { recursive?: boolean } = {}
390
+ ): Promise<SignaturesToolOutput[]> {
391
+ const absolutePath = path.isAbsolute(dirPath) ? dirPath : path.join(projectPath, dirPath)
392
+
393
+ const results: SignaturesToolOutput[] = []
394
+
395
+ async function processDir(dir: string): Promise<void> {
396
+ const entries = await fs.readdir(dir, { withFileTypes: true })
397
+
398
+ for (const entry of entries) {
399
+ const fullPath = path.join(dir, entry.name)
400
+ const relativePath = path.relative(projectPath, fullPath)
401
+
402
+ if (entry.isDirectory()) {
403
+ // Skip common ignore patterns
404
+ if (entry.name === 'node_modules' || entry.name === '.git' || entry.name.startsWith('.')) {
405
+ continue
406
+ }
407
+ if (options.recursive) {
408
+ await processDir(fullPath)
409
+ }
410
+ } else if (entry.isFile()) {
411
+ const ext = path.extname(entry.name).toLowerCase()
412
+ if (EXTENSION_TO_LANGUAGE[ext]) {
413
+ const result = await extractSignatures(relativePath, projectPath)
414
+ results.push(result)
415
+ }
416
+ }
417
+ }
418
+ }
419
+
420
+ await processDir(absolutePath)
421
+ return results
422
+ }
423
+
424
+ // =============================================================================
425
+ // Helper Functions
426
+ // =============================================================================
427
+
428
+ /**
429
+ * Extract signatures from content using patterns
430
+ */
431
+ function extractFromContent(content: string, patterns: ExtractionPattern[]): CodeSignature[] {
432
+ const signatures: CodeSignature[] = []
433
+ const lines = content.split('\n')
434
+
435
+ // Track what we've extracted to avoid duplicates
436
+ const seen = new Set<string>()
437
+
438
+ for (const patternDef of patterns) {
439
+ // Reset lastIndex for global regex
440
+ patternDef.pattern.lastIndex = 0
441
+
442
+ let match
443
+ while ((match = patternDef.pattern.exec(content)) !== null) {
444
+ const name = match[patternDef.nameIndex]
445
+ if (!name) continue
446
+
447
+ // Create a key for deduplication
448
+ const key = `${patternDef.type}:${name}`
449
+ if (seen.has(key)) continue
450
+ seen.add(key)
451
+
452
+ // Get line number
453
+ const matchIndex = match.index
454
+ const lineNumber = content.substring(0, matchIndex).split('\n').length
455
+
456
+ // Extract the full signature line
457
+ const signatureLine = match[0].trim()
458
+
459
+ // Try to extract docstring (line before the signature)
460
+ let docstring: string | undefined
461
+ if (lineNumber > 1) {
462
+ const prevLine = lines[lineNumber - 2]?.trim()
463
+ if (
464
+ prevLine?.startsWith('/**') ||
465
+ prevLine?.startsWith('///') ||
466
+ prevLine?.startsWith('#')
467
+ ) {
468
+ docstring = prevLine
469
+ }
470
+ }
471
+
472
+ signatures.push({
473
+ type: patternDef.type,
474
+ name,
475
+ signature: cleanSignature(signatureLine),
476
+ exported: patternDef.exported || false,
477
+ line: lineNumber,
478
+ docstring,
479
+ })
480
+ }
481
+ }
482
+
483
+ // Sort by line number
484
+ return signatures.sort((a, b) => a.line - b.line)
485
+ }
486
+
487
+ /**
488
+ * Clean up a signature line for display
489
+ */
490
+ function cleanSignature(signature: string): string {
491
+ return signature
492
+ .replace(/\{$/, '') // Remove trailing {
493
+ .replace(/\s+/g, ' ') // Normalize whitespace
494
+ .trim()
495
+ }
496
+
497
+ // =============================================================================
498
+ // Exports
499
+ // =============================================================================
500
+
501
+ export default { extractSignatures, extractDirectorySignatures }