@polymorphism-tech/morph-spec 4.8.18 → 4.9.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 +98 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +15 -56
- package/bin/task-manager.js +115 -14
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +21 -0
- package/framework/agents.json +758 -164
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/phase-utils.js +3 -0
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +2 -2
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +40 -8
- package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
- package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
- package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
- package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
- package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
- package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
- package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
- package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
- package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
- package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
- package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
- package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +1 -5
- package/framework/workflows/configs/nodejs-cli.json +40 -0
- package/package.json +2 -6
- package/src/commands/agents/dispatch-agents.js +55 -4
- package/src/commands/project/doctor.js +16 -47
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/state/advance-phase.js +120 -30
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +15 -0
- package/src/core/state/state-manager.js +28 -54
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/phase-chain/phase-validator.js +330 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +240 -224
- package/src/utils/agents-installer.js +2 -2
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +258 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/templates/template-validator.js +0 -296
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
package/src/core/orchestrator.js
DELETED
|
@@ -1,171 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview AutoContextOrchestrator - Main orchestrator for CLI auto-detection
|
|
3
|
-
* @module morph-spec/orchestrator
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import chalk from 'chalk';
|
|
7
|
-
import { ProjectScanner } from '../scanner/project-scanner.js';
|
|
8
|
-
import { ContextSanitizer } from '../sanitizer/context-sanitizer.js';
|
|
9
|
-
import { ConfigGenerator } from '../generator/config-generator.js';
|
|
10
|
-
import { UserReview } from '../ui/user-review.js';
|
|
11
|
-
import { InteractiveWizard } from '../ui/interactive-wizard.js';
|
|
12
|
-
import { FileWriter } from '../writer/file-writer.js';
|
|
13
|
-
import { readFile, access } from 'fs/promises';
|
|
14
|
-
import { join } from 'path';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {import('../types/index.js').GeneratedConfigs} GeneratedConfigs
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* AutoContextOrchestrator - Orchestrates the complete auto-detection flow
|
|
22
|
-
* @class
|
|
23
|
-
*/
|
|
24
|
-
export class AutoContextOrchestrator {
|
|
25
|
-
constructor() {
|
|
26
|
-
this.scanner = new ProjectScanner();
|
|
27
|
-
this.sanitizer = new ContextSanitizer();
|
|
28
|
-
this.configGenerator = new ConfigGenerator();
|
|
29
|
-
this.userReview = new UserReview();
|
|
30
|
-
this.wizard = new InteractiveWizard();
|
|
31
|
-
this.fileWriter = new FileWriter();
|
|
32
|
-
|
|
33
|
-
// Handle Ctrl+C gracefully
|
|
34
|
-
this.setupSignalHandlers();
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Execute the complete auto-detection flow
|
|
39
|
-
* @param {string} cwd - Current working directory
|
|
40
|
-
* @param {Object} [options] - Orchestration options
|
|
41
|
-
* @param {boolean} [options.skipReview] - Skip user review (auto-approve)
|
|
42
|
-
* @returns {Promise<{success: boolean, configs: GeneratedConfigs|null}>}
|
|
43
|
-
*/
|
|
44
|
-
async execute(cwd, options = {}) {
|
|
45
|
-
const {
|
|
46
|
-
skipReview = false,
|
|
47
|
-
} = options;
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
console.log(chalk.bold.cyan('\n🔍 MORPH-SPEC Auto Context Detection\n'));
|
|
51
|
-
|
|
52
|
-
let projectConfig;
|
|
53
|
-
|
|
54
|
-
// Use interactive wizard (LLM invocation not implemented; wizard provides all context)
|
|
55
|
-
console.log(chalk.yellow(' Using interactive wizard mode\n'));
|
|
56
|
-
projectConfig = await this.wizard.run();
|
|
57
|
-
|
|
58
|
-
// Step 2: Generate configs
|
|
59
|
-
console.log(chalk.dim(' [4/6] Generating configuration files...'));
|
|
60
|
-
const configs = await this.configGenerator.generate(projectConfig);
|
|
61
|
-
|
|
62
|
-
// Step 3: User review (unless skipped)
|
|
63
|
-
let finalConfigs = configs;
|
|
64
|
-
|
|
65
|
-
if (!skipReview) {
|
|
66
|
-
console.log(chalk.dim(' [5/6] Requesting user approval...'));
|
|
67
|
-
|
|
68
|
-
// Check if updating existing configs
|
|
69
|
-
const existingConfigs = await this.readExistingConfigs(cwd);
|
|
70
|
-
|
|
71
|
-
const approvalResponse = await this.userReview.promptForApproval(
|
|
72
|
-
configs,
|
|
73
|
-
projectConfig,
|
|
74
|
-
existingConfigs
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (approvalResponse.action === 'cancel') {
|
|
78
|
-
console.log(chalk.yellow('\n❌ Operation canceled by user\n'));
|
|
79
|
-
console.log(chalk.dim(` Reason: ${approvalResponse.cancelReason || 'User canceled'}\n`));
|
|
80
|
-
return { success: false, configs: null };
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (approvalResponse.editedConfigs) {
|
|
84
|
-
finalConfigs = approvalResponse.editedConfigs;
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
console.log(chalk.dim(' [5/6] Skipping user review (auto-approve)'));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Step 4: Save configs
|
|
91
|
-
console.log(chalk.dim(' [6/6] Saving configuration files...'));
|
|
92
|
-
|
|
93
|
-
// Backup existing configs if they exist
|
|
94
|
-
await this.configGenerator.backupExisting(cwd);
|
|
95
|
-
|
|
96
|
-
// Write new configs
|
|
97
|
-
await this.fileWriter.save(cwd, finalConfigs);
|
|
98
|
-
|
|
99
|
-
console.log(chalk.bold.green('🎉 Auto-detection complete!\n'));
|
|
100
|
-
|
|
101
|
-
return { success: true, configs: finalConfigs };
|
|
102
|
-
} catch (error) {
|
|
103
|
-
console.log(chalk.bold.red('\n❌ Auto-detection failed\n'));
|
|
104
|
-
console.log(chalk.red(` Error: ${error.message}\n`));
|
|
105
|
-
|
|
106
|
-
if (error.stack && process.env.DEBUG) {
|
|
107
|
-
console.log(chalk.dim(error.stack));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return { success: false, configs: null };
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Read existing configs (if they exist)
|
|
116
|
-
* @param {string} cwd - Current working directory
|
|
117
|
-
* @returns {Promise<Object|null>} Existing configs or null
|
|
118
|
-
*/
|
|
119
|
-
async readExistingConfigs(cwd) {
|
|
120
|
-
try {
|
|
121
|
-
const projectMdPath = join(cwd, '.morph', 'project.md');
|
|
122
|
-
const configJsonPath = join(cwd, '.morph', 'config', 'config.json');
|
|
123
|
-
|
|
124
|
-
const [projectMdExists, configJsonExists] = await Promise.all([
|
|
125
|
-
this.fileExists(projectMdPath),
|
|
126
|
-
this.fileExists(configJsonPath)
|
|
127
|
-
]);
|
|
128
|
-
|
|
129
|
-
if (!projectMdExists && !configJsonExists) {
|
|
130
|
-
return null; // No existing configs
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const [projectMd, configJson] = await Promise.all([
|
|
134
|
-
projectMdExists ? readFile(projectMdPath, 'utf-8') : null,
|
|
135
|
-
configJsonExists ? readFile(configJsonPath, 'utf-8') : null
|
|
136
|
-
]);
|
|
137
|
-
|
|
138
|
-
return { projectMd, configJson };
|
|
139
|
-
} catch (error) {
|
|
140
|
-
return null; // Error reading configs, treat as non-existent
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Check if file exists
|
|
146
|
-
* @param {string} filepath - File path
|
|
147
|
-
* @returns {Promise<boolean>}
|
|
148
|
-
*/
|
|
149
|
-
async fileExists(filepath) {
|
|
150
|
-
try {
|
|
151
|
-
await access(filepath);
|
|
152
|
-
return true;
|
|
153
|
-
} catch {
|
|
154
|
-
return false;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Setup signal handlers for graceful shutdown
|
|
160
|
-
*/
|
|
161
|
-
setupSignalHandlers() {
|
|
162
|
-
const handleExit = () => {
|
|
163
|
-
console.log(chalk.yellow('\n\n⚠️ Operation interrupted by user (Ctrl+C)\n'));
|
|
164
|
-
console.log(chalk.dim(' No files were modified\n'));
|
|
165
|
-
process.exit(0);
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
process.on('SIGINT', handleExit);
|
|
169
|
-
process.on('SIGTERM', handleExit);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Command Registry — DEPRECATED
|
|
3
|
-
*
|
|
4
|
-
* This module is no longer used. CLI commands are registered directly
|
|
5
|
-
* in bin/morph-spec.js via Commander.js. This file is kept as an empty
|
|
6
|
-
* stub to prevent import errors from any remaining references.
|
|
7
|
-
*
|
|
8
|
-
* @module command-registry
|
|
9
|
-
* @deprecated
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
export const commandMetadata = {};
|
|
13
|
-
|
|
14
|
-
export async function loadCommand(commandName) {
|
|
15
|
-
throw new Error(`Command registry is deprecated. Use bin/morph-spec.js directly.`);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function getAllCommandNames() {
|
|
19
|
-
return [];
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function getCommandsByCategory() {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export function getAllCategories() {
|
|
27
|
-
return [];
|
|
28
|
-
}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validator Registry - Technology-Based Auto-Discovery
|
|
3
|
-
*
|
|
4
|
-
* Maps validator types to their modules for dynamic loading.
|
|
5
|
-
* Organized by technology (blazor, css, architecture, etc.)
|
|
6
|
-
*
|
|
7
|
-
* @module validator-registry
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Validator Metadata Registry
|
|
12
|
-
*
|
|
13
|
-
* Technology-scoped validator discovery.
|
|
14
|
-
*/
|
|
15
|
-
export const validatorMetadata = {
|
|
16
|
-
// Blazor Validators
|
|
17
|
-
'blazor': {
|
|
18
|
-
technology: 'blazor',
|
|
19
|
-
validators: [
|
|
20
|
-
{
|
|
21
|
-
id: 'blazor-validator',
|
|
22
|
-
name: 'Blazor Patterns Validator',
|
|
23
|
-
description: 'Validates Fluent UI Blazor patterns and components',
|
|
24
|
-
module: () => import('../../lib/validators/blazor/blazor-validator.js'),
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: 'blazor-state',
|
|
28
|
-
name: 'Blazor State Validator',
|
|
29
|
-
description: 'Validates Blazor state management patterns',
|
|
30
|
-
module: () => import('../../lib/validators/blazor/blazor-state-validator.js'),
|
|
31
|
-
},
|
|
32
|
-
{
|
|
33
|
-
id: 'blazor-concurrency',
|
|
34
|
-
name: 'Blazor Concurrency Analyzer',
|
|
35
|
-
description: 'Analyzes Blazor concurrency and DbContext issues',
|
|
36
|
-
module: () => import('../../lib/validators/blazor/blazor-concurrency-analyzer.js'),
|
|
37
|
-
},
|
|
38
|
-
],
|
|
39
|
-
},
|
|
40
|
-
|
|
41
|
-
// CSS Validators
|
|
42
|
-
'css': {
|
|
43
|
-
technology: 'css',
|
|
44
|
-
validators: [
|
|
45
|
-
{
|
|
46
|
-
id: 'css-validator',
|
|
47
|
-
name: 'CSS Validator',
|
|
48
|
-
description: 'Validates CSS classes and design system compliance',
|
|
49
|
-
module: () => import('../../lib/validators/css/css-validator.js'),
|
|
50
|
-
},
|
|
51
|
-
],
|
|
52
|
-
},
|
|
53
|
-
|
|
54
|
-
// Architecture Validators
|
|
55
|
-
'architecture': {
|
|
56
|
-
technology: 'architecture',
|
|
57
|
-
validators: [
|
|
58
|
-
{
|
|
59
|
-
id: 'architecture-validator',
|
|
60
|
-
name: 'Architecture Validator',
|
|
61
|
-
description: 'Validates architectural patterns (DI, CQRS, etc.)',
|
|
62
|
-
module: () => import('../../lib/validators/architecture/architecture-validator.js'),
|
|
63
|
-
},
|
|
64
|
-
],
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
// Design System Validators
|
|
68
|
-
'design-system': {
|
|
69
|
-
technology: 'design-system',
|
|
70
|
-
validators: [
|
|
71
|
-
{
|
|
72
|
-
id: 'design-system-validator',
|
|
73
|
-
name: 'Design System Validator',
|
|
74
|
-
description: 'Validates design system compliance',
|
|
75
|
-
module: () => import('../../lib/validators/design-system/design-system-validator.js'),
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
},
|
|
79
|
-
|
|
80
|
-
// Content Validators
|
|
81
|
-
'content': {
|
|
82
|
-
technology: 'content',
|
|
83
|
-
validators: [
|
|
84
|
-
{
|
|
85
|
-
id: 'content-validator',
|
|
86
|
-
name: 'Content Validator',
|
|
87
|
-
description: 'Validates content and specification format',
|
|
88
|
-
module: () => import('../../lib/validators/content/content-validator.js'),
|
|
89
|
-
},
|
|
90
|
-
],
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
// Contract Validators
|
|
94
|
-
'contracts': {
|
|
95
|
-
technology: 'contracts',
|
|
96
|
-
validators: [
|
|
97
|
-
{
|
|
98
|
-
id: 'contract-compliance-validator',
|
|
99
|
-
name: 'Contract Compliance Validator',
|
|
100
|
-
description: 'Validates API contract compliance',
|
|
101
|
-
module: () => import('../../lib/validators/contracts/contract-compliance-validator.js'),
|
|
102
|
-
},
|
|
103
|
-
],
|
|
104
|
-
},
|
|
105
|
-
|
|
106
|
-
// Package Validators
|
|
107
|
-
'packages': {
|
|
108
|
-
technology: 'packages',
|
|
109
|
-
validators: [
|
|
110
|
-
{
|
|
111
|
-
id: 'package-validator',
|
|
112
|
-
name: 'Package Validator',
|
|
113
|
-
description: 'Validates package dependencies and versions',
|
|
114
|
-
module: () => import('../../lib/validators/packages/package-validator.js'),
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
},
|
|
118
|
-
|
|
119
|
-
// UI Validators
|
|
120
|
-
'ui': {
|
|
121
|
-
technology: 'ui',
|
|
122
|
-
validators: [
|
|
123
|
-
{
|
|
124
|
-
id: 'ui-contrast-validator',
|
|
125
|
-
name: 'UI Contrast Validator',
|
|
126
|
-
description: 'Validates UI contrast and accessibility',
|
|
127
|
-
module: () => import('../../lib/validators/ui/ui-contrast-validator.js'),
|
|
128
|
-
},
|
|
129
|
-
],
|
|
130
|
-
},
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Load validator by ID
|
|
135
|
-
*
|
|
136
|
-
* @param {string} validatorId - Validator ID (e.g., 'blazor-validator')
|
|
137
|
-
* @returns {Promise<Object>} Validator module
|
|
138
|
-
*/
|
|
139
|
-
export async function loadValidator(validatorId) {
|
|
140
|
-
for (const techMeta of Object.values(validatorMetadata)) {
|
|
141
|
-
const validator = techMeta.validators.find(v => v.id === validatorId);
|
|
142
|
-
if (validator) {
|
|
143
|
-
return await validator.module();
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
throw new Error(`Validator not found: ${validatorId}`);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Load all validators for a technology
|
|
152
|
-
*
|
|
153
|
-
* @param {string} technology - Technology name (e.g., 'blazor', 'css')
|
|
154
|
-
* @returns {Promise<Object[]>} Array of validator modules
|
|
155
|
-
*/
|
|
156
|
-
export async function loadValidatorsForTechnology(technology) {
|
|
157
|
-
const techMeta = validatorMetadata[technology];
|
|
158
|
-
|
|
159
|
-
if (!techMeta) {
|
|
160
|
-
throw new Error(`No validators found for technology: ${technology}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
return await Promise.all(
|
|
164
|
-
techMeta.validators.map(v => v.module())
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Get all validator IDs
|
|
170
|
-
*
|
|
171
|
-
* @returns {string[]} Array of validator IDs
|
|
172
|
-
*/
|
|
173
|
-
export function getAllValidatorIds() {
|
|
174
|
-
const ids = [];
|
|
175
|
-
for (const techMeta of Object.values(validatorMetadata)) {
|
|
176
|
-
ids.push(...techMeta.validators.map(v => v.id));
|
|
177
|
-
}
|
|
178
|
-
return ids;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Get all technologies
|
|
183
|
-
*
|
|
184
|
-
* @returns {string[]} Array of technology names
|
|
185
|
-
*/
|
|
186
|
-
export function getAllTechnologies() {
|
|
187
|
-
return Object.keys(validatorMetadata);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Get validator metadata by ID
|
|
192
|
-
*
|
|
193
|
-
* @param {string} validatorId - Validator ID
|
|
194
|
-
* @returns {Object|null} Validator metadata or null
|
|
195
|
-
*/
|
|
196
|
-
export function getValidatorMetadata(validatorId) {
|
|
197
|
-
for (const techMeta of Object.values(validatorMetadata)) {
|
|
198
|
-
const validator = techMeta.validators.find(v => v.id === validatorId);
|
|
199
|
-
if (validator) {
|
|
200
|
-
return { ...validator, technology: techMeta.technology };
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Template Validator - Validates Handlebars templates
|
|
3
|
-
* Checks for:
|
|
4
|
-
* - Required placeholders are present
|
|
5
|
-
* - No deprecated pre-computed variables ({FEATURE_NAME_PASCAL})
|
|
6
|
-
* - Valid Handlebars syntax
|
|
7
|
-
* - Proper helper usage
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { readFileSync } from 'fs';
|
|
11
|
-
import { getTemplateById, getAllTemplates } from './template-registry.js';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Deprecated placeholder patterns (v1.0 pre-computed variables)
|
|
15
|
-
*/
|
|
16
|
-
const DEPRECATED_PATTERNS = [
|
|
17
|
-
{ pattern: /\{\{FEATURE_NAME_PASCAL\}\}/g, replacement: '{{pascalCase FEATURE_NAME}}' },
|
|
18
|
-
{ pattern: /\{\{FEATURE_NAME_CAMEL\}\}/g, replacement: '{{camelCase FEATURE_NAME}}' },
|
|
19
|
-
{ pattern: /\{\{FEATURE_NAME_SNAKE\}\}/g, replacement: '{{snakeCase FEATURE_NAME}}' },
|
|
20
|
-
{ pattern: /\{\{FEATURE_NAME_UPPER_SNAKE\}\}/g, replacement: '{{upperSnakeCase FEATURE_NAME}}' },
|
|
21
|
-
{ pattern: /\{\{FEATURE_NAME_TITLE\}\}/g, replacement: '{{titleCase FEATURE_NAME}}' },
|
|
22
|
-
{ pattern: /\{\{FEATURE_NAME_KEBAB\}\}/g, replacement: '{{kebabCase FEATURE_NAME}}' },
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Old template syntax (non-Handlebars)
|
|
27
|
-
*/
|
|
28
|
-
const OLD_SYNTAX_PATTERNS = [
|
|
29
|
-
{ pattern: /\{Feature\}/g, description: 'Old {Feature} syntax (use {{pascalCase FEATURE_NAME}})' },
|
|
30
|
-
{ pattern: /\{feature\}/g, description: 'Old {feature} syntax (use {{kebabCase FEATURE_NAME}})' },
|
|
31
|
-
];
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Validation result
|
|
35
|
-
*/
|
|
36
|
-
class ValidationResult {
|
|
37
|
-
constructor(templateId, templatePath) {
|
|
38
|
-
this.templateId = templateId;
|
|
39
|
-
this.templatePath = templatePath;
|
|
40
|
-
this.valid = true;
|
|
41
|
-
this.errors = [];
|
|
42
|
-
this.warnings = [];
|
|
43
|
-
this.info = [];
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
addError(message) {
|
|
47
|
-
this.errors.push(message);
|
|
48
|
-
this.valid = false;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
addWarning(message) {
|
|
52
|
-
this.warnings.push(message);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
addInfo(message) {
|
|
56
|
-
this.info.push(message);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get hasIssues() {
|
|
60
|
-
return this.errors.length > 0 || this.warnings.length > 0;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Validates a single template
|
|
66
|
-
* @param {string} templateId - Template ID
|
|
67
|
-
* @param {string} projectPath - Project root path
|
|
68
|
-
* @returns {ValidationResult}
|
|
69
|
-
*/
|
|
70
|
-
export function validateTemplate(templateId, projectPath = process.cwd()) {
|
|
71
|
-
const template = getTemplateById(templateId, projectPath);
|
|
72
|
-
|
|
73
|
-
if (!template) {
|
|
74
|
-
const result = new ValidationResult(templateId, null);
|
|
75
|
-
result.addError(`Template not found: ${templateId}`);
|
|
76
|
-
return result;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const result = new ValidationResult(templateId, template.path);
|
|
80
|
-
|
|
81
|
-
// Read template content
|
|
82
|
-
let content;
|
|
83
|
-
try {
|
|
84
|
-
const fullPath = `${projectPath}/framework/templates/${template.path}`;
|
|
85
|
-
content = readFileSync(fullPath, 'utf-8');
|
|
86
|
-
} catch (error) {
|
|
87
|
-
result.addError(`Failed to read template: ${error.message}`);
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Check for deprecated pre-computed variables
|
|
92
|
-
for (const { pattern, replacement } of DEPRECATED_PATTERNS) {
|
|
93
|
-
const matches = content.match(pattern);
|
|
94
|
-
if (matches) {
|
|
95
|
-
result.addError(
|
|
96
|
-
`Deprecated placeholder found: ${matches[0]} (use ${replacement} instead)`
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
// Check for old non-Handlebars syntax
|
|
102
|
-
for (const { pattern, description } of OLD_SYNTAX_PATTERNS) {
|
|
103
|
-
const matches = content.match(pattern);
|
|
104
|
-
if (matches) {
|
|
105
|
-
const count = matches.length;
|
|
106
|
-
result.addWarning(
|
|
107
|
-
`${count} occurrence(s) of old syntax: ${description}`
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Check for required placeholders if specified
|
|
113
|
-
if (template.placeholders && template.placeholders.length > 0) {
|
|
114
|
-
const missingPlaceholders = [];
|
|
115
|
-
|
|
116
|
-
for (const placeholder of template.placeholders) {
|
|
117
|
-
// Check for exact placeholder or helper usage
|
|
118
|
-
const exactMatch = new RegExp(`\\{\\{${placeholder}\\}\\}`, 'g');
|
|
119
|
-
const helperMatch = new RegExp(`\\{\\{\\w+\\s+${placeholder}\\}\\}`, 'g');
|
|
120
|
-
|
|
121
|
-
if (!exactMatch.test(content) && !helperMatch.test(content)) {
|
|
122
|
-
missingPlaceholders.push(placeholder);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
if (missingPlaceholders.length > 0) {
|
|
127
|
-
result.addWarning(
|
|
128
|
-
`Template declares placeholders but they're not used: ${missingPlaceholders.join(', ')}`
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Check for balanced Handlebars blocks
|
|
134
|
-
const openBlocks = (content.match(/\{\{#\w+/g) || []).length;
|
|
135
|
-
const closeBlocks = (content.match(/\{\{\/\w+/g) || []).length;
|
|
136
|
-
|
|
137
|
-
if (openBlocks !== closeBlocks) {
|
|
138
|
-
result.addError(
|
|
139
|
-
`Unbalanced Handlebars blocks: ${openBlocks} opening, ${closeBlocks} closing`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Info: Count Handlebars placeholders
|
|
144
|
-
const placeholderMatches = content.match(/\{\{[^}]+\}\}/g) || [];
|
|
145
|
-
const uniquePlaceholders = [...new Set(placeholderMatches)];
|
|
146
|
-
result.addInfo(`Found ${uniquePlaceholders.length} unique Handlebars expressions`);
|
|
147
|
-
|
|
148
|
-
// Info: Check if template uses helpers
|
|
149
|
-
const helperMatches = content.match(/\{\{(pascalCase|camelCase|snakeCase|titleCase|kebabCase|upperSnakeCase|pluralize|singularize|formatDate|uppercase|lowercase|capitalize|trim|replace|concat|substr|startsWith|endsWith|contains|length|add|subtract|multiply|divide|mod|round|join|first|last|slice|now|year|default|json)/g) || [];
|
|
150
|
-
if (helperMatches.length > 0) {
|
|
151
|
-
const uniqueHelpers = [...new Set(helperMatches.map(m => m.replace('{{', '')))];
|
|
152
|
-
result.addInfo(`Uses helpers: ${uniqueHelpers.join(', ')}`);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return result;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* Validates all templates
|
|
160
|
-
* @param {string} projectPath - Project root path
|
|
161
|
-
* @param {Object} options - Validation options
|
|
162
|
-
* @returns {Array<ValidationResult>}
|
|
163
|
-
*/
|
|
164
|
-
export function validateAllTemplates(projectPath = process.cwd(), options = {}) {
|
|
165
|
-
const {
|
|
166
|
-
skipDeprecated = true,
|
|
167
|
-
category = null,
|
|
168
|
-
technology = null,
|
|
169
|
-
} = options;
|
|
170
|
-
|
|
171
|
-
const templates = getAllTemplates(projectPath);
|
|
172
|
-
const results = [];
|
|
173
|
-
|
|
174
|
-
for (const template of templates) {
|
|
175
|
-
// Skip deprecated if requested
|
|
176
|
-
if (skipDeprecated && template.deprecated) {
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// Filter by category if specified
|
|
181
|
-
if (category && template.category !== category) {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Filter by technology if specified
|
|
186
|
-
if (technology && template.technology !== technology) {
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const result = validateTemplate(template.id, projectPath);
|
|
191
|
-
results.push(result);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
return results;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Generates a validation summary
|
|
199
|
-
* @param {Array<ValidationResult>} results - Validation results
|
|
200
|
-
* @returns {Object}
|
|
201
|
-
*/
|
|
202
|
-
export function summarizeValidation(results) {
|
|
203
|
-
const summary = {
|
|
204
|
-
total: results.length,
|
|
205
|
-
valid: 0,
|
|
206
|
-
hasErrors: 0,
|
|
207
|
-
hasWarnings: 0,
|
|
208
|
-
errors: [],
|
|
209
|
-
warnings: [],
|
|
210
|
-
};
|
|
211
|
-
|
|
212
|
-
for (const result of results) {
|
|
213
|
-
if (result.valid && result.warnings.length === 0) {
|
|
214
|
-
summary.valid++;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (result.errors.length > 0) {
|
|
218
|
-
summary.hasErrors++;
|
|
219
|
-
summary.errors.push({
|
|
220
|
-
templateId: result.templateId,
|
|
221
|
-
errors: result.errors,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
if (result.warnings.length > 0) {
|
|
226
|
-
summary.hasWarnings++;
|
|
227
|
-
summary.warnings.push({
|
|
228
|
-
templateId: result.templateId,
|
|
229
|
-
warnings: result.warnings,
|
|
230
|
-
});
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return summary;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Formats validation results for display
|
|
239
|
-
* @param {Array<ValidationResult>} results - Validation results
|
|
240
|
-
* @param {Object} options - Display options
|
|
241
|
-
* @returns {string}
|
|
242
|
-
*/
|
|
243
|
-
export function formatValidationResults(results, options = {}) {
|
|
244
|
-
const { showInfo = false, showValid = false } = options;
|
|
245
|
-
|
|
246
|
-
let output = '';
|
|
247
|
-
let validCount = 0;
|
|
248
|
-
let errorCount = 0;
|
|
249
|
-
let warningCount = 0;
|
|
250
|
-
|
|
251
|
-
for (const result of results) {
|
|
252
|
-
if (result.valid && result.warnings.length === 0) {
|
|
253
|
-
validCount++;
|
|
254
|
-
if (showValid) {
|
|
255
|
-
output += `✅ ${result.templateId}\n`;
|
|
256
|
-
}
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
if (result.errors.length > 0) {
|
|
261
|
-
errorCount++;
|
|
262
|
-
output += `\n❌ ${result.templateId}\n`;
|
|
263
|
-
output += ` Path: ${result.templatePath}\n`;
|
|
264
|
-
for (const error of result.errors) {
|
|
265
|
-
output += ` ERROR: ${error}\n`;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (result.warnings.length > 0) {
|
|
270
|
-
warningCount++;
|
|
271
|
-
if (result.errors.length === 0) {
|
|
272
|
-
output += `\n⚠️ ${result.templateId}\n`;
|
|
273
|
-
output += ` Path: ${result.templatePath}\n`;
|
|
274
|
-
}
|
|
275
|
-
for (const warning of result.warnings) {
|
|
276
|
-
output += ` WARNING: ${warning}\n`;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (showInfo && result.info.length > 0) {
|
|
281
|
-
for (const info of result.info) {
|
|
282
|
-
output += ` INFO: ${info}\n`;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Summary
|
|
288
|
-
output += `\n${'='.repeat(60)}\n`;
|
|
289
|
-
output += `SUMMARY:\n`;
|
|
290
|
-
output += ` Total templates: ${results.length}\n`;
|
|
291
|
-
output += ` ✅ Valid: ${validCount}\n`;
|
|
292
|
-
output += ` ❌ Errors: ${errorCount}\n`;
|
|
293
|
-
output += ` ⚠️ Warnings: ${warningCount}\n`;
|
|
294
|
-
|
|
295
|
-
return output;
|
|
296
|
-
}
|