prelude-context 1.0.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 (94) hide show
  1. package/README.md +354 -0
  2. package/dist/bin/prelude.d.ts +3 -0
  3. package/dist/bin/prelude.d.ts.map +1 -0
  4. package/dist/bin/prelude.js +35 -0
  5. package/dist/bin/prelude.js.map +1 -0
  6. package/dist/src/commands/decision.d.ts +3 -0
  7. package/dist/src/commands/decision.d.ts.map +1 -0
  8. package/dist/src/commands/decision.js +74 -0
  9. package/dist/src/commands/decision.js.map +1 -0
  10. package/dist/src/commands/export.d.ts +3 -0
  11. package/dist/src/commands/export.d.ts.map +1 -0
  12. package/dist/src/commands/export.js +132 -0
  13. package/dist/src/commands/export.js.map +1 -0
  14. package/dist/src/commands/init.d.ts +3 -0
  15. package/dist/src/commands/init.d.ts.map +1 -0
  16. package/dist/src/commands/init.js +80 -0
  17. package/dist/src/commands/init.js.map +1 -0
  18. package/dist/src/commands/share.d.ts +3 -0
  19. package/dist/src/commands/share.d.ts.map +1 -0
  20. package/dist/src/commands/share.js +133 -0
  21. package/dist/src/commands/share.js.map +1 -0
  22. package/dist/src/commands/watch.d.ts +3 -0
  23. package/dist/src/commands/watch.d.ts.map +1 -0
  24. package/dist/src/commands/watch.js +58 -0
  25. package/dist/src/commands/watch.js.map +1 -0
  26. package/dist/src/constants.d.ts +16 -0
  27. package/dist/src/constants.d.ts.map +1 -0
  28. package/dist/src/constants.js +49 -0
  29. package/dist/src/constants.js.map +1 -0
  30. package/dist/src/core/exporter.d.ts +4 -0
  31. package/dist/src/core/exporter.d.ts.map +1 -0
  32. package/dist/src/core/exporter.js +196 -0
  33. package/dist/src/core/exporter.js.map +1 -0
  34. package/dist/src/core/infer.d.ts +6 -0
  35. package/dist/src/core/infer.d.ts.map +1 -0
  36. package/dist/src/core/infer.js +900 -0
  37. package/dist/src/core/infer.js.map +1 -0
  38. package/dist/src/core/updater.d.ts +8 -0
  39. package/dist/src/core/updater.d.ts.map +1 -0
  40. package/dist/src/core/updater.js +120 -0
  41. package/dist/src/core/updater.js.map +1 -0
  42. package/dist/src/core/watcher.d.ts +15 -0
  43. package/dist/src/core/watcher.d.ts.map +1 -0
  44. package/dist/src/core/watcher.js +119 -0
  45. package/dist/src/core/watcher.js.map +1 -0
  46. package/dist/src/schema/architecture.d.ts +75 -0
  47. package/dist/src/schema/architecture.d.ts.map +1 -0
  48. package/dist/src/schema/architecture.js +24 -0
  49. package/dist/src/schema/architecture.js.map +1 -0
  50. package/dist/src/schema/constraints.d.ts +146 -0
  51. package/dist/src/schema/constraints.d.ts.map +1 -0
  52. package/dist/src/schema/constraints.js +39 -0
  53. package/dist/src/schema/constraints.js.map +1 -0
  54. package/dist/src/schema/decisions.d.ts +122 -0
  55. package/dist/src/schema/decisions.d.ts.map +1 -0
  56. package/dist/src/schema/decisions.js +23 -0
  57. package/dist/src/schema/decisions.js.map +1 -0
  58. package/dist/src/schema/index.d.ts +7 -0
  59. package/dist/src/schema/index.d.ts.map +1 -0
  60. package/dist/src/schema/index.js +7 -0
  61. package/dist/src/schema/index.js.map +1 -0
  62. package/dist/src/schema/project.d.ts +69 -0
  63. package/dist/src/schema/project.d.ts.map +1 -0
  64. package/dist/src/schema/project.js +24 -0
  65. package/dist/src/schema/project.js.map +1 -0
  66. package/dist/src/schema/session.d.ts +210 -0
  67. package/dist/src/schema/session.d.ts.map +1 -0
  68. package/dist/src/schema/session.js +26 -0
  69. package/dist/src/schema/session.js.map +1 -0
  70. package/dist/src/schema/stack.d.ts +58 -0
  71. package/dist/src/schema/stack.d.ts.map +1 -0
  72. package/dist/src/schema/stack.js +23 -0
  73. package/dist/src/schema/stack.js.map +1 -0
  74. package/dist/src/utils/fs.d.ts +10 -0
  75. package/dist/src/utils/fs.d.ts.map +1 -0
  76. package/dist/src/utils/fs.js +63 -0
  77. package/dist/src/utils/fs.js.map +1 -0
  78. package/dist/src/utils/log.d.ts +20 -0
  79. package/dist/src/utils/log.d.ts.map +1 -0
  80. package/dist/src/utils/log.js +45 -0
  81. package/dist/src/utils/log.js.map +1 -0
  82. package/dist/src/utils/time.d.ts +5 -0
  83. package/dist/src/utils/time.d.ts.map +1 -0
  84. package/dist/src/utils/time.js +33 -0
  85. package/dist/src/utils/time.js.map +1 -0
  86. package/package.json +64 -0
  87. package/schemas/architecture.schema.json +98 -0
  88. package/schemas/constraints.schema.json +161 -0
  89. package/schemas/decisions.schema.json +95 -0
  90. package/schemas/export.schema.json +46 -0
  91. package/schemas/project.schema.json +102 -0
  92. package/schemas/session.schema.json +101 -0
  93. package/schemas/stack.schema.json +103 -0
  94. package/spec.md +717 -0
@@ -0,0 +1,900 @@
1
+ import { readdir, readFile } from 'fs/promises';
2
+ import { join, basename, relative } from 'path';
3
+ import { fileExists, readJSON, getDirectoryTree } from '../utils/fs.js';
4
+ import { getCurrentTimestamp } from '../utils/time.js';
5
+ // --- ADD THESE CONSTANTS ---
6
+ const PRELUDE_VERSION = "1.0.0";
7
+ const SCHEMA_URL = "https://adjective.us/prelude/schemas/v1";
8
+ async function scanMonorepoPackages(rootDir) {
9
+ const packages = [];
10
+ // Check common monorepo locations
11
+ const locations = ['apps', 'packages', 'libs', 'services', 'tools'];
12
+ for (const location of locations) {
13
+ const locationPath = join(rootDir, location);
14
+ if (!(await fileExists(locationPath)))
15
+ continue;
16
+ try {
17
+ const entries = await readdir(locationPath, { withFileTypes: true });
18
+ for (const entry of entries) {
19
+ if (!entry.isDirectory())
20
+ continue;
21
+ const pkgPath = join(locationPath, entry.name, 'package.json');
22
+ if (await fileExists(pkgPath)) {
23
+ const pkg = await readJSON(pkgPath);
24
+ packages.push({
25
+ location: `${location}/${entry.name}`,
26
+ name: pkg.name || entry.name,
27
+ dependencies: pkg.dependencies,
28
+ devDependencies: pkg.devDependencies,
29
+ scripts: pkg.scripts
30
+ });
31
+ }
32
+ }
33
+ }
34
+ catch (error) {
35
+ // Skip if can't read directory
36
+ }
37
+ }
38
+ return packages;
39
+ }
40
+ function aggregateDependencies(packages) {
41
+ const allDeps = {};
42
+ for (const pkg of packages) {
43
+ Object.assign(allDeps, pkg.dependencies || {});
44
+ Object.assign(allDeps, pkg.devDependencies || {});
45
+ }
46
+ return allDeps;
47
+ }
48
+ async function detectDockerConfig(rootDir) {
49
+ const configs = [];
50
+ if (await fileExists(join(rootDir, 'Dockerfile')))
51
+ configs.push('Dockerfile');
52
+ if (await fileExists(join(rootDir, 'docker-compose.yml')))
53
+ configs.push('Docker Compose');
54
+ if (await fileExists(join(rootDir, '.dockerignore')))
55
+ configs.push('Docker optimized');
56
+ return configs;
57
+ }
58
+ async function detectEnvFiles(rootDir) {
59
+ const envFiles = [];
60
+ const patterns = ['.env', '.env.local', '.env.development', '.env.production', '.env.example', '.env.template'];
61
+ for (const pattern of patterns) {
62
+ if (await fileExists(join(rootDir, pattern))) {
63
+ envFiles.push(pattern);
64
+ }
65
+ }
66
+ return envFiles;
67
+ }
68
+ async function analyzeGitConfig(rootDir) {
69
+ const gitConfig = {};
70
+ if (await fileExists(join(rootDir, '.git'))) {
71
+ gitConfig.isGitRepo = true;
72
+ // Check for common git hooks
73
+ const hooksDir = join(rootDir, '.git/hooks');
74
+ if (await fileExists(hooksDir)) {
75
+ gitConfig.hasHooks = true;
76
+ }
77
+ }
78
+ // Check for GitHub-specific files
79
+ if (await fileExists(join(rootDir, '.github'))) {
80
+ gitConfig.github = true;
81
+ if (await fileExists(join(rootDir, '.github/workflows'))) {
82
+ gitConfig.githubActions = true;
83
+ }
84
+ if (await fileExists(join(rootDir, '.github/CODEOWNERS'))) {
85
+ gitConfig.hasCodeowners = true;
86
+ }
87
+ }
88
+ return gitConfig;
89
+ }
90
+ export async function inferProjectMetadata(rootDir) {
91
+ const packageJsonPath = join(rootDir, 'package.json');
92
+ const hasPackageJson = await fileExists(packageJsonPath);
93
+ let projectData = {};
94
+ if (hasPackageJson) {
95
+ projectData = await readJSON(packageJsonPath);
96
+ }
97
+ // Try to read README for better description
98
+ let description = projectData.description || 'No description provided';
99
+ const readmePath = join(rootDir, 'README.md');
100
+ if (await fileExists(readmePath)) {
101
+ try {
102
+ const readme = await readFile(readmePath, 'utf-8');
103
+ const lines = readme.split('\n').filter(l => l.trim());
104
+ // Try to get first meaningful paragraph
105
+ const firstParagraph = lines.find(l => !l.startsWith('#') && l.length > 20);
106
+ if (firstParagraph && (!projectData.description || projectData.description === '')) {
107
+ description = firstParagraph.slice(0, 200);
108
+ }
109
+ }
110
+ catch { }
111
+ }
112
+ const name = projectData.name || basename(rootDir);
113
+ const projectVersion = projectData.version; // Use the renamed field
114
+ const repository = projectData.repository?.url || projectData.repository;
115
+ const license = projectData.license;
116
+ const homepage = projectData.homepage;
117
+ // Detect team info from package.json
118
+ const team = [];
119
+ if (projectData.author) {
120
+ if (typeof projectData.author === 'string') {
121
+ team.push({ name: projectData.author });
122
+ }
123
+ else {
124
+ team.push(projectData.author);
125
+ }
126
+ }
127
+ if (projectData.contributors) {
128
+ team.push(...projectData.contributors);
129
+ }
130
+ return {
131
+ $schema: `${SCHEMA_URL}/project.schema.json`,
132
+ version: PRELUDE_VERSION,
133
+ name,
134
+ description,
135
+ projectVersion, // Correctly use renamed field
136
+ createdAt: getCurrentTimestamp(),
137
+ updatedAt: getCurrentTimestamp(),
138
+ repository,
139
+ license,
140
+ homepage,
141
+ team: team.length > 0 ? team : undefined
142
+ };
143
+ }
144
+ export async function inferStack(rootDir) {
145
+ // --- MODIFIED INITIALIZATION ---
146
+ const stack = {
147
+ $schema: `${SCHEMA_URL}/stack.schema.json`,
148
+ version: PRELUDE_VERSION,
149
+ // We must set language to a default, as it's required in the schema
150
+ language: 'unknown'
151
+ };
152
+ // Check for Node.js project
153
+ const packageJsonPath = join(rootDir, 'package.json');
154
+ if (await fileExists(packageJsonPath)) {
155
+ const pkg = await readJSON(packageJsonPath);
156
+ stack.language = 'TypeScript/JavaScript';
157
+ // Detect runtime
158
+ if (pkg.engines?.node) {
159
+ stack.runtime = `Node.js ${pkg.engines.node}`;
160
+ }
161
+ else if (await fileExists(join(rootDir, '.nvmrc'))) {
162
+ const nvmrc = await readFile(join(rootDir, '.nvmrc'), 'utf-8');
163
+ stack.runtime = `Node.js ${nvmrc.trim()}`;
164
+ }
165
+ // Detect package manager
166
+ if (await fileExists(join(rootDir, 'pnpm-lock.yaml')) || await fileExists(join(rootDir, 'pnpm-workspace.yaml'))) {
167
+ stack.packageManager = 'pnpm';
168
+ }
169
+ else if (await fileExists(join(rootDir, 'yarn.lock'))) {
170
+ stack.packageManager = 'yarn';
171
+ }
172
+ else if (await fileExists(join(rootDir, 'bun.lockb'))) {
173
+ stack.packageManager = 'bun';
174
+ }
175
+ else if (await fileExists(join(rootDir, 'package-lock.json'))) {
176
+ stack.packageManager = 'npm';
177
+ }
178
+ // Check if it's a monorepo
179
+ const isMonorepo = await fileExists(join(rootDir, 'pnpm-workspace.yaml')) ||
180
+ await fileExists(join(rootDir, 'turbo.json')) ||
181
+ await fileExists(join(rootDir, 'lerna.json')) ||
182
+ await fileExists(join(rootDir, 'nx.json')) ||
183
+ pkg.workspaces;
184
+ // --- THIS IS THE CRITICAL FIX ---
185
+ let allDeps = {};
186
+ if (isMonorepo) {
187
+ // Scan all packages in monorepo
188
+ const packages = await scanMonorepoPackages(rootDir);
189
+ allDeps = aggregateDependencies([{ ...pkg, location: 'root', name: 'root' }, ...packages]);
190
+ }
191
+ else {
192
+ allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
193
+ }
194
+ // ---------------------------------
195
+ stack.dependencies = pkg.dependencies || {};
196
+ stack.devDependencies = pkg.devDependencies || {};
197
+ // === FRAMEWORKS ===
198
+ const frameworks = [];
199
+ // Frontend Frameworks
200
+ if (allDeps['next'])
201
+ frameworks.push('Next.js');
202
+ if (allDeps['react'])
203
+ frameworks.push('React');
204
+ if (allDeps['vue'])
205
+ frameworks.push('Vue');
206
+ if (allDeps['@angular/core'])
207
+ frameworks.push('Angular');
208
+ if (allDeps['svelte'])
209
+ frameworks.push('Svelte');
210
+ if (allDeps['solid-js'])
211
+ frameworks.push('Solid');
212
+ if (allDeps['qwik'])
213
+ frameworks.push('Qwik');
214
+ if (allDeps['astro'])
215
+ frameworks.push('Astro');
216
+ if (allDeps['remix'])
217
+ frameworks.push('Remix');
218
+ if (allDeps['nuxt'])
219
+ frameworks.push('Nuxt');
220
+ if (allDeps['gatsby'])
221
+ frameworks.push('Gatsby');
222
+ if (allDeps['preact'])
223
+ frameworks.push('Preact');
224
+ // Backend Frameworks
225
+ if (allDeps['express'])
226
+ frameworks.push('Express');
227
+ if (allDeps['fastify'])
228
+ frameworks.push('Fastify');
229
+ if (allDeps['@nestjs/core'])
230
+ frameworks.push('NestJS');
231
+ if (allDeps['hono'])
232
+ frameworks.push('Hono');
233
+ if (allDeps['koa'])
234
+ frameworks.push('Koa');
235
+ if (allDeps['@hapi/hapi'])
236
+ frameworks.push('Hapi');
237
+ if (allDeps['apollo-server'])
238
+ frameworks.push('Apollo Server');
239
+ if (allDeps['trpc'])
240
+ frameworks.push('tRPC');
241
+ // Meta-frameworks
242
+ if (allDeps['@redwoodjs/core'])
243
+ frameworks.push('RedwoodJS');
244
+ if (allDeps['blitz'])
245
+ frameworks.push('Blitz.js');
246
+ stack.frameworks = frameworks;
247
+ stack.framework = frameworks[0];
248
+ // === BUILD TOOLS ===
249
+ const buildTools = [];
250
+ if (allDeps['vite'])
251
+ buildTools.push('Vite');
252
+ if (allDeps['webpack'])
253
+ buildTools.push('Webpack');
254
+ if (allDeps['turbopack'])
255
+ buildTools.push('Turbopack');
256
+ if (allDeps['esbuild'])
257
+ buildTools.push('esbuild');
258
+ if (allDeps['tsup'])
259
+ buildTools.push('tsup');
260
+ if (allDeps['rollup'])
261
+ buildTools.push('Rollup');
262
+ if (allDeps['parcel'])
263
+ buildTools.push('Parcel');
264
+ if (allDeps['swc'])
265
+ buildTools.push('SWC');
266
+ if (allDeps['turbo'] || await fileExists(join(rootDir, 'turbo.json')))
267
+ buildTools.push('Turborepo');
268
+ if (allDeps['nx'] || await fileExists(join(rootDir, 'nx.json')))
269
+ buildTools.push('Nx');
270
+ if (allDeps['lerna'] || await fileExists(join(rootDir, 'lerna.json')))
271
+ buildTools.push('Lerna');
272
+ stack.buildTools = buildTools;
273
+ // === TESTING FRAMEWORKS ===
274
+ const testingFrameworks = [];
275
+ if (allDeps['vitest'])
276
+ testingFrameworks.push('Vitest');
277
+ if (allDeps['jest'])
278
+ testingFrameworks.push('Jest');
279
+ if (allDeps['mocha'])
280
+ testingFrameworks.push('Mocha');
281
+ if (allDeps['ava'])
282
+ testingFrameworks.push('AVA');
283
+ if (allDeps['@playwright/test'])
284
+ testingFrameworks.push('Playwright');
285
+ if (allDeps['cypress'])
286
+ testingFrameworks.push('Cypress');
287
+ if (allDeps['@testing-library/react'])
288
+ testingFrameworks.push('React Testing Library');
289
+ if (allDeps['@testing-library/vue'])
290
+ testingFrameworks.push('Vue Testing Library');
291
+ if (allDeps['puppeteer'])
292
+ testingFrameworks.push('Puppeteer');
293
+ if (allDeps['selenium-webdriver'])
294
+ testingFrameworks.push('Selenium');
295
+ stack.testingFrameworks = testingFrameworks;
296
+ // === STYLING ===
297
+ const styling = [];
298
+ if (allDeps['tailwindcss'] || await fileExists(join(rootDir, 'tailwind.config.js')) || await fileExists(join(rootDir, 'tailwind.config.ts'))) {
299
+ styling.push('Tailwind CSS');
300
+ }
301
+ if (allDeps['styled-components'])
302
+ styling.push('Styled Components');
303
+ if (allDeps['@emotion/react'])
304
+ styling.push('Emotion');
305
+ if (allDeps['@emotion/styled'])
306
+ styling.push('Emotion');
307
+ if (allDeps['sass'] || allDeps['node-sass'])
308
+ styling.push('Sass/SCSS');
309
+ if (allDeps['less'])
310
+ styling.push('Less');
311
+ if (allDeps['postcss'])
312
+ styling.push('PostCSS');
313
+ if (allDeps['styled-jsx'])
314
+ styling.push('Styled JSX');
315
+ if (allDeps['@vanilla-extract/css'])
316
+ styling.push('Vanilla Extract');
317
+ if (allDeps['@stitches/react'])
318
+ styling.push('Stitches');
319
+ if (allDeps['@mui/material'])
320
+ styling.push('Material-UI');
321
+ if (allDeps['@chakra-ui/react'])
322
+ styling.push('Chakra UI');
323
+ if (allDeps['@mantine/core'])
324
+ styling.push('Mantine');
325
+ if (allDeps['antd'])
326
+ styling.push('Ant Design');
327
+ if (allDeps['@radix-ui/react-primitive'])
328
+ styling.push('Radix UI');
329
+ if (allDeps['@headlessui/react'])
330
+ styling.push('Headless UI');
331
+ if (allDeps['daisyui'])
332
+ styling.push('DaisyUI');
333
+ if (allDeps['shadcn-ui'] || allDeps['@shadcn/ui'])
334
+ styling.push('shadcn/ui');
335
+ stack.styling = styling;
336
+ // === ORM/DATABASE ===
337
+ if (allDeps['drizzle-orm'])
338
+ stack.orm = 'Drizzle ORM';
339
+ else if (allDeps['prisma'])
340
+ stack.orm = 'Prisma';
341
+ else if (allDeps['typeorm'])
342
+ stack.orm = 'TypeORM';
343
+ else if (allDeps['sequelize'])
344
+ stack.orm = 'Sequelize';
345
+ else if (allDeps['mongoose'])
346
+ stack.orm = 'Mongoose';
347
+ else if (allDeps['kysely'])
348
+ stack.orm = 'Kysely';
349
+ else if (allDeps['knex'])
350
+ stack.orm = 'Knex.js';
351
+ else if (allDeps['mikro-orm'])
352
+ stack.orm = 'MikroORM';
353
+ // Database Clients & Services
354
+ const databases = [];
355
+ if (allDeps['@supabase/supabase-js'])
356
+ databases.push('Supabase');
357
+ if (allDeps['firebase'] || allDeps['firebase-admin'])
358
+ databases.push('Firebase');
359
+ if (allDeps['pg'] || allDeps['postgres'])
360
+ databases.push('PostgreSQL');
361
+ if (allDeps['mysql'] || allDeps['mysql2'])
362
+ databases.push('MySQL');
363
+ if (allDeps['sqlite3'] || allDeps['better-sqlite3'])
364
+ databases.push('SQLite');
365
+ if (allDeps['mongodb'])
366
+ databases.push('MongoDB');
367
+ if (allDeps['redis'] || allDeps['ioredis'])
368
+ databases.push('Redis');
369
+ if (allDeps['@planetscale/database'])
370
+ databases.push('PlanetScale');
371
+ if (allDeps['@vercel/postgres'])
372
+ databases.push('Vercel Postgres');
373
+ if (allDeps['@neondatabase/serverless'])
374
+ databases.push('Neon');
375
+ if (allDeps['@upstash/redis'])
376
+ databases.push('Upstash Redis');
377
+ if (databases.length > 0) {
378
+ stack.database = databases.join(', ');
379
+ }
380
+ // === STATE MANAGEMENT ===
381
+ const stateManagement = [];
382
+ if (allDeps['redux'])
383
+ stateManagement.push('Redux');
384
+ if (allDeps['@reduxjs/toolkit'])
385
+ stateManagement.push('Redux Toolkit');
386
+ if (allDeps['zustand'])
387
+ stateManagement.push('Zustand');
388
+ if (allDeps['jotai'])
389
+ stateManagement.push('Jotai');
390
+ if (allDeps['recoil'])
391
+ stateManagement.push('Recoil');
392
+ if (allDeps['mobx'])
393
+ stateManagement.push('MobX');
394
+ if (allDeps['valtio'])
395
+ stateManagement.push('Valtio');
396
+ if (allDeps['xstate'])
397
+ stateManagement.push('XState');
398
+ if (allDeps['@tanstack/react-query'])
399
+ stateManagement.push('TanStack Query');
400
+ if (allDeps['swr'])
401
+ stateManagement.push('SWR');
402
+ if (stateManagement.length > 0) {
403
+ stack.stateManagement = stateManagement.join(', ');
404
+ }
405
+ // === AUTHENTICATION ===
406
+ const auth = [];
407
+ if (allDeps['next-auth'])
408
+ auth.push('NextAuth.js');
409
+ if (allDeps['@clerk/nextjs'])
410
+ auth.push('Clerk');
411
+ if (allDeps['@auth0/nextjs'])
412
+ auth.push('Auth0');
413
+ if (allDeps['@supabase/auth-helpers-nextjs'])
414
+ auth.push('Supabase Auth');
415
+ if (allDeps['passport'])
416
+ auth.push('Passport.js');
417
+ if (allDeps['lucia'])
418
+ auth.push('Lucia');
419
+ if (allDeps['better-auth'])
420
+ auth.push('Better Auth');
421
+ // === API/DATA FETCHING ===
422
+ const apiTools = [];
423
+ if (allDeps['@trpc/server'])
424
+ apiTools.push('tRPC');
425
+ if (allDeps['graphql'])
426
+ apiTools.push('GraphQL');
427
+ if (allDeps['@apollo/client'])
428
+ apiTools.push('Apollo Client');
429
+ if (allDeps['axios'])
430
+ apiTools.push('Axios');
431
+ if (allDeps['ky'])
432
+ apiTools.push('Ky');
433
+ if (allDeps['@tanstack/react-query'])
434
+ apiTools.push('TanStack Query');
435
+ if (allDeps['swr'])
436
+ apiTools.push('SWR');
437
+ // === VALIDATION ===
438
+ const validation = [];
439
+ if (allDeps['zod'])
440
+ validation.push('Zod');
441
+ if (allDeps['yup'])
442
+ validation.push('Yup');
443
+ if (allDeps['joi'])
444
+ validation.push('Joi');
445
+ if (allDeps['ajv'])
446
+ validation.push('AJV');
447
+ if (allDeps['valibot'])
448
+ validation.push('Valibot');
449
+ if (allDeps['superstruct'])
450
+ validation.push('Superstruct');
451
+ // === FORMS ===
452
+ const forms = [];
453
+ if (allDeps['react-hook-form'])
454
+ forms.push('React Hook Form');
455
+ if (allDeps['formik'])
456
+ forms.push('Formik');
457
+ if (allDeps['@tanstack/react-form'])
458
+ forms.push('TanStack Form');
459
+ if (allDeps['@conform-to/react'])
460
+ forms.push('Conform');
461
+ // === ANIMATION ===
462
+ const animation = [];
463
+ if (allDeps['framer-motion'])
464
+ animation.push('Framer Motion');
465
+ if (allDeps['@react-spring/web'])
466
+ animation.push('React Spring');
467
+ if (allDeps['gsap'])
468
+ animation.push('GSAP');
469
+ if (allDeps['anime'])
470
+ animation.push('Anime.js');
471
+ // === CI/CD ===
472
+ const cicd = [];
473
+ if (await fileExists(join(rootDir, '.github/workflows')))
474
+ cicd.push('GitHub Actions');
475
+ if (await fileExists(join(rootDir, '.gitlab-ci.yml')))
476
+ cicd.push('GitLab CI');
477
+ if (await fileExists(join(rootDir, '.circleci')))
478
+ cicd.push('CircleCI');
479
+ if (await fileExists(join(rootDir, 'jenkins')))
480
+ cicd.push('Jenkins');
481
+ if (await fileExists(join(rootDir, '.travis.yml')))
482
+ cicd.push('Travis CI');
483
+ if (await fileExists(join(rootDir, 'azure-pipelines.yml')))
484
+ cicd.push('Azure Pipelines');
485
+ stack.cicd = cicd;
486
+ // === DEPLOYMENT ===
487
+ if (await fileExists(join(rootDir, 'vercel.json')))
488
+ stack.deployment = 'Vercel';
489
+ else if (await fileExists(join(rootDir, 'netlify.toml')))
490
+ stack.deployment = 'Netlify';
491
+ else if (await fileExists(join(rootDir, 'Dockerfile')))
492
+ stack.deployment = 'Docker';
493
+ else if (await fileExists(join(rootDir, 'railway.json')))
494
+ stack.deployment = 'Railway';
495
+ else if (await fileExists(join(rootDir, 'fly.toml')))
496
+ stack.deployment = 'Fly.io';
497
+ else if (await fileExists(join(rootDir, 'render.yaml')))
498
+ stack.deployment = 'Render';
499
+ else if (allDeps['@aws-sdk/client-s3'])
500
+ stack.deployment = 'AWS'; // This now works
501
+ }
502
+ // Check for Python project
503
+ const requirementsPath = join(rootDir, 'requirements.txt');
504
+ const pyprojectPath = join(rootDir, 'pyproject.toml');
505
+ if (await fileExists(requirementsPath) || await fileExists(pyprojectPath)) {
506
+ stack.language = 'Python';
507
+ stack.packageManager = await fileExists(pyprojectPath) ? 'poetry' : 'pip';
508
+ // Check for common Python frameworks
509
+ if (await fileExists(requirementsPath)) {
510
+ const requirements = await readFile(requirementsPath, 'utf-8');
511
+ const frameworks = [];
512
+ if (requirements.includes('django'))
513
+ frameworks.push('Django');
514
+ if (requirements.includes('flask'))
515
+ frameworks.push('Flask');
516
+ if (requirements.includes('fastapi'))
517
+ frameworks.push('FastAPI');
518
+ if (requirements.includes('tornado'))
519
+ frameworks.push('Tornado');
520
+ if (requirements.includes('pyramid'))
521
+ frameworks.push('Pyramid');
522
+ stack.frameworks = frameworks;
523
+ }
524
+ }
525
+ // Check for Rust project
526
+ const cargoPath = join(rootDir, 'Cargo.toml');
527
+ if (await fileExists(cargoPath)) {
528
+ stack.language = 'Rust';
529
+ stack.packageManager = 'cargo';
530
+ }
531
+ // Check for Go project
532
+ const goModPath = join(rootDir, 'go.mod');
533
+ if (await fileExists(goModPath)) {
534
+ stack.language = 'Go';
535
+ stack.packageManager = 'go';
536
+ }
537
+ return stack;
538
+ }
539
+ export async function inferArchitecture(rootDir) {
540
+ // --- MODIFIED INITIALIZATION ---
541
+ const architecture = {
542
+ $schema: `${SCHEMA_URL}/architecture.schema.json`,
543
+ version: PRELUDE_VERSION,
544
+ directories: []
545
+ };
546
+ // Get directory structure
547
+ const dirs = await getDirectoryTree(rootDir, 3); // Increased depth to 3
548
+ const relativeDirs = dirs.map(dir => relative(rootDir, dir));
549
+ // Count files in each directory
550
+ const dirInfo = await Promise.all(relativeDirs.map(async (dir) => {
551
+ const fullPath = join(rootDir, dir);
552
+ try {
553
+ const files = await readdir(fullPath);
554
+ // Determine purpose based on directory name
555
+ let purpose = undefined;
556
+ if (dir.includes('components'))
557
+ purpose = 'UI components';
558
+ else if (dir.includes('pages'))
559
+ purpose = 'Route pages';
560
+ else if (dir.includes('app'))
561
+ purpose = 'Application code';
562
+ else if (dir.includes('lib') || dir.includes('utils'))
563
+ purpose = 'Utility functions';
564
+ else if (dir.includes('hooks'))
565
+ purpose = 'React hooks';
566
+ else if (dir.includes('context'))
567
+ purpose = 'React context';
568
+ else if (dir.includes('store'))
569
+ purpose = 'State management';
570
+ else if (dir.includes('api'))
571
+ purpose = 'API routes';
572
+ else if (dir.includes('services'))
573
+ purpose = 'Business logic';
574
+ else if (dir.includes('db') || dir.includes('database'))
575
+ purpose = 'Database layer';
576
+ else if (dir.includes('schema'))
577
+ purpose = 'Data schemas';
578
+ else if (dir.includes('types'))
579
+ purpose = 'TypeScript types';
580
+ else if (dir.includes('config'))
581
+ purpose = 'Configuration';
582
+ else if (dir.includes('public'))
583
+ purpose = 'Static assets';
584
+ else if (dir.includes('styles'))
585
+ purpose = 'Stylesheets';
586
+ else if (dir.includes('tests') || dir.includes('__tests__'))
587
+ purpose = 'Tests';
588
+ return {
589
+ path: dir,
590
+ fileCount: files.length,
591
+ purpose
592
+ };
593
+ }
594
+ catch {
595
+ return {
596
+ path: dir,
597
+ fileCount: 0
598
+ };
599
+ }
600
+ }));
601
+ architecture.directories = dirInfo.filter(d => d.fileCount > 0);
602
+ // Infer project type
603
+ const hasPages = relativeDirs.some(d => d.includes('pages') && !d.includes('api'));
604
+ const hasApp = relativeDirs.some(d => d.match(/^apps?\//) || d === 'app');
605
+ const hasSrc = relativeDirs.some(d => d === 'src');
606
+ const hasLib = relativeDirs.some(d => d.includes('lib'));
607
+ const hasPackages = relativeDirs.some(d => d === 'packages');
608
+ const hasApps = relativeDirs.some(d => d === 'apps');
609
+ const hasServices = relativeDirs.some(d => d === 'services');
610
+ if (hasPackages || hasApps) {
611
+ architecture.type = 'monorepo';
612
+ }
613
+ else if (hasServices) {
614
+ architecture.type = 'microservices';
615
+ }
616
+ else if (hasApp && hasSrc) {
617
+ architecture.type = 'fullstack';
618
+ }
619
+ else if (hasLib && !hasApp) {
620
+ architecture.type = 'library';
621
+ }
622
+ else if (await fileExists(join(rootDir, 'bin'))) {
623
+ architecture.type = 'cli';
624
+ }
625
+ else if (hasPages || hasApp) {
626
+ architecture.type = 'frontend';
627
+ }
628
+ else {
629
+ architecture.type = 'backend';
630
+ }
631
+ // Detect routing
632
+ if (hasPages) {
633
+ architecture.routing = 'file-based';
634
+ }
635
+ else if (relativeDirs.some(d => d.includes('app') && d.includes('routes'))) {
636
+ architecture.routing = 'file-based';
637
+ }
638
+ else if (relativeDirs.some(d => d.includes('routes') || d.includes('router'))) {
639
+ architecture.routing = 'config-based';
640
+ }
641
+ // Detect API style
642
+ const packageJsonPath = join(rootDir, 'package.json');
643
+ if (await fileExists(packageJsonPath)) {
644
+ const pkg = await readJSON(packageJsonPath);
645
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
646
+ if (allDeps['@trpc/server'])
647
+ architecture.apiStyle = 'tRPC';
648
+ else if (allDeps['graphql'])
649
+ architecture.apiStyle = 'GraphQL';
650
+ else if (allDeps['@grpc/grpc-js'])
651
+ architecture.apiStyle = 'gRPC';
652
+ else if (relativeDirs.some(d => d.includes('api') || d.includes('routes')))
653
+ architecture.apiStyle = 'REST';
654
+ }
655
+ // Detect state management
656
+ if (await fileExists(packageJsonPath)) {
657
+ const pkg = await readJSON(packageJsonPath);
658
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
659
+ if (allDeps['zustand'])
660
+ architecture.stateManagement = 'Zustand';
661
+ else if (allDeps['@reduxjs/toolkit'])
662
+ architecture.stateManagement = 'Redux Toolkit';
663
+ else if (allDeps['redux'])
664
+ architecture.stateManagement = 'Redux';
665
+ else if (allDeps['jotai'])
666
+ architecture.stateManagement = 'Jotai';
667
+ else if (allDeps['recoil'])
668
+ architecture.stateManagement = 'Recoil';
669
+ else if (allDeps['mobx'])
670
+ architecture.stateManagement = 'MobX';
671
+ }
672
+ // Detect patterns
673
+ const patterns = [];
674
+ if (relativeDirs.some(d => d.includes('components')))
675
+ patterns.push('Component-based architecture');
676
+ if (relativeDirs.some(d => d.includes('hooks')))
677
+ patterns.push('Custom hooks pattern');
678
+ if (relativeDirs.some(d => d.includes('utils')))
679
+ patterns.push('Utility modules');
680
+ if (relativeDirs.some(d => d.includes('services')))
681
+ patterns.push('Service layer');
682
+ if (relativeDirs.some(d => d.includes('store') || d.includes('state')))
683
+ patterns.push('State management');
684
+ if (relativeDirs.some(d => d.includes('api')))
685
+ patterns.push('API routes');
686
+ if (relativeDirs.some(d => d.includes('db') || d.includes('database')))
687
+ patterns.push('Database layer');
688
+ if (relativeDirs.some(d => d.includes('config')))
689
+ patterns.push('Configuration management');
690
+ if (relativeDirs.some(d => d.includes('middleware')))
691
+ patterns.push('Middleware pattern');
692
+ if (relativeDirs.some(d => d.includes('providers')))
693
+ patterns.push('Provider pattern');
694
+ if (relativeDirs.some(d => d.includes('context')))
695
+ patterns.push('Context API');
696
+ if (relativeDirs.some(d => d.includes('layouts')))
697
+ patterns.push('Layout components');
698
+ if (relativeDirs.some(d => d.includes('features')))
699
+ patterns.push('Feature-based organization');
700
+ if (relativeDirs.some(d => d.includes('modules')))
701
+ patterns.push('Module pattern');
702
+ architecture.patterns = patterns;
703
+ // Detect conventions
704
+ const conventions = [];
705
+ if (await fileExists(join(rootDir, '.prettierrc')) || await fileExists(join(rootDir, '.prettierrc.json'))) {
706
+ conventions.push('Prettier code formatting');
707
+ }
708
+ if (await fileExists(join(rootDir, '.eslintrc.json')) || await fileExists(join(rootDir, '.eslintrc.js')) || await fileExists(join(rootDir, 'eslint.config.js'))) {
709
+ conventions.push('ESLint code linting');
710
+ }
711
+ if (await fileExists(join(rootDir, 'tsconfig.json'))) {
712
+ conventions.push('TypeScript strict mode');
713
+ }
714
+ if (await fileExists(join(rootDir, '.editorconfig'))) {
715
+ conventions.push('EditorConfig');
716
+ }
717
+ if (await fileExists(join(rootDir, '.husky'))) {
718
+ conventions.push('Git hooks (Husky)');
719
+ }
720
+ architecture.conventions = conventions;
721
+ // Detect entry points
722
+ const entryPoints = [];
723
+ if (await fileExists(join(rootDir, 'src/index.ts')))
724
+ entryPoints.push({ file: 'src/index.ts', purpose: 'Main entry point' });
725
+ else if (await fileExists(join(rootDir, 'src/index.tsx')))
726
+ entryPoints.push({ file: 'src/index.tsx', purpose: 'Main entry point' });
727
+ else if (await fileExists(join(rootDir, 'index.ts')))
728
+ entryPoints.push({ file: 'index.ts', purpose: 'Main entry point' });
729
+ if (await fileExists(join(rootDir, 'src/main.ts')))
730
+ entryPoints.push({ file: 'src/main.ts', purpose: 'Application entry' });
731
+ if (await fileExists(join(rootDir, 'src/app.ts')))
732
+ entryPoints.push({ file: 'src/app.ts', purpose: 'Application setup' });
733
+ if (await fileExists(join(rootDir, 'src/server.ts')))
734
+ entryPoints.push({ file: 'src/server.ts', purpose: 'Server entry' });
735
+ architecture.entryPoints = entryPoints;
736
+ return architecture;
737
+ }
738
+ export async function inferConstraints(rootDir) {
739
+ // --- MODIFIED INITIALIZATION ---
740
+ const constraints = {
741
+ $schema: `${SCHEMA_URL}/constraints.schema.json`,
742
+ version: PRELUDE_VERSION,
743
+ mustUse: [],
744
+ mustNotUse: [],
745
+ preferences: []
746
+ };
747
+ // Check for ESLint
748
+ if (await fileExists(join(rootDir, '.eslintrc.json')) ||
749
+ await fileExists(join(rootDir, '.eslintrc.js')) ||
750
+ await fileExists(join(rootDir, 'eslint.config.js'))) {
751
+ constraints.codeStyle = {
752
+ linter: 'ESLint'
753
+ };
754
+ // Try to read ESLint config for rules
755
+ try {
756
+ let eslintConfig;
757
+ if (await fileExists(join(rootDir, '.eslintrc.json'))) {
758
+ eslintConfig = await readJSON(join(rootDir, '.eslintrc.json'));
759
+ }
760
+ if (eslintConfig?.extends) {
761
+ const rules = [];
762
+ if (Array.isArray(eslintConfig.extends)) {
763
+ rules.push(...eslintConfig.extends);
764
+ }
765
+ else {
766
+ rules.push(eslintConfig.extends);
767
+ }
768
+ constraints.codeStyle.rules = rules;
769
+ }
770
+ }
771
+ catch { }
772
+ }
773
+ // Check for Prettier
774
+ if (await fileExists(join(rootDir, '.prettierrc')) ||
775
+ await fileExists(join(rootDir, '.prettierrc.json')) ||
776
+ await fileExists(join(rootDir, 'prettier.config.js'))) {
777
+ constraints.codeStyle = {
778
+ ...constraints.codeStyle,
779
+ formatter: 'Prettier'
780
+ };
781
+ }
782
+ // Check for TypeScript
783
+ if (await fileExists(join(rootDir, 'tsconfig.json'))) {
784
+ constraints.mustUse?.push('TypeScript for type safety');
785
+ // Try to read tsconfig for strictness
786
+ try {
787
+ const tsconfig = await readJSON(join(rootDir, 'tsconfig.json'));
788
+ if (tsconfig.compilerOptions?.strict) {
789
+ constraints.mustUse?.push('TypeScript strict mode');
790
+ }
791
+ }
792
+ catch { }
793
+ }
794
+ // Check for Tailwind
795
+ if (await fileExists(join(rootDir, 'tailwind.config.js')) ||
796
+ await fileExists(join(rootDir, 'tailwind.config.ts'))) {
797
+ constraints.mustUse?.push('Tailwind CSS for styling');
798
+ }
799
+ // Check for testing requirements
800
+ const packageJsonPath = join(rootDir, 'package.json');
801
+ if (await fileExists(packageJsonPath)) {
802
+ const pkg = await readJSON(packageJsonPath);
803
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
804
+ if (allDeps['vitest'] || allDeps['jest'] || allDeps['@playwright/test']) {
805
+ constraints.testing = {
806
+ required: true,
807
+ strategy: 'Unit and integration tests'
808
+ };
809
+ // Check for coverage requirements
810
+ if (pkg.scripts?.['test:coverage']) {
811
+ constraints.testing.coverage = 80; // Default assumption
812
+ }
813
+ }
814
+ // Check for monorepo tools
815
+ if (allDeps['turbo'] || await fileExists(join(rootDir, 'turbo.json'))) {
816
+ constraints.mustUse?.push('Turborepo for monorepo management');
817
+ }
818
+ if (allDeps['nx'] || await fileExists(join(rootDir, 'nx.json'))) {
819
+ constraints.mustUse?.push('Nx for monorepo management');
820
+ }
821
+ // Check for commit conventions
822
+ if (allDeps['@commitlint/cli']) {
823
+ constraints.preferences?.push({
824
+ category: 'Version Control',
825
+ preference: 'Conventional Commits',
826
+ rationale: 'Standardized commit messages'
827
+ });
828
+ }
829
+ // Check for code quality tools
830
+ if (allDeps['husky']) {
831
+ constraints.preferences?.push({
832
+ category: 'Code Quality',
833
+ preference: 'Git hooks with Husky',
834
+ rationale: 'Pre-commit and pre-push validations'
835
+ });
836
+ }
837
+ }
838
+ // Detect naming conventions from actual files
839
+ const naming = {};
840
+ // Check component naming
841
+ const componentsDir = join(rootDir, 'src/components');
842
+ if (await fileExists(componentsDir)) {
843
+ const files = await readdir(componentsDir);
844
+ const hasPascalCase = files.some(f => /^[A-Z]/.test(f));
845
+ const hasKebabCase = files.some(f => f.includes('-'));
846
+ if (hasPascalCase)
847
+ naming.components = 'PascalCase';
848
+ else if (hasKebabCase)
849
+ naming.components = 'kebab-case';
850
+ }
851
+ if (Object.keys(naming).length > 0) {
852
+ constraints.naming = naming;
853
+ }
854
+ // File organization preferences
855
+ const fileOrg = [];
856
+ if (await fileExists(join(rootDir, 'src')))
857
+ fileOrg.push('All source code in src/ directory');
858
+ if (await fileExists(join(rootDir, 'src/components')))
859
+ fileOrg.push('Components organized by feature or type');
860
+ if (await fileExists(join(rootDir, 'src/lib')))
861
+ fileOrg.push('Shared utilities in lib/ directory');
862
+ constraints.fileOrganization = fileOrg;
863
+ // Documentation requirements
864
+ if (await fileExists(join(rootDir, 'README.md'))) {
865
+ constraints.documentation = {
866
+ required: true,
867
+ style: 'Markdown'
868
+ };
869
+ }
870
+ // Performance constraints
871
+ const performance = [];
872
+ const packageJsonExists = await fileExists(packageJsonPath);
873
+ if (packageJsonExists) {
874
+ const pkg = await readJSON(packageJsonPath);
875
+ const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
876
+ if (allDeps['@next/bundle-analyzer'])
877
+ performance.push('Bundle size monitoring');
878
+ if (allDeps['lighthouse'])
879
+ performance.push('Lighthouse CI');
880
+ if (allDeps['web-vitals'])
881
+ performance.push('Web Vitals tracking');
882
+ }
883
+ if (performance.length > 0) {
884
+ constraints.performance = performance;
885
+ }
886
+ // Security constraints
887
+ const security = [];
888
+ if (await fileExists(join(rootDir, '.env.example'))) {
889
+ security.push('Environment variables documented in .env.example');
890
+ }
891
+ const envFiles = await detectEnvFiles(rootDir);
892
+ if (envFiles.length > 0) {
893
+ security.push('Separate .env files for different environments');
894
+ }
895
+ if (security.length > 0) {
896
+ constraints.security = security;
897
+ }
898
+ return constraints;
899
+ }
900
+ //# sourceMappingURL=infer.js.map