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.
- package/README.md +354 -0
- package/dist/bin/prelude.d.ts +3 -0
- package/dist/bin/prelude.d.ts.map +1 -0
- package/dist/bin/prelude.js +35 -0
- package/dist/bin/prelude.js.map +1 -0
- package/dist/src/commands/decision.d.ts +3 -0
- package/dist/src/commands/decision.d.ts.map +1 -0
- package/dist/src/commands/decision.js +74 -0
- package/dist/src/commands/decision.js.map +1 -0
- package/dist/src/commands/export.d.ts +3 -0
- package/dist/src/commands/export.d.ts.map +1 -0
- package/dist/src/commands/export.js +132 -0
- package/dist/src/commands/export.js.map +1 -0
- package/dist/src/commands/init.d.ts +3 -0
- package/dist/src/commands/init.d.ts.map +1 -0
- package/dist/src/commands/init.js +80 -0
- package/dist/src/commands/init.js.map +1 -0
- package/dist/src/commands/share.d.ts +3 -0
- package/dist/src/commands/share.d.ts.map +1 -0
- package/dist/src/commands/share.js +133 -0
- package/dist/src/commands/share.js.map +1 -0
- package/dist/src/commands/watch.d.ts +3 -0
- package/dist/src/commands/watch.d.ts.map +1 -0
- package/dist/src/commands/watch.js +58 -0
- package/dist/src/commands/watch.js.map +1 -0
- package/dist/src/constants.d.ts +16 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.js +49 -0
- package/dist/src/constants.js.map +1 -0
- package/dist/src/core/exporter.d.ts +4 -0
- package/dist/src/core/exporter.d.ts.map +1 -0
- package/dist/src/core/exporter.js +196 -0
- package/dist/src/core/exporter.js.map +1 -0
- package/dist/src/core/infer.d.ts +6 -0
- package/dist/src/core/infer.d.ts.map +1 -0
- package/dist/src/core/infer.js +900 -0
- package/dist/src/core/infer.js.map +1 -0
- package/dist/src/core/updater.d.ts +8 -0
- package/dist/src/core/updater.d.ts.map +1 -0
- package/dist/src/core/updater.js +120 -0
- package/dist/src/core/updater.js.map +1 -0
- package/dist/src/core/watcher.d.ts +15 -0
- package/dist/src/core/watcher.d.ts.map +1 -0
- package/dist/src/core/watcher.js +119 -0
- package/dist/src/core/watcher.js.map +1 -0
- package/dist/src/schema/architecture.d.ts +75 -0
- package/dist/src/schema/architecture.d.ts.map +1 -0
- package/dist/src/schema/architecture.js +24 -0
- package/dist/src/schema/architecture.js.map +1 -0
- package/dist/src/schema/constraints.d.ts +146 -0
- package/dist/src/schema/constraints.d.ts.map +1 -0
- package/dist/src/schema/constraints.js +39 -0
- package/dist/src/schema/constraints.js.map +1 -0
- package/dist/src/schema/decisions.d.ts +122 -0
- package/dist/src/schema/decisions.d.ts.map +1 -0
- package/dist/src/schema/decisions.js +23 -0
- package/dist/src/schema/decisions.js.map +1 -0
- package/dist/src/schema/index.d.ts +7 -0
- package/dist/src/schema/index.d.ts.map +1 -0
- package/dist/src/schema/index.js +7 -0
- package/dist/src/schema/index.js.map +1 -0
- package/dist/src/schema/project.d.ts +69 -0
- package/dist/src/schema/project.d.ts.map +1 -0
- package/dist/src/schema/project.js +24 -0
- package/dist/src/schema/project.js.map +1 -0
- package/dist/src/schema/session.d.ts +210 -0
- package/dist/src/schema/session.d.ts.map +1 -0
- package/dist/src/schema/session.js +26 -0
- package/dist/src/schema/session.js.map +1 -0
- package/dist/src/schema/stack.d.ts +58 -0
- package/dist/src/schema/stack.d.ts.map +1 -0
- package/dist/src/schema/stack.js +23 -0
- package/dist/src/schema/stack.js.map +1 -0
- package/dist/src/utils/fs.d.ts +10 -0
- package/dist/src/utils/fs.d.ts.map +1 -0
- package/dist/src/utils/fs.js +63 -0
- package/dist/src/utils/fs.js.map +1 -0
- package/dist/src/utils/log.d.ts +20 -0
- package/dist/src/utils/log.d.ts.map +1 -0
- package/dist/src/utils/log.js +45 -0
- package/dist/src/utils/log.js.map +1 -0
- package/dist/src/utils/time.d.ts +5 -0
- package/dist/src/utils/time.d.ts.map +1 -0
- package/dist/src/utils/time.js +33 -0
- package/dist/src/utils/time.js.map +1 -0
- package/package.json +64 -0
- package/schemas/architecture.schema.json +98 -0
- package/schemas/constraints.schema.json +161 -0
- package/schemas/decisions.schema.json +95 -0
- package/schemas/export.schema.json +46 -0
- package/schemas/project.schema.json +102 -0
- package/schemas/session.schema.json +101 -0
- package/schemas/stack.schema.json +103 -0
- 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
|