@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.
Files changed (138) hide show
  1. package/CLAUDE.md +98 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +15 -56
  4. package/bin/task-manager.js +115 -14
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +21 -0
  10. package/framework/agents.json +758 -164
  11. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  12. package/framework/hooks/claude-code/post-tool-use/dispatch.js +2 -2
  13. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +155 -0
  14. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +1 -1
  15. package/framework/hooks/claude-code/session-start/inject-morph-context.js +71 -2
  16. package/framework/hooks/claude-code/statusline.py +76 -30
  17. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  18. package/framework/hooks/shared/activity-logger.js +0 -24
  19. package/framework/hooks/shared/phase-utils.js +3 -0
  20. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  21. package/framework/hooks/shared/stale-task-reset.js +57 -0
  22. package/framework/hooks/shared/state-reader.js +2 -2
  23. package/framework/hooks/shared/worktree-helpers.js +53 -0
  24. package/framework/phases.json +40 -8
  25. package/framework/skills/level-0-meta/brainstorming/SKILL.md +1 -1
  26. package/framework/skills/level-0-meta/code-review/SKILL.md +1 -1
  27. package/framework/skills/level-0-meta/code-review-nextjs/SKILL.md +163 -163
  28. package/framework/skills/level-0-meta/frontend-review/SKILL.md +5 -5
  29. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  30. package/framework/skills/level-0-meta/morph-init/SKILL.md +5 -5
  31. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +4 -4
  32. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  33. package/framework/skills/level-0-meta/post-implementation/SKILL.md +59 -12
  34. package/framework/skills/level-0-meta/simulation-checklist/SKILL.md +1 -1
  35. package/framework/skills/level-0-meta/terminal-title/SKILL.md +1 -1
  36. package/framework/skills/level-0-meta/tool-usage-guide/SKILL.md +1 -1
  37. package/framework/skills/level-0-meta/tool-usage-guide/references/tools-per-phase.md +6 -5
  38. package/framework/skills/level-0-meta/verification-before-completion/SKILL.md +1 -1
  39. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +215 -189
  40. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +251 -251
  41. package/framework/skills/level-1-workflows/phase-design/SKILL.md +382 -365
  42. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +492 -450
  43. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +194 -190
  44. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +270 -270
  45. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +285 -285
  46. package/framework/standards/STANDARDS.json +640 -88
  47. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  48. package/framework/templates/REGISTRY.json +1825 -1909
  49. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  50. package/framework/templates/docs/onboarding.md +1 -5
  51. package/framework/workflows/configs/nodejs-cli.json +40 -0
  52. package/package.json +2 -6
  53. package/src/commands/agents/dispatch-agents.js +55 -4
  54. package/src/commands/project/doctor.js +16 -47
  55. package/src/commands/project/init.js +1 -1
  56. package/src/commands/project/status.js +2 -2
  57. package/src/commands/project/update.js +381 -365
  58. package/src/commands/project/worktree.js +154 -0
  59. package/src/commands/state/advance-phase.js +120 -30
  60. package/src/commands/state/approve.js +2 -2
  61. package/src/commands/state/index.js +7 -8
  62. package/src/commands/state/phase-runner.js +1 -1
  63. package/src/commands/state/state.js +61 -6
  64. package/src/commands/tasks/task.js +78 -99
  65. package/src/commands/templates/template-render.js +93 -173
  66. package/src/commands/trust/trust.js +26 -21
  67. package/src/core/paths/output-schema.js +15 -0
  68. package/src/core/state/state-manager.js +28 -54
  69. package/src/core/workflows/workflow-detector.js +9 -87
  70. package/src/lib/phase-chain/phase-validator.js +330 -0
  71. package/src/lib/stack/stack-profile.js +88 -0
  72. package/src/lib/tasks/task-classifier.js +16 -0
  73. package/src/lib/tasks/test-runner.js +77 -0
  74. package/src/lib/trust/trust-manager.js +32 -144
  75. package/src/lib/validators/spec-validator.js +58 -4
  76. package/src/lib/validators/validation-runner.js +23 -11
  77. package/src/scripts/setup-infra.js +240 -224
  78. package/src/utils/agents-installer.js +2 -2
  79. package/src/utils/banner.js +1 -1
  80. package/src/utils/claude-settings-manager.js +1 -1
  81. package/src/utils/file-copier.js +1 -0
  82. package/src/utils/hooks-installer.js +258 -8
  83. package/framework/hooks/dev/check-sync-health.js +0 -117
  84. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  85. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  86. package/framework/hooks/dev/sync-template-registry.js +0 -60
  87. package/framework/hooks/dev/validate-skill-format.js +0 -70
  88. package/framework/hooks/dev/validate-standard-format.js +0 -73
  89. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  90. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  91. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  92. package/framework/workflows/configs/design-impl.json +0 -49
  93. package/framework/workflows/configs/express.json +0 -45
  94. package/framework/workflows/configs/fast-track.json +0 -42
  95. package/framework/workflows/configs/full-morph.json +0 -79
  96. package/framework/workflows/configs/fusion.json +0 -39
  97. package/framework/workflows/configs/long-running.json +0 -33
  98. package/framework/workflows/configs/spec-only.json +0 -43
  99. package/framework/workflows/configs/ui-refresh.json +0 -49
  100. package/framework/workflows/configs/zero-touch.json +0 -82
  101. package/src/commands/project/monitor.js +0 -295
  102. package/src/commands/project/tutorial.js +0 -115
  103. package/src/commands/state/validate-phase.js +0 -238
  104. package/src/commands/templates/generate-contracts.js +0 -445
  105. package/src/core/orchestrator.js +0 -171
  106. package/src/core/registry/command-registry.js +0 -28
  107. package/src/core/registry/index.js +0 -8
  108. package/src/core/registry/validator-registry.js +0 -204
  109. package/src/core/templates/template-validator.js +0 -296
  110. package/src/generator/config-generator.js +0 -206
  111. package/src/generator/templates/config.json.template +0 -40
  112. package/src/generator/templates/project.md.template +0 -67
  113. package/src/lib/agents/micro-agent-factory.js +0 -161
  114. package/src/lib/analysis/complexity-analyzer.js +0 -441
  115. package/src/lib/analysis/index.js +0 -7
  116. package/src/lib/analytics/analytics-engine.js +0 -345
  117. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  118. package/src/lib/checkpoints/index.js +0 -7
  119. package/src/lib/context/context-bundler.js +0 -241
  120. package/src/lib/context/context-optimizer.js +0 -212
  121. package/src/lib/context/context-tracker.js +0 -273
  122. package/src/lib/context/core-four-tracker.js +0 -201
  123. package/src/lib/context/mcp-optimizer.js +0 -200
  124. package/src/lib/execution/fusion-executor.js +0 -304
  125. package/src/lib/execution/parallel-executor.js +0 -270
  126. package/src/lib/hooks/stop-hook-executor.js +0 -286
  127. package/src/lib/hops/hop-composer.js +0 -221
  128. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  129. package/src/lib/threads/thread-coordinator.js +0 -238
  130. package/src/lib/threads/thread-manager.js +0 -317
  131. package/src/lib/tracking/artifact-trail.js +0 -202
  132. package/src/scanner/project-scanner.js +0 -242
  133. package/src/ui/diff-display.js +0 -91
  134. package/src/ui/interactive-wizard.js +0 -96
  135. package/src/ui/user-review.js +0 -211
  136. package/src/ui/wizard-questions.js +0 -188
  137. package/src/utils/color-utils.js +0 -70
  138. package/src/utils/process-handler.js +0 -97
@@ -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,8 +0,0 @@
1
- /**
2
- * Core Registry System
3
- *
4
- * Auto-discovery for commands and validators.
5
- */
6
-
7
- export * from './command-registry.js';
8
- export * from './validator-registry.js';
@@ -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
- }