@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.
- package/CLAUDE.md +53 -44
- package/README.md +3 -4
- package/bin/morph-spec.js +38 -55
- package/bin/validate.js +5 -134
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +2 -2
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +41 -101
- package/framework/MORPH.md +95 -252
- package/framework/agents.json +75 -18
- package/framework/commands/create-umbraco-lp.md +2 -0
- package/framework/commands/migrate-lp-to-umbraco.md +2 -0
- package/framework/commands/morph-apply.md +5 -28
- package/framework/commands/morph-archive.md +2 -2
- package/framework/commands/morph-hotfix.md +8 -8
- package/framework/commands/morph-infra.md +17 -203
- package/framework/commands/morph-media.md +3 -0
- package/framework/commands/morph-preflight.md +7 -31
- package/framework/commands/morph-proposal.md +2 -50
- package/framework/commands/morph-status.md +3 -3
- package/framework/commands/morph-troubleshoot.md +2 -14
- package/framework/docs/CLI.md +585 -286
- package/framework/evals/review-checklist.md +82 -41
- package/framework/hooks/README.md +10 -11
- package/framework/hooks/claude-code/core/comment-guard.js +1 -1
- package/framework/hooks/claude-code/core/denylist.json +33 -3
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +16 -20
- package/framework/hooks/claude-code/core/gate-guard.js +2 -11
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +38 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +30 -242
- package/framework/hooks/claude-code/core/state-sync.js +17 -19
- package/framework/hooks/claude-code/core/telemetry-log.js +3 -3
- package/framework/hooks/claude-code/core/validate-completion.js +33 -97
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +31 -0
- package/framework/hooks/claude-code/core/worktree-context.js +5 -4
- package/framework/hooks/claude-code/statusline.py +55 -41
- package/framework/hooks/package.json +3 -0
- package/framework/hooks/shared/activity-logger.js +1 -1
- package/framework/hooks/shared/build-check.js +10 -6
- package/framework/hooks/shared/command-tokens.js +169 -0
- package/framework/hooks/shared/comment-rules.js +1 -1
- package/framework/hooks/shared/comment-scanner.js +10 -23
- package/framework/hooks/shared/comment-stripper.js +1 -1
- package/framework/hooks/shared/e2e-check.js +67 -31
- package/framework/hooks/shared/errata.js +72 -0
- package/framework/hooks/shared/feature-locator.js +1 -1
- package/framework/hooks/shared/frontend-locations.js +6 -0
- package/framework/hooks/shared/gate-question.js +18 -0
- package/framework/hooks/shared/phase-utils.js +15 -32
- package/framework/hooks/shared/slice-progress.js +118 -0
- package/framework/hooks/shared/spawn-command.js +23 -0
- package/framework/hooks/shared/state-reader.js +61 -87
- package/framework/hooks/shared/state-writer.js +3 -3
- package/framework/mcp/registry.json +3 -4
- package/framework/phases.json +23 -39
- package/framework/rules/ai-agents-standards.md +18 -16
- package/framework/rules/asaas-standards.md +3 -1
- package/framework/rules/csharp-standards.md +10 -8
- package/framework/rules/data-standards.md +5 -5
- package/framework/rules/dotnet-templates.md +1 -1
- package/framework/rules/frontend-standards.md +4 -2
- package/framework/rules/gsap-standards.md +10 -5
- package/framework/rules/hangfire-standards.md +3 -1
- package/framework/rules/infrastructure-standards.md +4 -2
- package/framework/rules/neon-auth-standards.md +3 -1
- package/framework/rules/neon-standards.md +6 -4
- package/framework/rules/nextjs-standards.md +13 -11
- package/framework/rules/nextjs-templates.md +1 -1
- package/framework/rules/resend-standards.md +3 -1
- package/framework/schemas/config.schema.json +14 -9
- package/framework/schemas/feature.schema.json +15 -52
- package/framework/skills/README.md +4 -5
- package/framework/skills/core/create-umbraco-lp/SKILL.md +3 -3
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +3 -3
- package/framework/skills/core/morph-apply/SKILL.md +150 -223
- package/framework/skills/core/morph-hotfix/SKILL.md +34 -43
- package/framework/skills/core/morph-machine/SKILL.md +4 -4
- package/framework/skills/core/morph-media/SKILL.md +3 -3
- package/framework/skills/core/morph-plan/SKILL.md +138 -300
- package/framework/skills/core/morph-review/SKILL.md +112 -137
- package/framework/skills/core/morph-standards/SKILL.md +29 -41
- package/framework/skills/core/morph-uiux/SKILL.md +78 -153
- package/framework/standards/STANDARDS.json +90 -1219
- package/framework/standards/STANDARDS.meta.json +1305 -0
- package/framework/standards/ai-agents/evals-with-cache.md +2 -3
- package/framework/standards/ai-agents/llm-runtime-defaults.md +1 -1
- package/framework/standards/ai-agents/modalities-speech-to-text.md +36 -26
- package/framework/standards/ai-agents/prompt-sources.md +6 -4
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +67 -39
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/structured-output.md +1 -1
- package/framework/standards/ai-agents/sweet-spot.md +1 -1
- package/framework/standards/architecture/code-comments.md +8 -8
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +18 -9
- package/framework/standards/backend/api/validation.md +135 -47
- package/framework/standards/backend/database/postgresql/database.md +11 -7
- package/framework/standards/backend/database/vector-search-rag.md +207 -324
- package/framework/standards/backend/dotnet/fluent-validation-vsa.md +14 -8
- package/framework/standards/backend/dotnet/result-pattern.md +40 -6
- package/framework/standards/backend/dotnet/scalar-ui.md +13 -7
- package/framework/standards/backend/dotnet/scrutor.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +1 -1
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +7 -7
- package/framework/standards/backend/integrations/asaas/asaas-api.md +309 -165
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +134 -154
- package/framework/standards/data/nosql/cache/redis.md +120 -44
- package/framework/standards/data/nosql/cosmos-db.md +114 -71
- package/framework/standards/frontend/design-system/animations.md +1 -1
- package/framework/standards/frontend/design-system/naming.md +1 -1
- package/framework/standards/frontend/nextjs/motion-patterns.md +45 -29
- package/framework/standards/frontend/nextjs/naming-conventions.md +1 -1
- package/framework/standards/infrastructure/docker/coolify-deploy.md +8 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +120 -75
- package/framework/standards/infrastructure/neon/neon-setup.md +53 -31
- package/framework/standards/integration/mcp/mcp-tools.md +7 -7
- package/framework/templates/README.md +2 -3
- package/framework/templates/REGISTRY.json +6 -170
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +2 -2
- package/framework/templates/code/dotnet/extensions/database-extensions.cs +1 -1
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +27 -0
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +2 -2
- package/framework/templates/docs/plan.md +72 -0
- package/framework/templates/dotnet/ai-kit/README.md +1 -9
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +1 -1
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +0 -10
- package/framework/templates/infrastructure/docker/coolify.template.json +3 -3
- package/framework/templates/infrastructure/github/README.md +2 -1
- package/framework/templates/infrastructure/github/workflows/deploy-coolify.yml.hbs +2 -1
- package/framework/templates/meta-prompts/squad-leaders/backend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/frontend-squad.md +1 -1
- package/framework/templates/meta-prompts/squad-leaders/squad-leader.md +1 -1
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +3 -4
- package/package.json +1 -1
- package/src/commands/e2e/e2e.js +2 -2
- package/src/commands/observability/cost.js +18 -60
- package/src/commands/observability/telemetry.js +3 -5
- package/src/commands/project/doctor-procs.js +1 -1
- package/src/commands/project/doctor.js +9 -9
- package/src/commands/project/fleet.js +4 -4
- package/src/commands/project/heavy.js +3 -3
- package/src/commands/project/init.js +6 -1
- package/src/commands/project/setup-infra-cmd.js +2 -1
- package/src/commands/project/stack-scan.js +58 -0
- package/src/commands/project/standards.js +79 -25
- package/src/commands/project/status.js +29 -69
- package/src/commands/project/update.js +33 -19
- package/src/commands/project/worktree.js +1 -1
- package/src/commands/state/advance.js +62 -46
- package/src/commands/state/approve.js +95 -138
- package/src/commands/state/archive.js +20 -84
- package/src/commands/state/auto-approval.js +68 -0
- package/src/commands/state/create.js +28 -1
- package/src/commands/state/delete.js +1 -1
- package/src/commands/state/finish.js +11 -23
- package/src/commands/state/gate-check.js +86 -95
- package/src/commands/state/gate-decision.js +6 -4
- package/src/commands/state/handoff.js +1 -1
- package/src/commands/state/list.js +9 -9
- package/src/commands/state/report-check.js +157 -311
- package/src/commands/state/retype.js +9 -3
- package/src/commands/validation/mutate.js +77 -14
- package/src/commands/validation/plan-check.js +81 -0
- package/src/commands/validation/validate-feature.js +60 -135
- package/src/commands/validation/verify.js +41 -82
- package/src/core/paths/output-schema.js +20 -67
- package/src/core/state/gate-order.js +5 -6
- package/src/core/state/lifecycle-audit.js +2 -2
- package/src/core/state/state-manager.js +17 -98
- package/src/core/templates/template-registry.js +2 -2
- package/src/core/templates/template-seed.js +22 -0
- package/src/core/work-types.js +10 -10
- package/src/dashboard/app/App.jsx +0 -2
- package/src/dashboard/app/components/ActivityFeed.jsx +2 -3
- package/src/dashboard/app/components/CommandPalette.jsx +7 -8
- package/src/dashboard/app/components/EntityLink.jsx +1 -2
- package/src/dashboard/app/components/Markdown.jsx +1 -1
- package/src/dashboard/app/components/Sidebar.jsx +1 -2
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -1
- package/src/dashboard/app/lib/phases.js +2 -4
- package/src/dashboard/app/pages/Activity.jsx +1 -1
- package/src/dashboard/app/pages/FeatureDetail.jsx +139 -129
- package/src/dashboard/app/pages/Features.jsx +9 -9
- package/src/dashboard/app/pages/Guide.jsx +2 -2
- package/src/dashboard/app/pages/PhasePipeline.jsx +3 -4
- package/src/dashboard/app/pages/RulesMap.jsx +3 -3
- package/src/dashboard/app/pages/SkillsLibrary.jsx +3 -3
- package/src/dashboard/app/pages/Standards.jsx +1 -1
- package/src/dashboard/scanner/activity-scanner.js +6 -16
- package/src/dashboard/scanner/agents-scanner.js +1 -1
- package/src/dashboard/scanner/docs-scanner.js +5 -23
- package/src/dashboard/scanner/features-scanner.js +95 -60
- package/src/dashboard/scanner/index.js +0 -2
- package/src/dashboard/scanner/project-api.js +1 -1
- package/src/dashboard/scanner/rules-scanner.js +15 -10
- package/src/dashboard/scanner/skills-scanner.js +2 -3
- package/src/dashboard/scanner/standards-scanner.js +2 -1
- package/src/lib/ci/ci-verify.js +17 -1
- package/src/lib/ci/github-ci.js +1 -1
- package/src/lib/detectors/index.js +3 -8
- package/src/lib/generators/handoff-generator.js +34 -65
- package/src/lib/generators/settings-generator.js +11 -89
- package/src/lib/git/evidence-staleness.js +1 -1
- package/src/lib/git/tree-fingerprint.js +19 -7
- package/src/lib/git/tree-state.js +2 -2
- package/src/lib/heavy/cleanup.js +1 -5
- package/src/lib/installers/memory-installer.js +6 -8
- package/src/lib/mutation/commands.js +3 -2
- package/src/lib/{tasks/dotnet-test-introspection.js → mutation/dotnet-introspection.js} +17 -282
- package/src/lib/mutation/plan.js +14 -6
- package/src/lib/mutation/runner.js +1 -1
- package/src/lib/mutation/subset.js +46 -9
- package/src/lib/plan/artifact-vocabulary.js +60 -0
- package/src/lib/plan/checks.js +234 -0
- package/src/lib/plan/plan-parser.js +209 -0
- package/src/lib/plan/plan-template.js +18 -0
- package/src/lib/review-reports.js +222 -0
- package/src/lib/risk-detector.js +134 -135
- package/src/lib/specs/adr-index.js +50 -69
- package/src/lib/specs/design-seed.js +18 -0
- package/src/lib/specs/frontmatter.js +44 -0
- package/src/lib/specs/gfm-table.js +17 -0
- package/src/lib/specs/old-format.js +68 -0
- package/src/lib/specs/revocations.js +37 -95
- package/src/lib/specs/screen-checklist.js +18 -17
- package/src/lib/stack/dotnet-build-ignores.js +47 -0
- package/src/lib/stack/stack-profile.js +1 -1
- package/src/lib/stack/stack-scan.js +267 -0
- package/src/lib/standards/registry.js +43 -0
- package/src/lib/standards/sections.js +58 -0
- package/src/lib/tasks/eval-node.js +4 -12
- package/src/lib/tasks/test-nodes.js +1 -74
- package/src/lib/tasks/test-runner.js +75 -117
- package/src/lib/telemetry/metrics.js +8 -69
- package/src/lib/telemetry/telemetry-reader.js +5 -118
- package/src/lib/telemetry/verify-event.js +5 -7
- package/src/lib/validators/code-budget/code-budget-validator.js +1 -1
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -2
- package/src/lib/validators/reuse/reuse-validator.js +24 -48
- package/src/lib/validators/reuse/symbol-scanner.js +2 -4
- package/src/lib/validators/traceability-check.js +85 -129
- package/src/lib/validators/validation-runner.js +8 -88
- package/src/lib/validators/verify-runner.js +134 -301
- package/src/lib/worktree/docker-audit.js +1 -2
- package/src/lib/worktree/fleet-scanner.js +25 -38
- package/src/scripts/setup-infra.js +42 -22
- package/src/utils/agents-installer.js +92 -13
- package/src/utils/claude-settings-manager.js +0 -35
- package/src/utils/file-copier.js +34 -6
- package/src/utils/hooks-installer.js +16 -9
- package/src/utils/skills-installer.js +1 -2
- package/framework/commands/commit.md +0 -166
- package/framework/evals/trace-schema.md +0 -67
- package/framework/hooks/claude-code/core/tasks-json-guard.js +0 -83
- package/framework/hooks/claude-code/core/trace-autogen.js +0 -277
- package/framework/hooks/git/pre-commit/specs.sh +0 -50
- package/framework/hooks/shared/tasks-validator.js +0 -111
- package/framework/rubrics/backend.json +0 -41
- package/framework/rubrics/default.json +0 -36
- package/framework/rubrics/frontend.json +0 -36
- package/framework/rules/morph-task-tracking.md +0 -54
- package/framework/rules/morph-workflow.md +0 -244
- package/framework/schemas/tasks.schema.json +0 -79
- package/framework/skills/core/morph-brainstorming/SKILL.md +0 -161
- package/framework/skills/core/morph-implement/SKILL.md +0 -194
- package/framework/skills/core/morph-proposal/SKILL.md +0 -152
- package/framework/skills/core/morph-stack-scan/SKILL.md +0 -145
- package/framework/templates/docs/proposal.md +0 -192
- package/framework/templates/docs/spec.md +0 -57
- package/framework/templates/docs/ui-components.md +0 -52
- package/framework/templates/docs/ui-design-system.md +0 -76
- package/framework/templates/docs/ui-flows.md +0 -167
- package/framework/templates/examples/spec-examples.md +0 -91
- package/framework/templates/feature/decisions.md +0 -124
- package/framework/templates/feature/recap.md +0 -145
- package/framework/templates/project/validate.js +0 -97
- package/framework/templates/tasks.template.json +0 -17
- package/src/commands/state/usecase.js +0 -143
- package/src/commands/tasks/dag.js +0 -181
- package/src/dashboard/app/pages/Rubrics.jsx +0 -97
- package/src/dashboard/scanner/rubrics-scanner.js +0 -12
- package/src/lib/agents/output-collector.js +0 -76
- package/src/lib/evaluator-report.js +0 -91
- package/src/lib/specs/acervo-format.js +0 -70
- package/src/lib/specs/acervo-lifecycle.js +0 -264
- package/src/lib/specs/done-criteria-check.js +0 -200
- package/src/lib/specs/usecase-check.js +0 -301
- package/src/lib/specs/usecase-promoter.js +0 -301
- package/src/lib/specs/usecase-registry.js +0 -237
- package/src/lib/tasks/dispatch-planner.js +0 -319
- package/src/lib/tasks/mermaid-renderer.js +0 -132
- package/src/lib/tasks/task-classifier.js +0 -30
- package/src/lib/tasks/task-parser.js +0 -209
- package/src/lib/tasks/tasks-json-parser.js +0 -296
- package/src/lib/tasks/test-scope.js +0 -597
- package/src/lib/validators/architecture/architecture-validator.js +0 -270
- package/src/lib/validators/architecture/index.js +0 -7
- package/src/lib/validators/css/css-validator.js +0 -340
- package/src/lib/validators/css/index.js +0 -7
- package/src/lib/validators/design-system/design-system-validator.js +0 -237
- package/src/lib/validators/design-system/index.js +0 -7
- package/src/lib/validators/score-aggregator.js +0 -244
- package/src/lib/validators/ui/index.js +0 -7
- 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,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
|
-
}
|