@polymorphism-tech/morph-spec 8.46.0 → 9.0.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 (304) hide show
  1. package/CLAUDE.md +53 -44
  2. package/README.md +3 -4
  3. package/bin/morph-spec.js +38 -55
  4. package/bin/validate.js +5 -134
  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 +41 -101
  9. package/framework/MORPH.md +95 -252
  10. package/framework/agents.json +75 -18
  11. package/framework/commands/create-umbraco-lp.md +2 -0
  12. package/framework/commands/migrate-lp-to-umbraco.md +2 -0
  13. package/framework/commands/morph-apply.md +5 -28
  14. package/framework/commands/morph-archive.md +2 -2
  15. package/framework/commands/morph-hotfix.md +8 -8
  16. package/framework/commands/morph-infra.md +17 -203
  17. package/framework/commands/morph-media.md +3 -0
  18. package/framework/commands/morph-preflight.md +7 -31
  19. package/framework/commands/morph-proposal.md +2 -50
  20. package/framework/commands/morph-status.md +3 -3
  21. package/framework/commands/morph-troubleshoot.md +2 -14
  22. package/framework/docs/CLI.md +585 -286
  23. package/framework/evals/review-checklist.md +82 -41
  24. package/framework/hooks/README.md +10 -11
  25. package/framework/hooks/claude-code/core/comment-guard.js +1 -1
  26. package/framework/hooks/claude-code/core/denylist.json +33 -3
  27. package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
  28. package/framework/hooks/claude-code/core/gate-guard.js +2 -11
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
  30. package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
  31. package/framework/hooks/claude-code/core/state-sync.js +17 -19
  32. package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
  33. package/framework/hooks/claude-code/core/validate-completion.js +33 -97
  34. package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
  35. package/framework/hooks/claude-code/core/worktree-context.js +5 -4
  36. package/framework/hooks/claude-code/statusline.py +55 -41
  37. package/framework/hooks/package.json +3 -0
  38. package/framework/hooks/shared/activity-logger.js +1 -1
  39. package/framework/hooks/shared/build-check.js +10 -6
  40. package/framework/hooks/shared/command-tokens.js +169 -0
  41. package/framework/hooks/shared/comment-rules.js +1 -1
  42. package/framework/hooks/shared/comment-scanner.js +10 -23
  43. package/framework/hooks/shared/comment-stripper.js +1 -1
  44. package/framework/hooks/shared/e2e-check.js +67 -31
  45. package/framework/hooks/shared/errata.js +72 -0
  46. package/framework/hooks/shared/feature-locator.js +1 -1
  47. package/framework/hooks/shared/frontend-locations.js +6 -0
  48. package/framework/hooks/shared/gate-question.js +18 -0
  49. package/framework/hooks/shared/phase-utils.js +15 -32
  50. package/framework/hooks/shared/slice-progress.js +118 -0
  51. package/framework/hooks/shared/spawn-command.js +23 -0
  52. package/framework/hooks/shared/state-reader.js +61 -87
  53. package/framework/hooks/shared/state-writer.js +3 -3
  54. package/framework/mcp/registry.json +3 -4
  55. package/framework/phases.json +23 -39
  56. package/framework/rules/ai-agents-standards.md +18 -16
  57. package/framework/rules/asaas-standards.md +3 -1
  58. package/framework/rules/csharp-standards.md +10 -8
  59. package/framework/rules/data-standards.md +5 -5
  60. package/framework/rules/dotnet-templates.md +1 -1
  61. package/framework/rules/frontend-standards.md +4 -2
  62. package/framework/rules/gsap-standards.md +10 -5
  63. package/framework/rules/hangfire-standards.md +3 -1
  64. package/framework/rules/infrastructure-standards.md +4 -2
  65. package/framework/rules/neon-auth-standards.md +3 -1
  66. package/framework/rules/neon-standards.md +6 -4
  67. package/framework/rules/nextjs-standards.md +13 -11
  68. package/framework/rules/nextjs-templates.md +1 -1
  69. package/framework/rules/resend-standards.md +3 -1
  70. package/framework/schemas/config.schema.json +14 -9
  71. package/framework/schemas/feature.schema.json +15 -52
  72. package/framework/skills/README.md +4 -5
  73. package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
  74. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
  75. package/framework/skills/core/morph-apply/SKILL.md +150 -223
  76. package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
  77. package/framework/skills/core/morph-machine/SKILL.md +4 -4
  78. package/framework/skills/core/morph-media/SKILL.md +3 -3
  79. package/framework/skills/core/morph-plan/SKILL.md +138 -300
  80. package/framework/skills/core/morph-review/SKILL.md +112 -137
  81. package/framework/skills/core/morph-standards/SKILL.md +29 -41
  82. package/framework/skills/core/morph-uiux/SKILL.md +78 -153
  83. package/framework/standards/STANDARDS.json +90 -1219
  84. package/framework/standards/STANDARDS.meta.json +1305 -0
  85. package/framework/standards/ai-agents/evals-with-cache.md +2 -3
  86. package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
  87. package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
  88. package/framework/standards/ai-agents/prompt-sources.md +6 -4
  89. package/framework/standards/ai-agents/providers/model-registry.md +1 -1
  90. package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
  91. package/framework/standards/ai-agents/setup.md +2 -2
  92. package/framework/standards/ai-agents/structured-output.md +1 -1
  93. package/framework/standards/ai-agents/sweet-spot.md +1 -1
  94. package/framework/standards/architecture/code-comments.md +8 -8
  95. package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
  96. package/framework/standards/backend/api/validation.md +135 -47
  97. package/framework/standards/backend/database/postgresql/database.md +11 -7
  98. package/framework/standards/backend/database/vector-search-rag.md +207 -324
  99. package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
  100. package/framework/standards/backend/dotnet/result-pattern.md +40 -6
  101. package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
  102. package/framework/standards/backend/dotnet/scrutor.md +1 -1
  103. package/framework/standards/backend/dotnet/testing.md +1 -1
  104. package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
  105. package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
  106. package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
  107. package/framework/standards/data/nosql/cache/redis.md +120 -44
  108. package/framework/standards/data/nosql/cosmos-db.md +114 -71
  109. package/framework/standards/frontend/design-system/animations.md +1 -1
  110. package/framework/standards/frontend/design-system/naming.md +1 -1
  111. package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
  112. package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
  113. package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
  114. package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
  115. package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
  116. package/framework/standards/integration/mcp/mcp-tools.md +7 -7
  117. package/framework/templates/README.md +2 -3
  118. package/framework/templates/REGISTRY.json +6 -170
  119. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
  120. package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
  121. package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
  122. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
  123. package/framework/templates/docs/plan.md +72 -0
  124. package/framework/templates/dotnet/ai-kit/README.md +1 -9
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
  127. package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
  128. package/framework/templates/infrastructure/github/README.md +2 -1
  129. package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
  130. package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
  131. package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
  132. package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
  133. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
  134. package/package.json +1 -1
  135. package/src/commands/e2e/e2e.js +2 -2
  136. package/src/commands/observability/cost.js +18 -60
  137. package/src/commands/observability/telemetry.js +3 -5
  138. package/src/commands/project/doctor-procs.js +1 -1
  139. package/src/commands/project/doctor.js +9 -9
  140. package/src/commands/project/fleet.js +4 -4
  141. package/src/commands/project/heavy.js +3 -3
  142. package/src/commands/project/init.js +6 -1
  143. package/src/commands/project/setup-infra-cmd.js +2 -1
  144. package/src/commands/project/stack-scan.js +58 -0
  145. package/src/commands/project/standards.js +79 -25
  146. package/src/commands/project/status.js +29 -69
  147. package/src/commands/project/update.js +33 -19
  148. package/src/commands/project/worktree.js +1 -1
  149. package/src/commands/state/advance.js +62 -46
  150. package/src/commands/state/approve.js +95 -138
  151. package/src/commands/state/archive.js +20 -84
  152. package/src/commands/state/auto-approval.js +68 -0
  153. package/src/commands/state/create.js +28 -1
  154. package/src/commands/state/delete.js +1 -1
  155. package/src/commands/state/finish.js +11 -23
  156. package/src/commands/state/gate-check.js +86 -95
  157. package/src/commands/state/gate-decision.js +6 -4
  158. package/src/commands/state/handoff.js +1 -1
  159. package/src/commands/state/list.js +9 -9
  160. package/src/commands/state/report-check.js +157 -311
  161. package/src/commands/state/retype.js +9 -3
  162. package/src/commands/validation/mutate.js +77 -14
  163. package/src/commands/validation/plan-check.js +81 -0
  164. package/src/commands/validation/validate-feature.js +60 -135
  165. package/src/commands/validation/verify.js +41 -82
  166. package/src/core/paths/output-schema.js +20 -67
  167. package/src/core/state/gate-order.js +5 -6
  168. package/src/core/state/lifecycle-audit.js +2 -2
  169. package/src/core/state/state-manager.js +17 -98
  170. package/src/core/templates/template-registry.js +2 -2
  171. package/src/core/templates/template-seed.js +22 -0
  172. package/src/core/work-types.js +10 -10
  173. package/src/dashboard/app/App.jsx +0 -2
  174. package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
  175. package/src/dashboard/app/components/CommandPalette.jsx +7 -8
  176. package/src/dashboard/app/components/EntityLink.jsx +1 -2
  177. package/src/dashboard/app/components/Markdown.jsx +1 -1
  178. package/src/dashboard/app/components/Sidebar.jsx +1 -2
  179. package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
  180. package/src/dashboard/app/lib/phases.js +2 -4
  181. package/src/dashboard/app/pages/Activity.jsx +1 -1
  182. package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
  183. package/src/dashboard/app/pages/Features.jsx +9 -9
  184. package/src/dashboard/app/pages/Guide.jsx +2 -2
  185. package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
  186. package/src/dashboard/app/pages/RulesMap.jsx +3 -3
  187. package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
  188. package/src/dashboard/app/pages/Standards.jsx +1 -1
  189. package/src/dashboard/scanner/activity-scanner.js +6 -16
  190. package/src/dashboard/scanner/agents-scanner.js +1 -1
  191. package/src/dashboard/scanner/docs-scanner.js +5 -23
  192. package/src/dashboard/scanner/features-scanner.js +95 -60
  193. package/src/dashboard/scanner/index.js +0 -2
  194. package/src/dashboard/scanner/project-api.js +1 -1
  195. package/src/dashboard/scanner/rules-scanner.js +15 -10
  196. package/src/dashboard/scanner/skills-scanner.js +2 -3
  197. package/src/dashboard/scanner/standards-scanner.js +2 -1
  198. package/src/lib/ci/ci-verify.js +17 -1
  199. package/src/lib/ci/github-ci.js +1 -1
  200. package/src/lib/detectors/index.js +3 -8
  201. package/src/lib/generators/handoff-generator.js +34 -65
  202. package/src/lib/generators/settings-generator.js +11 -89
  203. package/src/lib/git/evidence-staleness.js +1 -1
  204. package/src/lib/git/tree-fingerprint.js +19 -7
  205. package/src/lib/git/tree-state.js +2 -2
  206. package/src/lib/heavy/cleanup.js +1 -5
  207. package/src/lib/installers/memory-installer.js +6 -8
  208. package/src/lib/mutation/commands.js +3 -2
  209. package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
  210. package/src/lib/mutation/plan.js +14 -6
  211. package/src/lib/mutation/runner.js +1 -1
  212. package/src/lib/mutation/subset.js +46 -9
  213. package/src/lib/plan/artifact-vocabulary.js +60 -0
  214. package/src/lib/plan/checks.js +234 -0
  215. package/src/lib/plan/plan-parser.js +209 -0
  216. package/src/lib/plan/plan-template.js +18 -0
  217. package/src/lib/review-reports.js +222 -0
  218. package/src/lib/risk-detector.js +134 -135
  219. package/src/lib/specs/adr-index.js +50 -69
  220. package/src/lib/specs/design-seed.js +18 -0
  221. package/src/lib/specs/frontmatter.js +44 -0
  222. package/src/lib/specs/gfm-table.js +17 -0
  223. package/src/lib/specs/old-format.js +68 -0
  224. package/src/lib/specs/revocations.js +37 -95
  225. package/src/lib/specs/screen-checklist.js +18 -17
  226. package/src/lib/stack/dotnet-build-ignores.js +47 -0
  227. package/src/lib/stack/stack-profile.js +1 -1
  228. package/src/lib/stack/stack-scan.js +267 -0
  229. package/src/lib/standards/registry.js +43 -0
  230. package/src/lib/standards/sections.js +58 -0
  231. package/src/lib/tasks/eval-node.js +4 -12
  232. package/src/lib/tasks/test-nodes.js +1 -74
  233. package/src/lib/tasks/test-runner.js +75 -117
  234. package/src/lib/telemetry/metrics.js +8 -69
  235. package/src/lib/telemetry/telemetry-reader.js +5 -118
  236. package/src/lib/telemetry/verify-event.js +5 -7
  237. package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
  238. package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
  239. package/src/lib/validators/reuse/reuse-validator.js +24 -48
  240. package/src/lib/validators/reuse/symbol-scanner.js +2 -4
  241. package/src/lib/validators/traceability-check.js +85 -129
  242. package/src/lib/validators/validation-runner.js +8 -88
  243. package/src/lib/validators/verify-runner.js +134 -301
  244. package/src/lib/worktree/docker-audit.js +1 -2
  245. package/src/lib/worktree/fleet-scanner.js +25 -38
  246. package/src/scripts/setup-infra.js +42 -22
  247. package/src/utils/agents-installer.js +92 -13
  248. package/src/utils/claude-settings-manager.js +0 -35
  249. package/src/utils/file-copier.js +34 -6
  250. package/src/utils/hooks-installer.js +16 -9
  251. package/src/utils/skills-installer.js +1 -2
  252. package/framework/commands/commit.md +0 -166
  253. package/framework/evals/trace-schema.md +0 -67
  254. package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
  255. package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
  256. package/framework/hooks/git/pre-commit/specs.sh +0 -50
  257. package/framework/hooks/shared/tasks-validator.js +0 -111
  258. package/framework/rubrics/backend.json +0 -41
  259. package/framework/rubrics/default.json +0 -36
  260. package/framework/rubrics/frontend.json +0 -36
  261. package/framework/rules/morph-task-tracking.md +0 -54
  262. package/framework/rules/morph-workflow.md +0 -244
  263. package/framework/schemas/tasks.schema.json +0 -79
  264. package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
  265. package/framework/skills/core/morph-implement/SKILL.md +0 -194
  266. package/framework/skills/core/morph-proposal/SKILL.md +0 -152
  267. package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
  268. package/framework/templates/docs/proposal.md +0 -192
  269. package/framework/templates/docs/spec.md +0 -57
  270. package/framework/templates/docs/ui-components.md +0 -52
  271. package/framework/templates/docs/ui-design-system.md +0 -76
  272. package/framework/templates/docs/ui-flows.md +0 -167
  273. package/framework/templates/examples/spec-examples.md +0 -91
  274. package/framework/templates/feature/decisions.md +0 -124
  275. package/framework/templates/feature/recap.md +0 -145
  276. package/framework/templates/project/validate.js +0 -97
  277. package/framework/templates/tasks.template.json +0 -17
  278. package/src/commands/state/usecase.js +0 -143
  279. package/src/commands/tasks/dag.js +0 -181
  280. package/src/dashboard/app/pages/Rubrics.jsx +0 -97
  281. package/src/dashboard/scanner/rubrics-scanner.js +0 -12
  282. package/src/lib/agents/output-collector.js +0 -76
  283. package/src/lib/evaluator-report.js +0 -91
  284. package/src/lib/specs/acervo-format.js +0 -70
  285. package/src/lib/specs/acervo-lifecycle.js +0 -264
  286. package/src/lib/specs/done-criteria-check.js +0 -200
  287. package/src/lib/specs/usecase-check.js +0 -301
  288. package/src/lib/specs/usecase-promoter.js +0 -301
  289. package/src/lib/specs/usecase-registry.js +0 -237
  290. package/src/lib/tasks/dispatch-planner.js +0 -319
  291. package/src/lib/tasks/mermaid-renderer.js +0 -132
  292. package/src/lib/tasks/task-classifier.js +0 -30
  293. package/src/lib/tasks/task-parser.js +0 -209
  294. package/src/lib/tasks/tasks-json-parser.js +0 -296
  295. package/src/lib/tasks/test-scope.js +0 -597
  296. package/src/lib/validators/architecture/architecture-validator.js +0 -270
  297. package/src/lib/validators/architecture/index.js +0 -7
  298. package/src/lib/validators/css/css-validator.js +0 -340
  299. package/src/lib/validators/css/index.js +0 -7
  300. package/src/lib/validators/design-system/design-system-validator.js +0 -237
  301. package/src/lib/validators/design-system/index.js +0 -7
  302. package/src/lib/validators/score-aggregator.js +0 -244
  303. package/src/lib/validators/ui/index.js +0 -7
  304. 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';