@njeybe/nativ 2.4.1 → 2.5.0

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 (139) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +26 -0
  3. package/dist/commands/db/db-formatter.d.ts +5 -0
  4. package/dist/commands/db/db-formatter.js +116 -0
  5. package/dist/commands/db/db-introspect.d.ts +11 -0
  6. package/dist/commands/db/db-introspect.js +84 -0
  7. package/dist/commands/db.js +4 -206
  8. package/dist/commands/profile.d.ts +3 -0
  9. package/dist/commands/profile.js +39 -0
  10. package/dist/commands/task/task-add.d.ts +11 -0
  11. package/dist/commands/task/task-add.js +169 -0
  12. package/dist/commands/task/task-common.d.ts +57 -0
  13. package/dist/commands/task/task-common.js +45 -0
  14. package/dist/commands/task/task-escalate.d.ts +7 -0
  15. package/dist/commands/task/task-escalate.js +119 -0
  16. package/dist/commands/task/task-lifecycle.d.ts +5 -0
  17. package/dist/commands/task/task-lifecycle.js +252 -0
  18. package/dist/commands/task/task-query.d.ts +10 -0
  19. package/dist/commands/task/task-query.js +269 -0
  20. package/dist/commands/task.d.ts +9 -87
  21. package/dist/commands/task.js +8 -850
  22. package/dist/commands/triage/triage-store.d.ts +56 -0
  23. package/dist/commands/triage/triage-store.js +108 -0
  24. package/dist/commands/triage/triage-ui.d.ts +14 -0
  25. package/dist/commands/triage/triage-ui.js +104 -0
  26. package/dist/commands/triage.d.ts +3 -52
  27. package/dist/commands/triage.js +8 -216
  28. package/dist/core/enforcement.d.ts +1 -0
  29. package/dist/core/enforcement.js +19 -0
  30. package/dist/core/human-gate.d.ts +2 -1
  31. package/dist/core/human-gate.js +9 -2
  32. package/dist/core/profiles.d.ts +14 -0
  33. package/dist/core/profiles.js +82 -0
  34. package/dist/core/setup/config-merger.d.ts +59 -0
  35. package/dist/core/setup/config-merger.js +107 -0
  36. package/dist/core/setup/session-context.d.ts +4 -0
  37. package/dist/core/setup/session-context.js +65 -0
  38. package/dist/core/setup/setup-planner.d.ts +14 -0
  39. package/dist/core/setup/setup-planner.js +180 -0
  40. package/dist/core/setup/template-planner.d.ts +8 -0
  41. package/dist/core/setup/template-planner.js +32 -0
  42. package/dist/core/setup/types.d.ts +65 -0
  43. package/dist/core/setup/types.js +55 -0
  44. package/dist/core/setup-assets.d.ts +8 -119
  45. package/dist/core/setup-assets.js +7 -437
  46. package/dist/core/tier1-liaison.d.ts +2 -0
  47. package/dist/core/tier1-liaison.js +64 -3
  48. package/dist/core/verifier/executor.d.ts +5 -0
  49. package/dist/core/verifier/executor.js +62 -0
  50. package/dist/core/verifier/phases.d.ts +5 -0
  51. package/dist/core/verifier/phases.js +71 -0
  52. package/dist/core/verifier/task-verifier.d.ts +37 -0
  53. package/dist/core/verifier/task-verifier.js +233 -0
  54. package/dist/core/verifier/types.d.ts +51 -0
  55. package/dist/core/verifier/types.js +1 -0
  56. package/dist/core/verifier.d.ts +5 -93
  57. package/dist/core/verifier.js +5 -359
  58. package/dist/db/introspector.js +59 -59
  59. package/dist/governor/evaluator.d.ts +4 -64
  60. package/dist/governor/evaluator.js +21 -686
  61. package/dist/governor/patch-applier.d.ts +8 -0
  62. package/dist/governor/patch-applier.js +121 -0
  63. package/dist/governor/self-healing.d.ts +24 -0
  64. package/dist/governor/self-healing.js +351 -0
  65. package/dist/governor/test-integrity.d.ts +41 -0
  66. package/dist/governor/test-integrity.js +237 -0
  67. package/dist/index.js +8 -0
  68. package/dist/mcp/capture.d.ts +4 -0
  69. package/dist/mcp/capture.js +33 -0
  70. package/dist/mcp/resources.d.ts +39 -0
  71. package/dist/mcp/resources.js +44 -0
  72. package/dist/mcp/server.d.ts +11 -0
  73. package/dist/mcp/server.js +14 -89
  74. package/dist/mcp/tools.d.ts +1 -0
  75. package/dist/mcp/tools.js +1 -0
  76. package/dist/runner/agent-supervisor.d.ts +8 -302
  77. package/dist/runner/agent-supervisor.js +41 -1030
  78. package/dist/runner/cli-config.d.ts +84 -0
  79. package/dist/runner/cli-config.js +215 -0
  80. package/dist/runner/native-driver.d.ts +18 -0
  81. package/dist/runner/native-driver.js +208 -0
  82. package/dist/runner/native-engine.d.ts +27 -0
  83. package/dist/runner/native-engine.js +184 -0
  84. package/dist/runner/native-tools.d.ts +31 -0
  85. package/dist/runner/native-tools.js +241 -0
  86. package/dist/runner/process-utils.d.ts +12 -0
  87. package/dist/runner/process-utils.js +96 -0
  88. package/dist/runner/run-lifecycle.d.ts +6 -0
  89. package/dist/runner/run-lifecycle.js +42 -0
  90. package/dist/runner/types.d.ts +215 -0
  91. package/dist/runner/types.js +41 -0
  92. package/dist/scanner/detector.js +42 -663
  93. package/dist/scanner/detectors/ecosystem-detector.d.ts +16 -0
  94. package/dist/scanner/detectors/ecosystem-detector.js +354 -0
  95. package/dist/scanner/detectors/orm-detector.d.ts +5 -0
  96. package/dist/scanner/detectors/orm-detector.js +59 -0
  97. package/dist/scanner/detectors/workspace-detector.d.ts +19 -0
  98. package/dist/scanner/detectors/workspace-detector.js +266 -0
  99. package/dist/server/cli-runner.d.ts +15 -0
  100. package/dist/server/cli-runner.js +43 -0
  101. package/dist/server/event-hub.d.ts +42 -0
  102. package/dist/server/event-hub.js +169 -0
  103. package/dist/server/http-utils.d.ts +15 -0
  104. package/dist/server/http-utils.js +56 -0
  105. package/dist/server/plan-utils.d.ts +9 -0
  106. package/dist/server/plan-utils.js +31 -0
  107. package/dist/server/routes/db-routes.d.ts +57 -0
  108. package/dist/server/routes/db-routes.js +484 -0
  109. package/dist/server/routes/task-routes.d.ts +266 -0
  110. package/dist/server/routes/task-routes.js +285 -0
  111. package/dist/server/routes/telemetry-routes.d.ts +128 -0
  112. package/dist/server/routes/telemetry-routes.js +179 -0
  113. package/dist/server/routes/triage-routes.d.ts +81 -0
  114. package/dist/server/routes/triage-routes.js +244 -0
  115. package/dist/server/routes/worktree-routes.d.ts +31 -0
  116. package/dist/server/routes/worktree-routes.js +144 -0
  117. package/dist/server/studio-server.d.ts +4 -11
  118. package/dist/server/studio-server.js +34 -1578
  119. package/dist/server/studio-ui/shell/pipeline-state.js +2 -0
  120. package/dist/server/studio-ui/shell/triage.js +10 -0
  121. package/package.json +2 -2
  122. package/plugin/.claude-plugin/plugin.json +1 -1
  123. package/plugin/agents/architect.md +4 -2
  124. package/plugin/agents/verifier.md +1 -1
  125. package/plugin/agents/worker.md +7 -4
  126. package/plugin/skills/nativ/SKILL.md +1 -0
  127. package/templates/.claudeignore +62 -45
  128. package/templates/AGENTS.md +1 -0
  129. package/templates/CLAUDE.md +13 -0
  130. package/templates/claude-agents/architect.md +4 -2
  131. package/templates/claude-agents/verifier.md +1 -1
  132. package/templates/claude-agents/worker.md +7 -4
  133. package/templates/dot-ai/api_contracts.json +66 -66
  134. package/templates/dot-ai/context.md +38 -38
  135. package/templates/dot-ai/db_schema.json +39 -39
  136. package/dist/commands/session.d.ts +0 -22
  137. package/dist/commands/session.js +0 -106
  138. package/dist/core/session-memory.d.ts +0 -42
  139. package/dist/core/session-memory.js +0 -121
@@ -1,5 +1,8 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
+ import { detectWorkspacesAndServices } from './detectors/workspace-detector.js';
4
+ import { detectEcosystems } from './detectors/ecosystem-detector.js';
5
+ import { detectOrm } from './detectors/orm-detector.js';
3
6
  export function detectProject(targetDir) {
4
7
  const resolvedTarget = path.resolve(targetDir);
5
8
  const baseName = path.basename(resolvedTarget);
@@ -16,11 +19,6 @@ export function detectProject(targetDir) {
16
19
  let architecturePattern = 'Modular / Layered';
17
20
  let verificationCommand = 'npm test';
18
21
  let repositoryType = 'Standard Monolith';
19
- let detectedOrmConfig = '';
20
- const monorepoWorkspaces = [];
21
- const ecosystemManifests = [];
22
- const keyDirectories = [];
23
- // Helper safely reading file content
24
22
  const readFileSafe = (filePath) => {
25
23
  try {
26
24
  if (fs.existsSync(filePath)) {
@@ -32,650 +30,46 @@ export function detectProject(targetDir) {
32
30
  }
33
31
  return '';
34
32
  };
35
- // Helper checking existence
36
33
  const fileExists = (relPath) => {
37
34
  return fs.existsSync(path.join(resolvedTarget, relPath));
38
35
  };
39
- // -------------------------------------------------------------
40
- // 1. Structure & Workspace Detection (Monorepo / Multi-service)
41
- // -------------------------------------------------------------
42
- const monorepoIndicators = [];
43
- if (fileExists('pnpm-workspace.yaml')) {
44
- ecosystemManifests.push('pnpm-workspace.yaml');
45
- monorepoIndicators.push('pnpm workspaces');
46
- const pnpmWorkspace = readFileSafe(path.join(resolvedTarget, 'pnpm-workspace.yaml'));
47
- const packageLines = pnpmWorkspace
48
- .split('\n')
49
- .map((l) => l.trim())
50
- .filter((l) => l.startsWith('-'))
51
- .map((l) => l.replace(/^-\s*['"]?/, '').replace(/['"]?$/, '').trim());
52
- if (packageLines.length > 0) {
53
- monorepoWorkspaces.push(...packageLines);
54
- }
55
- }
56
- if (fileExists('turbo.json')) {
57
- ecosystemManifests.push('turbo.json');
58
- monorepoIndicators.push('Turborepo');
59
- }
60
- if (fileExists('nx.json')) {
61
- ecosystemManifests.push('nx.json');
62
- monorepoIndicators.push('Nx');
63
- }
64
- if (fileExists('lerna.json')) {
65
- ecosystemManifests.push('lerna.json');
66
- monorepoIndicators.push('Lerna');
67
- }
68
- // Check subdirectories: apps, packages, services, libs, modules
69
- const candidateWorkspaceDirs = ['apps', 'packages', 'services', 'libs', 'modules'];
70
- let detectedSubpackagesCount = 0;
71
- for (const wDir of candidateWorkspaceDirs) {
72
- const fullWDir = path.join(resolvedTarget, wDir);
73
- if (fs.existsSync(fullWDir) && fs.statSync(fullWDir).isDirectory()) {
74
- keyDirectories.push(wDir);
75
- if (!monorepoWorkspaces.includes(`${wDir}/*`)) {
76
- monorepoWorkspaces.push(`${wDir}/*`);
77
- }
78
- try {
79
- const children = fs.readdirSync(fullWDir);
80
- for (const child of children) {
81
- const childPath = path.join(fullWDir, child);
82
- if (fs.statSync(childPath).isDirectory()) {
83
- if (fs.existsSync(path.join(childPath, 'package.json')) ||
84
- fs.existsSync(path.join(childPath, 'go.mod')) ||
85
- fs.existsSync(path.join(childPath, 'pom.xml')) ||
86
- fs.existsSync(path.join(childPath, 'composer.json'))) {
87
- detectedSubpackagesCount++;
88
- }
89
- }
90
- }
91
- }
92
- catch {
93
- // ignore read error
94
- }
95
- }
96
- }
97
- const independentServices = [];
98
- try {
99
- const rootItems = fs.readdirSync(resolvedTarget);
100
- for (const item of rootItems) {
101
- if (item.startsWith('.') ||
102
- item === 'node_modules' ||
103
- item === 'vendor' ||
104
- item === 'dist' ||
105
- item === 'build' ||
106
- item === 'templates' ||
107
- item === 'bin' ||
108
- item === 'public') {
109
- continue;
110
- }
111
- const itemPath = path.join(resolvedTarget, item);
112
- if (fs.existsSync(itemPath) && fs.statSync(itemPath).isDirectory()) {
113
- const subPathItem = (filename) => fs.existsSync(path.join(itemPath, filename));
114
- const subManifests = [];
115
- if (subPathItem('package.json'))
116
- subManifests.push('package.json');
117
- if (subPathItem('composer.json'))
118
- subManifests.push('composer.json');
119
- if (subPathItem('go.mod'))
120
- subManifests.push('go.mod');
121
- if (subPathItem('Cargo.toml'))
122
- subManifests.push('Cargo.toml');
123
- if (subPathItem('pom.xml'))
124
- subManifests.push('pom.xml');
125
- if (subPathItem('build.gradle'))
126
- subManifests.push('build.gradle');
127
- if (subPathItem('build.gradle.kts'))
128
- subManifests.push('build.gradle.kts');
129
- if (subPathItem('pyproject.toml'))
130
- subManifests.push('pyproject.toml');
131
- if (subPathItem('requirements.txt'))
132
- subManifests.push('requirements.txt');
133
- if (subPathItem('pubspec.yaml'))
134
- subManifests.push('pubspec.yaml');
135
- if (subPathItem('Podfile'))
136
- subManifests.push('Podfile');
137
- if (subPathItem('AndroidManifest.xml'))
138
- subManifests.push('AndroidManifest.xml');
139
- if (subManifests.length > 0) {
140
- if (!keyDirectories.includes(item)) {
141
- keyDirectories.push(item);
142
- }
143
- let subTech = 'Service';
144
- let subRuntime = '';
145
- let subFramework = '';
146
- let subOrm = '';
147
- let subOrmConfig = '';
148
- // Check mobile & ecosystem profiles
149
- if (subPathItem('pubspec.yaml')) {
150
- subTech = 'Flutter Mobile App';
151
- subRuntime = 'Dart / Flutter';
152
- subFramework = 'Flutter';
153
- }
154
- else if (subPathItem('Podfile') || subPathItem('Info.plist') || fs.existsSync(path.join(itemPath, 'ios'))) {
155
- subTech = 'Native iOS App (Swift/Obj-C)';
156
- subRuntime = 'Swift / Objective-C';
157
- subFramework = 'iOS Native';
158
- }
159
- else if (subPathItem('AndroidManifest.xml') || (subPathItem('build.gradle') && !subPathItem('package.json'))) {
160
- subTech = 'Native Android App (Kotlin/Java)';
161
- subRuntime = 'Kotlin / Java (Android)';
162
- subFramework = 'Android Native';
163
- }
164
- else if (subPathItem('composer.json')) {
165
- subRuntime = 'PHP';
166
- subTech = 'PHP Backend';
167
- try {
168
- const subComposer = JSON.parse(readFileSafe(path.join(itemPath, 'composer.json')));
169
- const allReq = { ...subComposer.require, ...subComposer['require-dev'] };
170
- if (allReq['laravel/framework']) {
171
- subFramework = 'Laravel';
172
- subTech = 'Laravel Backend (PHP)';
173
- subOrm = 'Eloquent ORM';
174
- }
175
- else if (allReq['symfony/framework-bundle']) {
176
- subFramework = 'Symfony';
177
- subTech = 'Symfony Backend (PHP)';
178
- subOrm = 'Doctrine ORM';
179
- }
180
- }
181
- catch { }
182
- if (fs.existsSync(path.join(itemPath, 'database/migrations'))) {
183
- subOrmConfig = `${item}/database/migrations`;
184
- if (!subOrm)
185
- subOrm = 'SQL Migrations';
186
- }
187
- }
188
- else if (subPathItem('pyproject.toml') || subPathItem('requirements.txt')) {
189
- subRuntime = 'Python';
190
- subTech = 'Python Backend';
191
- const pyStr = readFileSafe(path.join(itemPath, 'requirements.txt')) + readFileSafe(path.join(itemPath, 'pyproject.toml'));
192
- if (pyStr.includes('fastapi')) {
193
- subFramework = 'FastAPI';
194
- subTech = 'FastAPI Backend (Python)';
195
- }
196
- else if (pyStr.includes('django')) {
197
- subFramework = 'Django';
198
- subTech = 'Django Backend (Python)';
199
- subOrm = 'Django ORM';
200
- }
201
- else if (pyStr.includes('flask')) {
202
- subFramework = 'Flask';
203
- subTech = 'Flask Backend (Python)';
204
- }
205
- if (pyStr.includes('sqlalchemy') || fs.existsSync(path.join(itemPath, 'alembic.ini'))) {
206
- subOrm = 'SQLAlchemy';
207
- if (fs.existsSync(path.join(itemPath, 'alembic.ini'))) {
208
- subOrmConfig = `${item}/alembic.ini`;
209
- }
210
- }
211
- }
212
- else if (subPathItem('go.mod')) {
213
- subRuntime = 'Go';
214
- subTech = 'Go Backend';
215
- const goStr = readFileSafe(path.join(itemPath, 'go.mod'));
216
- if (goStr.includes('gin-gonic/gin')) {
217
- subFramework = 'Gin';
218
- subTech = 'Gin Backend (Go)';
219
- }
220
- else if (goStr.includes('gofiber/fiber')) {
221
- subFramework = 'Fiber';
222
- subTech = 'Fiber Backend (Go)';
223
- }
224
- if (goStr.includes('gorm.io/gorm'))
225
- subOrm = 'GORM';
226
- }
227
- else if (subPathItem('package.json')) {
228
- subRuntime = 'Node.js';
229
- subTech = 'Node.js Service';
230
- try {
231
- const subPkg = JSON.parse(readFileSafe(path.join(itemPath, 'package.json')));
232
- const subDeps = { ...subPkg.dependencies, ...subPkg.devDependencies };
233
- if (subDeps.typescript || fs.existsSync(path.join(itemPath, 'tsconfig.json'))) {
234
- subRuntime = 'Node.js (TypeScript)';
235
- }
236
- if (subDeps['react-native'] || subDeps['expo']) {
237
- subTech = 'React Native / Expo Mobile App';
238
- subFramework = subDeps.expo ? 'Expo Mobile App' : 'React Native';
239
- }
240
- else if (subDeps.next) {
241
- subTech = 'Next.js Web App';
242
- subFramework = 'Next.js';
243
- }
244
- else if (subDeps.nuxt) {
245
- subTech = 'Nuxt Web App';
246
- subFramework = 'Nuxt';
247
- }
248
- else if (subDeps.vite || subDeps.react || subDeps.vue) {
249
- subTech = 'React / Web Frontend';
250
- subFramework = subDeps.vite ? 'Vite Frontend' : (subDeps.react ? 'React SPA' : 'Vue SPA');
251
- }
252
- else if (subDeps.express || subDeps['@nestjs/core'] || subDeps.fastify) {
253
- subTech = 'Node.js Backend';
254
- subFramework = subDeps['@nestjs/core'] ? 'NestJS' : (subDeps.fastify ? 'Fastify' : 'Express.js');
255
- }
256
- if (subDeps.prisma || fs.existsSync(path.join(itemPath, 'prisma/schema.prisma'))) {
257
- subOrm = 'Prisma ORM';
258
- subOrmConfig = `${item}/prisma/schema.prisma`;
259
- }
260
- else if (subDeps['drizzle-orm'] || fs.existsSync(path.join(itemPath, 'drizzle.config.ts'))) {
261
- subOrm = 'Drizzle ORM';
262
- subOrmConfig = `${item}/drizzle.config.ts`;
263
- }
264
- else if (subDeps.typeorm) {
265
- subOrm = 'TypeORM';
266
- }
267
- else if (subDeps.mongoose) {
268
- subOrm = 'Mongoose';
269
- }
270
- }
271
- catch {
272
- subTech = 'Node.js / Web App';
273
- }
274
- }
275
- independentServices.push({
276
- name: item,
277
- dirPath: itemPath,
278
- manifests: subManifests,
279
- tech: subTech,
280
- runtime: subRuntime,
281
- framework: subFramework,
282
- orm: subOrm,
283
- ormConfig: subOrmConfig,
284
- });
285
- if (!monorepoWorkspaces.includes(`${item}/*`) && !candidateWorkspaceDirs.includes(item)) {
286
- monorepoWorkspaces.push(`${item}/*`);
287
- }
288
- }
289
- }
290
- }
291
- }
292
- catch {
293
- // ignore read error
294
- }
295
- // -------------------------------------------------------------
296
- // 2. Language & Framework Profiling Across Ecosystems
297
- // -------------------------------------------------------------
298
- // --- Ecosystem A: Node.js / JavaScript / TypeScript ---
299
- const pkgPath = path.join(resolvedTarget, 'package.json');
300
- if (fileExists('package.json')) {
301
- ecosystemManifests.push('package.json');
302
- try {
303
- const pkg = JSON.parse(readFileSafe(pkgPath));
304
- if (pkg.name)
305
- projectName = pkg.name;
306
- if (pkg.description)
307
- projectGoal = pkg.description;
308
- // Check package.json workspaces
309
- if (pkg.workspaces) {
310
- if (Array.isArray(pkg.workspaces)) {
311
- monorepoWorkspaces.push(...pkg.workspaces);
312
- }
313
- else if (Array.isArray(pkg.workspaces.packages)) {
314
- monorepoWorkspaces.push(...pkg.workspaces.packages);
315
- }
316
- if (!monorepoIndicators.includes('npm/yarn workspaces')) {
317
- monorepoIndicators.push('npm/yarn workspaces');
318
- }
319
- }
320
- const deps = { ...pkg.dependencies, ...pkg.devDependencies };
321
- // Language & Runtime
322
- if (deps.typescript || fileExists('tsconfig.json')) {
323
- runtime = 'Node.js (TypeScript)';
324
- if (fileExists('tsconfig.json'))
325
- ecosystemManifests.push('tsconfig.json');
326
- }
327
- else {
328
- runtime = 'Node.js (JavaScript)';
329
- }
330
- // Frameworks
331
- if (deps['react-native'] || deps.expo) {
332
- framework = deps.expo ? 'Expo Mobile App' : 'React Native';
333
- }
334
- else if (deps.next)
335
- framework = `Next.js (${deps.next})`;
336
- else if (deps.nuxt)
337
- framework = `Nuxt (${deps.nuxt})`;
338
- else if (deps.remix || deps['@remix-run/react'])
339
- framework = 'Remix';
340
- else if (deps.astro)
341
- framework = 'Astro';
342
- else if (deps['@sveltejs/kit'] || deps.svelte)
343
- framework = 'Svelte / SvelteKit';
344
- else if (deps.vite)
345
- framework = 'Vite / React/Vue';
346
- else if (deps['@nestjs/core'])
347
- framework = 'NestJS';
348
- else if (deps.hono)
349
- framework = 'Hono';
350
- else if (deps.fastify)
351
- framework = 'Fastify';
352
- else if (deps.express)
353
- framework = 'Express.js';
354
- else if (deps['@angular/core'])
355
- framework = 'Angular';
356
- else if (deps.react)
357
- framework = 'React SPA';
358
- else if (deps.vue)
359
- framework = 'Vue.js SPA';
360
- // Database & ORMs in deps
361
- if (deps.prisma || deps['@prisma/client'])
362
- databaseOrm = 'Prisma ORM';
363
- else if (deps['drizzle-orm'])
364
- databaseOrm = 'Drizzle ORM';
365
- else if (deps.typeorm)
366
- databaseOrm = 'TypeORM';
367
- else if (deps.mongoose)
368
- databaseOrm = 'Mongoose / MongoDB';
369
- else if (deps.sequelize)
370
- databaseOrm = 'Sequelize';
371
- else if (deps.knex)
372
- databaseOrm = 'Knex';
373
- else if (deps['@supabase/supabase-js'])
374
- databaseOrm = 'Supabase Client';
375
- else if (deps.pg)
376
- databaseOrm = 'pg (node-postgres)';
377
- else if (deps.mysql2)
378
- databaseOrm = 'mysql2';
379
- else if (deps['better-sqlite3'] || deps.sqlite3)
380
- databaseOrm = 'SQLite';
381
- // Styling
382
- if (deps.tailwindcss)
383
- styling = 'Tailwind CSS';
384
- else if (deps['styled-components'])
385
- styling = 'Styled Components';
386
- else if (deps['@emotion/react'])
387
- styling = 'Emotion';
388
- else if (deps.sass)
389
- styling = 'SASS / SCSS';
390
- // Testing
391
- if (deps.vitest) {
392
- testing = 'Vitest';
393
- verificationCommand = 'npm run test';
394
- }
395
- else if (deps.jest) {
396
- testing = 'Jest';
397
- verificationCommand = 'npm test';
398
- }
399
- else if (deps.playwright || deps['@playwright/test']) {
400
- testing = 'Playwright';
401
- verificationCommand = 'npx playwright test';
402
- }
403
- // Package Manager
404
- if (fileExists('pnpm-lock.yaml')) {
405
- packageManager = 'pnpm';
406
- ecosystemManifests.push('pnpm-lock.yaml');
407
- }
408
- else if (fileExists('yarn.lock')) {
409
- packageManager = 'yarn';
410
- ecosystemManifests.push('yarn.lock');
411
- }
412
- else if (fileExists('bun.lockb') || fileExists('bun.lock')) {
413
- packageManager = 'bun';
414
- ecosystemManifests.push(fileExists('bun.lockb') ? 'bun.lockb' : 'bun.lock');
415
- }
416
- else if (fileExists('package-lock.json')) {
417
- packageManager = 'npm';
418
- ecosystemManifests.push('package-lock.json');
419
- }
420
- else {
421
- packageManager = 'npm';
422
- }
423
- // Verification command adjustment
424
- if (pkg.scripts?.test && pkg.scripts.test !== 'echo "Error: no test specified" && exit 1') {
425
- verificationCommand = `${packageManager === 'npm' ? 'npm test' : `${packageManager} test`}`;
426
- }
427
- else if (pkg.scripts?.build) {
428
- verificationCommand = `${packageManager === 'npm' ? 'npm run build' : `${packageManager} build`}`;
429
- }
430
- projectType = framework !== 'None detected' ? `Web Application (${framework})` : 'Node.js Application';
431
- }
432
- catch {
433
- // ignore parse failure
434
- }
435
- }
436
- // --- Ecosystem B: PHP (composer.json) ---
437
- if (fileExists('composer.json')) {
438
- ecosystemManifests.push('composer.json');
439
- if (runtime === 'Unknown / Greenfield') {
440
- runtime = 'PHP';
441
- packageManager = 'composer';
442
- projectType = 'PHP Application';
443
- verificationCommand = 'composer test';
444
- }
445
- const composerRaw = readFileSafe(path.join(resolvedTarget, 'composer.json'));
446
- try {
447
- const composerPkg = JSON.parse(composerRaw);
448
- if (composerPkg.name && projectName === baseName) {
449
- projectName = composerPkg.name;
450
- }
451
- if (composerPkg.description && projectGoal.includes('Awaiting specification')) {
452
- projectGoal = composerPkg.description;
453
- }
454
- const allPhpDeps = { ...composerPkg.require, ...composerPkg['require-dev'] };
455
- if (allPhpDeps['laravel/framework']) {
456
- framework = 'Laravel';
457
- projectType = 'Laravel Web Application';
458
- databaseOrm = 'Eloquent ORM';
459
- verificationCommand = 'php artisan test';
460
- }
461
- else if (allPhpDeps['symfony/framework-bundle'] || allPhpDeps['symfony/symfony']) {
462
- framework = 'Symfony';
463
- projectType = 'Symfony Application';
464
- databaseOrm = 'Doctrine ORM';
465
- verificationCommand = 'bin/phpunit';
466
- }
467
- else if (allPhpDeps['slim/slim']) {
468
- framework = 'Slim Framework';
469
- }
470
- else if (allPhpDeps['roots/bedrock'] || composerRaw.includes('wordpress')) {
471
- framework = 'WordPress';
472
- }
473
- if (allPhpDeps['pestphp/pest']) {
474
- testing = 'Pest';
475
- }
476
- else if (allPhpDeps['phpunit/phpunit']) {
477
- testing = 'PHPUnit';
478
- }
479
- }
480
- catch {
481
- if (composerRaw.includes('laravel/framework')) {
482
- framework = 'Laravel';
483
- databaseOrm = 'Eloquent ORM';
484
- }
485
- }
486
- }
487
- // --- Ecosystem C: Python (pyproject.toml, requirements.txt, poetry.lock, Pipfile) ---
488
- const hasPyproject = fileExists('pyproject.toml');
489
- const hasRequirements = fileExists('requirements.txt');
490
- const hasPipfile = fileExists('Pipfile');
491
- if (hasPyproject || hasRequirements || hasPipfile) {
492
- if (hasPyproject)
493
- ecosystemManifests.push('pyproject.toml');
494
- if (hasRequirements)
495
- ecosystemManifests.push('requirements.txt');
496
- if (hasPipfile)
497
- ecosystemManifests.push('Pipfile');
498
- if (fileExists('poetry.lock'))
499
- ecosystemManifests.push('poetry.lock');
500
- if (fileExists('uv.lock'))
501
- ecosystemManifests.push('uv.lock');
502
- if (runtime === 'Unknown / Greenfield') {
503
- runtime = 'Python 3.x';
504
- projectType = 'Python Service / Application';
505
- packageManager = fileExists('poetry.lock')
506
- ? 'Poetry'
507
- : fileExists('uv.lock')
508
- ? 'uv'
509
- : fileExists('Pipfile')
510
- ? 'Pipenv'
511
- : 'pip';
512
- verificationCommand = 'pytest';
513
- }
514
- let pyContent = '';
515
- if (hasRequirements)
516
- pyContent += readFileSafe(path.join(resolvedTarget, 'requirements.txt'));
517
- if (hasPyproject)
518
- pyContent += readFileSafe(path.join(resolvedTarget, 'pyproject.toml'));
519
- if (pyContent.includes('fastapi'))
520
- framework = 'FastAPI';
521
- else if (pyContent.includes('django')) {
522
- framework = 'Django';
523
- databaseOrm = 'Django ORM';
524
- verificationCommand = 'python manage.py test';
525
- }
526
- else if (pyContent.includes('flask'))
527
- framework = 'Flask';
528
- else if (pyContent.includes('litestar'))
529
- framework = 'Litestar';
530
- if (pyContent.includes('sqlalchemy'))
531
- databaseOrm = 'SQLAlchemy';
532
- else if (pyContent.includes('tortoise-orm'))
533
- databaseOrm = 'Tortoise ORM';
534
- else if (pyContent.includes('sqlmodel'))
535
- databaseOrm = 'SQLModel';
536
- if (pyContent.includes('pytest'))
537
- testing = 'pytest';
538
- }
539
- // --- Ecosystem D: Go (go.mod) ---
540
- if (fileExists('go.mod')) {
541
- ecosystemManifests.push('go.mod');
542
- if (runtime === 'Unknown / Greenfield') {
543
- runtime = 'Go';
544
- projectType = 'Go Service';
545
- packageManager = 'go modules';
546
- verificationCommand = 'go test ./...';
547
- }
548
- const modContent = readFileSafe(path.join(resolvedTarget, 'go.mod'));
549
- const moduleMatch = modContent.match(/module\s+([^\s\n]+)/);
550
- if (moduleMatch && projectName === baseName) {
551
- projectName = path.basename(moduleMatch[1]);
552
- }
553
- if (modContent.includes('gin-gonic/gin'))
554
- framework = 'Gin';
555
- else if (modContent.includes('gofiber/fiber'))
556
- framework = 'Fiber';
557
- else if (modContent.includes('labstack/echo'))
558
- framework = 'Echo';
559
- else if (modContent.includes('go-chi/chi'))
560
- framework = 'Chi';
561
- if (modContent.includes('gorm.io/gorm'))
562
- databaseOrm = 'GORM';
563
- else if (modContent.includes('entgo.io/ent'))
564
- databaseOrm = 'Ent';
565
- else if (modContent.includes('jmoiron/sqlx'))
566
- databaseOrm = 'sqlx';
567
- }
568
- // --- Ecosystem E: Rust (Cargo.toml) ---
569
- if (fileExists('Cargo.toml')) {
570
- ecosystemManifests.push('Cargo.toml');
571
- if (runtime === 'Unknown / Greenfield') {
572
- runtime = 'Rust';
573
- projectType = 'Rust Application';
574
- packageManager = 'cargo';
575
- verificationCommand = 'cargo test';
576
- }
577
- const cargoContent = readFileSafe(path.join(resolvedTarget, 'Cargo.toml'));
578
- if (cargoContent.includes('actix-web'))
579
- framework = 'Actix Web';
580
- else if (cargoContent.includes('axum'))
581
- framework = 'Axum';
582
- else if (cargoContent.includes('rocket'))
583
- framework = 'Rocket';
584
- else if (cargoContent.includes('tauri'))
585
- framework = 'Tauri';
586
- if (cargoContent.includes('diesel'))
587
- databaseOrm = 'Diesel';
588
- else if (cargoContent.includes('sqlx'))
589
- databaseOrm = 'SQLx';
590
- else if (cargoContent.includes('sea-orm'))
591
- databaseOrm = 'SeaORM';
592
- }
593
- // --- Ecosystem F: Java / JVM (pom.xml, build.gradle) ---
594
- if (fileExists('pom.xml') || fileExists('build.gradle') || fileExists('build.gradle.kts')) {
595
- if (fileExists('pom.xml'))
596
- ecosystemManifests.push('pom.xml');
597
- if (fileExists('build.gradle'))
598
- ecosystemManifests.push('build.gradle');
599
- if (fileExists('build.gradle.kts'))
600
- ecosystemManifests.push('build.gradle.kts');
601
- if (runtime === 'Unknown / Greenfield') {
602
- runtime = 'Java / JVM';
603
- projectType = 'Java Service / Application';
604
- packageManager = fileExists('pom.xml') ? 'Maven' : 'Gradle';
605
- verificationCommand = fileExists('pom.xml') ? 'mvn test' : './gradlew test';
606
- }
607
- const jvmContent = readFileSafe(path.join(resolvedTarget, fileExists('pom.xml') ? 'pom.xml' : 'build.gradle'));
608
- if (jvmContent.includes('spring-boot'))
609
- framework = 'Spring Boot';
610
- else if (jvmContent.includes('quarkus'))
611
- framework = 'Quarkus';
612
- else if (jvmContent.includes('micronaut'))
613
- framework = 'Micronaut';
614
- if (jvmContent.includes('hibernate') || jvmContent.includes('spring-boot-starter-data-jpa')) {
615
- databaseOrm = 'Hibernate / JPA';
616
- }
617
- }
618
- // --- Ecosystem G: Mobile / Flutter (pubspec.yaml) ---
619
- if (fileExists('pubspec.yaml')) {
620
- ecosystemManifests.push('pubspec.yaml');
621
- if (runtime === 'Unknown / Greenfield') {
622
- runtime = 'Dart / Flutter';
623
- framework = 'Flutter Mobile App';
624
- projectType = 'Mobile Application (Flutter)';
625
- packageManager = 'flutter pub';
626
- verificationCommand = 'flutter test';
627
- }
628
- }
629
- // -------------------------------------------------------------
630
- // 3. Database & ORM Layer Detection (Config files & Migrations)
631
- // -------------------------------------------------------------
632
- if (fileExists('prisma/schema.prisma')) {
633
- detectedOrmConfig = 'prisma/schema.prisma';
634
- databaseOrm = 'Prisma ORM';
635
- }
636
- else if (fileExists('schema.prisma')) {
637
- detectedOrmConfig = 'schema.prisma';
638
- databaseOrm = 'Prisma ORM';
639
- }
640
- if (fileExists('drizzle.config.ts')) {
641
- detectedOrmConfig = 'drizzle.config.ts';
642
- databaseOrm = 'Drizzle ORM';
643
- }
644
- else if (fileExists('drizzle.config.js')) {
645
- detectedOrmConfig = 'drizzle.config.js';
646
- databaseOrm = 'Drizzle ORM';
647
- }
648
- else if (fileExists('drizzle.config.json')) {
649
- detectedOrmConfig = 'drizzle.config.json';
650
- databaseOrm = 'Drizzle ORM';
651
- }
652
- if (fileExists('knexfile.js')) {
653
- detectedOrmConfig = 'knexfile.js';
654
- databaseOrm = 'Knex';
655
- }
656
- else if (fileExists('knexfile.ts')) {
657
- detectedOrmConfig = 'knexfile.ts';
658
- databaseOrm = 'Knex';
659
- }
660
- if (fileExists('alembic.ini')) {
661
- detectedOrmConfig = 'alembic.ini';
662
- databaseOrm = databaseOrm === 'None detected' ? 'SQLAlchemy (Alembic)' : `${databaseOrm} (Alembic)`;
663
- }
664
- if (fileExists('database/migrations')) {
665
- const isDir = fs.statSync(path.join(resolvedTarget, 'database/migrations')).isDirectory();
666
- if (isDir) {
667
- detectedOrmConfig = 'database/migrations';
668
- if (databaseOrm === 'None detected')
669
- databaseOrm = 'SQL Migrations';
670
- }
671
- }
672
- if (fileExists('ormconfig.json')) {
673
- detectedOrmConfig = 'ormconfig.json';
674
- databaseOrm = 'TypeORM';
675
- }
676
- // -------------------------------------------------------------
36
+ // 1. Structure & Workspace Detection
37
+ const wsResult = detectWorkspacesAndServices(resolvedTarget, fileExists, readFileSafe);
38
+ const monorepoIndicators = [...wsResult.monorepoIndicators];
39
+ const monorepoWorkspaces = [...wsResult.monorepoWorkspaces];
40
+ const keyDirectories = [...wsResult.keyDirectories];
41
+ const ecosystemManifests = [...wsResult.ecosystemManifests];
42
+ const { detectedSubpackagesCount, independentServices } = wsResult;
43
+ // 2. Language & Framework Profiling
44
+ const ecoResult = detectEcosystems(resolvedTarget, baseName, fileExists, readFileSafe);
45
+ if (ecoResult.projectName)
46
+ projectName = ecoResult.projectName;
47
+ if (ecoResult.projectGoal)
48
+ projectGoal = ecoResult.projectGoal;
49
+ if (ecoResult.projectType)
50
+ projectType = ecoResult.projectType;
51
+ if (ecoResult.runtime !== 'Unknown / Greenfield')
52
+ runtime = ecoResult.runtime;
53
+ if (ecoResult.framework !== 'None detected')
54
+ framework = ecoResult.framework;
55
+ if (ecoResult.databaseOrm !== 'None detected')
56
+ databaseOrm = ecoResult.databaseOrm;
57
+ if (ecoResult.styling !== 'None detected')
58
+ styling = ecoResult.styling;
59
+ if (ecoResult.testing !== 'None detected')
60
+ testing = ecoResult.testing;
61
+ if (ecoResult.packageManager !== 'None detected')
62
+ packageManager = ecoResult.packageManager;
63
+ if (ecoResult.verificationCommand)
64
+ verificationCommand = ecoResult.verificationCommand;
65
+ ecosystemManifests.push(...ecoResult.ecosystemManifests.filter((m) => !ecosystemManifests.includes(m)));
66
+ monorepoWorkspaces.push(...ecoResult.monorepoWorkspaces.filter((w) => !monorepoWorkspaces.includes(w)));
67
+ monorepoIndicators.push(...ecoResult.monorepoIndicators.filter((i) => !monorepoIndicators.includes(i)));
68
+ // 3. Database & ORM Config Detection
69
+ const ormResult = detectOrm(resolvedTarget, fileExists, databaseOrm);
70
+ let detectedOrmConfig = ormResult.detectedOrmConfig;
71
+ databaseOrm = ormResult.databaseOrm;
677
72
  // 4. Resolve Repository Type & Workspaces
678
- // -------------------------------------------------------------
679
73
  if (monorepoIndicators.length > 0 || detectedSubpackagesCount > 0) {
680
74
  const toolLabel = monorepoIndicators.length > 0 ? monorepoIndicators.join(' + ') : 'Directory Workspaces';
681
75
  repositoryType = `Monorepo (${toolLabel})`;
@@ -684,7 +78,6 @@ export function detectProject(targetDir) {
684
78
  }
685
79
  }
686
80
  else if (ecosystemManifests.length === 0 && independentServices.length > 0) {
687
- // Polyrepo / Multi-Service with no root manifest
688
81
  repositoryType = 'Polyrepo / Multi-Service Workspace';
689
82
  projectType = 'Multi-Service Application Suite';
690
83
  architecturePattern = 'Polyrepo (Independent Sub-services)';
@@ -710,25 +103,11 @@ export function detectProject(targetDir) {
710
103
  else {
711
104
  repositoryType = 'Generic Application (Greenfield)';
712
105
  }
713
- // -------------------------------------------------------------
714
106
  // 5. Check Key Directories & Source Root
715
- // -------------------------------------------------------------
716
107
  const candidateDirs = [
717
- 'src',
718
- 'app',
719
- 'pages',
720
- 'components',
721
- 'lib',
722
- 'api',
723
- 'controllers',
724
- 'routes',
725
- 'models',
726
- 'tests',
727
- 'test',
728
- 'public',
729
- 'prisma',
730
- 'templates',
731
- 'bin',
108
+ 'src', 'app', 'pages', 'components', 'lib', 'api',
109
+ 'controllers', 'routes', 'models', 'tests', 'test',
110
+ 'public', 'prisma', 'templates', 'bin',
732
111
  ];
733
112
  for (const dir of candidateDirs) {
734
113
  if (fileExists(dir) && !keyDirectories.includes(dir)) {