@polymorphism-tech/morph-spec 8.1.0 → 8.1.1
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 +2 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +1 -1
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +1 -1
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/worktree.js +83 -1
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/skills-installer.js +174 -174
- package/framework/templates/.idea/morph-templates.xml +0 -92
- package/framework/templates/.vscode/morph-templates.code-snippets +0 -186
|
@@ -1,217 +1,217 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Project Detector
|
|
3
|
-
*
|
|
4
|
-
* Scans a project directory for stack, architecture, UI library, and integrations.
|
|
5
|
-
* Used by mcp-setup, morph-init, and session-start hooks.
|
|
6
|
-
*
|
|
7
|
-
* @module detectors/index
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Detect project structure from filesystem evidence.
|
|
15
|
-
*
|
|
16
|
-
* @param {string} targetPath - Root path of the project
|
|
17
|
-
* @param {Object} [opts] - Options
|
|
18
|
-
* @param {boolean} [opts.generateStandards=false] - Whether to generate inferred markdown
|
|
19
|
-
* @returns {{ structure: { stack: string, architecture: string|null, uiLibrary: string|null, integrations: string[], hasVercel: boolean }, inferred: { markdown: string } }}
|
|
20
|
-
*/
|
|
21
|
-
export function detectProject(targetPath, opts = {}) {
|
|
22
|
-
const structure = {
|
|
23
|
-
stack: 'unknown',
|
|
24
|
-
architecture: null,
|
|
25
|
-
uiLibrary: null,
|
|
26
|
-
integrations: [],
|
|
27
|
-
hasVercel: false,
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
try {
|
|
31
|
-
// ── Package.json analysis ──────────────────────────────────────────────
|
|
32
|
-
// Collect deps from root and common monorepo subfolders
|
|
33
|
-
const pkgLocations = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
|
|
34
|
-
const allDeps = {};
|
|
35
|
-
let rootPkg = null;
|
|
36
|
-
let scripts = [];
|
|
37
|
-
for (const loc of pkgLocations) {
|
|
38
|
-
const pkgPath = join(targetPath, loc, 'package.json');
|
|
39
|
-
if (!existsSync(pkgPath)) continue;
|
|
40
|
-
try {
|
|
41
|
-
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
42
|
-
if (loc === '.') rootPkg = pkg;
|
|
43
|
-
Object.assign(allDeps, pkg.dependencies, pkg.devDependencies);
|
|
44
|
-
scripts.push(...Object.keys(pkg.scripts || {}));
|
|
45
|
-
// Track frontend path for config
|
|
46
|
-
if (loc !== '.' && (pkg.dependencies?.next || pkg.dependencies?.react || pkg.dependencies?.vue)) {
|
|
47
|
-
structure.frontendPath = loc;
|
|
48
|
-
}
|
|
49
|
-
} catch { /* invalid JSON */ }
|
|
50
|
-
}
|
|
51
|
-
const depNames = Object.keys(allDeps);
|
|
52
|
-
|
|
53
|
-
if (depNames.length > 0) {
|
|
54
|
-
|
|
55
|
-
// Stack detection from deps
|
|
56
|
-
if (depNames.includes('next')) {
|
|
57
|
-
structure.stack = 'nextjs';
|
|
58
|
-
} else if (depNames.includes('react')) {
|
|
59
|
-
structure.stack = 'react';
|
|
60
|
-
} else if (depNames.includes('vue')) {
|
|
61
|
-
structure.stack = 'vue';
|
|
62
|
-
} else if (depNames.includes('express') || depNames.includes('fastify') || depNames.includes('hono')) {
|
|
63
|
-
structure.stack = 'nodejs';
|
|
64
|
-
} else if (rootPkg?.name) {
|
|
65
|
-
structure.stack = 'nodejs';
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// UI library detection
|
|
69
|
-
if (existsSync(join(targetPath, 'components.json')) || existsSync(join(targetPath, structure.frontendPath || '.', 'components.json')) || depNames.includes('@shadcn/ui')) {
|
|
70
|
-
structure.uiLibrary = 'shadcn';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Integrations
|
|
74
|
-
if (depNames.includes('@neondatabase/serverless') || depNames.includes('postgres') || depNames.includes('pg')) {
|
|
75
|
-
structure.integrations.push('neon');
|
|
76
|
-
}
|
|
77
|
-
if (depNames.includes('@neondatabase/auth')) {
|
|
78
|
-
structure.integrations.push('neon-auth');
|
|
79
|
-
}
|
|
80
|
-
if (depNames.includes('stripe')) {
|
|
81
|
-
structure.integrations.push('stripe');
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Vercel detection
|
|
85
|
-
if (depNames.includes('vercel') || scripts.includes('vercel')) {
|
|
86
|
-
structure.hasVercel = true;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// ── .csproj detection ──────────────────────────────────────────────────
|
|
91
|
-
let hasDotnet = false;
|
|
92
|
-
const csprojLocations = ['.', 'src', 'src/Api', 'src/Web', 'src/backend'];
|
|
93
|
-
|
|
94
|
-
function checkCsproj(filePath) {
|
|
95
|
-
hasDotnet = true;
|
|
96
|
-
try {
|
|
97
|
-
const content = readFileSync(filePath, 'utf8');
|
|
98
|
-
if (content.includes('Hangfire')) {
|
|
99
|
-
if (!structure.integrations.includes('hangfire')) structure.integrations.push('hangfire');
|
|
100
|
-
}
|
|
101
|
-
} catch { /* ignore */ }
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
for (const loc of csprojLocations) {
|
|
105
|
-
try {
|
|
106
|
-
const dir = join(targetPath, loc);
|
|
107
|
-
if (!existsSync(dir)) continue;
|
|
108
|
-
const entries = readdirSyncSafe(dir);
|
|
109
|
-
// Direct .csproj files
|
|
110
|
-
for (const e of entries.filter(f => f.endsWith('.csproj'))) {
|
|
111
|
-
checkCsproj(join(dir, e));
|
|
112
|
-
}
|
|
113
|
-
// One level deeper (e.g., src/backend/MyProject/*.csproj)
|
|
114
|
-
for (const sub of entries) {
|
|
115
|
-
const subDir = join(dir, sub);
|
|
116
|
-
for (const e of readdirSyncSafe(subDir).filter(f => f.endsWith('.csproj'))) {
|
|
117
|
-
checkCsproj(join(subDir, e));
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
} catch { /* ignore */ }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Determine stack: monorepo (nextjs+dotnet), dotnet, or keep frontend stack
|
|
124
|
-
if (hasDotnet) {
|
|
125
|
-
if (structure.stack === 'nextjs' || structure.stack === 'react' || structure.stack === 'vue') {
|
|
126
|
-
// Monorepo: frontend + .NET backend
|
|
127
|
-
structure.stack = `${structure.stack}+dotnet`;
|
|
128
|
-
} else if (structure.stack === 'unknown') {
|
|
129
|
-
structure.stack = 'dotnet';
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// ── vercel.json detection ──────────────────────────────────────────────
|
|
134
|
-
if (existsSync(join(targetPath, 'vercel.json'))) {
|
|
135
|
-
structure.hasVercel = true;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ── tsconfig.json detection (fallback for stack) ────────────────────────
|
|
139
|
-
if (structure.stack === 'unknown' && existsSync(join(targetPath, 'tsconfig.json'))) {
|
|
140
|
-
structure.stack = 'nodejs';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ── Architecture detection ─────────────────────────────────────────────
|
|
144
|
-
try {
|
|
145
|
-
const configPath = join(targetPath, '.morph/config/config.json');
|
|
146
|
-
if (existsSync(configPath)) {
|
|
147
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
148
|
-
if (config.architecture?.style) {
|
|
149
|
-
structure.architecture = config.architecture.style;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
} catch { /* ignore */ }
|
|
153
|
-
|
|
154
|
-
// ── Docker detection ───────────────────────────────────────────────────
|
|
155
|
-
if (existsSync(join(targetPath, 'docker-compose.yml')) ||
|
|
156
|
-
existsSync(join(targetPath, 'docker-compose.yaml')) ||
|
|
157
|
-
existsSync(join(targetPath, 'Dockerfile'))) {
|
|
158
|
-
if (!structure.integrations.includes('docker')) structure.integrations.push('docker');
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// ── Azure detection ────────────────────────────────────────────────────
|
|
162
|
-
const azureSignals = [
|
|
163
|
-
'.azure',
|
|
164
|
-
'azure-pipelines.yml',
|
|
165
|
-
'infra/main.bicep',
|
|
166
|
-
'infra',
|
|
167
|
-
];
|
|
168
|
-
const hasAzure = azureSignals.some(signal => {
|
|
169
|
-
const p = join(targetPath, signal);
|
|
170
|
-
if (!existsSync(p)) return false;
|
|
171
|
-
if (signal === 'infra') {
|
|
172
|
-
// Check for .bicep files inside infra/
|
|
173
|
-
return readdirSyncSafe(p).some(f => f.endsWith('.bicep'));
|
|
174
|
-
}
|
|
175
|
-
return true;
|
|
176
|
-
});
|
|
177
|
-
if (hasAzure && !structure.integrations.includes('azure')) {
|
|
178
|
-
structure.integrations.push('azure');
|
|
179
|
-
}
|
|
180
|
-
} catch {
|
|
181
|
-
// Fail-safe — return defaults
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
structure,
|
|
186
|
-
inferred: {
|
|
187
|
-
markdown: opts.generateStandards ? generateInferredMarkdown(structure) : '',
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Safe readdir that returns [] on error.
|
|
194
|
-
* @param {string} dir
|
|
195
|
-
* @returns {string[]}
|
|
196
|
-
*/
|
|
197
|
-
function readdirSyncSafe(dir) {
|
|
198
|
-
try {
|
|
199
|
-
return readdirSync(dir);
|
|
200
|
-
} catch {
|
|
201
|
-
return [];
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Generate a markdown summary from detected structure.
|
|
207
|
-
* @param {Object} structure
|
|
208
|
-
* @returns {string}
|
|
209
|
-
*/
|
|
210
|
-
function generateInferredMarkdown(structure) {
|
|
211
|
-
const lines = [`## Detected Stack: ${structure.stack}`];
|
|
212
|
-
if (structure.architecture) lines.push(`Architecture: ${structure.architecture}`);
|
|
213
|
-
if (structure.uiLibrary) lines.push(`UI Library: ${structure.uiLibrary}`);
|
|
214
|
-
if (structure.integrations.length > 0) lines.push(`Integrations: ${structure.integrations.join(', ')}`);
|
|
215
|
-
if (structure.hasVercel) lines.push(`Vercel: detected`);
|
|
216
|
-
return lines.join('\n');
|
|
217
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Project Detector
|
|
3
|
+
*
|
|
4
|
+
* Scans a project directory for stack, architecture, UI library, and integrations.
|
|
5
|
+
* Used by mcp-setup, morph-init, and session-start hooks.
|
|
6
|
+
*
|
|
7
|
+
* @module detectors/index
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Detect project structure from filesystem evidence.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} targetPath - Root path of the project
|
|
17
|
+
* @param {Object} [opts] - Options
|
|
18
|
+
* @param {boolean} [opts.generateStandards=false] - Whether to generate inferred markdown
|
|
19
|
+
* @returns {{ structure: { stack: string, architecture: string|null, uiLibrary: string|null, integrations: string[], hasVercel: boolean }, inferred: { markdown: string } }}
|
|
20
|
+
*/
|
|
21
|
+
export function detectProject(targetPath, opts = {}) {
|
|
22
|
+
const structure = {
|
|
23
|
+
stack: 'unknown',
|
|
24
|
+
architecture: null,
|
|
25
|
+
uiLibrary: null,
|
|
26
|
+
integrations: [],
|
|
27
|
+
hasVercel: false,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// ── Package.json analysis ──────────────────────────────────────────────
|
|
32
|
+
// Collect deps from root and common monorepo subfolders
|
|
33
|
+
const pkgLocations = ['.', 'src/frontend', 'src/web', 'frontend', 'apps/web', 'packages/app'];
|
|
34
|
+
const allDeps = {};
|
|
35
|
+
let rootPkg = null;
|
|
36
|
+
let scripts = [];
|
|
37
|
+
for (const loc of pkgLocations) {
|
|
38
|
+
const pkgPath = join(targetPath, loc, 'package.json');
|
|
39
|
+
if (!existsSync(pkgPath)) continue;
|
|
40
|
+
try {
|
|
41
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
42
|
+
if (loc === '.') rootPkg = pkg;
|
|
43
|
+
Object.assign(allDeps, pkg.dependencies, pkg.devDependencies);
|
|
44
|
+
scripts.push(...Object.keys(pkg.scripts || {}));
|
|
45
|
+
// Track frontend path for config
|
|
46
|
+
if (loc !== '.' && (pkg.dependencies?.next || pkg.dependencies?.react || pkg.dependencies?.vue)) {
|
|
47
|
+
structure.frontendPath = loc;
|
|
48
|
+
}
|
|
49
|
+
} catch { /* invalid JSON */ }
|
|
50
|
+
}
|
|
51
|
+
const depNames = Object.keys(allDeps);
|
|
52
|
+
|
|
53
|
+
if (depNames.length > 0) {
|
|
54
|
+
|
|
55
|
+
// Stack detection from deps
|
|
56
|
+
if (depNames.includes('next')) {
|
|
57
|
+
structure.stack = 'nextjs';
|
|
58
|
+
} else if (depNames.includes('react')) {
|
|
59
|
+
structure.stack = 'react';
|
|
60
|
+
} else if (depNames.includes('vue')) {
|
|
61
|
+
structure.stack = 'vue';
|
|
62
|
+
} else if (depNames.includes('express') || depNames.includes('fastify') || depNames.includes('hono')) {
|
|
63
|
+
structure.stack = 'nodejs';
|
|
64
|
+
} else if (rootPkg?.name) {
|
|
65
|
+
structure.stack = 'nodejs';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// UI library detection
|
|
69
|
+
if (existsSync(join(targetPath, 'components.json')) || existsSync(join(targetPath, structure.frontendPath || '.', 'components.json')) || depNames.includes('@shadcn/ui')) {
|
|
70
|
+
structure.uiLibrary = 'shadcn';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Integrations
|
|
74
|
+
if (depNames.includes('@neondatabase/serverless') || depNames.includes('postgres') || depNames.includes('pg')) {
|
|
75
|
+
structure.integrations.push('neon');
|
|
76
|
+
}
|
|
77
|
+
if (depNames.includes('@neondatabase/auth')) {
|
|
78
|
+
structure.integrations.push('neon-auth');
|
|
79
|
+
}
|
|
80
|
+
if (depNames.includes('stripe')) {
|
|
81
|
+
structure.integrations.push('stripe');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Vercel detection
|
|
85
|
+
if (depNames.includes('vercel') || scripts.includes('vercel')) {
|
|
86
|
+
structure.hasVercel = true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ── .csproj detection ──────────────────────────────────────────────────
|
|
91
|
+
let hasDotnet = false;
|
|
92
|
+
const csprojLocations = ['.', 'src', 'src/Api', 'src/Web', 'src/backend'];
|
|
93
|
+
|
|
94
|
+
function checkCsproj(filePath) {
|
|
95
|
+
hasDotnet = true;
|
|
96
|
+
try {
|
|
97
|
+
const content = readFileSync(filePath, 'utf8');
|
|
98
|
+
if (content.includes('Hangfire')) {
|
|
99
|
+
if (!structure.integrations.includes('hangfire')) structure.integrations.push('hangfire');
|
|
100
|
+
}
|
|
101
|
+
} catch { /* ignore */ }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
for (const loc of csprojLocations) {
|
|
105
|
+
try {
|
|
106
|
+
const dir = join(targetPath, loc);
|
|
107
|
+
if (!existsSync(dir)) continue;
|
|
108
|
+
const entries = readdirSyncSafe(dir);
|
|
109
|
+
// Direct .csproj files
|
|
110
|
+
for (const e of entries.filter(f => f.endsWith('.csproj'))) {
|
|
111
|
+
checkCsproj(join(dir, e));
|
|
112
|
+
}
|
|
113
|
+
// One level deeper (e.g., src/backend/MyProject/*.csproj)
|
|
114
|
+
for (const sub of entries) {
|
|
115
|
+
const subDir = join(dir, sub);
|
|
116
|
+
for (const e of readdirSyncSafe(subDir).filter(f => f.endsWith('.csproj'))) {
|
|
117
|
+
checkCsproj(join(subDir, e));
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} catch { /* ignore */ }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Determine stack: monorepo (nextjs+dotnet), dotnet, or keep frontend stack
|
|
124
|
+
if (hasDotnet) {
|
|
125
|
+
if (structure.stack === 'nextjs' || structure.stack === 'react' || structure.stack === 'vue') {
|
|
126
|
+
// Monorepo: frontend + .NET backend
|
|
127
|
+
structure.stack = `${structure.stack}+dotnet`;
|
|
128
|
+
} else if (structure.stack === 'unknown') {
|
|
129
|
+
structure.stack = 'dotnet';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── vercel.json detection ──────────────────────────────────────────────
|
|
134
|
+
if (existsSync(join(targetPath, 'vercel.json'))) {
|
|
135
|
+
structure.hasVercel = true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ── tsconfig.json detection (fallback for stack) ────────────────────────
|
|
139
|
+
if (structure.stack === 'unknown' && existsSync(join(targetPath, 'tsconfig.json'))) {
|
|
140
|
+
structure.stack = 'nodejs';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ── Architecture detection ─────────────────────────────────────────────
|
|
144
|
+
try {
|
|
145
|
+
const configPath = join(targetPath, '.morph/config/config.json');
|
|
146
|
+
if (existsSync(configPath)) {
|
|
147
|
+
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
148
|
+
if (config.architecture?.style) {
|
|
149
|
+
structure.architecture = config.architecture.style;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch { /* ignore */ }
|
|
153
|
+
|
|
154
|
+
// ── Docker detection ───────────────────────────────────────────────────
|
|
155
|
+
if (existsSync(join(targetPath, 'docker-compose.yml')) ||
|
|
156
|
+
existsSync(join(targetPath, 'docker-compose.yaml')) ||
|
|
157
|
+
existsSync(join(targetPath, 'Dockerfile'))) {
|
|
158
|
+
if (!structure.integrations.includes('docker')) structure.integrations.push('docker');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ── Azure detection ────────────────────────────────────────────────────
|
|
162
|
+
const azureSignals = [
|
|
163
|
+
'.azure',
|
|
164
|
+
'azure-pipelines.yml',
|
|
165
|
+
'infra/main.bicep',
|
|
166
|
+
'infra',
|
|
167
|
+
];
|
|
168
|
+
const hasAzure = azureSignals.some(signal => {
|
|
169
|
+
const p = join(targetPath, signal);
|
|
170
|
+
if (!existsSync(p)) return false;
|
|
171
|
+
if (signal === 'infra') {
|
|
172
|
+
// Check for .bicep files inside infra/
|
|
173
|
+
return readdirSyncSafe(p).some(f => f.endsWith('.bicep'));
|
|
174
|
+
}
|
|
175
|
+
return true;
|
|
176
|
+
});
|
|
177
|
+
if (hasAzure && !structure.integrations.includes('azure')) {
|
|
178
|
+
structure.integrations.push('azure');
|
|
179
|
+
}
|
|
180
|
+
} catch {
|
|
181
|
+
// Fail-safe — return defaults
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
structure,
|
|
186
|
+
inferred: {
|
|
187
|
+
markdown: opts.generateStandards ? generateInferredMarkdown(structure) : '',
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Safe readdir that returns [] on error.
|
|
194
|
+
* @param {string} dir
|
|
195
|
+
* @returns {string[]}
|
|
196
|
+
*/
|
|
197
|
+
function readdirSyncSafe(dir) {
|
|
198
|
+
try {
|
|
199
|
+
return readdirSync(dir);
|
|
200
|
+
} catch {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Generate a markdown summary from detected structure.
|
|
207
|
+
* @param {Object} structure
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
function generateInferredMarkdown(structure) {
|
|
211
|
+
const lines = [`## Detected Stack: ${structure.stack}`];
|
|
212
|
+
if (structure.architecture) lines.push(`Architecture: ${structure.architecture}`);
|
|
213
|
+
if (structure.uiLibrary) lines.push(`UI Library: ${structure.uiLibrary}`);
|
|
214
|
+
if (structure.integrations.length > 0) lines.push(`Integrations: ${structure.integrations.join(', ')}`);
|
|
215
|
+
if (structure.hasVercel) lines.push(`Vercel: detected`);
|
|
216
|
+
return lines.join('\n');
|
|
217
|
+
}
|