prjct-cli 0.45.0 → 0.45.4

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 +82 -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
@@ -0,0 +1,420 @@
1
+ /**
2
+ * ContextSelector - Task-based context selection for Smart Context
3
+ *
4
+ * When user runs `p. task "add payment webhook"`:
5
+ * 1. Detects relevant domains from task description
6
+ * 2. Filters files to only those in matching domains
7
+ * 3. Returns precise, focused context
8
+ *
9
+ * Benefits:
10
+ * - 70-90% reduction in context tokens
11
+ * - More relevant responses from LLM
12
+ * - Faster task understanding
13
+ */
14
+
15
+ import { type DomainDefinition, indexStorage, type ScoredFile } from '../storage/index-storage'
16
+
17
+ // ============================================================================
18
+ // TYPES
19
+ // ============================================================================
20
+
21
+ export interface SelectedContext {
22
+ files: ScoredFile[]
23
+ domains: string[]
24
+ metrics: {
25
+ totalFiles: number
26
+ selectedFiles: number
27
+ compressionRate: number
28
+ estimatedTokensSaved: number
29
+ }
30
+ }
31
+
32
+ export interface ContextSelectionOptions {
33
+ maxFiles?: number // Max files to return (default: 50)
34
+ minScore?: number // Min relevance score (default: 30)
35
+ includeGeneral?: boolean // Include 'general' domain files (default: true)
36
+ tokenBudget?: number // Max estimated tokens (default: 50000)
37
+ }
38
+
39
+ // ============================================================================
40
+ // DOMAIN DETECTION PATTERNS
41
+ // ============================================================================
42
+
43
+ /**
44
+ * Keywords that indicate specific domains in task descriptions
45
+ */
46
+ const DOMAIN_KEYWORDS: Record<string, string[]> = {
47
+ payments: [
48
+ 'payment',
49
+ 'pay',
50
+ 'stripe',
51
+ 'billing',
52
+ 'checkout',
53
+ 'invoice',
54
+ 'subscription',
55
+ 'charge',
56
+ 'refund',
57
+ 'transaction',
58
+ 'pricing',
59
+ 'price',
60
+ ],
61
+ auth: [
62
+ 'auth',
63
+ 'login',
64
+ 'logout',
65
+ 'signup',
66
+ 'sign up',
67
+ 'sign in',
68
+ 'register',
69
+ 'password',
70
+ 'session',
71
+ 'token',
72
+ 'jwt',
73
+ 'oauth',
74
+ 'sso',
75
+ 'permission',
76
+ 'role',
77
+ 'access',
78
+ 'user',
79
+ ],
80
+ api: [
81
+ 'api',
82
+ 'endpoint',
83
+ 'route',
84
+ 'rest',
85
+ 'graphql',
86
+ 'webhook',
87
+ 'request',
88
+ 'response',
89
+ 'http',
90
+ 'fetch',
91
+ 'axios',
92
+ ],
93
+ database: [
94
+ 'database',
95
+ 'db',
96
+ 'model',
97
+ 'schema',
98
+ 'migration',
99
+ 'query',
100
+ 'sql',
101
+ 'prisma',
102
+ 'drizzle',
103
+ 'mongoose',
104
+ 'sequelize',
105
+ 'typeorm',
106
+ ],
107
+ frontend: [
108
+ 'component',
109
+ 'page',
110
+ 'view',
111
+ 'ui',
112
+ 'button',
113
+ 'form',
114
+ 'modal',
115
+ 'layout',
116
+ 'style',
117
+ 'css',
118
+ 'react',
119
+ 'vue',
120
+ 'svelte',
121
+ 'html',
122
+ ],
123
+ testing: [
124
+ 'test',
125
+ 'spec',
126
+ 'unit',
127
+ 'e2e',
128
+ 'cypress',
129
+ 'jest',
130
+ 'vitest',
131
+ 'mocha',
132
+ 'coverage',
133
+ 'mock',
134
+ ],
135
+ integrations: [
136
+ 'integration',
137
+ 'integrate',
138
+ 'connect',
139
+ 'sync',
140
+ 'webhook',
141
+ 'oauth',
142
+ 'linear',
143
+ 'jira',
144
+ 'github',
145
+ 'slack',
146
+ 'discord',
147
+ ],
148
+ config: ['config', 'configuration', 'setting', 'env', 'environment', 'setup'],
149
+ utilities: ['util', 'utility', 'helper', 'lib', 'common', 'shared', 'tool'],
150
+ services: ['service', 'handler', 'processor', 'worker', 'job', 'queue', 'cron'],
151
+ types: ['type', 'interface', 'dto', 'schema', 'definition'],
152
+ }
153
+
154
+ // ============================================================================
155
+ // CONTEXT SELECTOR CLASS
156
+ // ============================================================================
157
+
158
+ export class ContextSelector {
159
+ private readonly CHARS_PER_TOKEN = 4
160
+
161
+ // ==========================================================================
162
+ // MAIN METHODS
163
+ // ==========================================================================
164
+
165
+ /**
166
+ * Select relevant files for a task description
167
+ */
168
+ async selectForTask(
169
+ taskDescription: string,
170
+ projectId: string,
171
+ options: ContextSelectionOptions = {}
172
+ ): Promise<SelectedContext> {
173
+ const maxFiles = options.maxFiles || 50
174
+ const minScore = options.minScore || 30
175
+ const includeGeneral = options.includeGeneral !== false
176
+ const tokenBudget = options.tokenBudget || 50000
177
+
178
+ // Load index and categories
179
+ const [index, domainsData, categoriesCache] = await Promise.all([
180
+ indexStorage.readIndex(projectId),
181
+ indexStorage.readDomains(projectId),
182
+ indexStorage.readCategories(projectId),
183
+ ])
184
+
185
+ if (!index || !domainsData || !categoriesCache) {
186
+ // No categorization data available, return all relevant files
187
+ return this.fallbackSelection(index?.relevantFiles || [], options)
188
+ }
189
+
190
+ // Detect domains from task description
191
+ const detectedDomains = this.detectTaskDomains(taskDescription, domainsData.domains)
192
+
193
+ // Get files for detected domains
194
+ const selectedPaths = new Set<string>()
195
+
196
+ for (const domain of detectedDomains) {
197
+ const domainFiles = categoriesCache.domainIndex[domain] || []
198
+ for (const filePath of domainFiles) {
199
+ selectedPaths.add(filePath)
200
+ }
201
+ }
202
+
203
+ // Optionally include 'general' files
204
+ if (includeGeneral && categoriesCache.domainIndex.general) {
205
+ // Add top general files by score
206
+ const generalFiles = categoriesCache.domainIndex.general.slice(0, 10)
207
+ for (const filePath of generalFiles) {
208
+ selectedPaths.add(filePath)
209
+ }
210
+ }
211
+
212
+ // Filter to only files in the index with score >= minScore
213
+ const selectedFiles = index.relevantFiles.filter(
214
+ (f) => selectedPaths.has(f.path) && f.score >= minScore
215
+ )
216
+
217
+ // Sort by score and limit
218
+ selectedFiles.sort((a, b) => b.score - a.score)
219
+
220
+ // Apply token budget
221
+ let estimatedTokens = 0
222
+ const budgetedFiles: ScoredFile[] = []
223
+
224
+ for (const file of selectedFiles) {
225
+ const fileTokens = Math.ceil(file.size / this.CHARS_PER_TOKEN)
226
+ if (estimatedTokens + fileTokens > tokenBudget) {
227
+ break
228
+ }
229
+ if (budgetedFiles.length >= maxFiles) {
230
+ break
231
+ }
232
+ budgetedFiles.push(file)
233
+ estimatedTokens += fileTokens
234
+ }
235
+
236
+ // Calculate metrics
237
+ const totalTokens = Math.ceil(
238
+ index.relevantFiles.reduce((sum, f) => sum + f.size, 0) / this.CHARS_PER_TOKEN
239
+ )
240
+ const compressionRate = totalTokens > 0 ? (totalTokens - estimatedTokens) / totalTokens : 0
241
+
242
+ return {
243
+ files: budgetedFiles,
244
+ domains: detectedDomains,
245
+ metrics: {
246
+ totalFiles: index.relevantFiles.length,
247
+ selectedFiles: budgetedFiles.length,
248
+ compressionRate,
249
+ estimatedTokensSaved: totalTokens - estimatedTokens,
250
+ },
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Detect domains from task description
256
+ */
257
+ detectTaskDomains(description: string, projectDomains: DomainDefinition[]): string[] {
258
+ const normalizedDesc = description.toLowerCase()
259
+ const detectedDomains = new Set<string>()
260
+
261
+ // Check against keyword patterns
262
+ for (const [domain, keywords] of Object.entries(DOMAIN_KEYWORDS)) {
263
+ for (const keyword of keywords) {
264
+ if (normalizedDesc.includes(keyword)) {
265
+ detectedDomains.add(domain)
266
+ break
267
+ }
268
+ }
269
+ }
270
+
271
+ // Check against project-specific domains
272
+ for (const domain of projectDomains) {
273
+ // Check domain name
274
+ if (normalizedDesc.includes(domain.name.toLowerCase())) {
275
+ detectedDomains.add(domain.name)
276
+ continue
277
+ }
278
+
279
+ // Check domain keywords
280
+ for (const keyword of domain.keywords) {
281
+ if (normalizedDesc.includes(keyword.toLowerCase())) {
282
+ detectedDomains.add(domain.name)
283
+ break
284
+ }
285
+ }
286
+ }
287
+
288
+ // If no domains detected, return common ones based on task type
289
+ if (detectedDomains.size === 0) {
290
+ // Default to common development domains
291
+ detectedDomains.add('services')
292
+ detectedDomains.add('api')
293
+ }
294
+
295
+ return Array.from(detectedDomains)
296
+ }
297
+
298
+ /**
299
+ * Filter files by specific domains
300
+ */
301
+ async filterByDomains(
302
+ projectId: string,
303
+ domains: string[],
304
+ files?: ScoredFile[]
305
+ ): Promise<ScoredFile[]> {
306
+ const categoriesCache = await indexStorage.readCategories(projectId)
307
+ if (!categoriesCache) {
308
+ return files || []
309
+ }
310
+
311
+ // Get all file paths for requested domains
312
+ const domainFilePaths = new Set<string>()
313
+ for (const domain of domains) {
314
+ const domainFiles = categoriesCache.domainIndex[domain] || []
315
+ for (const filePath of domainFiles) {
316
+ domainFilePaths.add(filePath)
317
+ }
318
+ }
319
+
320
+ // If files provided, filter them; otherwise use all categorized files
321
+ if (files) {
322
+ return files.filter((f) => domainFilePaths.has(f.path))
323
+ }
324
+
325
+ // Load index and filter
326
+ const index = await indexStorage.readIndex(projectId)
327
+ if (!index) {
328
+ return []
329
+ }
330
+
331
+ return index.relevantFiles.filter((f) => domainFilePaths.has(f.path))
332
+ }
333
+
334
+ /**
335
+ * Get domains for a specific file
336
+ */
337
+ async getFilesDomains(projectId: string, filePaths: string[]): Promise<Map<string, string[]>> {
338
+ return indexStorage.getFileCategories(projectId, filePaths)
339
+ }
340
+
341
+ // ==========================================================================
342
+ // HELPER METHODS
343
+ // ==========================================================================
344
+
345
+ /**
346
+ * Fallback selection when no categorization data available
347
+ */
348
+ private fallbackSelection(
349
+ files: ScoredFile[],
350
+ options: ContextSelectionOptions
351
+ ): SelectedContext {
352
+ const maxFiles = options.maxFiles || 50
353
+ const minScore = options.minScore || 30
354
+ const tokenBudget = options.tokenBudget || 50000
355
+
356
+ // Filter and sort by score
357
+ const filteredFiles = files.filter((f) => f.score >= minScore).sort((a, b) => b.score - a.score)
358
+
359
+ // Apply token budget
360
+ let estimatedTokens = 0
361
+ const selectedFiles: ScoredFile[] = []
362
+
363
+ for (const file of filteredFiles) {
364
+ const fileTokens = Math.ceil(file.size / this.CHARS_PER_TOKEN)
365
+ if (estimatedTokens + fileTokens > tokenBudget) {
366
+ break
367
+ }
368
+ if (selectedFiles.length >= maxFiles) {
369
+ break
370
+ }
371
+ selectedFiles.push(file)
372
+ estimatedTokens += fileTokens
373
+ }
374
+
375
+ const totalTokens = Math.ceil(files.reduce((sum, f) => sum + f.size, 0) / this.CHARS_PER_TOKEN)
376
+
377
+ return {
378
+ files: selectedFiles,
379
+ domains: [], // No domain data available
380
+ metrics: {
381
+ totalFiles: files.length,
382
+ selectedFiles: selectedFiles.length,
383
+ compressionRate: totalTokens > 0 ? (totalTokens - estimatedTokens) / totalTokens : 0,
384
+ estimatedTokensSaved: totalTokens - estimatedTokens,
385
+ },
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Suggest related domains based on detected ones
391
+ */
392
+ suggestRelatedDomains(domains: string[]): string[] {
393
+ const related = new Set<string>()
394
+
395
+ const relationships: Record<string, string[]> = {
396
+ payments: ['api', 'database', 'services'],
397
+ auth: ['api', 'database', 'users'],
398
+ api: ['services', 'types'],
399
+ database: ['types', 'services'],
400
+ frontend: ['types', 'utilities'],
401
+ testing: ['services', 'api'],
402
+ }
403
+
404
+ for (const domain of domains) {
405
+ const relatedDomains = relationships[domain]
406
+ if (relatedDomains) {
407
+ for (const r of relatedDomains) {
408
+ if (!domains.includes(r)) {
409
+ related.add(r)
410
+ }
411
+ }
412
+ }
413
+ }
414
+
415
+ return Array.from(related)
416
+ }
417
+ }
418
+
419
+ export const contextSelector = new ContextSelector()
420
+ export default ContextSelector