@vibecheckai/cli 3.2.2 → 3.2.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 (170) hide show
  1. package/bin/.generated +25 -25
  2. package/bin/dev/run-v2-torture.js +30 -30
  3. package/bin/runners/ENHANCEMENT_GUIDE.md +121 -121
  4. package/bin/runners/lib/__tests__/entitlements-v2.test.js +295 -295
  5. package/bin/runners/lib/agent-firewall/ai/false-positive-analyzer.js +474 -0
  6. package/bin/runners/lib/agent-firewall/claims/extractor.js +117 -28
  7. package/bin/runners/lib/agent-firewall/evidence/env-evidence.js +23 -14
  8. package/bin/runners/lib/agent-firewall/evidence/route-evidence.js +72 -1
  9. package/bin/runners/lib/agent-firewall/interceptor/base.js +2 -2
  10. package/bin/runners/lib/agent-firewall/policy/default-policy.json +6 -0
  11. package/bin/runners/lib/agent-firewall/policy/engine.js +34 -3
  12. package/bin/runners/lib/agent-firewall/policy/rules/fake-success.js +29 -4
  13. package/bin/runners/lib/agent-firewall/policy/rules/ghost-route.js +12 -0
  14. package/bin/runners/lib/agent-firewall/truthpack/loader.js +21 -0
  15. package/bin/runners/lib/agent-firewall/utils/ignore-checker.js +118 -0
  16. package/bin/runners/lib/analyzers.js +606 -325
  17. package/bin/runners/lib/auth-truth.js +193 -193
  18. package/bin/runners/lib/backup.js +62 -62
  19. package/bin/runners/lib/billing.js +107 -107
  20. package/bin/runners/lib/claims.js +118 -118
  21. package/bin/runners/lib/cli-ui.js +540 -540
  22. package/bin/runners/lib/contracts/auth-contract.js +202 -202
  23. package/bin/runners/lib/contracts/env-contract.js +181 -181
  24. package/bin/runners/lib/contracts/external-contract.js +206 -206
  25. package/bin/runners/lib/contracts/guard.js +168 -168
  26. package/bin/runners/lib/contracts/index.js +89 -89
  27. package/bin/runners/lib/contracts/plan-validator.js +311 -311
  28. package/bin/runners/lib/contracts/route-contract.js +199 -199
  29. package/bin/runners/lib/contracts.js +804 -804
  30. package/bin/runners/lib/detect.js +89 -89
  31. package/bin/runners/lib/doctor/autofix.js +254 -254
  32. package/bin/runners/lib/doctor/index.js +37 -37
  33. package/bin/runners/lib/doctor/modules/dependencies.js +325 -325
  34. package/bin/runners/lib/doctor/modules/index.js +46 -46
  35. package/bin/runners/lib/doctor/modules/network.js +250 -250
  36. package/bin/runners/lib/doctor/modules/project.js +312 -312
  37. package/bin/runners/lib/doctor/modules/runtime.js +224 -224
  38. package/bin/runners/lib/doctor/modules/security.js +348 -348
  39. package/bin/runners/lib/doctor/modules/system.js +213 -213
  40. package/bin/runners/lib/doctor/modules/vibecheck.js +394 -394
  41. package/bin/runners/lib/doctor/reporter.js +262 -262
  42. package/bin/runners/lib/doctor/service.js +262 -262
  43. package/bin/runners/lib/doctor/types.js +113 -113
  44. package/bin/runners/lib/doctor/ui.js +263 -263
  45. package/bin/runners/lib/doctor-v2.js +608 -608
  46. package/bin/runners/lib/drift.js +425 -425
  47. package/bin/runners/lib/enforcement.js +72 -72
  48. package/bin/runners/lib/engines/accessibility-engine.js +190 -0
  49. package/bin/runners/lib/engines/api-consistency-engine.js +162 -0
  50. package/bin/runners/lib/engines/ast-cache.js +99 -0
  51. package/bin/runners/lib/engines/code-quality-engine.js +255 -0
  52. package/bin/runners/lib/engines/console-logs-engine.js +115 -0
  53. package/bin/runners/lib/engines/cross-file-analysis-engine.js +268 -0
  54. package/bin/runners/lib/engines/dead-code-engine.js +198 -0
  55. package/bin/runners/lib/engines/deprecated-api-engine.js +226 -0
  56. package/bin/runners/lib/engines/empty-catch-engine.js +150 -0
  57. package/bin/runners/lib/engines/file-filter.js +131 -0
  58. package/bin/runners/lib/engines/hardcoded-secrets-engine.js +251 -0
  59. package/bin/runners/lib/engines/mock-data-engine.js +272 -0
  60. package/bin/runners/lib/engines/parallel-processor.js +71 -0
  61. package/bin/runners/lib/engines/performance-issues-engine.js +265 -0
  62. package/bin/runners/lib/engines/security-vulnerabilities-engine.js +243 -0
  63. package/bin/runners/lib/engines/todo-fixme-engine.js +115 -0
  64. package/bin/runners/lib/engines/type-aware-engine.js +152 -0
  65. package/bin/runners/lib/engines/unsafe-regex-engine.js +225 -0
  66. package/bin/runners/lib/engines/vibecheck-engines/README.md +53 -0
  67. package/bin/runners/lib/engines/vibecheck-engines/index.js +15 -0
  68. package/bin/runners/lib/engines/vibecheck-engines/lib/ast-cache.js +164 -0
  69. package/bin/runners/lib/engines/vibecheck-engines/lib/code-quality-engine.js +291 -0
  70. package/bin/runners/lib/engines/vibecheck-engines/lib/console-logs-engine.js +83 -0
  71. package/bin/runners/lib/engines/vibecheck-engines/lib/dead-code-engine.js +198 -0
  72. package/bin/runners/lib/engines/vibecheck-engines/lib/deprecated-api-engine.js +275 -0
  73. package/bin/runners/lib/engines/vibecheck-engines/lib/empty-catch-engine.js +167 -0
  74. package/bin/runners/lib/engines/vibecheck-engines/lib/file-filter.js +217 -0
  75. package/bin/runners/lib/engines/vibecheck-engines/lib/hardcoded-secrets-engine.js +139 -0
  76. package/bin/runners/lib/engines/vibecheck-engines/lib/mock-data-engine.js +140 -0
  77. package/bin/runners/lib/engines/vibecheck-engines/lib/parallel-processor.js +164 -0
  78. package/bin/runners/lib/engines/vibecheck-engines/lib/performance-issues-engine.js +234 -0
  79. package/bin/runners/lib/engines/vibecheck-engines/lib/type-aware-engine.js +217 -0
  80. package/bin/runners/lib/engines/vibecheck-engines/lib/unsafe-regex-engine.js +78 -0
  81. package/bin/runners/lib/engines/vibecheck-engines/package.json +13 -0
  82. package/bin/runners/lib/enterprise-detect.js +603 -603
  83. package/bin/runners/lib/enterprise-init.js +942 -942
  84. package/bin/runners/lib/env-resolver.js +417 -417
  85. package/bin/runners/lib/env-template.js +66 -66
  86. package/bin/runners/lib/env.js +189 -189
  87. package/bin/runners/lib/extractors/client-calls.js +990 -990
  88. package/bin/runners/lib/extractors/fastify-route-dump.js +573 -573
  89. package/bin/runners/lib/extractors/fastify-routes.js +426 -426
  90. package/bin/runners/lib/extractors/index.js +363 -363
  91. package/bin/runners/lib/extractors/next-routes.js +524 -524
  92. package/bin/runners/lib/extractors/proof-graph.js +431 -431
  93. package/bin/runners/lib/extractors/route-matcher.js +451 -451
  94. package/bin/runners/lib/extractors/truthpack-v2.js +377 -377
  95. package/bin/runners/lib/extractors/ui-bindings.js +547 -547
  96. package/bin/runners/lib/findings-schema.js +281 -281
  97. package/bin/runners/lib/firewall-prompt.js +50 -50
  98. package/bin/runners/lib/global-flags.js +213 -213
  99. package/bin/runners/lib/graph/graph-builder.js +265 -265
  100. package/bin/runners/lib/graph/html-renderer.js +413 -413
  101. package/bin/runners/lib/graph/index.js +32 -32
  102. package/bin/runners/lib/graph/runtime-collector.js +215 -215
  103. package/bin/runners/lib/graph/static-extractor.js +518 -518
  104. package/bin/runners/lib/html-report.js +650 -650
  105. package/bin/runners/lib/interactive-menu.js +1496 -1496
  106. package/bin/runners/lib/llm.js +75 -75
  107. package/bin/runners/lib/meter.js +61 -61
  108. package/bin/runners/lib/missions/evidence.js +126 -126
  109. package/bin/runners/lib/patch.js +40 -40
  110. package/bin/runners/lib/permissions/auth-model.js +213 -213
  111. package/bin/runners/lib/permissions/idor-prover.js +205 -205
  112. package/bin/runners/lib/permissions/index.js +45 -45
  113. package/bin/runners/lib/permissions/matrix-builder.js +198 -198
  114. package/bin/runners/lib/pkgjson.js +28 -28
  115. package/bin/runners/lib/policy.js +295 -295
  116. package/bin/runners/lib/preflight.js +142 -142
  117. package/bin/runners/lib/reality/correlation-detectors.js +359 -359
  118. package/bin/runners/lib/reality/index.js +318 -318
  119. package/bin/runners/lib/reality/request-hashing.js +416 -416
  120. package/bin/runners/lib/reality/request-mapper.js +453 -453
  121. package/bin/runners/lib/reality/safety-rails.js +463 -463
  122. package/bin/runners/lib/reality/semantic-snapshot.js +408 -408
  123. package/bin/runners/lib/reality/toast-detector.js +393 -393
  124. package/bin/runners/lib/reality-findings.js +84 -84
  125. package/bin/runners/lib/receipts.js +179 -179
  126. package/bin/runners/lib/redact.js +29 -29
  127. package/bin/runners/lib/replay/capsule-manager.js +154 -154
  128. package/bin/runners/lib/replay/index.js +263 -263
  129. package/bin/runners/lib/replay/player.js +348 -348
  130. package/bin/runners/lib/replay/recorder.js +331 -331
  131. package/bin/runners/lib/report-output.js +187 -187
  132. package/bin/runners/lib/report.js +135 -135
  133. package/bin/runners/lib/route-detection.js +1140 -1140
  134. package/bin/runners/lib/sandbox/index.js +59 -59
  135. package/bin/runners/lib/sandbox/proof-chain.js +399 -399
  136. package/bin/runners/lib/sandbox/sandbox-runner.js +205 -205
  137. package/bin/runners/lib/sandbox/worktree.js +174 -174
  138. package/bin/runners/lib/scan-output.js +525 -190
  139. package/bin/runners/lib/schema-validator.js +350 -350
  140. package/bin/runners/lib/schemas/contracts.schema.json +160 -160
  141. package/bin/runners/lib/schemas/finding.schema.json +100 -100
  142. package/bin/runners/lib/schemas/mission-pack.schema.json +206 -206
  143. package/bin/runners/lib/schemas/proof-graph.schema.json +176 -176
  144. package/bin/runners/lib/schemas/reality-report.schema.json +162 -162
  145. package/bin/runners/lib/schemas/share-pack.schema.json +180 -180
  146. package/bin/runners/lib/schemas/ship-report.schema.json +117 -117
  147. package/bin/runners/lib/schemas/truthpack-v2.schema.json +303 -303
  148. package/bin/runners/lib/schemas/validator.js +438 -438
  149. package/bin/runners/lib/score-history.js +282 -282
  150. package/bin/runners/lib/share-pack.js +239 -239
  151. package/bin/runners/lib/snippets.js +67 -67
  152. package/bin/runners/lib/status-output.js +253 -253
  153. package/bin/runners/lib/terminal-ui.js +351 -271
  154. package/bin/runners/lib/upsell.js +510 -510
  155. package/bin/runners/lib/usage.js +153 -153
  156. package/bin/runners/lib/validate-patch.js +156 -156
  157. package/bin/runners/lib/verdict-engine.js +628 -628
  158. package/bin/runners/reality/engine.js +917 -917
  159. package/bin/runners/reality/flows.js +122 -122
  160. package/bin/runners/reality/report.js +378 -378
  161. package/bin/runners/reality/session.js +193 -193
  162. package/bin/runners/runGuard.js +168 -168
  163. package/bin/runners/runProof.zip +0 -0
  164. package/bin/runners/runProve.js +8 -0
  165. package/bin/runners/runReality.js +14 -0
  166. package/bin/runners/runScan.js +17 -1
  167. package/bin/runners/runTruth.js +15 -3
  168. package/mcp-server/tier-auth.js +4 -4
  169. package/mcp-server/tools/index.js +72 -72
  170. package/package.json +1 -1
@@ -1,603 +1,603 @@
1
- // bin/runners/lib/enterprise-detect.js
2
- // Enterprise-grade project detection: frameworks, databases, cloud, CI/CD, security tools
3
- const fs = require("fs");
4
- const path = require("path");
5
-
6
- function fileExists(root, rel) {
7
- return fs.existsSync(path.join(root, rel));
8
- }
9
-
10
- function readJsonSafe(filePath) {
11
- try {
12
- return JSON.parse(fs.readFileSync(filePath, "utf8"));
13
- } catch {
14
- return null;
15
- }
16
- }
17
-
18
- function readFileSafe(filePath) {
19
- try {
20
- return fs.readFileSync(filePath, "utf8");
21
- } catch {
22
- return "";
23
- }
24
- }
25
-
26
- // ============================================================================
27
- // FRAMEWORK DETECTION
28
- // ============================================================================
29
- const FRAMEWORKS = {
30
- nextjs: {
31
- name: "Next.js",
32
- icon: "▲",
33
- category: "frontend",
34
- detect: (root, pkg) => {
35
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
36
- return !!(deps.next || fileExists(root, "next.config.js") ||
37
- fileExists(root, "next.config.mjs") || fileExists(root, "next.config.ts"));
38
- },
39
- checks: ["routes", "auth", "api", "security", "ssr"],
40
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH"],
41
- },
42
- remix: {
43
- name: "Remix",
44
- icon: "💿",
45
- category: "frontend",
46
- detect: (root, pkg) => {
47
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
48
- return !!deps["@remix-run/node"] || !!deps["@remix-run/react"];
49
- },
50
- checks: ["routes", "auth", "api", "security", "loaders"],
51
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
52
- },
53
- nuxt: {
54
- name: "Nuxt",
55
- icon: "💚",
56
- category: "frontend",
57
- detect: (root, pkg) => {
58
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
59
- return !!deps.nuxt || fileExists(root, "nuxt.config.ts") || fileExists(root, "nuxt.config.js");
60
- },
61
- checks: ["routes", "auth", "api", "security"],
62
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
63
- },
64
- sveltekit: {
65
- name: "SvelteKit",
66
- icon: "🔥",
67
- category: "frontend",
68
- detect: (root, pkg) => {
69
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
70
- return !!deps["@sveltejs/kit"] || fileExists(root, "svelte.config.js");
71
- },
72
- checks: ["routes", "auth", "api", "security"],
73
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
74
- },
75
- react: {
76
- name: "React",
77
- icon: "⚛️",
78
- category: "frontend",
79
- detect: (root, pkg) => {
80
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
81
- return !!deps.react && !FRAMEWORKS.nextjs.detect(root, pkg) && !FRAMEWORKS.remix.detect(root, pkg);
82
- },
83
- checks: ["components", "api", "security"],
84
- invariants: ["INV_NO_FAKE_SUCCESS"],
85
- },
86
- vue: {
87
- name: "Vue",
88
- icon: "💚",
89
- category: "frontend",
90
- detect: (root, pkg) => {
91
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
92
- return !!deps.vue && !FRAMEWORKS.nuxt.detect(root, pkg);
93
- },
94
- checks: ["components", "api", "security"],
95
- invariants: ["INV_NO_FAKE_SUCCESS"],
96
- },
97
- fastify: {
98
- name: "Fastify",
99
- icon: "⚡",
100
- category: "backend",
101
- detect: (root, pkg) => {
102
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
103
- return !!deps.fastify;
104
- },
105
- checks: ["routes", "auth", "api", "security", "webhooks"],
106
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH", "INV_NO_SILENT_CATCH_AUTH"],
107
- },
108
- express: {
109
- name: "Express",
110
- icon: "🚂",
111
- category: "backend",
112
- detect: (root, pkg) => {
113
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
114
- return !!deps.express;
115
- },
116
- checks: ["routes", "auth", "api", "security"],
117
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH"],
118
- },
119
- hono: {
120
- name: "Hono",
121
- icon: "🔥",
122
- category: "backend",
123
- detect: (root, pkg) => {
124
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
125
- return !!deps.hono;
126
- },
127
- checks: ["routes", "auth", "api", "security"],
128
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
129
- },
130
- nestjs: {
131
- name: "NestJS",
132
- icon: "🐈",
133
- category: "backend",
134
- detect: (root, pkg) => {
135
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
136
- return !!deps["@nestjs/core"];
137
- },
138
- checks: ["routes", "auth", "api", "security", "guards"],
139
- invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH"],
140
- },
141
- };
142
-
143
- // ============================================================================
144
- // DATABASE DETECTION
145
- // ============================================================================
146
- const DATABASES = {
147
- prisma: {
148
- name: "Prisma",
149
- icon: "◮",
150
- detect: (root, pkg) => {
151
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
152
- return !!deps["@prisma/client"] || fileExists(root, "prisma/schema.prisma");
153
- },
154
- checks: ["migrations", "models"],
155
- },
156
- drizzle: {
157
- name: "Drizzle",
158
- icon: "💧",
159
- detect: (root, pkg) => {
160
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
161
- return !!deps["drizzle-orm"] || fileExists(root, "drizzle.config.ts");
162
- },
163
- checks: ["migrations", "schema"],
164
- },
165
- mongoose: {
166
- name: "Mongoose",
167
- icon: "🍃",
168
- detect: (root, pkg) => {
169
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
170
- return !!deps.mongoose;
171
- },
172
- checks: ["models", "connections"],
173
- },
174
- supabase: {
175
- name: "Supabase",
176
- icon: "⚡",
177
- detect: (root, pkg) => {
178
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
179
- return !!deps["@supabase/supabase-js"];
180
- },
181
- checks: ["rls", "auth", "realtime"],
182
- },
183
- firebase: {
184
- name: "Firebase",
185
- icon: "🔥",
186
- detect: (root, pkg) => {
187
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
188
- return !!deps.firebase || !!deps["firebase-admin"];
189
- },
190
- checks: ["rules", "auth", "functions"],
191
- },
192
- redis: {
193
- name: "Redis",
194
- icon: "🔴",
195
- detect: (root, pkg) => {
196
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
197
- return !!deps.redis || !!deps.ioredis || !!deps["@upstash/redis"];
198
- },
199
- checks: ["connections", "caching"],
200
- },
201
- };
202
-
203
- // ============================================================================
204
- // AUTH PROVIDER DETECTION
205
- // ============================================================================
206
- const AUTH_PROVIDERS = {
207
- nextauth: {
208
- name: "NextAuth.js",
209
- icon: "🔐",
210
- detect: (root, pkg) => {
211
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
212
- return !!deps["next-auth"] || !!deps["@auth/core"];
213
- },
214
- checks: ["session", "providers", "callbacks"],
215
- },
216
- clerk: {
217
- name: "Clerk",
218
- icon: "🔑",
219
- detect: (root, pkg) => {
220
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
221
- return !!deps["@clerk/nextjs"] || !!deps["@clerk/clerk-sdk-node"];
222
- },
223
- checks: ["middleware", "organizations"],
224
- },
225
- auth0: {
226
- name: "Auth0",
227
- icon: "🔒",
228
- detect: (root, pkg) => {
229
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
230
- return !!deps["@auth0/nextjs-auth0"] || !!deps["auth0"];
231
- },
232
- checks: ["rules", "actions", "rbac"],
233
- },
234
- lucia: {
235
- name: "Lucia",
236
- icon: "🔐",
237
- detect: (root, pkg) => {
238
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
239
- return !!deps.lucia;
240
- },
241
- checks: ["sessions", "adapters"],
242
- },
243
- passport: {
244
- name: "Passport.js",
245
- icon: "🛂",
246
- detect: (root, pkg) => {
247
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
248
- return !!deps.passport;
249
- },
250
- checks: ["strategies", "serialization"],
251
- },
252
- };
253
-
254
- // ============================================================================
255
- // PAYMENT PROVIDER DETECTION
256
- // ============================================================================
257
- const PAYMENT_PROVIDERS = {
258
- stripe: {
259
- name: "Stripe",
260
- icon: "💳",
261
- detect: (root, pkg) => {
262
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
263
- return !!deps.stripe || !!deps["@stripe/stripe-js"];
264
- },
265
- checks: ["webhooks", "idempotency", "signature"],
266
- invariants: ["INV_NO_BYPASS_ENTITLEMENTS", "INV_STRIPE_WEBHOOK_VERIFIED", "INV_STRIPE_IDEMPOTENT"],
267
- },
268
- lemonSqueezy: {
269
- name: "Lemon Squeezy",
270
- icon: "🍋",
271
- detect: (root, pkg) => {
272
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
273
- return !!deps["@lemonsqueezy/lemonsqueezy.js"];
274
- },
275
- checks: ["webhooks", "subscriptions"],
276
- },
277
- paddle: {
278
- name: "Paddle",
279
- icon: "🏓",
280
- detect: (root, pkg) => {
281
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
282
- return !!deps["@paddle/paddle-js"];
283
- },
284
- checks: ["webhooks", "subscriptions"],
285
- },
286
- };
287
-
288
- // ============================================================================
289
- // CI/CD DETECTION
290
- // ============================================================================
291
- const CI_PROVIDERS = {
292
- githubActions: {
293
- name: "GitHub Actions",
294
- icon: "🐙",
295
- detect: (root) => fileExists(root, ".github/workflows") &&
296
- fs.readdirSync(path.join(root, ".github/workflows")).some(f => f.endsWith(".yml") || f.endsWith(".yaml")),
297
- configPath: ".github/workflows/vibecheck.yml",
298
- },
299
- gitlabCI: {
300
- name: "GitLab CI",
301
- icon: "🦊",
302
- detect: (root) => fileExists(root, ".gitlab-ci.yml"),
303
- configPath: ".gitlab-ci.yml",
304
- },
305
- circleci: {
306
- name: "CircleCI",
307
- icon: "⭕",
308
- detect: (root) => fileExists(root, ".circleci/config.yml"),
309
- configPath: ".circleci/config.yml",
310
- },
311
- jenkins: {
312
- name: "Jenkins",
313
- icon: "🎩",
314
- detect: (root) => fileExists(root, "Jenkinsfile"),
315
- configPath: "Jenkinsfile",
316
- },
317
- };
318
-
319
- // ============================================================================
320
- // DEPLOYMENT PLATFORM DETECTION
321
- // ============================================================================
322
- const DEPLOY_PLATFORMS = {
323
- vercel: {
324
- name: "Vercel",
325
- icon: "▲",
326
- detect: (root) => fileExists(root, "vercel.json") || fileExists(root, ".vercel"),
327
- configPath: "vercel.json",
328
- },
329
- netlify: {
330
- name: "Netlify",
331
- icon: "◆",
332
- detect: (root) => fileExists(root, "netlify.toml"),
333
- configPath: "netlify.toml",
334
- },
335
- railway: {
336
- name: "Railway",
337
- icon: "🚂",
338
- detect: (root) => fileExists(root, "railway.toml") || fileExists(root, "railway.json"),
339
- configPath: "railway.toml",
340
- },
341
- render: {
342
- name: "Render",
343
- icon: "🎨",
344
- detect: (root) => fileExists(root, "render.yaml"),
345
- configPath: "render.yaml",
346
- },
347
- fly: {
348
- name: "Fly.io",
349
- icon: "🪁",
350
- detect: (root) => fileExists(root, "fly.toml"),
351
- configPath: "fly.toml",
352
- },
353
- docker: {
354
- name: "Docker",
355
- icon: "🐳",
356
- detect: (root) => fileExists(root, "Dockerfile") || fileExists(root, "docker-compose.yml"),
357
- configPath: "Dockerfile",
358
- },
359
- kubernetes: {
360
- name: "Kubernetes",
361
- icon: "☸️",
362
- detect: (root) => fileExists(root, "k8s") || fileExists(root, "kubernetes") ||
363
- fileExists(root, "charts") || fileExists(root, "helm"),
364
- configPath: "k8s/",
365
- },
366
- };
367
-
368
- // ============================================================================
369
- // TESTING FRAMEWORK DETECTION
370
- // ============================================================================
371
- const TEST_FRAMEWORKS = {
372
- jest: {
373
- name: "Jest",
374
- icon: "🃏",
375
- detect: (root, pkg) => {
376
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
377
- return !!deps.jest || fileExists(root, "jest.config.js") || fileExists(root, "jest.config.ts");
378
- },
379
- },
380
- vitest: {
381
- name: "Vitest",
382
- icon: "⚡",
383
- detect: (root, pkg) => {
384
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
385
- return !!deps.vitest || fileExists(root, "vitest.config.ts");
386
- },
387
- },
388
- playwright: {
389
- name: "Playwright",
390
- icon: "🎭",
391
- detect: (root, pkg) => {
392
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
393
- return !!deps["@playwright/test"] || fileExists(root, "playwright.config.ts");
394
- },
395
- },
396
- cypress: {
397
- name: "Cypress",
398
- icon: "🌲",
399
- detect: (root, pkg) => {
400
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
401
- return !!deps.cypress || fileExists(root, "cypress.config.ts") || fileExists(root, "cypress.config.js");
402
- },
403
- },
404
- };
405
-
406
- // ============================================================================
407
- // SECURITY TOOLS DETECTION
408
- // ============================================================================
409
- const SECURITY_TOOLS = {
410
- eslintSecurity: {
411
- name: "ESLint Security",
412
- icon: "🔒",
413
- detect: (root, pkg) => {
414
- const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
415
- return !!deps["eslint-plugin-security"];
416
- },
417
- },
418
- snyk: {
419
- name: "Snyk",
420
- icon: "🐕",
421
- detect: (root) => fileExists(root, ".snyk"),
422
- },
423
- dependabot: {
424
- name: "Dependabot",
425
- icon: "🤖",
426
- detect: (root) => fileExists(root, ".github/dependabot.yml"),
427
- },
428
- codeql: {
429
- name: "CodeQL",
430
- icon: "🔬",
431
- detect: (root) => {
432
- const workflowDir = path.join(root, ".github/workflows");
433
- if (!fs.existsSync(workflowDir)) return false;
434
- const files = fs.readdirSync(workflowDir);
435
- return files.some(f => {
436
- const content = readFileSafe(path.join(workflowDir, f));
437
- return content.includes("codeql") || content.includes("CodeQL");
438
- });
439
- },
440
- },
441
- };
442
-
443
- // ============================================================================
444
- // COMPREHENSIVE DETECTION
445
- // ============================================================================
446
- function detectAll(root) {
447
- const pkgPath = path.join(root, "package.json");
448
- const pkg = readJsonSafe(pkgPath);
449
-
450
- if (!pkg) {
451
- return { error: "No package.json found", pkg: null };
452
- }
453
-
454
- const result = {
455
- pkg,
456
- packageManager: detectPackageManager(root),
457
- monorepo: detectMonorepo(root, pkg),
458
- frameworks: {},
459
- databases: {},
460
- auth: {},
461
- payments: {},
462
- ci: {},
463
- deploy: {},
464
- testing: {},
465
- security: {},
466
- };
467
-
468
- // Detect all categories
469
- for (const [key, fw] of Object.entries(FRAMEWORKS)) {
470
- if (fw.detect(root, pkg)) {
471
- result.frameworks[key] = { ...fw, detected: true };
472
- }
473
- }
474
-
475
- for (const [key, db] of Object.entries(DATABASES)) {
476
- if (db.detect(root, pkg)) {
477
- result.databases[key] = { ...db, detected: true };
478
- }
479
- }
480
-
481
- for (const [key, auth] of Object.entries(AUTH_PROVIDERS)) {
482
- if (auth.detect(root, pkg)) {
483
- result.auth[key] = { ...auth, detected: true };
484
- }
485
- }
486
-
487
- for (const [key, pay] of Object.entries(PAYMENT_PROVIDERS)) {
488
- if (pay.detect(root, pkg)) {
489
- result.payments[key] = { ...pay, detected: true };
490
- }
491
- }
492
-
493
- for (const [key, ci] of Object.entries(CI_PROVIDERS)) {
494
- if (ci.detect(root)) {
495
- result.ci[key] = { ...ci, detected: true };
496
- }
497
- }
498
-
499
- for (const [key, dp] of Object.entries(DEPLOY_PLATFORMS)) {
500
- if (dp.detect(root)) {
501
- result.deploy[key] = { ...dp, detected: true };
502
- }
503
- }
504
-
505
- for (const [key, tf] of Object.entries(TEST_FRAMEWORKS)) {
506
- if (tf.detect(root, pkg)) {
507
- result.testing[key] = { ...tf, detected: true };
508
- }
509
- }
510
-
511
- for (const [key, st] of Object.entries(SECURITY_TOOLS)) {
512
- if (st.detect(root, pkg)) {
513
- result.security[key] = { ...st, detected: true };
514
- }
515
- }
516
-
517
- // Aggregate invariants from detected frameworks and payments
518
- result.invariants = aggregateInvariants(result);
519
-
520
- // Aggregate checks
521
- result.checks = aggregateChecks(result);
522
-
523
- return result;
524
- }
525
-
526
- function detectPackageManager(root) {
527
- if (fileExists(root, "pnpm-lock.yaml")) return "pnpm";
528
- if (fileExists(root, "yarn.lock")) return "yarn";
529
- if (fileExists(root, "bun.lockb")) return "bun";
530
- if (fileExists(root, "package-lock.json")) return "npm";
531
- return "npm";
532
- }
533
-
534
- function detectMonorepo(root, pkg) {
535
- const indicators = {
536
- pnpmWorkspaces: fileExists(root, "pnpm-workspace.yaml"),
537
- lernaConfig: fileExists(root, "lerna.json"),
538
- nxConfig: fileExists(root, "nx.json"),
539
- turborepo: fileExists(root, "turbo.json"),
540
- workspaces: Array.isArray(pkg?.workspaces) || typeof pkg?.workspaces === "object",
541
- };
542
-
543
- const isMonorepo = Object.values(indicators).some(Boolean);
544
- return { isMonorepo, indicators };
545
- }
546
-
547
- function aggregateInvariants(detection) {
548
- const invariants = new Set();
549
-
550
- // From frameworks
551
- for (const fw of Object.values(detection.frameworks)) {
552
- if (fw.invariants) {
553
- fw.invariants.forEach(inv => invariants.add(inv));
554
- }
555
- }
556
-
557
- // From payments
558
- for (const pay of Object.values(detection.payments)) {
559
- if (pay.invariants) {
560
- pay.invariants.forEach(inv => invariants.add(inv));
561
- }
562
- }
563
-
564
- // Always include base invariants
565
- invariants.add("INV_NO_HARDCODED_SECRETS");
566
-
567
- return Array.from(invariants).sort();
568
- }
569
-
570
- function aggregateChecks(detection) {
571
- const checks = new Set(["security", "quality"]);
572
-
573
- for (const fw of Object.values(detection.frameworks)) {
574
- if (fw.checks) {
575
- fw.checks.forEach(c => checks.add(c));
576
- }
577
- }
578
-
579
- for (const db of Object.values(detection.databases)) {
580
- if (db.checks) {
581
- db.checks.forEach(c => checks.add(c));
582
- }
583
- }
584
-
585
- return Array.from(checks).sort();
586
- }
587
-
588
- // ============================================================================
589
- // EXPORTS
590
- // ============================================================================
591
- module.exports = {
592
- detectAll,
593
- detectPackageManager,
594
- detectMonorepo,
595
- FRAMEWORKS,
596
- DATABASES,
597
- AUTH_PROVIDERS,
598
- PAYMENT_PROVIDERS,
599
- CI_PROVIDERS,
600
- DEPLOY_PLATFORMS,
601
- TEST_FRAMEWORKS,
602
- SECURITY_TOOLS,
603
- };
1
+ // bin/runners/lib/enterprise-detect.js
2
+ // Enterprise-grade project detection: frameworks, databases, cloud, CI/CD, security tools
3
+ const fs = require("fs");
4
+ const path = require("path");
5
+
6
+ function fileExists(root, rel) {
7
+ return fs.existsSync(path.join(root, rel));
8
+ }
9
+
10
+ function readJsonSafe(filePath) {
11
+ try {
12
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
13
+ } catch {
14
+ return null;
15
+ }
16
+ }
17
+
18
+ function readFileSafe(filePath) {
19
+ try {
20
+ return fs.readFileSync(filePath, "utf8");
21
+ } catch {
22
+ return "";
23
+ }
24
+ }
25
+
26
+ // ============================================================================
27
+ // FRAMEWORK DETECTION
28
+ // ============================================================================
29
+ const FRAMEWORKS = {
30
+ nextjs: {
31
+ name: "Next.js",
32
+ icon: "▲",
33
+ category: "frontend",
34
+ detect: (root, pkg) => {
35
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
36
+ return !!(deps.next || fileExists(root, "next.config.js") ||
37
+ fileExists(root, "next.config.mjs") || fileExists(root, "next.config.ts"));
38
+ },
39
+ checks: ["routes", "auth", "api", "security", "ssr"],
40
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH"],
41
+ },
42
+ remix: {
43
+ name: "Remix",
44
+ icon: "💿",
45
+ category: "frontend",
46
+ detect: (root, pkg) => {
47
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
48
+ return !!deps["@remix-run/node"] || !!deps["@remix-run/react"];
49
+ },
50
+ checks: ["routes", "auth", "api", "security", "loaders"],
51
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
52
+ },
53
+ nuxt: {
54
+ name: "Nuxt",
55
+ icon: "💚",
56
+ category: "frontend",
57
+ detect: (root, pkg) => {
58
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
59
+ return !!deps.nuxt || fileExists(root, "nuxt.config.ts") || fileExists(root, "nuxt.config.js");
60
+ },
61
+ checks: ["routes", "auth", "api", "security"],
62
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
63
+ },
64
+ sveltekit: {
65
+ name: "SvelteKit",
66
+ icon: "🔥",
67
+ category: "frontend",
68
+ detect: (root, pkg) => {
69
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
70
+ return !!deps["@sveltejs/kit"] || fileExists(root, "svelte.config.js");
71
+ },
72
+ checks: ["routes", "auth", "api", "security"],
73
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
74
+ },
75
+ react: {
76
+ name: "React",
77
+ icon: "⚛️",
78
+ category: "frontend",
79
+ detect: (root, pkg) => {
80
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
81
+ return !!deps.react && !FRAMEWORKS.nextjs.detect(root, pkg) && !FRAMEWORKS.remix.detect(root, pkg);
82
+ },
83
+ checks: ["components", "api", "security"],
84
+ invariants: ["INV_NO_FAKE_SUCCESS"],
85
+ },
86
+ vue: {
87
+ name: "Vue",
88
+ icon: "💚",
89
+ category: "frontend",
90
+ detect: (root, pkg) => {
91
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
92
+ return !!deps.vue && !FRAMEWORKS.nuxt.detect(root, pkg);
93
+ },
94
+ checks: ["components", "api", "security"],
95
+ invariants: ["INV_NO_FAKE_SUCCESS"],
96
+ },
97
+ fastify: {
98
+ name: "Fastify",
99
+ icon: "⚡",
100
+ category: "backend",
101
+ detect: (root, pkg) => {
102
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
103
+ return !!deps.fastify;
104
+ },
105
+ checks: ["routes", "auth", "api", "security", "webhooks"],
106
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH", "INV_NO_SILENT_CATCH_AUTH"],
107
+ },
108
+ express: {
109
+ name: "Express",
110
+ icon: "🚂",
111
+ category: "backend",
112
+ detect: (root, pkg) => {
113
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
114
+ return !!deps.express;
115
+ },
116
+ checks: ["routes", "auth", "api", "security"],
117
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH"],
118
+ },
119
+ hono: {
120
+ name: "Hono",
121
+ icon: "🔥",
122
+ category: "backend",
123
+ detect: (root, pkg) => {
124
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
125
+ return !!deps.hono;
126
+ },
127
+ checks: ["routes", "auth", "api", "security"],
128
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION"],
129
+ },
130
+ nestjs: {
131
+ name: "NestJS",
132
+ icon: "🐈",
133
+ category: "backend",
134
+ detect: (root, pkg) => {
135
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
136
+ return !!deps["@nestjs/core"];
137
+ },
138
+ checks: ["routes", "auth", "api", "security", "guards"],
139
+ invariants: ["INV_NO_FAKE_SUCCESS", "INV_NO_ROUTE_INVENTION", "INV_NO_GHOST_AUTH"],
140
+ },
141
+ };
142
+
143
+ // ============================================================================
144
+ // DATABASE DETECTION
145
+ // ============================================================================
146
+ const DATABASES = {
147
+ prisma: {
148
+ name: "Prisma",
149
+ icon: "◮",
150
+ detect: (root, pkg) => {
151
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
152
+ return !!deps["@prisma/client"] || fileExists(root, "prisma/schema.prisma");
153
+ },
154
+ checks: ["migrations", "models"],
155
+ },
156
+ drizzle: {
157
+ name: "Drizzle",
158
+ icon: "💧",
159
+ detect: (root, pkg) => {
160
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
161
+ return !!deps["drizzle-orm"] || fileExists(root, "drizzle.config.ts");
162
+ },
163
+ checks: ["migrations", "schema"],
164
+ },
165
+ mongoose: {
166
+ name: "Mongoose",
167
+ icon: "🍃",
168
+ detect: (root, pkg) => {
169
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
170
+ return !!deps.mongoose;
171
+ },
172
+ checks: ["models", "connections"],
173
+ },
174
+ supabase: {
175
+ name: "Supabase",
176
+ icon: "⚡",
177
+ detect: (root, pkg) => {
178
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
179
+ return !!deps["@supabase/supabase-js"];
180
+ },
181
+ checks: ["rls", "auth", "realtime"],
182
+ },
183
+ firebase: {
184
+ name: "Firebase",
185
+ icon: "🔥",
186
+ detect: (root, pkg) => {
187
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
188
+ return !!deps.firebase || !!deps["firebase-admin"];
189
+ },
190
+ checks: ["rules", "auth", "functions"],
191
+ },
192
+ redis: {
193
+ name: "Redis",
194
+ icon: "🔴",
195
+ detect: (root, pkg) => {
196
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
197
+ return !!deps.redis || !!deps.ioredis || !!deps["@upstash/redis"];
198
+ },
199
+ checks: ["connections", "caching"],
200
+ },
201
+ };
202
+
203
+ // ============================================================================
204
+ // AUTH PROVIDER DETECTION
205
+ // ============================================================================
206
+ const AUTH_PROVIDERS = {
207
+ nextauth: {
208
+ name: "NextAuth.js",
209
+ icon: "🔐",
210
+ detect: (root, pkg) => {
211
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
212
+ return !!deps["next-auth"] || !!deps["@auth/core"];
213
+ },
214
+ checks: ["session", "providers", "callbacks"],
215
+ },
216
+ clerk: {
217
+ name: "Clerk",
218
+ icon: "🔑",
219
+ detect: (root, pkg) => {
220
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
221
+ return !!deps["@clerk/nextjs"] || !!deps["@clerk/clerk-sdk-node"];
222
+ },
223
+ checks: ["middleware", "organizations"],
224
+ },
225
+ auth0: {
226
+ name: "Auth0",
227
+ icon: "🔒",
228
+ detect: (root, pkg) => {
229
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
230
+ return !!deps["@auth0/nextjs-auth0"] || !!deps["auth0"];
231
+ },
232
+ checks: ["rules", "actions", "rbac"],
233
+ },
234
+ lucia: {
235
+ name: "Lucia",
236
+ icon: "🔐",
237
+ detect: (root, pkg) => {
238
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
239
+ return !!deps.lucia;
240
+ },
241
+ checks: ["sessions", "adapters"],
242
+ },
243
+ passport: {
244
+ name: "Passport.js",
245
+ icon: "🛂",
246
+ detect: (root, pkg) => {
247
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
248
+ return !!deps.passport;
249
+ },
250
+ checks: ["strategies", "serialization"],
251
+ },
252
+ };
253
+
254
+ // ============================================================================
255
+ // PAYMENT PROVIDER DETECTION
256
+ // ============================================================================
257
+ const PAYMENT_PROVIDERS = {
258
+ stripe: {
259
+ name: "Stripe",
260
+ icon: "💳",
261
+ detect: (root, pkg) => {
262
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
263
+ return !!deps.stripe || !!deps["@stripe/stripe-js"];
264
+ },
265
+ checks: ["webhooks", "idempotency", "signature"],
266
+ invariants: ["INV_NO_BYPASS_ENTITLEMENTS", "INV_STRIPE_WEBHOOK_VERIFIED", "INV_STRIPE_IDEMPOTENT"],
267
+ },
268
+ lemonSqueezy: {
269
+ name: "Lemon Squeezy",
270
+ icon: "🍋",
271
+ detect: (root, pkg) => {
272
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
273
+ return !!deps["@lemonsqueezy/lemonsqueezy.js"];
274
+ },
275
+ checks: ["webhooks", "subscriptions"],
276
+ },
277
+ paddle: {
278
+ name: "Paddle",
279
+ icon: "🏓",
280
+ detect: (root, pkg) => {
281
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
282
+ return !!deps["@paddle/paddle-js"];
283
+ },
284
+ checks: ["webhooks", "subscriptions"],
285
+ },
286
+ };
287
+
288
+ // ============================================================================
289
+ // CI/CD DETECTION
290
+ // ============================================================================
291
+ const CI_PROVIDERS = {
292
+ githubActions: {
293
+ name: "GitHub Actions",
294
+ icon: "🐙",
295
+ detect: (root) => fileExists(root, ".github/workflows") &&
296
+ fs.readdirSync(path.join(root, ".github/workflows")).some(f => f.endsWith(".yml") || f.endsWith(".yaml")),
297
+ configPath: ".github/workflows/vibecheck.yml",
298
+ },
299
+ gitlabCI: {
300
+ name: "GitLab CI",
301
+ icon: "🦊",
302
+ detect: (root) => fileExists(root, ".gitlab-ci.yml"),
303
+ configPath: ".gitlab-ci.yml",
304
+ },
305
+ circleci: {
306
+ name: "CircleCI",
307
+ icon: "⭕",
308
+ detect: (root) => fileExists(root, ".circleci/config.yml"),
309
+ configPath: ".circleci/config.yml",
310
+ },
311
+ jenkins: {
312
+ name: "Jenkins",
313
+ icon: "🎩",
314
+ detect: (root) => fileExists(root, "Jenkinsfile"),
315
+ configPath: "Jenkinsfile",
316
+ },
317
+ };
318
+
319
+ // ============================================================================
320
+ // DEPLOYMENT PLATFORM DETECTION
321
+ // ============================================================================
322
+ const DEPLOY_PLATFORMS = {
323
+ vercel: {
324
+ name: "Vercel",
325
+ icon: "▲",
326
+ detect: (root) => fileExists(root, "vercel.json") || fileExists(root, ".vercel"),
327
+ configPath: "vercel.json",
328
+ },
329
+ netlify: {
330
+ name: "Netlify",
331
+ icon: "◆",
332
+ detect: (root) => fileExists(root, "netlify.toml"),
333
+ configPath: "netlify.toml",
334
+ },
335
+ railway: {
336
+ name: "Railway",
337
+ icon: "🚂",
338
+ detect: (root) => fileExists(root, "railway.toml") || fileExists(root, "railway.json"),
339
+ configPath: "railway.toml",
340
+ },
341
+ render: {
342
+ name: "Render",
343
+ icon: "🎨",
344
+ detect: (root) => fileExists(root, "render.yaml"),
345
+ configPath: "render.yaml",
346
+ },
347
+ fly: {
348
+ name: "Fly.io",
349
+ icon: "🪁",
350
+ detect: (root) => fileExists(root, "fly.toml"),
351
+ configPath: "fly.toml",
352
+ },
353
+ docker: {
354
+ name: "Docker",
355
+ icon: "🐳",
356
+ detect: (root) => fileExists(root, "Dockerfile") || fileExists(root, "docker-compose.yml"),
357
+ configPath: "Dockerfile",
358
+ },
359
+ kubernetes: {
360
+ name: "Kubernetes",
361
+ icon: "☸️",
362
+ detect: (root) => fileExists(root, "k8s") || fileExists(root, "kubernetes") ||
363
+ fileExists(root, "charts") || fileExists(root, "helm"),
364
+ configPath: "k8s/",
365
+ },
366
+ };
367
+
368
+ // ============================================================================
369
+ // TESTING FRAMEWORK DETECTION
370
+ // ============================================================================
371
+ const TEST_FRAMEWORKS = {
372
+ jest: {
373
+ name: "Jest",
374
+ icon: "🃏",
375
+ detect: (root, pkg) => {
376
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
377
+ return !!deps.jest || fileExists(root, "jest.config.js") || fileExists(root, "jest.config.ts");
378
+ },
379
+ },
380
+ vitest: {
381
+ name: "Vitest",
382
+ icon: "⚡",
383
+ detect: (root, pkg) => {
384
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
385
+ return !!deps.vitest || fileExists(root, "vitest.config.ts");
386
+ },
387
+ },
388
+ playwright: {
389
+ name: "Playwright",
390
+ icon: "🎭",
391
+ detect: (root, pkg) => {
392
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
393
+ return !!deps["@playwright/test"] || fileExists(root, "playwright.config.ts");
394
+ },
395
+ },
396
+ cypress: {
397
+ name: "Cypress",
398
+ icon: "🌲",
399
+ detect: (root, pkg) => {
400
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
401
+ return !!deps.cypress || fileExists(root, "cypress.config.ts") || fileExists(root, "cypress.config.js");
402
+ },
403
+ },
404
+ };
405
+
406
+ // ============================================================================
407
+ // SECURITY TOOLS DETECTION
408
+ // ============================================================================
409
+ const SECURITY_TOOLS = {
410
+ eslintSecurity: {
411
+ name: "ESLint Security",
412
+ icon: "🔒",
413
+ detect: (root, pkg) => {
414
+ const deps = { ...(pkg?.dependencies || {}), ...(pkg?.devDependencies || {}) };
415
+ return !!deps["eslint-plugin-security"];
416
+ },
417
+ },
418
+ snyk: {
419
+ name: "Snyk",
420
+ icon: "🐕",
421
+ detect: (root) => fileExists(root, ".snyk"),
422
+ },
423
+ dependabot: {
424
+ name: "Dependabot",
425
+ icon: "🤖",
426
+ detect: (root) => fileExists(root, ".github/dependabot.yml"),
427
+ },
428
+ codeql: {
429
+ name: "CodeQL",
430
+ icon: "🔬",
431
+ detect: (root) => {
432
+ const workflowDir = path.join(root, ".github/workflows");
433
+ if (!fs.existsSync(workflowDir)) return false;
434
+ const files = fs.readdirSync(workflowDir);
435
+ return files.some(f => {
436
+ const content = readFileSafe(path.join(workflowDir, f));
437
+ return content.includes("codeql") || content.includes("CodeQL");
438
+ });
439
+ },
440
+ },
441
+ };
442
+
443
+ // ============================================================================
444
+ // COMPREHENSIVE DETECTION
445
+ // ============================================================================
446
+ function detectAll(root) {
447
+ const pkgPath = path.join(root, "package.json");
448
+ const pkg = readJsonSafe(pkgPath);
449
+
450
+ if (!pkg) {
451
+ return { error: "No package.json found", pkg: null };
452
+ }
453
+
454
+ const result = {
455
+ pkg,
456
+ packageManager: detectPackageManager(root),
457
+ monorepo: detectMonorepo(root, pkg),
458
+ frameworks: {},
459
+ databases: {},
460
+ auth: {},
461
+ payments: {},
462
+ ci: {},
463
+ deploy: {},
464
+ testing: {},
465
+ security: {},
466
+ };
467
+
468
+ // Detect all categories
469
+ for (const [key, fw] of Object.entries(FRAMEWORKS)) {
470
+ if (fw.detect(root, pkg)) {
471
+ result.frameworks[key] = { ...fw, detected: true };
472
+ }
473
+ }
474
+
475
+ for (const [key, db] of Object.entries(DATABASES)) {
476
+ if (db.detect(root, pkg)) {
477
+ result.databases[key] = { ...db, detected: true };
478
+ }
479
+ }
480
+
481
+ for (const [key, auth] of Object.entries(AUTH_PROVIDERS)) {
482
+ if (auth.detect(root, pkg)) {
483
+ result.auth[key] = { ...auth, detected: true };
484
+ }
485
+ }
486
+
487
+ for (const [key, pay] of Object.entries(PAYMENT_PROVIDERS)) {
488
+ if (pay.detect(root, pkg)) {
489
+ result.payments[key] = { ...pay, detected: true };
490
+ }
491
+ }
492
+
493
+ for (const [key, ci] of Object.entries(CI_PROVIDERS)) {
494
+ if (ci.detect(root)) {
495
+ result.ci[key] = { ...ci, detected: true };
496
+ }
497
+ }
498
+
499
+ for (const [key, dp] of Object.entries(DEPLOY_PLATFORMS)) {
500
+ if (dp.detect(root)) {
501
+ result.deploy[key] = { ...dp, detected: true };
502
+ }
503
+ }
504
+
505
+ for (const [key, tf] of Object.entries(TEST_FRAMEWORKS)) {
506
+ if (tf.detect(root, pkg)) {
507
+ result.testing[key] = { ...tf, detected: true };
508
+ }
509
+ }
510
+
511
+ for (const [key, st] of Object.entries(SECURITY_TOOLS)) {
512
+ if (st.detect(root, pkg)) {
513
+ result.security[key] = { ...st, detected: true };
514
+ }
515
+ }
516
+
517
+ // Aggregate invariants from detected frameworks and payments
518
+ result.invariants = aggregateInvariants(result);
519
+
520
+ // Aggregate checks
521
+ result.checks = aggregateChecks(result);
522
+
523
+ return result;
524
+ }
525
+
526
+ function detectPackageManager(root) {
527
+ if (fileExists(root, "pnpm-lock.yaml")) return "pnpm";
528
+ if (fileExists(root, "yarn.lock")) return "yarn";
529
+ if (fileExists(root, "bun.lockb")) return "bun";
530
+ if (fileExists(root, "package-lock.json")) return "npm";
531
+ return "npm";
532
+ }
533
+
534
+ function detectMonorepo(root, pkg) {
535
+ const indicators = {
536
+ pnpmWorkspaces: fileExists(root, "pnpm-workspace.yaml"),
537
+ lernaConfig: fileExists(root, "lerna.json"),
538
+ nxConfig: fileExists(root, "nx.json"),
539
+ turborepo: fileExists(root, "turbo.json"),
540
+ workspaces: Array.isArray(pkg?.workspaces) || typeof pkg?.workspaces === "object",
541
+ };
542
+
543
+ const isMonorepo = Object.values(indicators).some(Boolean);
544
+ return { isMonorepo, indicators };
545
+ }
546
+
547
+ function aggregateInvariants(detection) {
548
+ const invariants = new Set();
549
+
550
+ // From frameworks
551
+ for (const fw of Object.values(detection.frameworks)) {
552
+ if (fw.invariants) {
553
+ fw.invariants.forEach(inv => invariants.add(inv));
554
+ }
555
+ }
556
+
557
+ // From payments
558
+ for (const pay of Object.values(detection.payments)) {
559
+ if (pay.invariants) {
560
+ pay.invariants.forEach(inv => invariants.add(inv));
561
+ }
562
+ }
563
+
564
+ // Always include base invariants
565
+ invariants.add("INV_NO_HARDCODED_SECRETS");
566
+
567
+ return Array.from(invariants).sort();
568
+ }
569
+
570
+ function aggregateChecks(detection) {
571
+ const checks = new Set(["security", "quality"]);
572
+
573
+ for (const fw of Object.values(detection.frameworks)) {
574
+ if (fw.checks) {
575
+ fw.checks.forEach(c => checks.add(c));
576
+ }
577
+ }
578
+
579
+ for (const db of Object.values(detection.databases)) {
580
+ if (db.checks) {
581
+ db.checks.forEach(c => checks.add(c));
582
+ }
583
+ }
584
+
585
+ return Array.from(checks).sort();
586
+ }
587
+
588
+ // ============================================================================
589
+ // EXPORTS
590
+ // ============================================================================
591
+ module.exports = {
592
+ detectAll,
593
+ detectPackageManager,
594
+ detectMonorepo,
595
+ FRAMEWORKS,
596
+ DATABASES,
597
+ AUTH_PROVIDERS,
598
+ PAYMENT_PROVIDERS,
599
+ CI_PROVIDERS,
600
+ DEPLOY_PLATFORMS,
601
+ TEST_FRAMEWORKS,
602
+ SECURITY_TOOLS,
603
+ };