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
@@ -4,14 +4,14 @@
4
4
  * Handles project initialization detection, author management, and directory analysis.
5
5
  */
6
6
 
7
+ import { ProjectError } from '../errors'
8
+ import authorDetector from '../infrastructure/author-detector'
7
9
  import configManager from '../infrastructure/config-manager'
8
10
  import pathManager from '../infrastructure/path-manager'
9
- import authorDetector from '../infrastructure/author-detector'
11
+ import type { Author, CommandResult } from '../types'
12
+ import { isNotFoundError } from '../types/fs'
10
13
  import * as fileHelper from '../utils/file-helper'
11
14
  import out from '../utils/output'
12
- import { isNotFoundError } from '../types/fs'
13
- import type { Author, CommandResult } from '../types'
14
- import { ProjectError } from '../errors'
15
15
 
16
16
  export class ProjectService {
17
17
  private currentAuthor: Author | null = null
@@ -12,15 +12,14 @@
12
12
  * @version 1.0.0
13
13
  */
14
14
 
15
- import fs from 'fs/promises'
16
- import path from 'path'
17
- import os from 'os'
18
- import { promisify } from 'util'
19
- import { exec as execCallback } from 'child_process'
15
+ import { exec as execCallback } from 'node:child_process'
16
+ import fs from 'node:fs/promises'
17
+ import os from 'node:os'
18
+ import path from 'node:path'
19
+ import { promisify } from 'node:util'
20
20
  import { glob } from 'glob'
21
-
22
- import { skillLock } from './skill-lock'
23
21
  import type { SkillLockEntry } from './skill-lock'
22
+ import { skillLock } from './skill-lock'
24
23
 
25
24
  const exec = promisify(execCallback)
26
25
 
@@ -103,7 +102,9 @@ export function parseSource(source: string): ParsedSource {
103
102
  }
104
103
  }
105
104
 
106
- throw new Error(`Invalid source format: "${source}". Expected "owner/repo", "owner/repo@skill-name", or "./local-path"`)
105
+ throw new Error(
106
+ `Invalid source format: "${source}". Expected "owner/repo", "owner/repo@skill-name", or "./local-path"`
107
+ )
107
108
  }
108
109
 
109
110
  // =============================================================================
@@ -131,7 +132,7 @@ async function discoverSkills(dir: string): Promise<Array<{ name: string; filePa
131
132
  for (const filePath of subdirSkills) {
132
133
  const name = path.basename(path.dirname(filePath))
133
134
  // Avoid duplicate if already found as root
134
- if (!skills.some(s => s.name === name)) {
135
+ if (!skills.some((s) => s.name === name)) {
135
136
  skills.push({ name, filePath })
136
137
  }
137
138
  }
@@ -140,7 +141,7 @@ async function discoverSkills(dir: string): Promise<Array<{ name: string; filePa
140
141
  const nestedSkills = await glob('skills/*/SKILL.md', { cwd: dir, absolute: true })
141
142
  for (const filePath of nestedSkills) {
142
143
  const name = path.basename(path.dirname(filePath))
143
- if (!skills.some(s => s.name === name)) {
144
+ if (!skills.some((s) => s.name === name)) {
144
145
  skills.push({ name, filePath })
145
146
  }
146
147
  }
@@ -155,11 +156,7 @@ async function discoverSkills(dir: string): Promise<Array<{ name: string; filePa
155
156
  /**
156
157
  * Add _prjct source tracking metadata to a skill's frontmatter
157
158
  */
158
- function injectSourceMetadata(
159
- content: string,
160
- source: ParsedSource,
161
- sha?: string
162
- ): string {
159
+ function injectSourceMetadata(content: string, source: ParsedSource, sha?: string): string {
163
160
  const now = new Date().toISOString()
164
161
  const prjctBlock = [
165
162
  `_prjct:`,
@@ -181,7 +178,7 @@ function injectSourceMetadata(
181
178
  frontmatter = frontmatter.replace(/\n?_prjct:[\s\S]*?(?=\n[a-zA-Z]|\n---|\s*$)/g, '')
182
179
 
183
180
  // Append _prjct block
184
- const updatedFrontmatter = frontmatter.trimEnd() + '\n' + prjctBlock.join('\n')
181
+ const updatedFrontmatter = `${frontmatter.trimEnd()}\n${prjctBlock.join('\n')}`
185
182
  return content.replace(frontmatterRegex, `---\n${updatedFrontmatter}\n---`)
186
183
  }
187
184
 
@@ -264,7 +261,7 @@ async function installFromGitHub(source: ParsedSource): Promise<InstallResult> {
264
261
 
265
262
  // Filter to specific skill if requested
266
263
  const skillsToInstall = source.skillName
267
- ? discoveredSkills.filter(s => s.name === source.skillName)
264
+ ? discoveredSkills.filter((s) => s.name === source.skillName)
268
265
  : discoveredSkills
269
266
 
270
267
  if (source.skillName && skillsToInstall.length === 0) {
@@ -9,9 +9,9 @@
9
9
  * @version 1.0.0
10
10
  */
11
11
 
12
- import fs from 'fs/promises'
13
- import path from 'path'
14
- import os from 'os'
12
+ import fs from 'node:fs/promises'
13
+ import os from 'node:os'
14
+ import path from 'node:path'
15
15
 
16
16
  // =============================================================================
17
17
  // Types
@@ -16,11 +16,11 @@
16
16
  * @version 1.1.0
17
17
  */
18
18
 
19
- import fs from 'fs/promises'
20
- import path from 'path'
19
+ import fs from 'node:fs/promises'
20
+ import path from 'node:path'
21
21
  import { glob } from 'glob'
22
22
 
23
- import type { SkillMetadata, Skill, SkillSearchResult } from '../types'
23
+ import type { Skill, SkillMetadata, SkillSearchResult } from '../types'
24
24
  import type { AIProviderName } from '../types/provider'
25
25
 
26
26
  /**
@@ -46,7 +46,10 @@ function parseFrontmatter(content: string): { metadata: Record<string, unknown>;
46
46
 
47
47
  // Handle arrays [item1, item2]
48
48
  if (typeof value === 'string' && value.startsWith('[') && value.endsWith(']')) {
49
- value = value.slice(1, -1).split(',').map(s => s.trim().replace(/['"]/g, ''))
49
+ value = value
50
+ .slice(1, -1)
51
+ .split(',')
52
+ .map((s) => s.trim().replace(/['"]/g, ''))
50
53
  }
51
54
  // Remove quotes
52
55
  else if (typeof value === 'string' && (value.startsWith('"') || value.startsWith("'"))) {
@@ -83,7 +86,10 @@ class SkillService {
83
86
  /**
84
87
  * Get all skill directories in order of priority
85
88
  */
86
- private getSkillDirs(projectPath?: string, provider?: AIProviderName): Array<{ dir: string; source: Skill['source'] }> {
89
+ private getSkillDirs(
90
+ projectPath?: string,
91
+ provider?: AIProviderName
92
+ ): Array<{ dir: string; source: Skill['source'] }> {
87
93
  const homeDir = process.env.HOME || process.env.USERPROFILE || '~'
88
94
  const dirs: Array<{ dir: string; source: Skill['source'] }> = []
89
95
 
@@ -237,7 +243,7 @@ class SkillService {
237
243
  }
238
244
 
239
245
  // Tag match
240
- if (skill.metadata.tags?.some(t => t.toLowerCase().includes(queryLower))) {
246
+ if (skill.metadata.tags?.some((t) => t.toLowerCase().includes(queryLower))) {
241
247
  relevance += 3
242
248
  }
243
249
 
@@ -0,0 +1,245 @@
1
+ /**
2
+ * StackDetector - Detects project technology stack
3
+ *
4
+ * Analyzes the project to detect:
5
+ * - Frontend frameworks (React, Vue, Svelte, Angular)
6
+ * - Backend frameworks (Express, Fastify, Hono, etc.)
7
+ * - Database usage (Prisma, Mongoose, etc.)
8
+ * - Docker configuration
9
+ * - Testing frameworks
10
+ */
11
+
12
+ import fs from 'node:fs/promises'
13
+ import path from 'node:path'
14
+
15
+ // ============================================================================
16
+ // TYPES
17
+ // ============================================================================
18
+
19
+ export interface StackDetection {
20
+ hasFrontend: boolean
21
+ hasBackend: boolean
22
+ hasDatabase: boolean
23
+ hasDocker: boolean
24
+ hasTesting: boolean
25
+ frontendType: 'web' | 'mobile' | 'both' | null
26
+ frameworks: string[]
27
+ }
28
+
29
+ interface PackageJson {
30
+ dependencies?: Record<string, string>
31
+ devDependencies?: Record<string, string>
32
+ }
33
+
34
+ // ============================================================================
35
+ // STACK DETECTOR
36
+ // ============================================================================
37
+
38
+ export class StackDetector {
39
+ private projectPath: string
40
+
41
+ constructor(projectPath: string) {
42
+ this.projectPath = projectPath
43
+ }
44
+
45
+ /**
46
+ * Detect the full technology stack of the project
47
+ */
48
+ async detect(): Promise<StackDetection> {
49
+ const stack: StackDetection = {
50
+ hasFrontend: false,
51
+ hasBackend: false,
52
+ hasDatabase: false,
53
+ hasDocker: false,
54
+ hasTesting: false,
55
+ frontendType: null,
56
+ frameworks: [],
57
+ }
58
+
59
+ // Try to read package.json for JS/TS projects
60
+ const pkg = await this.readPackageJson()
61
+
62
+ if (pkg) {
63
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies }
64
+
65
+ // Detect each category
66
+ this.detectFrontend(deps, stack)
67
+ this.detectBackend(deps, stack)
68
+ this.detectDatabase(deps, stack)
69
+ this.detectTesting(deps, pkg, stack)
70
+ this.collectFrameworks(deps, stack)
71
+ }
72
+
73
+ // Docker detection (file-based)
74
+ stack.hasDocker = await this.detectDocker()
75
+
76
+ return stack
77
+ }
78
+
79
+ // ==========================================================================
80
+ // DETECTION METHODS
81
+ // ==========================================================================
82
+
83
+ /**
84
+ * Detect frontend frameworks and type (web/mobile/both)
85
+ */
86
+ private detectFrontend(deps: Record<string, string>, stack: StackDetection): void {
87
+ // Web frameworks
88
+ if (deps.react || deps.vue || deps.svelte || deps['@angular/core']) {
89
+ stack.hasFrontend = true
90
+ stack.frontendType = 'web'
91
+ }
92
+
93
+ // Mobile frameworks
94
+ if (deps['react-native'] || deps.expo) {
95
+ stack.hasFrontend = true
96
+ stack.frontendType = stack.frontendType === 'web' ? 'both' : 'mobile'
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Detect backend frameworks
102
+ */
103
+ private detectBackend(deps: Record<string, string>, stack: StackDetection): void {
104
+ const backendFrameworks = [
105
+ 'express',
106
+ 'fastify',
107
+ 'hono',
108
+ 'koa',
109
+ '@nestjs/core',
110
+ 'nest',
111
+ '@hapi/hapi',
112
+ 'restify',
113
+ 'polka',
114
+ ]
115
+
116
+ if (backendFrameworks.some((fw) => deps[fw])) {
117
+ stack.hasBackend = true
118
+ }
119
+ }
120
+
121
+ /**
122
+ * Detect database/ORM usage
123
+ */
124
+ private detectDatabase(deps: Record<string, string>, stack: StackDetection): void {
125
+ const databaseLibs = [
126
+ 'prisma',
127
+ '@prisma/client',
128
+ 'mongoose',
129
+ 'pg',
130
+ 'mysql2',
131
+ 'sequelize',
132
+ 'typeorm',
133
+ 'drizzle-orm',
134
+ 'knex',
135
+ 'better-sqlite3',
136
+ 'mongodb',
137
+ 'redis',
138
+ 'ioredis',
139
+ ]
140
+
141
+ if (databaseLibs.some((lib) => deps[lib])) {
142
+ stack.hasDatabase = true
143
+ }
144
+ }
145
+
146
+ /**
147
+ * Detect testing frameworks
148
+ */
149
+ private detectTesting(
150
+ deps: Record<string, string>,
151
+ pkg: PackageJson,
152
+ stack: StackDetection
153
+ ): void {
154
+ const testingFrameworks = [
155
+ 'jest',
156
+ 'vitest',
157
+ 'mocha',
158
+ '@testing-library/react',
159
+ '@testing-library/vue',
160
+ 'cypress',
161
+ 'playwright',
162
+ '@playwright/test',
163
+ 'ava',
164
+ 'tap',
165
+ 'bun-types', // Bun's built-in test runner
166
+ ]
167
+
168
+ if (testingFrameworks.some((fw) => deps[fw] || pkg.devDependencies?.[fw])) {
169
+ stack.hasTesting = true
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Detect Docker configuration
175
+ */
176
+ private async detectDocker(): Promise<boolean> {
177
+ const dockerFiles = ['Dockerfile', 'docker-compose.yml', 'docker-compose.yaml', '.dockerignore']
178
+
179
+ for (const file of dockerFiles) {
180
+ if (await this.fileExists(file)) {
181
+ return true
182
+ }
183
+ }
184
+
185
+ return false
186
+ }
187
+
188
+ /**
189
+ * Collect detected frameworks into the frameworks array
190
+ */
191
+ private collectFrameworks(deps: Record<string, string>, stack: StackDetection): void {
192
+ // Frontend frameworks
193
+ if (deps.react) stack.frameworks.push('React')
194
+ if (deps.next) stack.frameworks.push('Next.js')
195
+ if (deps.vue) stack.frameworks.push('Vue')
196
+ if (deps.nuxt) stack.frameworks.push('Nuxt')
197
+ if (deps.svelte) stack.frameworks.push('Svelte')
198
+ if (deps['@angular/core']) stack.frameworks.push('Angular')
199
+ if (deps['react-native']) stack.frameworks.push('React Native')
200
+ if (deps.expo) stack.frameworks.push('Expo')
201
+
202
+ // Backend frameworks
203
+ if (deps.express) stack.frameworks.push('Express')
204
+ if (deps.fastify) stack.frameworks.push('Fastify')
205
+ if (deps.hono) stack.frameworks.push('Hono')
206
+ if (deps.koa) stack.frameworks.push('Koa')
207
+ if (deps['@nestjs/core'] || deps.nest) stack.frameworks.push('NestJS')
208
+
209
+ // Meta-frameworks
210
+ if (deps.astro) stack.frameworks.push('Astro')
211
+ if (deps.remix) stack.frameworks.push('Remix')
212
+ if (deps.gatsby) stack.frameworks.push('Gatsby')
213
+ }
214
+
215
+ // ==========================================================================
216
+ // HELPERS
217
+ // ==========================================================================
218
+
219
+ /**
220
+ * Read and parse package.json
221
+ */
222
+ private async readPackageJson(): Promise<PackageJson | null> {
223
+ try {
224
+ const pkgPath = path.join(this.projectPath, 'package.json')
225
+ const content = await fs.readFile(pkgPath, 'utf-8')
226
+ return JSON.parse(content)
227
+ } catch {
228
+ return null
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Check if a file exists in the project
234
+ */
235
+ private async fileExists(filename: string): Promise<boolean> {
236
+ try {
237
+ await fs.access(path.join(this.projectPath, filename))
238
+ return true
239
+ } catch {
240
+ return false
241
+ }
242
+ }
243
+ }
244
+
245
+ export default StackDetector