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,403 @@
1
+ /**
2
+ * Imports Tool - Build import/dependency graphs
3
+ *
4
+ * Analyzes:
5
+ * - What a file imports
6
+ * - What files import this file (reverse lookup)
7
+ * - Dependency tree to a given depth
8
+ *
9
+ * @module context-tools/imports-tool
10
+ * @version 1.0.0
11
+ */
12
+
13
+ import { exec as execCallback } from 'node:child_process'
14
+ import fs from 'node:fs/promises'
15
+ import path from 'node:path'
16
+ import { promisify } from 'node:util'
17
+ import { isNotFoundError } from '../types/fs'
18
+ import type { DependencyNode, ImportedBy, ImportRelation, ImportsToolOutput } from './types'
19
+
20
+ const exec = promisify(execCallback)
21
+
22
+ // =============================================================================
23
+ // Import Patterns by Language
24
+ // =============================================================================
25
+
26
+ interface ImportPattern {
27
+ pattern: RegExp
28
+ sourceIndex: number
29
+ namesIndex?: number
30
+ isDefault?: boolean
31
+ isNamespace?: boolean
32
+ }
33
+
34
+ /**
35
+ * TypeScript/JavaScript import patterns
36
+ */
37
+ const TS_IMPORT_PATTERNS: ImportPattern[] = [
38
+ // import { x, y } from 'module'
39
+ {
40
+ pattern: /import\s*\{([^}]+)\}\s*from\s*['"]([^'"]+)['"]/g,
41
+ sourceIndex: 2,
42
+ namesIndex: 1,
43
+ },
44
+ // import x from 'module'
45
+ {
46
+ pattern: /import\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
47
+ sourceIndex: 2,
48
+ namesIndex: 1,
49
+ isDefault: true,
50
+ },
51
+ // import * as x from 'module'
52
+ {
53
+ pattern: /import\s*\*\s*as\s+(\w+)\s+from\s*['"]([^'"]+)['"]/g,
54
+ sourceIndex: 2,
55
+ namesIndex: 1,
56
+ isNamespace: true,
57
+ },
58
+ // import 'module' (side-effect)
59
+ {
60
+ pattern: /import\s*['"]([^'"]+)['"]/g,
61
+ sourceIndex: 1,
62
+ },
63
+ // require('module')
64
+ {
65
+ pattern: /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
66
+ sourceIndex: 1,
67
+ },
68
+ // Dynamic import
69
+ {
70
+ pattern: /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
71
+ sourceIndex: 1,
72
+ },
73
+ ]
74
+
75
+ /**
76
+ * Python import patterns
77
+ */
78
+ const PYTHON_IMPORT_PATTERNS: ImportPattern[] = [
79
+ // from module import x, y
80
+ {
81
+ pattern: /from\s+([\w.]+)\s+import\s+([^;\n]+)/g,
82
+ sourceIndex: 1,
83
+ namesIndex: 2,
84
+ },
85
+ // import module
86
+ {
87
+ pattern: /^import\s+([\w.]+)(?:\s+as\s+\w+)?$/gm,
88
+ sourceIndex: 1,
89
+ },
90
+ ]
91
+
92
+ /**
93
+ * Go import patterns
94
+ */
95
+ const GO_IMPORT_PATTERNS: ImportPattern[] = [
96
+ // import "module"
97
+ {
98
+ pattern: /import\s*"([^"]+)"/g,
99
+ sourceIndex: 1,
100
+ },
101
+ // import ( "module1" "module2" )
102
+ {
103
+ pattern: /import\s*\([^)]*"([^"]+)"[^)]*\)/g,
104
+ sourceIndex: 1,
105
+ },
106
+ ]
107
+
108
+ /**
109
+ * Language to import patterns mapping
110
+ */
111
+ const IMPORT_PATTERNS: Record<string, ImportPattern[]> = {
112
+ typescript: TS_IMPORT_PATTERNS,
113
+ javascript: TS_IMPORT_PATTERNS,
114
+ python: PYTHON_IMPORT_PATTERNS,
115
+ go: GO_IMPORT_PATTERNS,
116
+ }
117
+
118
+ /**
119
+ * Extension to language mapping
120
+ */
121
+ const EXT_TO_LANG: Record<string, string> = {
122
+ '.ts': 'typescript',
123
+ '.tsx': 'typescript',
124
+ '.js': 'javascript',
125
+ '.jsx': 'javascript',
126
+ '.mjs': 'javascript',
127
+ '.cjs': 'javascript',
128
+ '.py': 'python',
129
+ '.go': 'go',
130
+ }
131
+
132
+ // =============================================================================
133
+ // Main Functions
134
+ // =============================================================================
135
+
136
+ /**
137
+ * Analyze imports for a file
138
+ *
139
+ * @param filePath - Path to the file
140
+ * @param projectPath - Project root path
141
+ * @param options - Analysis options
142
+ * @returns Import analysis with metrics
143
+ */
144
+ export async function analyzeImports(
145
+ filePath: string,
146
+ projectPath: string = process.cwd(),
147
+ options: {
148
+ reverse?: boolean // Include files that import this
149
+ depth?: number // Dependency tree depth (0 = no tree)
150
+ } = {}
151
+ ): Promise<ImportsToolOutput> {
152
+ const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectPath, filePath)
153
+
154
+ // Read file content
155
+ let content: string
156
+ try {
157
+ content = await fs.readFile(absolutePath, 'utf-8')
158
+ } catch (error) {
159
+ if (isNotFoundError(error)) {
160
+ return {
161
+ file: filePath,
162
+ imports: [],
163
+ importedBy: [],
164
+ metrics: {
165
+ totalImports: 0,
166
+ externalImports: 0,
167
+ internalImports: 0,
168
+ importedByCount: 0,
169
+ },
170
+ }
171
+ }
172
+ throw error
173
+ }
174
+
175
+ // Detect language
176
+ const ext = path.extname(filePath).toLowerCase()
177
+ const language = EXT_TO_LANG[ext] || 'unknown'
178
+ const patterns = IMPORT_PATTERNS[language] || []
179
+
180
+ // Extract imports
181
+ const imports = extractImports(content, patterns, absolutePath, projectPath)
182
+
183
+ // Get reverse imports if requested
184
+ let importedBy: ImportedBy[] = []
185
+ if (options.reverse) {
186
+ importedBy = await findImportedBy(filePath, projectPath)
187
+ }
188
+
189
+ // Build dependency tree if requested
190
+ let dependencyTree: DependencyNode | undefined
191
+ if (options.depth && options.depth > 0) {
192
+ dependencyTree = await buildDependencyTree(filePath, projectPath, options.depth)
193
+ }
194
+
195
+ // Calculate metrics
196
+ const externalImports = imports.filter((i) => i.isExternal).length
197
+ const internalImports = imports.filter((i) => !i.isExternal).length
198
+
199
+ return {
200
+ file: filePath,
201
+ imports,
202
+ importedBy,
203
+ dependencyTree,
204
+ metrics: {
205
+ totalImports: imports.length,
206
+ externalImports,
207
+ internalImports,
208
+ importedByCount: importedBy.length,
209
+ },
210
+ }
211
+ }
212
+
213
+ // =============================================================================
214
+ // Helper Functions
215
+ // =============================================================================
216
+
217
+ /**
218
+ * Extract imports from file content
219
+ */
220
+ function extractImports(
221
+ content: string,
222
+ patterns: ImportPattern[],
223
+ absolutePath: string,
224
+ projectPath: string
225
+ ): ImportRelation[] {
226
+ const imports: ImportRelation[] = []
227
+ const seen = new Set<string>()
228
+
229
+ for (const patternDef of patterns) {
230
+ patternDef.pattern.lastIndex = 0
231
+
232
+ let match
233
+ while ((match = patternDef.pattern.exec(content)) !== null) {
234
+ const source = match[patternDef.sourceIndex]
235
+ if (!source || seen.has(source)) continue
236
+ seen.add(source)
237
+
238
+ // Parse imported names
239
+ let importedNames: string[] | undefined
240
+ if (patternDef.namesIndex !== undefined) {
241
+ const namesStr = match[patternDef.namesIndex]
242
+ if (namesStr) {
243
+ importedNames = namesStr
244
+ .split(',')
245
+ .map((n) => n.trim().split(' as ')[0].trim())
246
+ .filter(Boolean)
247
+ }
248
+ }
249
+
250
+ // Determine if external
251
+ const isExternal =
252
+ !source.startsWith('.') && !source.startsWith('/') && !source.startsWith('@/')
253
+
254
+ // Resolve internal imports
255
+ let resolved: string | null = null
256
+ if (!isExternal) {
257
+ resolved = resolveImport(source, absolutePath, projectPath)
258
+ }
259
+
260
+ imports.push({
261
+ source,
262
+ resolved,
263
+ isExternal,
264
+ importedNames,
265
+ isDefault: patternDef.isDefault,
266
+ isNamespace: patternDef.isNamespace,
267
+ })
268
+ }
269
+ }
270
+
271
+ return imports
272
+ }
273
+
274
+ /**
275
+ * Resolve a relative import to an absolute path
276
+ */
277
+ function resolveImport(source: string, fromFile: string, projectPath: string): string | null {
278
+ const fileDir = path.dirname(fromFile)
279
+
280
+ // Handle path alias like @/
281
+ if (source.startsWith('@/')) {
282
+ const aliasPath = path.join(projectPath, 'src', source.slice(2))
283
+ return tryResolve(aliasPath, projectPath)
284
+ }
285
+
286
+ // Regular relative import
287
+ const resolved = path.resolve(fileDir, source)
288
+ return tryResolve(resolved, projectPath)
289
+ }
290
+
291
+ /**
292
+ * Try to resolve a path, adding extensions if needed
293
+ */
294
+ function tryResolve(basePath: string, projectPath: string): string | null {
295
+ const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.js']
296
+
297
+ for (const ext of extensions) {
298
+ const fullPath = basePath + ext
299
+ try {
300
+ // Check synchronously (we're in a hot path)
301
+ const fs = require('node:fs')
302
+ if (fs.existsSync(fullPath) && fs.statSync(fullPath).isFile()) {
303
+ return path.relative(projectPath, fullPath)
304
+ }
305
+ } catch {}
306
+ }
307
+
308
+ return null
309
+ }
310
+
311
+ /**
312
+ * Find all files that import the target file
313
+ */
314
+ async function findImportedBy(filePath: string, projectPath: string): Promise<ImportedBy[]> {
315
+ const importedBy: ImportedBy[] = []
316
+
317
+ // Get the base name without extension for matching
318
+ const baseName = path.basename(filePath, path.extname(filePath))
319
+ const _dirName = path.dirname(filePath)
320
+
321
+ try {
322
+ // Use ripgrep if available, otherwise grep
323
+ const searchPatterns = [
324
+ `from ['"].*${baseName}['"]`,
325
+ `from ['"]\\./${baseName}['"]`,
326
+ `import\\(['"'].*${baseName}['"]`,
327
+ `require\\(['"'].*${baseName}['"]`,
328
+ ]
329
+
330
+ const pattern = searchPatterns.join('|')
331
+
332
+ const { stdout } = await exec(
333
+ `grep -r -l -E '${pattern}' --include='*.ts' --include='*.tsx' --include='*.js' --include='*.jsx' . 2>/dev/null || true`,
334
+ { cwd: projectPath, maxBuffer: 10 * 1024 * 1024 }
335
+ )
336
+
337
+ const files = stdout
338
+ .trim()
339
+ .split('\n')
340
+ .filter(Boolean)
341
+ .map((f) => f.replace(/^\.\//, ''))
342
+ .filter((f) => f !== filePath) // Exclude self
343
+
344
+ for (const file of files) {
345
+ importedBy.push({ file })
346
+ }
347
+ } catch (_error) {
348
+ // grep not available or error - return empty
349
+ }
350
+
351
+ return importedBy
352
+ }
353
+
354
+ /**
355
+ * Build a dependency tree to a given depth
356
+ */
357
+ async function buildDependencyTree(
358
+ filePath: string,
359
+ projectPath: string,
360
+ maxDepth: number,
361
+ currentDepth: number = 0,
362
+ visited: Set<string> = new Set()
363
+ ): Promise<DependencyNode> {
364
+ const node: DependencyNode = {
365
+ file: filePath,
366
+ imports: [],
367
+ depth: currentDepth,
368
+ }
369
+
370
+ if (currentDepth >= maxDepth || visited.has(filePath)) {
371
+ return node
372
+ }
373
+
374
+ visited.add(filePath)
375
+
376
+ // Get imports for this file
377
+ const analysis = await analyzeImports(filePath, projectPath, {
378
+ reverse: false,
379
+ depth: 0,
380
+ })
381
+
382
+ // Recursively build tree for internal imports
383
+ for (const imp of analysis.imports) {
384
+ if (!imp.isExternal && imp.resolved) {
385
+ const childNode = await buildDependencyTree(
386
+ imp.resolved,
387
+ projectPath,
388
+ maxDepth,
389
+ currentDepth + 1,
390
+ visited
391
+ )
392
+ node.imports.push(childNode)
393
+ }
394
+ }
395
+
396
+ return node
397
+ }
398
+
399
+ // =============================================================================
400
+ // Exports
401
+ // =============================================================================
402
+
403
+ export default { analyzeImports }