@polymorphism-tech/morph-spec 2.2.0 → 2.4.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 +314 -1673
- package/LICENSE +72 -72
- package/README.md +515 -516
- package/bin/detect-agents.js +225 -225
- package/bin/morph-spec.js +358 -173
- package/bin/render-template.js +302 -302
- package/bin/semantic-detect-agents.js +246 -246
- package/bin/task-manager.js +429 -0
- package/bin/validate-agents-skills.js +251 -251
- package/bin/validate-agents.js +69 -69
- package/bin/validate-phase.js +263 -263
- package/bin/validate.js +369 -0
- package/content/.azure/README.md +293 -293
- package/content/.azure/docs/azure-devops-setup.md +454 -454
- package/content/.azure/docs/branch-strategy.md +398 -398
- package/content/.azure/docs/local-development.md +515 -515
- package/content/.azure/pipelines/pipeline-variables.yml +34 -34
- package/content/.azure/pipelines/prod-pipeline.yml +319 -319
- package/content/.azure/pipelines/staging-pipeline.yml +234 -234
- package/content/.azure/pipelines/templates/build-dotnet.yml +75 -75
- package/content/.azure/pipelines/templates/deploy-app-service.yml +94 -94
- package/content/.azure/pipelines/templates/deploy-container-app.yml +120 -120
- package/content/.azure/pipelines/templates/infra-deploy.yml +90 -90
- package/content/.claude/commands/morph-apply.md +221 -158
- package/content/.claude/commands/morph-archive.md +79 -79
- package/content/.claude/commands/morph-infra.md +209 -209
- package/content/.claude/commands/morph-preflight.md +227 -0
- package/content/.claude/commands/morph-proposal.md +122 -101
- package/content/.claude/commands/morph-status.md +86 -86
- package/content/.claude/commands/morph-troubleshoot.md +122 -0
- package/content/.claude/settings.local.json +15 -15
- package/content/.claude/skills/checklists/code-review.md +226 -0
- package/content/.claude/skills/checklists/morph-checklist.md +117 -0
- package/content/.claude/skills/checklists/simulation-checklist.md +77 -0
- package/content/.claude/skills/infra/bicep-architect.md +126 -419
- package/content/.claude/skills/infra/container-specialist.md +131 -437
- package/content/.claude/skills/infra/devops-engineer.md +119 -405
- package/content/.claude/skills/integrations/asaas-financial.md +130 -333
- package/content/.claude/skills/integrations/azure-identity.md +142 -309
- package/content/.claude/skills/integrations/clerk-auth.md +108 -290
- package/content/.claude/skills/integrations/resend-email.md +119 -0
- package/content/.claude/skills/specialists/ai-system-architect.md +192 -604
- package/content/.claude/skills/specialists/azure-architect.md +142 -142
- package/content/.claude/skills/specialists/code-analyzer.md +235 -0
- package/content/.claude/skills/specialists/dotnet-senior.md +287 -0
- package/content/.claude/skills/specialists/ef-modeler.md +113 -200
- package/content/.claude/skills/specialists/hangfire-orchestrator.md +126 -245
- package/content/.claude/skills/specialists/ms-agent-expert.md +109 -263
- package/content/.claude/skills/specialists/po-pm-advisor.md +197 -197
- package/content/.claude/skills/specialists/standards-architect.md +156 -78
- package/content/.claude/skills/specialists/testing-specialist.md +126 -0
- package/content/.claude/skills/specialists/ui-ux-designer.md +191 -1060
- package/content/.claude/skills/stacks/dotnet-blazor.md +210 -588
- package/content/.claude/skills/stacks/dotnet-nextjs.md +154 -402
- package/content/.claude/skills/workflows/morph-replicate.md +213 -0
- package/content/.claude/{commands/morph-clarify.md → skills/workflows/phase-clarify.md} +5 -58
- package/content/.claude/{commands/morph-design.md → skills/workflows/phase-design.md} +16 -86
- package/content/.claude/{commands/morph-setup.md → skills/workflows/phase-setup.md} +9 -17
- package/content/.claude/skills/workflows/phase-tasks.md +164 -0
- package/content/.claude/{commands/morph-uiux.md → skills/workflows/phase-uiux.md} +15 -88
- package/content/.morph/.morphversion +5 -5
- package/content/.morph/archive/.gitkeep +25 -25
- package/content/.morph/config/agents.json +378 -242
- package/content/.morph/config/config.template.json +89 -108
- package/content/.morph/docs/STORY-DRIVEN-DEVELOPMENT.md +392 -392
- package/content/.morph/docs/workflows/design-impl.md +37 -0
- package/content/.morph/docs/workflows/fast-track.md +29 -0
- package/content/.morph/docs/workflows/full-morph.md +76 -0
- package/content/.morph/docs/workflows/standard.md +44 -0
- package/content/.morph/docs/workflows/ui-refresh.md +39 -0
- package/content/.morph/examples/api-nextjs/README.md +241 -241
- package/content/.morph/examples/api-nextjs/contracts.ts +307 -307
- package/content/.morph/examples/api-nextjs/spec.md +399 -399
- package/content/.morph/examples/api-nextjs/tasks.md +168 -168
- package/content/.morph/examples/micro-saas/README.md +125 -125
- package/content/.morph/examples/micro-saas/contracts.cs +358 -358
- package/content/.morph/examples/micro-saas/decisions.md +246 -246
- package/content/.morph/examples/micro-saas/spec.md +236 -236
- package/content/.morph/examples/micro-saas/tasks.md +150 -150
- package/content/.morph/examples/multi-agent/README.md +309 -309
- package/content/.morph/examples/multi-agent/contracts.cs +433 -433
- package/content/.morph/examples/multi-agent/spec.md +479 -479
- package/content/.morph/examples/multi-agent/tasks.md +185 -185
- package/content/.morph/examples/scheduled-reports/decisions.md +158 -0
- package/content/.morph/examples/scheduled-reports/proposal.md +95 -0
- package/content/.morph/examples/scheduled-reports/spec.md +267 -0
- package/content/.morph/examples/state-v3.json +188 -0
- package/content/.morph/features/.gitkeep +25 -25
- package/content/.morph/hooks/README.md +190 -239
- package/content/.morph/hooks/pre-commit-agents.sh +24 -24
- package/content/.morph/hooks/pre-commit-all.sh +48 -48
- package/content/.morph/hooks/pre-commit-specs.sh +49 -49
- package/content/.morph/hooks/pre-commit-tests.sh +60 -60
- package/content/.morph/project.md +160 -160
- package/content/.morph/schemas/agent.schema.json +296 -296
- package/content/.morph/schemas/tasks.schema.json +220 -0
- package/content/.morph/specs/.gitkeep +20 -20
- package/content/.morph/standards/agent-framework-blazor-ui.md +359 -0
- package/content/.morph/standards/agent-framework-production.md +410 -0
- package/content/.morph/standards/agent-framework-setup.md +413 -453
- package/content/.morph/standards/agent-framework-workflows.md +349 -0
- package/content/.morph/standards/architecture.md +325 -325
- package/content/.morph/standards/azure.md +605 -379
- package/content/.morph/standards/coding.md +377 -377
- package/content/.morph/standards/dotnet10-migration.md +520 -494
- package/content/.morph/standards/fluent-ui-setup.md +590 -590
- package/content/.morph/standards/migration-guide.md +514 -514
- package/content/.morph/standards/passkeys-auth.md +423 -423
- package/content/.morph/standards/vector-search-rag.md +536 -536
- package/content/.morph/state.json +17 -17
- package/content/.morph/templates/FluentDesignTheme.cs +149 -149
- package/content/.morph/templates/MudTheme.cs +281 -281
- package/content/.morph/templates/agent.cs +163 -172
- package/content/.morph/templates/clarify-questions.md +159 -0
- package/content/.morph/templates/component.razor +239 -239
- package/content/.morph/templates/contracts/Commands.cs +74 -0
- package/content/.morph/templates/contracts/Entities.cs +25 -0
- package/content/.morph/templates/contracts/Queries.cs +74 -0
- package/content/.morph/templates/contracts/README.md +74 -0
- package/content/.morph/templates/contracts.cs +217 -217
- package/content/.morph/templates/decisions.md +123 -106
- package/content/.morph/templates/design-system.css +226 -226
- package/content/.morph/templates/infra/.dockerignore.example +89 -89
- package/content/.morph/templates/infra/Dockerfile.example +82 -82
- package/content/.morph/templates/infra/README.md +286 -286
- package/content/.morph/templates/infra/app-insights.bicep +63 -63
- package/content/.morph/templates/infra/app-service.bicep +164 -164
- package/content/.morph/templates/infra/container-app-env.bicep +49 -49
- package/content/.morph/templates/infra/container-app.bicep +156 -156
- package/content/.morph/templates/infra/deploy-checklist.md +426 -0
- package/content/.morph/templates/infra/deploy.ps1 +229 -229
- package/content/.morph/templates/infra/deploy.sh +208 -208
- package/content/.morph/templates/infra/key-vault.bicep +91 -91
- package/content/.morph/templates/infra/main.bicep +189 -189
- package/content/.morph/templates/infra/parameters.dev.json +29 -29
- package/content/.morph/templates/infra/parameters.prod.json +29 -29
- package/content/.morph/templates/infra/parameters.staging.json +29 -29
- package/content/.morph/templates/infra/sql-database.bicep +103 -103
- package/content/.morph/templates/infra/storage.bicep +106 -106
- package/content/.morph/templates/integrations/asaas-client.cs +387 -387
- package/content/.morph/templates/integrations/asaas-webhook.cs +351 -351
- package/content/.morph/templates/integrations/azure-identity-config.cs +288 -288
- package/content/.morph/templates/integrations/clerk-config.cs +258 -258
- package/content/.morph/templates/job.cs +171 -171
- package/content/.morph/templates/migration.cs +83 -83
- package/content/.morph/templates/proposal.md +141 -155
- package/content/.morph/templates/recap.md +94 -105
- package/content/.morph/templates/repository.cs +141 -141
- package/content/.morph/templates/saas/subscription.cs +347 -347
- package/content/.morph/templates/saas/tenant.cs +338 -338
- package/content/.morph/templates/service.cs +139 -139
- package/content/.morph/templates/simulation.md +353 -0
- package/content/.morph/templates/spec.md +149 -148
- package/content/.morph/templates/sprint-status.yaml +68 -68
- package/content/.morph/templates/state.template.json +222 -222
- package/content/.morph/templates/story.md +143 -143
- package/content/.morph/templates/tasks.md +257 -235
- package/content/.morph/templates/test.cs +239 -239
- package/content/.morph/templates/ui-components.md +362 -276
- package/content/.morph/templates/ui-design-system.md +286 -286
- package/content/.morph/templates/ui-flows.md +336 -336
- package/content/.morph/templates/ui-mockups.md +133 -133
- package/content/.morph/test-infra/example.bicep +59 -59
- package/content/CLAUDE.md +150 -442
- package/content/README.md +79 -79
- package/detectors/config-detector.js +223 -223
- package/detectors/conversation-analyzer.js +163 -163
- package/detectors/index.js +84 -84
- package/detectors/standards-generator.js +275 -275
- package/detectors/structure-detector.js +245 -250
- package/docs/README.md +144 -149
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +977 -977
- package/docs/api/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1048 -1048
- package/docs/api/scripts/collapse.js +38 -38
- package/docs/api/scripts/commonNav.js +28 -28
- package/docs/api/scripts/linenumber.js +25 -25
- package/docs/api/scripts/nav.js +12 -12
- package/docs/api/scripts/polyfill.js +3 -3
- package/docs/api/scripts/prettify/Apache-License-2.0.txt +202 -202
- package/docs/api/scripts/prettify/lang-css.js +2 -2
- package/docs/api/scripts/prettify/prettify.js +28 -28
- package/docs/api/scripts/search.js +98 -98
- package/docs/api/styles/jsdoc.css +776 -776
- package/docs/api/styles/prettify.css +80 -80
- package/docs/examples.md +328 -328
- package/docs/getting-started.md +301 -302
- package/docs/installation.md +361 -361
- package/docs/templates.md +418 -418
- package/docs/validation-checklist.md +265 -266
- package/package.json +80 -80
- package/scripts/postinstall.js +132 -132
- package/src/commands/advance-phase.js +183 -0
- package/src/commands/analyze-blazor-concurrency.js +193 -0
- package/src/commands/create-story.js +351 -351
- package/src/commands/detect-agents.js +139 -0
- package/src/commands/detect.js +104 -104
- package/src/commands/doctor.js +356 -280
- package/src/commands/generate.js +149 -149
- package/src/commands/init.js +258 -245
- package/src/commands/lint-fluent.js +352 -0
- package/src/commands/rollback-phase.js +185 -0
- package/src/commands/session-summary.js +291 -0
- package/src/commands/shard-spec.js +224 -224
- package/src/commands/sprint-status.js +250 -250
- package/src/commands/state.js +333 -333
- package/src/commands/sync.js +167 -167
- package/src/commands/task.js +78 -0
- package/src/commands/troubleshoot.js +222 -0
- package/src/commands/update.js +192 -159
- package/src/commands/validate-blazor-state.js +210 -0
- package/src/commands/validate-blazor.js +156 -0
- package/src/commands/validate-css.js +84 -0
- package/src/commands/validate-phase.js +221 -0
- package/src/lib/blazor-concurrency-analyzer.js +288 -0
- package/src/lib/blazor-state-validator.js +291 -0
- package/src/lib/blazor-validator.js +374 -0
- package/src/lib/complexity-analyzer.js +441 -292
- package/src/lib/continuous-validator.js +421 -0
- package/src/lib/css-validator.js +352 -0
- package/src/lib/decision-constraint-loader.js +109 -0
- package/src/lib/design-system-generator.js +298 -298
- package/src/lib/learning-system.js +520 -0
- package/src/lib/mockup-generator.js +366 -0
- package/src/lib/recap-generator.js +205 -0
- package/src/lib/state-manager.js +397 -340
- package/src/lib/troubleshoot-grep.js +194 -0
- package/src/lib/troubleshoot-index.js +144 -0
- package/src/lib/ui-detector.js +350 -0
- package/src/lib/validation-runner.js +231 -0
- package/src/lib/validators/architecture-validator.js +387 -0
- package/src/lib/validators/contract-compliance-validator.js +273 -0
- package/src/lib/validators/package-validator.js +360 -0
- package/src/lib/validators/ui-contrast-validator.js +422 -0
- package/src/utils/file-copier.js +179 -139
- package/src/utils/logger.js +32 -32
- package/src/utils/version-checker.js +175 -175
- package/content/.claude/commands/morph-costs.md +0 -206
- package/content/.claude/commands/morph-tasks.md +0 -319
- package/content/.claude/skills/specialists/cost-guardian.md +0 -110
- package/content/.claude/skills/stacks/shopify.md +0 -445
- package/content/.morph/config/azure-pricing.json +0 -70
- package/content/.morph/config/azure-pricing.schema.json +0 -50
- package/content/.morph/hooks/pre-commit-costs.sh +0 -91
- package/docs/api/cost-calculator.js.html +0 -513
- package/docs/api/design-system-generator.js.html +0 -382
- package/docs/api/global.html +0 -5263
- package/docs/api/index.html +0 -96
- package/docs/api/state-manager.js.html +0 -423
- package/src/commands/cost.js +0 -181
- package/src/commands/update-pricing.js +0 -206
- package/src/lib/cost-calculator.js +0 -429
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Troubleshoot Grep Fallback
|
|
3
|
+
* Searches markdown files for matching content when index doesn't find results
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, readdirSync, existsSync } from 'fs';
|
|
7
|
+
import { join, dirname, basename } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
// Directories to search
|
|
14
|
+
const SEARCH_PATHS = [
|
|
15
|
+
'framework/standards',
|
|
16
|
+
'content/.morph/standards',
|
|
17
|
+
'.wiki'
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get all markdown files from search paths
|
|
22
|
+
* @param {string} basePath - Base path of the project
|
|
23
|
+
* @returns {string[]} Array of file paths
|
|
24
|
+
*/
|
|
25
|
+
function getMarkdownFiles(basePath) {
|
|
26
|
+
const files = [];
|
|
27
|
+
|
|
28
|
+
for (const searchPath of SEARCH_PATHS) {
|
|
29
|
+
const fullPath = join(basePath, searchPath);
|
|
30
|
+
|
|
31
|
+
if (!existsSync(fullPath)) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const entries = readdirSync(fullPath, { withFileTypes: true });
|
|
37
|
+
|
|
38
|
+
for (const entry of entries) {
|
|
39
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
40
|
+
files.push(join(fullPath, entry.name));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Skip directories we can't read
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return files;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract section containing the match
|
|
53
|
+
* @param {string} content - File content
|
|
54
|
+
* @param {number} matchIndex - Index of the match
|
|
55
|
+
* @returns {Object} Section object with title and content
|
|
56
|
+
*/
|
|
57
|
+
function extractSection(content, matchIndex) {
|
|
58
|
+
const lines = content.split('\n');
|
|
59
|
+
let charCount = 0;
|
|
60
|
+
let matchLineIndex = 0;
|
|
61
|
+
|
|
62
|
+
// Find the line containing the match
|
|
63
|
+
for (let i = 0; i < lines.length; i++) {
|
|
64
|
+
charCount += lines[i].length + 1; // +1 for newline
|
|
65
|
+
if (charCount > matchIndex) {
|
|
66
|
+
matchLineIndex = i;
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Find the section header (## heading) above the match
|
|
72
|
+
let sectionTitle = 'Unknown Section';
|
|
73
|
+
let sectionStart = 0;
|
|
74
|
+
|
|
75
|
+
for (let i = matchLineIndex; i >= 0; i--) {
|
|
76
|
+
if (lines[i].startsWith('## ')) {
|
|
77
|
+
sectionTitle = lines[i].replace('## ', '').trim();
|
|
78
|
+
sectionStart = i;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Find the end of the section (next ## or end of file)
|
|
84
|
+
let sectionEnd = lines.length;
|
|
85
|
+
for (let i = sectionStart + 1; i < lines.length; i++) {
|
|
86
|
+
if (lines[i].startsWith('## ')) {
|
|
87
|
+
sectionEnd = i;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Extract section content (limit to ~20 lines around match)
|
|
93
|
+
const contextStart = Math.max(sectionStart, matchLineIndex - 10);
|
|
94
|
+
const contextEnd = Math.min(sectionEnd, matchLineIndex + 10);
|
|
95
|
+
const sectionContent = lines.slice(contextStart, contextEnd).join('\n');
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
title: sectionTitle,
|
|
99
|
+
content: sectionContent,
|
|
100
|
+
lineNumber: matchLineIndex + 1
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Search a file for keywords
|
|
106
|
+
* @param {string} filePath - Path to the file
|
|
107
|
+
* @param {string[]} keywords - Keywords to search for
|
|
108
|
+
* @returns {Object[]} Array of matches
|
|
109
|
+
*/
|
|
110
|
+
function searchFile(filePath, keywords) {
|
|
111
|
+
const matches = [];
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
115
|
+
const contentLower = content.toLowerCase();
|
|
116
|
+
|
|
117
|
+
for (const keyword of keywords) {
|
|
118
|
+
const keywordLower = keyword.toLowerCase();
|
|
119
|
+
let index = contentLower.indexOf(keywordLower);
|
|
120
|
+
|
|
121
|
+
while (index !== -1) {
|
|
122
|
+
const section = extractSection(content, index);
|
|
123
|
+
|
|
124
|
+
// Check if we already have a match for this section
|
|
125
|
+
const existingMatch = matches.find(m => m.section === section.title);
|
|
126
|
+
|
|
127
|
+
if (existingMatch) {
|
|
128
|
+
existingMatch.matchCount++;
|
|
129
|
+
existingMatch.keywords.add(keyword);
|
|
130
|
+
} else {
|
|
131
|
+
matches.push({
|
|
132
|
+
file: filePath,
|
|
133
|
+
fileName: basename(filePath),
|
|
134
|
+
section: section.title,
|
|
135
|
+
content: section.content,
|
|
136
|
+
lineNumber: section.lineNumber,
|
|
137
|
+
matchCount: 1,
|
|
138
|
+
keywords: new Set([keyword])
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
index = contentLower.indexOf(keywordLower, index + 1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
} catch (error) {
|
|
146
|
+
// Skip files we can't read
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return matches;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Search all markdown files for keywords
|
|
154
|
+
* @param {string[]} keywords - Keywords to search for
|
|
155
|
+
* @param {Object} options - Search options
|
|
156
|
+
* @returns {Object[]} Array of matches sorted by relevance
|
|
157
|
+
*/
|
|
158
|
+
export function searchGrep(keywords, options = {}) {
|
|
159
|
+
// Determine base path
|
|
160
|
+
const basePath = options.basePath || join(__dirname, '../..');
|
|
161
|
+
|
|
162
|
+
const files = getMarkdownFiles(basePath);
|
|
163
|
+
let allMatches = [];
|
|
164
|
+
|
|
165
|
+
for (const file of files) {
|
|
166
|
+
const matches = searchFile(file, keywords);
|
|
167
|
+
allMatches = allMatches.concat(matches);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Convert keywords Set to Array and calculate score
|
|
171
|
+
const results = allMatches.map(match => ({
|
|
172
|
+
...match,
|
|
173
|
+
keywords: Array.from(match.keywords),
|
|
174
|
+
score: match.matchCount * 5 + match.keywords.size * 3,
|
|
175
|
+
source: 'grep'
|
|
176
|
+
}));
|
|
177
|
+
|
|
178
|
+
// Sort by score (higher is better) and deduplicate
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
const uniqueResults = results
|
|
181
|
+
.sort((a, b) => b.score - a.score)
|
|
182
|
+
.filter(r => {
|
|
183
|
+
const key = `${r.file}:${r.section}`;
|
|
184
|
+
if (seen.has(key)) return false;
|
|
185
|
+
seen.add(key);
|
|
186
|
+
return true;
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return uniqueResults.slice(0, 10); // Limit to top 10
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export default {
|
|
193
|
+
searchGrep
|
|
194
|
+
};
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Troubleshoot Index Search
|
|
3
|
+
* Searches the troubleshooting-index.json for matching problems
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { fileURLToPath } from 'url';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load the troubleshooting index
|
|
15
|
+
* @returns {Object} The index object or null if not found
|
|
16
|
+
*/
|
|
17
|
+
export function loadIndex() {
|
|
18
|
+
const indexPath = join(__dirname, '../../framework/index/troubleshooting-index.json');
|
|
19
|
+
|
|
20
|
+
if (!existsSync(indexPath)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
const content = readFileSync(indexPath, 'utf-8');
|
|
26
|
+
return JSON.parse(content);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
console.error('Error loading troubleshooting index:', error.message);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Calculate relevance score for a problem based on keywords
|
|
35
|
+
* @param {Object} problem - The problem object from index
|
|
36
|
+
* @param {string[]} searchTerms - Search terms from user
|
|
37
|
+
* @returns {number} Relevance score (higher is better)
|
|
38
|
+
*/
|
|
39
|
+
function calculateScore(problem, searchTerms) {
|
|
40
|
+
let score = 0;
|
|
41
|
+
const searchLower = searchTerms.map(t => t.toLowerCase());
|
|
42
|
+
|
|
43
|
+
// Check keywords
|
|
44
|
+
for (const keyword of problem.keywords) {
|
|
45
|
+
const keywordLower = keyword.toLowerCase();
|
|
46
|
+
for (const term of searchLower) {
|
|
47
|
+
if (keywordLower.includes(term) || term.includes(keywordLower)) {
|
|
48
|
+
score += 10;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check title
|
|
54
|
+
const titleLower = problem.title.toLowerCase();
|
|
55
|
+
for (const term of searchLower) {
|
|
56
|
+
if (titleLower.includes(term)) {
|
|
57
|
+
score += 5;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check error pattern (regex match on search string)
|
|
62
|
+
if (problem.errorPattern) {
|
|
63
|
+
try {
|
|
64
|
+
const regex = new RegExp(problem.errorPattern, 'i');
|
|
65
|
+
const searchString = searchTerms.join(' ');
|
|
66
|
+
if (regex.test(searchString)) {
|
|
67
|
+
score += 20; // High score for error pattern match
|
|
68
|
+
}
|
|
69
|
+
} catch (e) {
|
|
70
|
+
// Invalid regex, skip
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Boost critical severity
|
|
75
|
+
if (problem.severity === 'critical') {
|
|
76
|
+
score *= 1.2;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return score;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Search the index for problems matching the keywords
|
|
84
|
+
* @param {string[]} keywords - Search keywords
|
|
85
|
+
* @param {Object} options - Search options
|
|
86
|
+
* @param {string} options.category - Filter by category
|
|
87
|
+
* @returns {Object[]} Array of matching problems with scores
|
|
88
|
+
*/
|
|
89
|
+
export function searchIndex(keywords, options = {}) {
|
|
90
|
+
const index = loadIndex();
|
|
91
|
+
|
|
92
|
+
if (!index || !index.problems) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let problems = index.problems;
|
|
97
|
+
|
|
98
|
+
// Filter by category if specified
|
|
99
|
+
if (options.category) {
|
|
100
|
+
problems = problems.filter(p => p.category === options.category);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Score and filter problems
|
|
104
|
+
const results = problems
|
|
105
|
+
.map(problem => ({
|
|
106
|
+
...problem,
|
|
107
|
+
score: calculateScore(problem, keywords)
|
|
108
|
+
}))
|
|
109
|
+
.filter(p => p.score > 0)
|
|
110
|
+
.sort((a, b) => b.score - a.score);
|
|
111
|
+
|
|
112
|
+
return results;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all categories from the index
|
|
117
|
+
* @returns {Object} Categories object
|
|
118
|
+
*/
|
|
119
|
+
export function getCategories() {
|
|
120
|
+
const index = loadIndex();
|
|
121
|
+
return index?.categories || {};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get a specific problem by ID
|
|
126
|
+
* @param {string} id - Problem ID
|
|
127
|
+
* @returns {Object|null} Problem object or null
|
|
128
|
+
*/
|
|
129
|
+
export function getProblemById(id) {
|
|
130
|
+
const index = loadIndex();
|
|
131
|
+
|
|
132
|
+
if (!index || !index.problems) {
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return index.problems.find(p => p.id === id) || null;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export default {
|
|
140
|
+
loadIndex,
|
|
141
|
+
searchIndex,
|
|
142
|
+
getCategories,
|
|
143
|
+
getProblemById
|
|
144
|
+
};
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UI Feature Detector
|
|
3
|
+
*
|
|
4
|
+
* Analyzes spec.md to detect UI needs automatically.
|
|
5
|
+
* Auto-triggers FASE 1.5: UI/UX when entities + CRUD are detected.
|
|
6
|
+
*
|
|
7
|
+
* MORPH-SPEC 3.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Detect UI needs from spec content
|
|
12
|
+
* @param {string} specContent - Content of spec.md
|
|
13
|
+
* @returns {Object} UI needs analysis
|
|
14
|
+
*/
|
|
15
|
+
export function detectUINeeds(specContent) {
|
|
16
|
+
const entities = parseEntities(specContent);
|
|
17
|
+
const crudOperations = detectCRUD(specContent);
|
|
18
|
+
const uiKeywords = detectUIKeywords(specContent);
|
|
19
|
+
|
|
20
|
+
const uiNeeds = [];
|
|
21
|
+
|
|
22
|
+
// Auto-detect CRUD UI needs
|
|
23
|
+
for (const entity of entities) {
|
|
24
|
+
if (crudOperations.create) {
|
|
25
|
+
uiNeeds.push({
|
|
26
|
+
type: 'form',
|
|
27
|
+
entity: entity.name,
|
|
28
|
+
operation: 'create',
|
|
29
|
+
fields: entity.properties,
|
|
30
|
+
priority: 'high'
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (crudOperations.read || crudOperations.list) {
|
|
35
|
+
uiNeeds.push({
|
|
36
|
+
type: 'list',
|
|
37
|
+
entity: entity.name,
|
|
38
|
+
fields: entity.properties.filter(p => p.displayInList !== false),
|
|
39
|
+
features: detectListFeatures(specContent),
|
|
40
|
+
priority: 'high'
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (crudOperations.update) {
|
|
45
|
+
uiNeeds.push({
|
|
46
|
+
type: 'form',
|
|
47
|
+
entity: entity.name,
|
|
48
|
+
operation: 'edit',
|
|
49
|
+
fields: entity.properties,
|
|
50
|
+
priority: 'medium'
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (crudOperations.delete) {
|
|
55
|
+
uiNeeds.push({
|
|
56
|
+
type: 'confirm-dialog',
|
|
57
|
+
entity: entity.name,
|
|
58
|
+
operation: 'delete',
|
|
59
|
+
priority: 'medium'
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Detect special UI components from keywords
|
|
65
|
+
if (uiKeywords.wizard) {
|
|
66
|
+
uiNeeds.push({
|
|
67
|
+
type: 'wizard',
|
|
68
|
+
steps: extractWizardSteps(specContent),
|
|
69
|
+
priority: 'high'
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (uiKeywords.dashboard) {
|
|
74
|
+
uiNeeds.push({
|
|
75
|
+
type: 'dashboard',
|
|
76
|
+
widgets: extractDashboardWidgets(specContent),
|
|
77
|
+
priority: 'high'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (uiKeywords.upload) {
|
|
82
|
+
uiNeeds.push({
|
|
83
|
+
type: 'file-upload',
|
|
84
|
+
maxSize: extractMaxFileSize(specContent),
|
|
85
|
+
allowedTypes: extractAllowedFileTypes(specContent),
|
|
86
|
+
priority: 'high'
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const shouldTriggerUIPhase = uiNeeds.length > 0 ||
|
|
91
|
+
entities.length > 0 ||
|
|
92
|
+
uiKeywords.hasUIKeywords;
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
shouldTrigger: shouldTriggerUIPhase,
|
|
96
|
+
entities,
|
|
97
|
+
crudOperations,
|
|
98
|
+
uiNeeds,
|
|
99
|
+
keywords: uiKeywords,
|
|
100
|
+
confidence: calculateConfidence(entities, crudOperations, uiKeywords)
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Parse entities from spec
|
|
106
|
+
*/
|
|
107
|
+
function parseEntities(specContent) {
|
|
108
|
+
const entities = [];
|
|
109
|
+
|
|
110
|
+
// Match pseudo-code entity definitions
|
|
111
|
+
const entityPattern = /(?:class|entity|table)\s+(\w+)\s*{([^}]+)}/gi;
|
|
112
|
+
let match;
|
|
113
|
+
|
|
114
|
+
while ((match = entityPattern.exec(specContent)) !== null) {
|
|
115
|
+
const entityName = match[1];
|
|
116
|
+
const body = match[2];
|
|
117
|
+
|
|
118
|
+
const properties = parseProperties(body);
|
|
119
|
+
|
|
120
|
+
entities.push({
|
|
121
|
+
name: entityName,
|
|
122
|
+
properties,
|
|
123
|
+
hasId: properties.some(p => p.name.toLowerCase() === 'id'),
|
|
124
|
+
hasTimestamps: properties.some(p =>
|
|
125
|
+
p.name.toLowerCase().includes('createdat') ||
|
|
126
|
+
p.name.toLowerCase().includes('updatedat')
|
|
127
|
+
)
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return entities;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Parse properties from entity body
|
|
136
|
+
*/
|
|
137
|
+
function parseProperties(body) {
|
|
138
|
+
const properties = [];
|
|
139
|
+
const lines = body.split('\n').map(l => l.trim()).filter(l => l);
|
|
140
|
+
|
|
141
|
+
for (const line of lines) {
|
|
142
|
+
// Match: Type PropertyName or PropertyName: Type
|
|
143
|
+
const match = line.match(/(?:(\w+)\s+(\w+)|(\w+)\s*:\s*(\w+))/);
|
|
144
|
+
if (!match) continue;
|
|
145
|
+
|
|
146
|
+
const type = match[1] || match[4];
|
|
147
|
+
const name = match[2] || match[3];
|
|
148
|
+
|
|
149
|
+
if (!type || !name) continue;
|
|
150
|
+
|
|
151
|
+
properties.push({
|
|
152
|
+
name,
|
|
153
|
+
type,
|
|
154
|
+
isRequired: !line.includes('?') && !line.includes('optional'),
|
|
155
|
+
isNullable: line.includes('?'),
|
|
156
|
+
displayInList: !isInternalProperty(name),
|
|
157
|
+
isEnum: type.endsWith('Status') || type.endsWith('Type') || type.endsWith('Kind')
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return properties;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Check if property is internal (not for display)
|
|
166
|
+
*/
|
|
167
|
+
function isInternalProperty(name) {
|
|
168
|
+
const internalNames = ['id', 'createdat', 'updatedat', 'deletedat', 'version', 'rowversion'];
|
|
169
|
+
return internalNames.includes(name.toLowerCase());
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Detect CRUD operations from spec
|
|
174
|
+
*/
|
|
175
|
+
function detectCRUD(specContent) {
|
|
176
|
+
const lower = specContent.toLowerCase();
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
create: /\b(create|add|new|insert|register|upload)\b/i.test(lower),
|
|
180
|
+
read: /\b(read|get|view|show|display|retrieve|fetch)\b/i.test(lower),
|
|
181
|
+
update: /\b(update|edit|modify|change)\b/i.test(lower),
|
|
182
|
+
delete: /\b(delete|remove|destroy)\b/i.test(lower),
|
|
183
|
+
list: /\b(list|index|all|search|filter|query)\b/i.test(lower)
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Detect UI-specific keywords
|
|
189
|
+
*/
|
|
190
|
+
function detectUIKeywords(specContent) {
|
|
191
|
+
const lower = specContent.toLowerCase();
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
wizard: /\b(wizard|step|multi-?step|flow)\b/i.test(lower),
|
|
195
|
+
dashboard: /\b(dashboard|overview|summary|stats|metrics)\b/i.test(lower),
|
|
196
|
+
chart: /\b(chart|graph|visualization|plot)\b/i.test(lower),
|
|
197
|
+
upload: /\b(upload|file|photo|image|document|attachment)\b/i.test(lower),
|
|
198
|
+
modal: /\b(modal|dialog|popup|overlay)\b/i.test(lower),
|
|
199
|
+
form: /\b(form|input|field|validation)\b/i.test(lower),
|
|
200
|
+
table: /\b(table|grid|list|datagrid)\b/i.test(lower),
|
|
201
|
+
hasUIKeywords: /\b(wizard|dashboard|chart|upload|modal|form|table|ui|ux|screen|page|component)\b/i.test(lower)
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Detect list features (pagination, filters, etc.)
|
|
207
|
+
*/
|
|
208
|
+
function detectListFeatures(specContent) {
|
|
209
|
+
const lower = specContent.toLowerCase();
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
pagination: /\b(paginat|page|per.?page|offset|limit)\b/i.test(lower),
|
|
213
|
+
sorting: /\b(sort|order|asc|desc)\b/i.test(lower),
|
|
214
|
+
filtering: /\b(filter|search|query|where)\b/i.test(lower),
|
|
215
|
+
selection: /\b(select|check|multi.?select|bulk)\b/i.test(lower),
|
|
216
|
+
actions: /\b(action|edit|delete|view|export)\b/i.test(lower)
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Extract wizard steps from spec
|
|
222
|
+
*/
|
|
223
|
+
function extractWizardSteps(specContent) {
|
|
224
|
+
const steps = [];
|
|
225
|
+
const stepPattern = /step\s+(\d+):\s*([^\n]+)/gi;
|
|
226
|
+
let match;
|
|
227
|
+
|
|
228
|
+
while ((match = stepPattern.exec(specContent)) !== null) {
|
|
229
|
+
steps.push({
|
|
230
|
+
number: parseInt(match[1]),
|
|
231
|
+
title: match[2].trim()
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return steps.length > 0 ? steps : [
|
|
236
|
+
{ number: 1, title: 'Information' },
|
|
237
|
+
{ number: 2, title: 'Confirmation' }
|
|
238
|
+
];
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Extract dashboard widgets from spec
|
|
243
|
+
*/
|
|
244
|
+
function extractDashboardWidgets(specContent) {
|
|
245
|
+
const widgets = [];
|
|
246
|
+
|
|
247
|
+
if (/\b(stat|metric|kpi|count)\b/i.test(specContent)) {
|
|
248
|
+
widgets.push({ type: 'stat-card', count: 3 });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (/\b(chart|graph|visualization)\b/i.test(specContent)) {
|
|
252
|
+
widgets.push({ type: 'chart', chartType: 'line' });
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (/\b(table|grid|list|recent)\b/i.test(specContent)) {
|
|
256
|
+
widgets.push({ type: 'data-table', showActions: true });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return widgets;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Extract max file size from spec
|
|
264
|
+
*/
|
|
265
|
+
function extractMaxFileSize(specContent) {
|
|
266
|
+
const match = specContent.match(/(\d+)\s*(mb|gb|kb)/i);
|
|
267
|
+
if (!match) return '10MB';
|
|
268
|
+
|
|
269
|
+
const size = parseInt(match[1]);
|
|
270
|
+
const unit = match[2].toLowerCase();
|
|
271
|
+
|
|
272
|
+
return `${size}${unit.toUpperCase()}`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Extract allowed file types from spec
|
|
277
|
+
*/
|
|
278
|
+
function extractAllowedFileTypes(specContent) {
|
|
279
|
+
const match = specContent.match(/\.(jpg|jpeg|png|pdf|doc|docx|xls|xlsx|csv|txt|zip)(?:\s*,\s*\.|\s+)/gi);
|
|
280
|
+
if (!match) return ['.jpg', '.jpeg', '.png'];
|
|
281
|
+
|
|
282
|
+
return match.map(m => m.trim().replace(/\s*,\s*$/, ''));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Calculate confidence score
|
|
287
|
+
*/
|
|
288
|
+
function calculateConfidence(entities, crudOperations, uiKeywords) {
|
|
289
|
+
let score = 0;
|
|
290
|
+
|
|
291
|
+
// Entities detected
|
|
292
|
+
if (entities.length > 0) score += 30;
|
|
293
|
+
if (entities.length > 2) score += 10;
|
|
294
|
+
|
|
295
|
+
// CRUD operations
|
|
296
|
+
const crudCount = Object.values(crudOperations).filter(v => v).length;
|
|
297
|
+
score += crudCount * 10;
|
|
298
|
+
|
|
299
|
+
// UI keywords
|
|
300
|
+
if (uiKeywords.hasUIKeywords) score += 20;
|
|
301
|
+
if (uiKeywords.wizard) score += 15;
|
|
302
|
+
if (uiKeywords.dashboard) score += 15;
|
|
303
|
+
|
|
304
|
+
return Math.min(100, score);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Generate UI phase trigger message
|
|
309
|
+
*/
|
|
310
|
+
export function generateUITriggerMessage(analysis) {
|
|
311
|
+
if (!analysis.shouldTrigger) {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const { entities, crudOperations, uiNeeds, confidence } = analysis;
|
|
316
|
+
|
|
317
|
+
const parts = [];
|
|
318
|
+
|
|
319
|
+
parts.push(`🎨 **UI/UX Auto-Detection** (${confidence}% confidence)`);
|
|
320
|
+
parts.push('');
|
|
321
|
+
|
|
322
|
+
if (entities.length > 0) {
|
|
323
|
+
parts.push(`**Entities Detected:** ${entities.map(e => e.name).join(', ')}`);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (Object.values(crudOperations).some(v => v)) {
|
|
327
|
+
const ops = Object.entries(crudOperations)
|
|
328
|
+
.filter(([k, v]) => v)
|
|
329
|
+
.map(([k]) => k.toUpperCase());
|
|
330
|
+
parts.push(`**CRUD Operations:** ${ops.join(', ')}`);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (uiNeeds.length > 0) {
|
|
334
|
+
parts.push('');
|
|
335
|
+
parts.push('**UI Needs Detected:**');
|
|
336
|
+
uiNeeds.forEach((need, i) => {
|
|
337
|
+
const icon = need.type === 'form' ? '📝' :
|
|
338
|
+
need.type === 'list' ? '📋' :
|
|
339
|
+
need.type === 'wizard' ? '🧙' :
|
|
340
|
+
need.type === 'dashboard' ? '📊' : '🔧';
|
|
341
|
+
parts.push(`${i + 1}. ${icon} ${need.type.toUpperCase()}: ${need.entity || need.operation || 'Generic'}`);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
parts.push('');
|
|
346
|
+
parts.push('**➡️ Auto-triggering FASE 1.5: UI/UX Design**');
|
|
347
|
+
parts.push('Generating wireframes, component specs, and design system...');
|
|
348
|
+
|
|
349
|
+
return parts.join('\n');
|
|
350
|
+
}
|