@polymorphism-tech/morph-spec 1.0.4 → 2.1.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/CLAUDE.md +1381 -0
- package/LICENSE +72 -0
- package/README.md +89 -6
- package/bin/detect-agents.js +225 -0
- package/bin/morph-spec.js +120 -0
- package/bin/render-template.js +302 -0
- package/bin/semantic-detect-agents.js +246 -0
- package/bin/validate-agents-skills.js +239 -0
- package/bin/validate-agents.js +69 -0
- package/bin/validate-phase.js +263 -0
- package/content/.azure/README.md +293 -0
- package/content/.azure/docs/azure-devops-setup.md +454 -0
- package/content/.azure/docs/branch-strategy.md +398 -0
- package/content/.azure/docs/local-development.md +515 -0
- package/content/.azure/pipelines/pipeline-variables.yml +34 -0
- package/content/.azure/pipelines/prod-pipeline.yml +319 -0
- package/content/.azure/pipelines/staging-pipeline.yml +234 -0
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -0
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -0
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -0
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -0
- package/content/.claude/commands/morph-apply.md +118 -26
- package/content/.claude/commands/morph-archive.md +9 -9
- package/content/.claude/commands/morph-clarify.md +184 -0
- package/content/.claude/commands/morph-design.md +275 -0
- package/content/.claude/commands/morph-proposal.md +56 -15
- package/content/.claude/commands/morph-setup.md +100 -0
- package/content/.claude/commands/morph-status.md +47 -32
- package/content/.claude/commands/morph-tasks.md +319 -0
- package/content/.claude/commands/morph-uiux.md +211 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +604 -0
- package/content/.claude/skills/specialists/ms-agent-expert.md +143 -89
- package/content/.claude/skills/specialists/ui-ux-designer.md +744 -9
- package/content/.claude/skills/stacks/dotnet-blazor.md +244 -8
- package/content/.claude/skills/stacks/dotnet-nextjs.md +2 -2
- package/content/.morph/.morphversion +5 -0
- package/content/.morph/config/agents.json +101 -8
- package/content/.morph/config/azure-pricing.json +70 -0
- package/content/.morph/config/azure-pricing.schema.json +50 -0
- package/content/.morph/config/config.template.json +15 -3
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -0
- package/content/.morph/hooks/README.md +239 -0
- package/content/.morph/hooks/pre-commit-agents.sh +24 -0
- package/content/.morph/hooks/pre-commit-all.sh +48 -0
- package/content/.morph/hooks/pre-commit-costs.sh +91 -0
- package/content/.morph/hooks/pre-commit-specs.sh +49 -0
- package/content/.morph/hooks/pre-commit-tests.sh +60 -0
- package/content/.morph/project.md +5 -4
- package/content/.morph/schemas/agent.schema.json +296 -0
- package/content/.morph/standards/agent-framework-setup.md +453 -0
- package/content/.morph/standards/architecture.md +142 -7
- package/content/.morph/standards/azure.md +218 -23
- package/content/.morph/standards/coding.md +47 -12
- package/content/.morph/standards/dotnet10-migration.md +494 -0
- package/content/.morph/standards/fluent-ui-setup.md +590 -0
- package/content/.morph/standards/migration-guide.md +514 -0
- package/content/.morph/standards/passkeys-auth.md +423 -0
- package/content/.morph/standards/vector-search-rag.md +536 -0
- package/content/.morph/state.json +18 -0
- package/content/.morph/templates/FluentDesignTheme.cs +149 -0
- package/content/.morph/templates/MudTheme.cs +281 -0
- package/content/.morph/templates/contracts.cs +55 -55
- package/content/.morph/templates/decisions.md +4 -4
- package/content/.morph/templates/design-system.css +226 -0
- package/content/.morph/templates/infra/.dockerignore.example +89 -0
- package/content/.morph/templates/infra/Dockerfile.example +82 -0
- package/content/.morph/templates/infra/README.md +286 -0
- package/content/.morph/templates/infra/app-service.bicep +164 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -0
- package/content/.morph/templates/infra/deploy.sh +208 -0
- package/content/.morph/templates/infra/main.bicep +41 -7
- package/content/.morph/templates/infra/parameters.dev.json +6 -0
- package/content/.morph/templates/infra/parameters.prod.json +6 -0
- package/content/.morph/templates/infra/parameters.staging.json +29 -0
- package/content/.morph/templates/proposal.md +3 -3
- package/content/.morph/templates/recap.md +3 -3
- package/content/.morph/templates/spec.md +9 -8
- package/content/.morph/templates/sprint-status.yaml +68 -0
- package/content/.morph/templates/state.template.json +222 -0
- package/content/.morph/templates/story.md +143 -0
- package/content/.morph/templates/tasks.md +1 -1
- package/content/.morph/templates/ui-components.md +276 -0
- package/content/.morph/templates/ui-design-system.md +286 -0
- package/content/.morph/templates/ui-flows.md +336 -0
- package/content/.morph/templates/ui-mockups.md +133 -0
- package/content/.morph/test-infra/example.bicep +59 -0
- package/content/CLAUDE.md +124 -0
- package/content/README.md +79 -0
- package/detectors/config-detector.js +223 -0
- package/detectors/conversation-analyzer.js +163 -0
- package/detectors/index.js +84 -0
- package/detectors/standards-generator.js +275 -0
- package/detectors/structure-detector.js +221 -0
- package/docs/README.md +149 -0
- package/docs/api/cost-calculator.js.html +513 -0
- package/docs/api/design-system-generator.js.html +382 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.eot +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.woff +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.eot +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.woff +0 -0
- package/docs/api/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
- package/docs/api/global.html +5263 -0
- package/docs/api/index.html +96 -0
- package/docs/api/scripts/collapse.js +39 -0
- package/docs/api/scripts/commonNav.js +28 -0
- package/docs/api/scripts/linenumber.js +25 -0
- package/docs/api/scripts/nav.js +12 -0
- package/docs/api/scripts/polyfill.js +4 -0
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/api/scripts/prettify/lang-css.js +2 -0
- package/docs/api/scripts/prettify/prettify.js +28 -0
- package/docs/api/scripts/search.js +99 -0
- package/docs/api/state-manager.js.html +423 -0
- package/docs/api/styles/jsdoc.css +776 -0
- package/docs/api/styles/prettify.css +80 -0
- package/docs/examples.md +328 -0
- package/docs/getting-started.md +302 -0
- package/docs/installation.md +361 -0
- package/docs/templates.md +418 -0
- package/docs/validation-checklist.md +266 -0
- package/package.json +39 -12
- package/src/commands/cost.js +181 -0
- package/src/commands/create-story.js +283 -0
- package/src/commands/detect.js +104 -0
- package/src/commands/doctor.js +67 -0
- package/src/commands/generate.js +149 -0
- package/src/commands/init.js +69 -45
- package/src/commands/shard-spec.js +224 -0
- package/src/commands/sprint-status.js +250 -0
- package/src/commands/state.js +333 -0
- package/src/commands/sync.js +167 -0
- package/src/commands/update-pricing.js +206 -0
- package/src/commands/update.js +88 -13
- package/src/lib/complexity-analyzer.js +292 -0
- package/src/lib/cost-calculator.js +429 -0
- package/src/lib/design-system-generator.js +298 -0
- package/src/lib/state-manager.js +340 -0
- package/src/utils/file-copier.js +59 -0
- package/src/utils/version-checker.js +175 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# MORPH-SPEC Content - Projeto Exemplo
|
|
2
|
+
|
|
3
|
+
Este diretório contém um **projeto exemplo** mostrando como usar o framework MORPH-SPEC em um projeto .NET/Blazor.
|
|
4
|
+
|
|
5
|
+
> 📘 **Nota**: Este não é o código do framework, apenas um exemplo de uso.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 📁 Estrutura
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
content/
|
|
13
|
+
├── .morph/ # Configuração MORPH do projeto exemplo
|
|
14
|
+
│ ├── project/ # Context e outputs do projeto
|
|
15
|
+
│ │ ├── context/
|
|
16
|
+
│ │ ├── standards/
|
|
17
|
+
│ │ └── outputs/
|
|
18
|
+
│ └── config.json # Link para framework global
|
|
19
|
+
│
|
|
20
|
+
├── CLAUDE.md # Instruções para Claude Code
|
|
21
|
+
└── src/ # (futuro) Código exemplo
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 🎯 Objetivo
|
|
27
|
+
|
|
28
|
+
Este exemplo demonstra:
|
|
29
|
+
|
|
30
|
+
1. Como inicializar MORPH em um projeto
|
|
31
|
+
2. Estrutura de `.morph/project/`
|
|
32
|
+
3. Como Claude Code usa o framework
|
|
33
|
+
4. Exemplos de features implementadas com MORPH
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 🚀 Como Usar Este Exemplo
|
|
38
|
+
|
|
39
|
+
### 1. Instalar MORPH globalmente
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npm install -g @polymorphism-tech/morph-spec
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Ver estrutura detectada
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
cd content/
|
|
49
|
+
morph-spec detect --verbose
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 3. Inicializar em seu próprio projeto
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
cd seu-projeto/
|
|
56
|
+
morph-spec init
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## 📚 Documentação
|
|
62
|
+
|
|
63
|
+
- **Framework**: Veja `../framework/` para standards e templates
|
|
64
|
+
- **Docs**: Veja `../docs/` para guias completos
|
|
65
|
+
- **CLI**: Execute `morph-spec --help`
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## ⚠️ Importante
|
|
70
|
+
|
|
71
|
+
Este é um **exemplo read-only**. Para criar seu próprio projeto:
|
|
72
|
+
|
|
73
|
+
1. NÃO copie este diretório
|
|
74
|
+
2. Execute `morph-spec init` no seu projeto
|
|
75
|
+
3. O framework será linkado automaticamente
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
*MORPH-SPEC by Polymorphism Tech*
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Detector - Reads configuration files to extract technologies, versions, and dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync, existsSync } from 'fs';
|
|
6
|
+
import { join } from 'path';
|
|
7
|
+
import { glob } from 'glob';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Detect project configuration
|
|
11
|
+
* @param {string} projectPath - Project root path
|
|
12
|
+
* @returns {Promise<Object>} Configuration detection results
|
|
13
|
+
*/
|
|
14
|
+
export async function detectConfig(projectPath) {
|
|
15
|
+
const result = {
|
|
16
|
+
language: 'unknown',
|
|
17
|
+
version: null,
|
|
18
|
+
packageManager: 'unknown',
|
|
19
|
+
dependencies: [],
|
|
20
|
+
technologies: [],
|
|
21
|
+
auth: null,
|
|
22
|
+
database: null,
|
|
23
|
+
hosting: null
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Detect .NET projects
|
|
27
|
+
const dotnetConfig = await detectDotNet(projectPath);
|
|
28
|
+
if (dotnetConfig) {
|
|
29
|
+
Object.assign(result, dotnetConfig);
|
|
30
|
+
return result;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Detect Node.js projects
|
|
34
|
+
const nodeConfig = await detectNode(projectPath);
|
|
35
|
+
if (nodeConfig) {
|
|
36
|
+
Object.assign(result, nodeConfig);
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Detect .NET configuration
|
|
45
|
+
*/
|
|
46
|
+
async function detectDotNet(projectPath) {
|
|
47
|
+
const programCs = join(projectPath, 'Program.cs');
|
|
48
|
+
const csprojFiles = await glob('**/*.csproj', { cwd: projectPath });
|
|
49
|
+
|
|
50
|
+
if (!existsSync(programCs) && csprojFiles.length === 0) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const config = {
|
|
55
|
+
language: 'csharp',
|
|
56
|
+
version: null,
|
|
57
|
+
packageManager: 'dotnet',
|
|
58
|
+
dependencies: [],
|
|
59
|
+
technologies: [],
|
|
60
|
+
auth: null,
|
|
61
|
+
database: null,
|
|
62
|
+
hosting: null
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// Read Program.cs
|
|
66
|
+
if (existsSync(programCs)) {
|
|
67
|
+
const content = readFileSync(programCs, 'utf8');
|
|
68
|
+
|
|
69
|
+
// Detect .NET version
|
|
70
|
+
if (content.includes('WebApplication.CreateBuilder') || content.includes('.NET')) {
|
|
71
|
+
config.version = detectDotNetVersion(content);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Detect technologies from Program.cs
|
|
75
|
+
config.technologies = detectTechnologiesFromProgramCs(content);
|
|
76
|
+
config.auth = detectAuthMethod(content);
|
|
77
|
+
config.database = detectDatabase(content);
|
|
78
|
+
config.hosting = detectHosting(content);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Read .csproj files
|
|
82
|
+
for (const csprojFile of csprojFiles.slice(0, 3)) { // Read first 3 csproj files
|
|
83
|
+
const csprojPath = join(projectPath, csprojFile);
|
|
84
|
+
const content = readFileSync(csprojPath, 'utf8');
|
|
85
|
+
|
|
86
|
+
config.dependencies.push(...extractNuGetPackages(content));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Remove duplicates
|
|
90
|
+
config.dependencies = [...new Set(config.dependencies)];
|
|
91
|
+
config.technologies = [...new Set(config.technologies)];
|
|
92
|
+
|
|
93
|
+
return config;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Detect Node.js configuration
|
|
98
|
+
*/
|
|
99
|
+
async function detectNode(projectPath) {
|
|
100
|
+
const packageJsonPath = join(projectPath, 'package.json');
|
|
101
|
+
|
|
102
|
+
if (!existsSync(packageJsonPath)) {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
107
|
+
|
|
108
|
+
const config = {
|
|
109
|
+
language: 'javascript',
|
|
110
|
+
version: packageJson.engines?.node || null,
|
|
111
|
+
packageManager: existsSync(join(projectPath, 'yarn.lock')) ? 'yarn' :
|
|
112
|
+
existsSync(join(projectPath, 'pnpm-lock.yaml')) ? 'pnpm' : 'npm',
|
|
113
|
+
dependencies: [],
|
|
114
|
+
technologies: [],
|
|
115
|
+
auth: null,
|
|
116
|
+
database: null,
|
|
117
|
+
hosting: null
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// Extract dependencies
|
|
121
|
+
const allDeps = {
|
|
122
|
+
...packageJson.dependencies,
|
|
123
|
+
...packageJson.devDependencies
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
config.dependencies = Object.keys(allDeps);
|
|
127
|
+
|
|
128
|
+
// Detect technologies
|
|
129
|
+
if (allDeps['next']) config.technologies.push('Next.js');
|
|
130
|
+
if (allDeps['react']) config.technologies.push('React');
|
|
131
|
+
if (allDeps['@clerk/nextjs']) config.auth = 'Clerk';
|
|
132
|
+
if (allDeps['prisma']) config.database = 'Prisma';
|
|
133
|
+
|
|
134
|
+
return config;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Detect .NET version from Program.cs
|
|
139
|
+
*/
|
|
140
|
+
function detectDotNetVersion(content) {
|
|
141
|
+
if (content.includes('.NET 10') || content.includes('net10.0')) return '.NET 10';
|
|
142
|
+
if (content.includes('.NET 9') || content.includes('net9.0')) return '.NET 9';
|
|
143
|
+
if (content.includes('.NET 8') || content.includes('net8.0')) return '.NET 8';
|
|
144
|
+
return 'unknown';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Detect technologies from Program.cs
|
|
149
|
+
*/
|
|
150
|
+
function detectTechnologiesFromProgramCs(content) {
|
|
151
|
+
const techs = [];
|
|
152
|
+
|
|
153
|
+
if (content.includes('AddBlazor') || content.includes('MapBlazorHub')) {
|
|
154
|
+
techs.push('Blazor Server');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (content.includes('AddHangfire')) {
|
|
158
|
+
techs.push('Hangfire');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (content.includes('IChatClient') || content.includes('Microsoft.Agents')) {
|
|
162
|
+
techs.push('Microsoft Agent Framework');
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (content.includes('AddDbContext') || content.includes('UseSqlServer')) {
|
|
166
|
+
techs.push('Entity Framework Core');
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (content.includes('AddFluentUIComponents') || content.includes('FluentUI')) {
|
|
170
|
+
techs.push('Fluent UI Blazor');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (content.includes('AddMudServices') || content.includes('MudBlazor')) {
|
|
174
|
+
techs.push('MudBlazor');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return techs;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Detect authentication method
|
|
182
|
+
*/
|
|
183
|
+
function detectAuthMethod(content) {
|
|
184
|
+
if (content.includes('AddClerk') || content.includes('Clerk')) return 'Clerk';
|
|
185
|
+
if (content.includes('AddMicrosoftIdentity') || content.includes('Entra')) return 'Microsoft Entra';
|
|
186
|
+
if (content.includes('AddAuthentication')) return 'ASP.NET Identity';
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Detect database
|
|
192
|
+
*/
|
|
193
|
+
function detectDatabase(content) {
|
|
194
|
+
if (content.includes('UseSqlServer')) return 'SQL Server';
|
|
195
|
+
if (content.includes('UsePostgres')) return 'PostgreSQL';
|
|
196
|
+
if (content.includes('UseSqlite')) return 'SQLite';
|
|
197
|
+
if (content.includes('UseCosmos')) return 'Cosmos DB';
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Detect hosting platform
|
|
203
|
+
*/
|
|
204
|
+
function detectHosting(content) {
|
|
205
|
+
if (content.includes('Azure') || content.includes('AddAzure')) return 'Azure';
|
|
206
|
+
if (content.includes('AWS')) return 'AWS';
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Extract NuGet packages from .csproj
|
|
212
|
+
*/
|
|
213
|
+
function extractNuGetPackages(csprojContent) {
|
|
214
|
+
const packages = [];
|
|
215
|
+
const regex = /<PackageReference\s+Include="([^"]+)"/g;
|
|
216
|
+
let match;
|
|
217
|
+
|
|
218
|
+
while ((match = regex.exec(csprojContent)) !== null) {
|
|
219
|
+
packages.push(match[1]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return packages;
|
|
223
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Conversation Analyzer - Analyzes Claude Code conversation history for user preferences
|
|
3
|
+
*
|
|
4
|
+
* Note: This is a basic implementation. Full conversation analysis would require
|
|
5
|
+
* access to Claude Code's message history API or local cache.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { existsSync, readFileSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Analyze conversation history
|
|
13
|
+
* @param {string} projectPath - Project root path
|
|
14
|
+
* @returns {Promise<Object>} Conversation analysis results
|
|
15
|
+
*/
|
|
16
|
+
export async function analyzeConversation(projectPath) {
|
|
17
|
+
const result = {
|
|
18
|
+
preferences: {},
|
|
19
|
+
decisions: [],
|
|
20
|
+
patterns: [],
|
|
21
|
+
available: false
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Check if .morph/project/context exists (past decisions)
|
|
25
|
+
const contextPath = join(projectPath, '.morph', 'project', 'context');
|
|
26
|
+
if (existsSync(contextPath)) {
|
|
27
|
+
result.available = true;
|
|
28
|
+
|
|
29
|
+
// Read decisions-history.md if exists
|
|
30
|
+
const decisionsFile = join(contextPath, 'decisions-history.md');
|
|
31
|
+
if (existsSync(decisionsFile)) {
|
|
32
|
+
const content = readFileSync(decisionsFile, 'utf8');
|
|
33
|
+
result.decisions = extractDecisions(content);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check .morph/project/outputs/*/decisions.md
|
|
38
|
+
const outputsPath = join(projectPath, '.morph', 'project', 'outputs');
|
|
39
|
+
if (existsSync(outputsPath)) {
|
|
40
|
+
const { readdirSync } = await import('fs');
|
|
41
|
+
const features = readdirSync(outputsPath, { withFileTypes: true })
|
|
42
|
+
.filter(dirent => dirent.isDirectory())
|
|
43
|
+
.map(dirent => dirent.name);
|
|
44
|
+
|
|
45
|
+
for (const feature of features) {
|
|
46
|
+
const decisionsFile = join(outputsPath, feature, 'decisions.md');
|
|
47
|
+
if (existsSync(decisionsFile)) {
|
|
48
|
+
const content = readFileSync(decisionsFile, 'utf8');
|
|
49
|
+
result.decisions.push(...extractDecisions(content));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Extract patterns from decisions
|
|
55
|
+
result.patterns = inferPatternsFromDecisions(result.decisions);
|
|
56
|
+
|
|
57
|
+
// Extract preferences
|
|
58
|
+
result.preferences = extractPreferences(result.decisions);
|
|
59
|
+
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Extract decisions from markdown
|
|
65
|
+
*/
|
|
66
|
+
function extractDecisions(markdown) {
|
|
67
|
+
const decisions = [];
|
|
68
|
+
const lines = markdown.split('\n');
|
|
69
|
+
|
|
70
|
+
let currentDecision = null;
|
|
71
|
+
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
// Look for decision headings
|
|
74
|
+
if (line.match(/^##\s+Decision/i) || line.match(/^###\s+ADR/i)) {
|
|
75
|
+
if (currentDecision) {
|
|
76
|
+
decisions.push(currentDecision);
|
|
77
|
+
}
|
|
78
|
+
currentDecision = { text: '', category: 'unknown', confidence: 0.5 };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (currentDecision) {
|
|
82
|
+
currentDecision.text += line + '\n';
|
|
83
|
+
|
|
84
|
+
// Categorize
|
|
85
|
+
if (line.includes('UI') || line.includes('component') || line.includes('Fluent') || line.includes('Mud')) {
|
|
86
|
+
currentDecision.category = 'ui-ux';
|
|
87
|
+
currentDecision.confidence = 0.8;
|
|
88
|
+
} else if (line.includes('pattern') || line.includes('architecture') || line.includes('CQRS')) {
|
|
89
|
+
currentDecision.category = 'architecture';
|
|
90
|
+
currentDecision.confidence = 0.8;
|
|
91
|
+
} else if (line.includes('Azure') || line.includes('Container') || line.includes('deployment')) {
|
|
92
|
+
currentDecision.category = 'azure';
|
|
93
|
+
currentDecision.confidence = 0.8;
|
|
94
|
+
} else if (line.includes('class') || line.includes('method') || line.includes('naming')) {
|
|
95
|
+
currentDecision.category = 'coding';
|
|
96
|
+
currentDecision.confidence = 0.8;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (currentDecision) {
|
|
102
|
+
decisions.push(currentDecision);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return decisions;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Infer patterns from decisions
|
|
110
|
+
*/
|
|
111
|
+
function inferPatternsFromDecisions(decisions) {
|
|
112
|
+
const patterns = [];
|
|
113
|
+
|
|
114
|
+
for (const decision of decisions) {
|
|
115
|
+
const text = decision.text.toLowerCase();
|
|
116
|
+
|
|
117
|
+
if (text.includes('always use') || text.includes('sempre usar')) {
|
|
118
|
+
patterns.push(`Pattern: ${decision.text.split('\n')[0].substring(0, 60)}...`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (text.includes('never') || text.includes('nunca')) {
|
|
122
|
+
patterns.push(`Anti-pattern: ${decision.text.split('\n')[0].substring(0, 60)}...`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return patterns;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Extract user preferences
|
|
131
|
+
*/
|
|
132
|
+
function extractPreferences(decisions) {
|
|
133
|
+
const preferences = {
|
|
134
|
+
uiLibrary: null,
|
|
135
|
+
architecture: null,
|
|
136
|
+
testing: null,
|
|
137
|
+
deployment: null
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
for (const decision of decisions) {
|
|
141
|
+
const text = decision.text.toLowerCase();
|
|
142
|
+
|
|
143
|
+
if (text.includes('fluent ui') && !text.includes('not')) {
|
|
144
|
+
preferences.uiLibrary = 'Fluent UI';
|
|
145
|
+
} else if (text.includes('mudblazor') && !text.includes('not')) {
|
|
146
|
+
preferences.uiLibrary = 'MudBlazor';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (text.includes('clean architecture')) {
|
|
150
|
+
preferences.architecture = 'Clean Architecture';
|
|
151
|
+
} else if (text.includes('cqrs')) {
|
|
152
|
+
preferences.architecture = 'CQRS';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (text.includes('container apps')) {
|
|
156
|
+
preferences.deployment = 'Azure Container Apps';
|
|
157
|
+
} else if (text.includes('app service')) {
|
|
158
|
+
preferences.deployment = 'Azure App Service';
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return preferences;
|
|
163
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MORPH-SPEC Detectors - Main Orchestrator
|
|
3
|
+
*
|
|
4
|
+
* Coordinates all detection modules to build a complete picture of the project.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { detectStructure } from './structure-detector.js';
|
|
8
|
+
import { detectConfig } from './config-detector.js';
|
|
9
|
+
import { analyzeConversation } from './conversation-analyzer.js';
|
|
10
|
+
import { generateStandards } from './standards-generator.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Main detection orchestrator
|
|
14
|
+
* @param {string} projectPath - Path to project root
|
|
15
|
+
* @param {Object} options - Detection options
|
|
16
|
+
* @returns {Promise<Object>} Detection results
|
|
17
|
+
*/
|
|
18
|
+
export async function detectProject(projectPath, options = {}) {
|
|
19
|
+
const results = {
|
|
20
|
+
path: projectPath,
|
|
21
|
+
timestamp: new Date().toISOString(),
|
|
22
|
+
structure: null,
|
|
23
|
+
config: null,
|
|
24
|
+
conversation: null,
|
|
25
|
+
inferred: null
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
// 1. Detect structure (stack, architecture, patterns)
|
|
30
|
+
if (options.structure !== false) {
|
|
31
|
+
results.structure = await detectStructure(projectPath);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 2. Detect config (technologies, dependencies, versions)
|
|
35
|
+
if (options.config !== false) {
|
|
36
|
+
results.config = await detectConfig(projectPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Analyze conversation history (if available)
|
|
40
|
+
if (options.conversation !== false) {
|
|
41
|
+
results.conversation = await analyzeConversation(projectPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// 4. Generate inferred standards
|
|
45
|
+
if (options.generateStandards !== false) {
|
|
46
|
+
results.inferred = await generateStandards(results);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return results;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new Error(`Detection failed: ${error.message}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Get detection summary
|
|
57
|
+
* @param {Object} results - Detection results
|
|
58
|
+
* @returns {string} Human-readable summary
|
|
59
|
+
*/
|
|
60
|
+
export function getDetectionSummary(results) {
|
|
61
|
+
const { structure, config } = results;
|
|
62
|
+
|
|
63
|
+
const lines = [
|
|
64
|
+
'## Detection Summary',
|
|
65
|
+
'',
|
|
66
|
+
'### Stack',
|
|
67
|
+
`- **Type**: ${structure?.stack || 'unknown'}`,
|
|
68
|
+
`- **Architecture**: ${structure?.architecture || 'unknown'}`,
|
|
69
|
+
`- **UI Library**: ${structure?.uiLibrary || 'none'}`,
|
|
70
|
+
'',
|
|
71
|
+
'### Technologies',
|
|
72
|
+
`- **Language**: ${config?.language || 'unknown'}`,
|
|
73
|
+
`- **Version**: ${config?.version || 'unknown'}`,
|
|
74
|
+
`- **Package Manager**: ${config?.packageManager || 'unknown'}`,
|
|
75
|
+
'',
|
|
76
|
+
'### Patterns Detected',
|
|
77
|
+
...(structure?.patterns || []).map(p => `- ${p}`),
|
|
78
|
+
'',
|
|
79
|
+
'### Recommendations',
|
|
80
|
+
...(results.inferred?.recommendations || []).map(r => `- ${r}`)
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
return lines.join('\n');
|
|
84
|
+
}
|