@polymorphism-tech/morph-spec 8.45.0 → 8.47.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 (146) hide show
  1. package/CLAUDE.md +6 -5
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +2 -4
  4. package/bin/validate.js +4 -25
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +2 -2
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/CLAUDE.md +2 -2
  9. package/framework/MORPH.md +9 -9
  10. package/framework/commands/create-umbraco-lp.md +2 -0
  11. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  12. package/framework/commands/morph-archive.md +1 -1
  13. package/framework/commands/morph-hotfix.md +1 -1
  14. package/framework/commands/morph-infra.md +17 -203
  15. package/framework/commands/morph-media.md +3 -0
  16. package/framework/commands/morph-preflight.md +5 -28
  17. package/framework/commands/morph-proposal.md +2 -2
  18. package/framework/commands/morph-troubleshoot.md +2 -14
  19. package/framework/docs/CLI.md +8 -7
  20. package/framework/hooks/claude-code/core/state-sync.js +13 -1
  21. package/framework/hooks/shared/e2e-check.js +3 -4
  22. package/framework/hooks/shared/state-writer.js +1 -0
  23. package/framework/rules/ai-agents-standards.md +18 -16
  24. package/framework/rules/asaas-standards.md +3 -1
  25. package/framework/rules/csharp-standards.md +10 -8
  26. package/framework/rules/data-standards.md +5 -5
  27. package/framework/rules/dotnet-templates.md +1 -1
  28. package/framework/rules/frontend-standards.md +4 -2
  29. package/framework/rules/gsap-standards.md +10 -5
  30. package/framework/rules/hangfire-standards.md +3 -1
  31. package/framework/rules/infrastructure-standards.md +4 -2
  32. package/framework/rules/morph-workflow.md +2 -2
  33. package/framework/rules/neon-auth-standards.md +3 -1
  34. package/framework/rules/neon-standards.md +6 -4
  35. package/framework/rules/nextjs-standards.md +13 -11
  36. package/framework/rules/nextjs-templates.md +1 -1
  37. package/framework/rules/resend-standards.md +3 -1
  38. package/framework/schemas/config.schema.json +4 -0
  39. package/framework/schemas/feature.schema.json +4 -4
  40. package/framework/skills/README.md +1 -1
  41. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  42. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  43. package/framework/skills/core/morph-apply/SKILL.md +3 -3
  44. package/framework/skills/core/morph-brainstorming/SKILL.md +3 -3
  45. package/framework/skills/core/morph-hotfix/SKILL.md +4 -4
  46. package/framework/skills/core/morph-implement/SKILL.md +3 -3
  47. package/framework/skills/core/morph-machine/SKILL.md +3 -3
  48. package/framework/skills/core/morph-media/SKILL.md +3 -3
  49. package/framework/skills/core/morph-plan/SKILL.md +3 -3
  50. package/framework/skills/core/morph-proposal/SKILL.md +3 -3
  51. package/framework/skills/core/morph-review/SKILL.md +30 -17
  52. package/framework/skills/core/morph-stack-scan/SKILL.md +3 -3
  53. package/framework/skills/core/morph-standards/SKILL.md +6 -6
  54. package/framework/skills/core/morph-uiux/SKILL.md +3 -3
  55. package/framework/standards/STANDARDS.json +59 -56
  56. package/framework/standards/ai-agents/evals-with-cache.md +1 -1
  57. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  58. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  59. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  60. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  61. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  62. package/framework/standards/ai-agents/setup.md +1 -1
  63. package/framework/standards/ai-agents/structured-output.md +1 -1
  64. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  65. package/framework/standards/architecture/code-comments.md +2 -2
  66. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  67. package/framework/standards/backend/api/validation.md +135 -47
  68. package/framework/standards/backend/database/postgresql/database.md +11 -7
  69. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  70. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  71. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  72. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  73. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  74. package/framework/standards/backend/dotnet/testing.md +1 -1
  75. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  76. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  77. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  78. package/framework/standards/data/nosql/cache/redis.md +120 -44
  79. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  80. package/framework/standards/frontend/design-system/animations.md +1 -1
  81. package/framework/standards/frontend/design-system/naming.md +1 -1
  82. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  83. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  84. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  85. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  86. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  87. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  88. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  89. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  90. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  91. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  92. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  93. package/framework/templates/infrastructure/github/README.md +2 -1
  94. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  95. package/package.json +1 -1
  96. package/src/commands/observability/telemetry.js +3 -5
  97. package/src/commands/project/init.js +2 -1
  98. package/src/commands/project/update.js +16 -18
  99. package/src/commands/state/approve.js +17 -0
  100. package/src/commands/state/gate-check.js +4 -14
  101. package/src/commands/validation/verify.js +99 -57
  102. package/src/core/paths/output-schema.js +1 -1
  103. package/src/dashboard/app/App.jsx +0 -2
  104. package/src/dashboard/app/components/CommandPalette.jsx +2 -3
  105. package/src/dashboard/app/components/EntityLink.jsx +0 -1
  106. package/src/dashboard/app/components/Markdown.jsx +1 -1
  107. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  108. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  109. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  110. package/src/dashboard/scanner/activity-scanner.js +1 -1
  111. package/src/dashboard/scanner/docs-scanner.js +5 -23
  112. package/src/dashboard/scanner/features-scanner.js +1 -1
  113. package/src/dashboard/scanner/index.js +0 -2
  114. package/src/dashboard/scanner/rules-scanner.js +15 -10
  115. package/src/dashboard/scanner/skills-scanner.js +2 -3
  116. package/src/lib/ci/ci-verify.js +118 -0
  117. package/src/lib/ci/github-ci.js +192 -0
  118. package/src/lib/ci/workflow-scaffold.js +122 -0
  119. package/src/lib/evaluator-report.js +13 -0
  120. package/src/lib/risk-detector.js +8 -0
  121. package/src/lib/stack/stack-profile.js +1 -1
  122. package/src/lib/telemetry/verify-event.js +3 -2
  123. package/src/lib/validators/reuse/reuse-validator.js +2 -3
  124. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  125. package/src/lib/validators/validation-runner.js +8 -88
  126. package/src/lib/validators/verify-runner.js +22 -72
  127. package/src/scripts/setup-infra.js +33 -22
  128. package/src/utils/agents-installer.js +20 -8
  129. package/src/utils/file-copier.js +7 -5
  130. package/framework/commands/commit.md +0 -166
  131. package/framework/hooks/claude-code/__pycache__/statusline.cpython-313.pyc +0 -0
  132. package/framework/rubrics/backend.json +0 -41
  133. package/framework/rubrics/default.json +0 -36
  134. package/framework/rubrics/frontend.json +0 -36
  135. package/framework/templates/project/validate.js +0 -97
  136. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  137. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  138. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  139. package/src/lib/validators/architecture/index.js +0 -7
  140. package/src/lib/validators/css/css-validator.js +0 -340
  141. package/src/lib/validators/css/index.js +0 -7
  142. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  143. package/src/lib/validators/design-system/index.js +0 -7
  144. package/src/lib/validators/score-aggregator.js +0 -244
  145. package/src/lib/validators/ui/index.js +0 -7
  146. package/src/lib/validators/ui/ui-contrast-validator.js +0 -422
@@ -1,237 +0,0 @@
1
- /**
2
- * Design System Validator
3
- *
4
- * Validates that CSS and Razor files only use design system tokens
5
- * instead of hardcoded colors, fonts, or spacing values.
6
- *
7
- * MORPH-SPEC 3.0 - Phase 3: Design System Enforcement
8
- */
9
-
10
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
11
- import { join, extname } from 'path';
12
- import { parseColors, parseTypography, parseSpacing } from '../../generators/design-system-generator.js';
13
- import { detectDesignSystem } from '../../detectors/design-system-detector.js';
14
-
15
- /**
16
- * Validate design system compliance
17
- * @param {string} projectPath - Root path of the project
18
- * @param {string} featureName - Feature name (optional)
19
- * @param {Object} options - Validation options
20
- * @returns {Object} { errors, warnings, issues }
21
- */
22
- export async function validateDesignSystem(projectPath, featureName = null, options = {}) {
23
- const result = {
24
- errors: 0,
25
- warnings: 0,
26
- issues: []
27
- };
28
-
29
- // Detect design system
30
- const dsDetection = detectDesignSystem(projectPath, featureName);
31
-
32
- if (!dsDetection.hasDesignSystem) {
33
- result.errors++;
34
- result.issues.push({
35
- level: 'error',
36
- message: 'No design system found',
37
- solution: 'Create a design system at .morph/context/design-system.md or run design system generator'
38
- });
39
- return result;
40
- }
41
-
42
- // Parse design system
43
- let designSystem;
44
- try {
45
- const markdown = readFileSync(dsDetection.path, 'utf8');
46
- designSystem = {
47
- colors: parseColors(markdown),
48
- typography: parseTypography(markdown),
49
- spacing: parseSpacing(markdown)
50
- };
51
- } catch (err) {
52
- result.errors++;
53
- result.issues.push({
54
- level: 'error',
55
- message: `Failed to parse design system: ${err.message}`,
56
- file: dsDetection.path
57
- });
58
- return result;
59
- }
60
-
61
- // Build allowed color set — iterate all color categories
62
- const allowedColors = new Set();
63
- const colorCategories = ['primary', 'accent', 'secondary', 'neutral', 'semantic', 'surface', 'text', 'other'];
64
- for (const category of colorCategories) {
65
- const catColors = designSystem.colors[category] || {};
66
- Object.values(catColors).forEach(val => {
67
- // Only add hex-like values (skip rgb tuples, rgba, var() references, etc.)
68
- if (typeof val === 'string' && val.startsWith('#')) {
69
- allowedColors.add(val.toLowerCase());
70
- }
71
- });
72
- }
73
-
74
- // Common system colors (allowed)
75
- const systemColors = new Set([
76
- '#ffffff', '#fff', '#000000', '#000', 'transparent', 'inherit', 'currentcolor'
77
- ]);
78
-
79
- // Scan CSS files
80
- const cssFiles = findFiles(projectPath, ['.css'], options.scanPaths || ['wwwroot/css', 'styles']);
81
- for (const cssFile of cssFiles) {
82
- const cssResult = validateCSSFile(cssFile, allowedColors, systemColors);
83
- result.warnings += cssResult.warnings;
84
- result.issues.push(...cssResult.issues);
85
- }
86
-
87
- // Scan Razor files
88
- const razorFiles = findFiles(projectPath, ['.razor'], options.scanPaths || ['Components', 'Pages']);
89
- for (const razorFile of razorFiles) {
90
- const razorResult = validateRazorFile(razorFile, allowedColors, systemColors);
91
- result.warnings += razorResult.warnings;
92
- result.issues.push(...razorResult.issues);
93
- }
94
-
95
- return result;
96
- }
97
-
98
- /**
99
- * Find files with specific extensions in directories
100
- */
101
- function findFiles(projectPath, extensions, scanPaths) {
102
- const files = [];
103
-
104
- for (const scanPath of scanPaths) {
105
- const fullPath = join(projectPath, scanPath);
106
- if (!existsSync(fullPath)) continue;
107
-
108
- try {
109
- walkDirectory(fullPath, extensions, files);
110
- } catch (err) {
111
- // Ignore permission errors
112
- }
113
- }
114
-
115
- return files;
116
- }
117
-
118
- /**
119
- * Recursively walk directory
120
- */
121
- function walkDirectory(dir, extensions, files) {
122
- const items = readdirSync(dir);
123
-
124
- for (const item of items) {
125
- const fullPath = join(dir, item);
126
- const stat = statSync(fullPath);
127
-
128
- if (stat.isDirectory()) {
129
- walkDirectory(fullPath, extensions, files);
130
- } else if (stat.isFile()) {
131
- const ext = extname(fullPath);
132
- if (extensions.includes(ext)) {
133
- files.push(fullPath);
134
- }
135
- }
136
- }
137
- }
138
-
139
- /**
140
- * Validate CSS file for hardcoded colors
141
- */
142
- function validateCSSFile(filePath, allowedColors, systemColors) {
143
- const result = {
144
- warnings: 0,
145
- issues: []
146
- };
147
-
148
- try {
149
- const content = readFileSync(filePath, 'utf8');
150
-
151
- // Match hex colors: #RGB or #RRGGBB
152
- const hexRegex = /#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})\b/g;
153
-
154
- let match;
155
- let lineNumber = 1;
156
- let lastIndex = 0;
157
-
158
- while ((match = hexRegex.exec(content)) !== null) {
159
- const hex = match[0].toLowerCase();
160
-
161
- // Count line number
162
- const upToMatch = content.substring(lastIndex, match.index);
163
- lineNumber += (upToMatch.match(/\n/g) || []).length;
164
- lastIndex = match.index;
165
-
166
- // Skip if in allowed or system colors
167
- if (allowedColors.has(hex) || systemColors.has(hex)) {
168
- continue;
169
- }
170
-
171
- result.warnings++;
172
- result.issues.push({
173
- level: 'warning',
174
- message: `Hardcoded color ${hex} not in design system palette`,
175
- file: filePath,
176
- line: lineNumber,
177
- solution: 'Use CSS variable like var(--color-primary-500) instead'
178
- });
179
- }
180
- } catch (err) {
181
- // Ignore read errors
182
- }
183
-
184
- return result;
185
- }
186
-
187
- /**
188
- * Validate Razor file for inline styles with hardcoded colors
189
- */
190
- function validateRazorFile(filePath, allowedColors, systemColors) {
191
- const result = {
192
- warnings: 0,
193
- issues: []
194
- };
195
-
196
- try {
197
- const content = readFileSync(filePath, 'utf8');
198
-
199
- // Match style="" attributes with hex colors
200
- const styleRegex = /style\s*=\s*"([^"]*)"/gi;
201
-
202
- let match;
203
- while ((match = styleRegex.exec(content)) !== null) {
204
- const styleContent = match[1];
205
-
206
- // Find hex colors in style attribute
207
- const hexRegex = /#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})\b/g;
208
- let hexMatch;
209
-
210
- while ((hexMatch = hexRegex.exec(styleContent)) !== null) {
211
- const hex = hexMatch[0].toLowerCase();
212
-
213
- // Skip if in allowed or system colors
214
- if (allowedColors.has(hex) || systemColors.has(hex)) {
215
- continue;
216
- }
217
-
218
- // Count line number
219
- const upToMatch = content.substring(0, match.index);
220
- const lineNumber = (upToMatch.match(/\n/g) || []).length + 1;
221
-
222
- result.warnings++;
223
- result.issues.push({
224
- level: 'warning',
225
- message: `Inline style with hardcoded color ${hex} not in design system`,
226
- file: filePath,
227
- line: lineNumber,
228
- solution: 'Use CSS class with design system variables or component props instead of inline styles'
229
- });
230
- }
231
- }
232
- } catch (err) {
233
- // Ignore read errors
234
- }
235
-
236
- return result;
237
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * Design System Validators
3
- *
4
- * Design system compliance validation.
5
- */
6
-
7
- export * from './design-system-validator.js';
@@ -1,244 +0,0 @@
1
- /**
2
- * MORPH-SPEC Score Aggregator
3
- *
4
- * Converts pass/fail validator results into weighted scores using rubric definitions.
5
- * Produces prioritized feedback for the retry loop.
6
- *
7
- * @module score-aggregator
8
- */
9
-
10
- import { existsSync, readFileSync } from 'fs';
11
- import { join, dirname } from 'path';
12
- import { fileURLToPath } from 'url';
13
-
14
- const __dirname = dirname(fileURLToPath(import.meta.url));
15
-
16
- /** Fallback rubric when none is configured or found. */
17
- const FALLBACK_RUBRIC = {
18
- id: 'fallback',
19
- version: '1.0.0',
20
- categories: {
21
- general: {
22
- weight: 1.0,
23
- validators: [],
24
- description: 'All validators (fallback — no rubric configured)'
25
- }
26
- },
27
- thresholds: { approve: 9.0, retry: 6.0 }
28
- };
29
-
30
- /**
31
- * Load a rubric by ID from the project or framework directory.
32
- *
33
- * Search order:
34
- * 1. .morph/framework/rubrics/{rubricId}.json (installed in project)
35
- * 2. npm package path (node_modules/@polymorphism-tech/morph-spec/framework/rubrics/)
36
- * 3. Local dev path (framework/rubrics/ relative to this module)
37
- *
38
- * @param {string} projectPath - Project root
39
- * @param {string} rubricId - Rubric identifier (e.g. 'default', 'frontend', 'backend')
40
- * @returns {Object} Parsed rubric object
41
- */
42
- export function loadRubric(projectPath, rubricId = 'default') {
43
- const filename = `${rubricId}.json`;
44
-
45
- const candidates = [
46
- join(projectPath, '.morph', 'framework', 'rubrics', filename),
47
- join(projectPath, 'node_modules', '@polymorphism-tech', 'morph-spec', 'framework', 'rubrics', filename),
48
- join(__dirname, '..', '..', '..', 'framework', 'rubrics', filename)
49
- ];
50
-
51
- for (const candidate of candidates) {
52
- if (existsSync(candidate)) {
53
- try {
54
- return JSON.parse(readFileSync(candidate, 'utf8'));
55
- } catch {
56
- // Corrupted file — try next candidate
57
- }
58
- }
59
- }
60
-
61
- return FALLBACK_RUBRIC;
62
- }
63
-
64
- /**
65
- * Score a single validator result.
66
- *
67
- * Scoring formula:
68
- * - Base score: 10
69
- * - Each error: -2 (min 0)
70
- * - Each warning: -1 (min 0)
71
- *
72
- * @param {Object} validatorResult - { passed: boolean, issues?: Array, error?: string }
73
- * @returns {number} Score 0–10
74
- */
75
- export function scoreValidator(validatorResult) {
76
- if (!validatorResult) return 10; // Validator didn't run — assume clean
77
-
78
- // Validator threw an error — can't assess
79
- if (validatorResult.error) return 5;
80
-
81
- const errors = (validatorResult.issues || []).filter(i => i.level === 'error').length;
82
- const warnings = (validatorResult.issues || []).filter(i => i.level === 'warning').length;
83
-
84
- return Math.max(0, 10 - (errors * 2) - (warnings * 1));
85
- }
86
-
87
- /**
88
- * Build a reverse map: validatorId → category name.
89
- * Validators not mapped to any category go into '_unmapped'.
90
- *
91
- * @param {Object} rubric
92
- * @returns {Map<string, string>}
93
- */
94
- function buildValidatorCategoryMap(rubric) {
95
- const map = new Map();
96
- for (const [categoryName, category] of Object.entries(rubric.categories)) {
97
- for (const validatorId of category.validators || []) {
98
- map.set(validatorId, categoryName);
99
- }
100
- }
101
- return map;
102
- }
103
-
104
- /**
105
- * Aggregate validator results into weighted scores using a rubric.
106
- *
107
- * @param {Object} validationResult - From validation-runner.js: { passed, errors, warnings, issues, results }
108
- * @param {Object} rubric - Loaded rubric object
109
- * @param {Object} [configOverrides] - Optional threshold overrides from config.json
110
- * @param {number} [configOverrides.approve] - Override approve threshold
111
- * @param {number} [configOverrides.retry] - Override retry threshold
112
- * @returns {{
113
- * score: number,
114
- * action: 'approve' | 'retry' | 'escalate',
115
- * breakdown: Object,
116
- * priorityFixes: Array<{ category: string, score: number, weight: number, deficit: number, issues: Array }>
117
- * }}
118
- */
119
- export function aggregateScores(validationResult, rubric, configOverrides = {}) {
120
- const results = validationResult.results || {};
121
- const validatorCategoryMap = buildValidatorCategoryMap(rubric);
122
-
123
- // Collect scores per category
124
- const breakdown = {};
125
- const unmappedScores = [];
126
-
127
- for (const [categoryName, category] of Object.entries(rubric.categories)) {
128
- const mappedValidators = (category.validators || []).filter(v => results[v] !== undefined);
129
- const scores = mappedValidators.map(v => scoreValidator(results[v]));
130
-
131
- breakdown[categoryName] = {
132
- score: scores.length > 0 ? scores.reduce((a, b) => a + b, 0) / scores.length : 10,
133
- weight: category.weight,
134
- validators: mappedValidators,
135
- issues: mappedValidators.flatMap(v => (results[v]?.issues || []))
136
- };
137
- }
138
-
139
- // Handle validators not mapped to any rubric category
140
- for (const [validatorId, result] of Object.entries(results)) {
141
- if (!validatorCategoryMap.has(validatorId)) {
142
- unmappedScores.push(scoreValidator(result));
143
- }
144
- }
145
-
146
- // Handle unmapped validators
147
- if (unmappedScores.length > 0) {
148
- const unmappedIds = Object.keys(results).filter(id => !validatorCategoryMap.has(id));
149
- if (Object.keys(breakdown).length === 0) {
150
- // No categories at all — put everything in 'general'
151
- breakdown.general = {
152
- score: unmappedScores.reduce((a, b) => a + b, 0) / unmappedScores.length,
153
- weight: 1.0,
154
- validators: unmappedIds,
155
- issues: unmappedIds.flatMap(id => results[id]?.issues || [])
156
- };
157
- } else {
158
- // Categories exist but some validators aren't mapped — add as low-weight 'other'
159
- const unmappedAvg = unmappedScores.reduce((a, b) => a + b, 0) / unmappedScores.length;
160
- breakdown._unmapped = {
161
- score: unmappedAvg,
162
- weight: 0.05,
163
- validators: unmappedIds,
164
- issues: unmappedIds.flatMap(id => results[id]?.issues || [])
165
- };
166
- }
167
- }
168
-
169
- // Calculate weighted final score
170
- const totalWeight = Object.values(breakdown).reduce((sum, cat) => sum + cat.weight, 0);
171
- const weightedScore = totalWeight > 0
172
- ? Object.values(breakdown).reduce((sum, cat) => sum + (cat.score * cat.weight), 0) / totalWeight
173
- : 10;
174
-
175
- // Round to 1 decimal
176
- const score = Math.round(weightedScore * 10) / 10;
177
-
178
- // Determine action
179
- const approveThreshold = configOverrides.approve ?? rubric.thresholds?.approve ?? 9.0;
180
- const retryThreshold = configOverrides.retry ?? rubric.thresholds?.retry ?? 6.0;
181
-
182
- let action;
183
- if (score >= approveThreshold) {
184
- action = 'approve';
185
- } else if (score >= retryThreshold) {
186
- action = 'retry';
187
- } else {
188
- action = 'escalate';
189
- }
190
-
191
- // Build priority fixes — sorted by impact (weight * deficit from 10)
192
- const priorityFixes = Object.entries(breakdown)
193
- .filter(([, cat]) => cat.issues.length > 0)
194
- .map(([categoryName, cat]) => ({
195
- category: categoryName,
196
- score: Math.round(cat.score * 10) / 10,
197
- weight: cat.weight,
198
- deficit: Math.round((10 - cat.score) * cat.weight * 10) / 10,
199
- issues: cat.issues
200
- }))
201
- .sort((a, b) => b.deficit - a.deficit);
202
-
203
- return { score, action, breakdown, priorityFixes, approveThreshold };
204
- }
205
-
206
- /**
207
- * Format score breakdown as a human-readable string for hook injection.
208
- *
209
- * @param {Object} scoreResult - From aggregateScores()
210
- * @param {number} [approveThreshold] - For display
211
- * @returns {string}
212
- */
213
- export function formatScoreBreakdown(scoreResult, approveThreshold = 9.0) {
214
- const lines = [];
215
-
216
- lines.push(`Score: ${scoreResult.score}/10 (threshold: ${approveThreshold})`);
217
- lines.push('');
218
-
219
- if (scoreResult.priorityFixes.length > 0) {
220
- lines.push('Priority fixes (by impact):');
221
- for (let i = 0; i < scoreResult.priorityFixes.length; i++) {
222
- const fix = scoreResult.priorityFixes[i];
223
- const topIssue = fix.issues[0];
224
- const issueText = topIssue
225
- ? `${topIssue.message}${topIssue.file ? ` (${topIssue.file}${topIssue.line ? ':' + topIssue.line : ''})` : ''}`
226
- : 'Issues detected';
227
- lines.push(`${i + 1}. [${fix.category}: ${fix.score}/10] ${issueText}`);
228
- // Show additional issues count if more than 1
229
- if (fix.issues.length > 1) {
230
- lines.push(` ... and ${fix.issues.length - 1} more issue(s) in this category`);
231
- }
232
- }
233
- lines.push('');
234
- }
235
-
236
- lines.push('Score breakdown:');
237
- for (const [category, data] of Object.entries(scoreResult.breakdown)) {
238
- const pct = Math.round(data.weight * 100);
239
- const indicator = data.score >= approveThreshold ? '\u2713' : data.score >= 6 ? '\u2193 needs work' : '\u2193\u2193 critical';
240
- lines.push(` ${category}: ${Math.round(data.score * 10) / 10}/10 (weight ${pct}%) ${indicator}`);
241
- }
242
-
243
- return lines.join('\n');
244
- }
@@ -1,7 +0,0 @@
1
- /**
2
- * UI Validators
3
- *
4
- * UI contrast and accessibility validation.
5
- */
6
-
7
- export * from './ui-contrast-validator.js';