@polymorphism-tech/morph-spec 9.0.0 → 9.2.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 +8 -9
- package/README.md +38 -36
- package/bin/morph-spec.js +11 -63
- package/bin/validate.js +1 -39
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +2 -1
- package/framework/MORPH.md +3 -3
- package/framework/agents.json +19 -11
- package/framework/commands/morph-apply.md +1 -1
- package/framework/commands/morph-status.md +1 -1
- package/framework/docs/CLI.md +22 -31
- package/framework/evals/review-checklist.md +12 -2
- package/framework/hooks/README.md +8 -44
- package/framework/hooks/claude-code/core/block-destructive-worktree-removal.js +6 -49
- package/framework/hooks/claude-code/core/block-raw-worktree-add.js +2 -35
- package/framework/hooks/claude-code/core/chain.js +4 -65
- package/framework/hooks/claude-code/core/comment-guard.js +2 -16
- package/framework/hooks/claude-code/core/enforce-phase-writes.js +6 -50
- package/framework/hooks/claude-code/core/gate-guard.js +7 -67
- package/framework/hooks/claude-code/core/graph-refresh-kick.js +3 -24
- package/framework/hooks/claude-code/core/heavy-gate.js +3 -21
- package/framework/hooks/claude-code/core/heavy-release.js +2 -13
- package/framework/hooks/claude-code/core/loop-detect.js +2 -31
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +0 -4
- package/framework/hooks/claude-code/core/protect-spec-files.js +7 -67
- package/framework/hooks/claude-code/core/state-sync.js +12 -68
- package/framework/hooks/claude-code/core/telemetry-log.js +12 -93
- package/framework/hooks/claude-code/core/validate-completion.js +11 -57
- package/framework/hooks/claude-code/core/warn-bash-source-write.js +1 -9
- package/framework/hooks/claude-code/core/worktree-context.js +10 -110
- package/framework/hooks/claude-code/core/worktree-create.js +6 -70
- package/framework/hooks/claude-code/core/worktree-env-inject.js +5 -56
- package/framework/hooks/claude-code/core/worktree-remove.js +5 -31
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/shared/activity-logger.js +9 -89
- package/framework/hooks/shared/agent-reap.js +2 -17
- package/framework/hooks/shared/build-check.js +6 -75
- package/framework/hooks/shared/claim-registry.js +9 -119
- package/framework/hooks/shared/command-tokens.js +6 -51
- package/framework/hooks/shared/comment-rules.js +5 -20
- package/framework/hooks/shared/comment-scanner.js +6 -25
- package/framework/hooks/shared/comment-stripper.js +11 -85
- package/framework/hooks/shared/e2e-check.js +0 -0
- package/framework/hooks/shared/errata.js +2 -15
- package/framework/hooks/shared/feature-locator.js +11 -95
- package/framework/hooks/shared/frontend-locations.js +1 -3
- package/framework/hooks/shared/gate-question.js +1 -7
- package/framework/hooks/shared/git-exclude.js +3 -20
- package/framework/hooks/shared/git-paths.js +8 -104
- package/framework/hooks/shared/graph-freshness.js +22 -166
- package/framework/hooks/shared/graph-refresh.js +21 -149
- package/framework/hooks/shared/heavy-command.js +4 -12
- package/framework/hooks/shared/hook-response.js +8 -83
- package/framework/hooks/shared/morph-cli.js +8 -52
- package/framework/hooks/shared/payload-utils.js +10 -132
- package/framework/hooks/shared/phase-utils.js +1 -42
- package/framework/hooks/shared/redact-secrets.js +4 -15
- package/framework/hooks/shared/reparse-scan.js +6 -66
- package/framework/hooks/shared/slice-progress.js +4 -23
- package/framework/hooks/shared/source-files.js +4 -17
- package/framework/hooks/shared/state-reader.js +13 -183
- package/framework/hooks/shared/state-writer.js +8 -52
- package/framework/hooks/shared/stdin-reader.js +1 -11
- package/framework/hooks/shared/telemetry-logger.js +7 -67
- package/framework/hooks/shared/transcript-usage.js +7 -29
- package/framework/hooks/shared/worktree-root.js +7 -29
- package/framework/rules/csharp-standards.md +1 -1
- package/framework/rules/frontend-standards.md +1 -0
- package/framework/rules/hangfire-standards.md +1 -1
- package/framework/rules/neon-standards.md +1 -1
- package/framework/rules/nextjs-standards.md +1 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +4 -4
- package/framework/skills/core/morph-hotfix/SKILL.md +3 -3
- package/framework/skills/core/morph-machine/SKILL.md +1 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +9 -7
- package/framework/skills/core/morph-review/SKILL.md +9 -6
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +29 -12
- package/framework/standards/STANDARDS.json +36 -14
- package/framework/standards/STANDARDS.meta.json +16 -2
- package/framework/standards/ai-agents/agent-archetypes.md +2 -2
- package/framework/standards/ai-agents/agent-session.md +1 -1
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +4 -4
- package/framework/standards/ai-agents/durable-workflows-hitl.md +3 -3
- package/framework/standards/ai-agents/guardrails.md +1 -1
- package/framework/standards/ai-agents/multi-agent-patterns.md +2 -2
- package/framework/standards/ai-agents/observability-patterns.md +1 -1
- package/framework/standards/ai-agents/production.md +1 -1
- package/framework/standards/ai-agents/prompt-sources.md +2 -2
- package/framework/standards/ai-agents/providers/model-registry.md +1 -1
- package/framework/standards/ai-agents/rag-custom-pgvector.md +2 -2
- package/framework/standards/ai-agents/setup.md +2 -2
- package/framework/standards/ai-agents/sweet-spot.md +2 -2
- package/framework/standards/ai-agents/tool-adapters.md +0 -2
- package/framework/standards/ai-agents/vector-data-extensions.md +4 -4
- package/framework/standards/ai-agents/workflows.md +1 -1
- package/framework/standards/architecture/code-comments.md +1 -1
- package/framework/standards/architecture/vertical-slice/vertical-slice.md +1 -1
- package/framework/standards/backend/api/validation.md +1 -1
- package/framework/standards/backend/dotnet/testing.md +10 -4
- package/framework/standards/backend/dotnet/vsa-handler-patterns.md +5 -5
- package/framework/standards/backend/integrations/hangfire/hangfire-jobs.md +1 -1
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +2 -2
- package/framework/standards/data/nosql/cache/redis.md +1 -1
- package/framework/standards/data/nosql/cosmos-db.md +3 -3
- package/framework/standards/frontend/admin-app/forms.md +1 -1
- package/framework/standards/frontend/design-system/art-direction.md +88 -0
- package/framework/standards/frontend/design-system/premium-finish.md +77 -2
- package/framework/standards/frontend/nextjs/testing.md +10 -6
- package/framework/standards/infrastructure/neon/neon-pgvector.md +5 -5
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/CLAUDE.md.core.template +3 -3
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +9 -9
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/docs/plan.md +3 -1
- package/framework/templates/docs/ui-mockups.md +4 -0
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/context/context-refresh.js +0 -16
- package/src/commands/dashboard/dashboard.js +2 -8
- package/src/commands/e2e/e2e.js +17 -104
- package/src/commands/graph/affected.js +0 -14
- package/src/commands/graph/build.js +11 -50
- package/src/commands/graph/explain.js +0 -16
- package/src/commands/graph/status.js +1 -12
- package/src/commands/mcp/mcp-setup.js +4 -62
- package/src/commands/observability/cost.js +5 -57
- package/src/commands/observability/telemetry.js +1 -14
- package/src/commands/project/branch.js +4 -45
- package/src/commands/project/claim.js +4 -48
- package/src/commands/project/doctor.js +48 -302
- package/src/commands/project/env.js +1 -27
- package/src/commands/project/fleet.js +3 -32
- package/src/commands/project/init.js +4 -68
- package/src/commands/project/install-plugin-cmd.js +0 -1
- package/src/commands/project/standards.js +1 -62
- package/src/commands/project/status.js +0 -53
- package/src/commands/project/update.js +31 -239
- package/src/commands/project/worktree.js +108 -882
- package/src/commands/scaffold/scaffold.js +0 -25
- package/src/commands/state/advance.js +14 -85
- package/src/commands/state/approve.js +15 -97
- package/src/commands/state/archive.js +12 -64
- package/src/commands/state/auto-approval.js +2 -5
- package/src/commands/state/create.js +22 -109
- package/src/commands/state/delete.js +11 -63
- package/src/commands/state/finish.js +98 -475
- package/src/commands/state/gate-check.js +22 -132
- package/src/commands/state/gate-decision.js +3 -39
- package/src/commands/state/handoff.js +3 -30
- package/src/commands/state/list.js +14 -84
- package/src/commands/state/report-check.js +8 -58
- package/src/commands/state/retype.js +4 -26
- package/src/commands/validation/index.js +0 -3
- package/src/commands/validation/mutate.js +11 -11
- package/src/commands/validation/plan-check.js +4 -2
- package/src/commands/validation/tests-audit.js +7 -0
- package/src/commands/validation/validate-feature.js +10 -97
- package/src/commands/validation/verify.js +30 -113
- package/src/core/paths/output-schema.js +2 -101
- package/src/core/state/feature-identity.js +1 -25
- package/src/core/state/gate-order.js +0 -27
- package/src/core/state/lifecycle-audit.js +8 -100
- package/src/core/state/state-manager.js +22 -304
- package/src/core/templates/template-registry.js +2 -97
- package/src/core/templates/template-renderer.js +13 -93
- package/src/core/templates/template-seed.js +0 -1
- package/src/core/work-types.js +1 -36
- package/src/dashboard/app/App.jsx +1 -8
- package/src/dashboard/app/components/ActivityFeed.jsx +0 -5
- package/src/dashboard/app/components/CommandPalette.jsx +0 -7
- package/src/dashboard/app/components/DetailPanel.jsx +1 -5
- package/src/dashboard/app/components/EntityLink.jsx +0 -12
- package/src/dashboard/app/components/ErrorBoundary.jsx +1 -8
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +0 -11
- package/src/dashboard/app/components/Markdown.jsx +0 -9
- package/src/dashboard/app/components/PhaseRail.jsx +0 -11
- package/src/dashboard/app/components/SearchFilter.jsx +0 -4
- package/src/dashboard/app/components/Sidebar.jsx +0 -5
- package/src/dashboard/app/hooks/useMorphQuery.js +1 -11
- package/src/dashboard/app/hooks/usePref.js +1 -5
- package/src/dashboard/app/lib/format.js +0 -2
- package/src/dashboard/app/lib/phases.js +1 -5
- package/src/dashboard/app/lib/project.js +0 -12
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +1 -8
- package/src/dashboard/app/pages/FeatureDetail.jsx +1 -5
- package/src/dashboard/app/pages/Features.jsx +1 -8
- package/src/dashboard/app/pages/Guide.jsx +0 -1
- package/src/dashboard/app/pages/HookInspector.jsx +2 -23
- package/src/dashboard/app/pages/PhasePipeline.jsx +0 -3
- package/src/dashboard/app/pages/Portfolio.jsx +0 -1
- package/src/dashboard/app/pages/RulesMap.jsx +1 -16
- package/src/dashboard/app/pages/SkillsLibrary.jsx +1 -16
- package/src/dashboard/app/pages/Standards.jsx +2 -17
- package/src/dashboard/scanner/activity-scanner.js +10 -69
- package/src/dashboard/scanner/agents-scanner.js +1 -23
- package/src/dashboard/scanner/docs-scanner.js +0 -6
- package/src/dashboard/scanner/feature-status.js +2 -5
- package/src/dashboard/scanner/features-scanner.js +10 -66
- package/src/dashboard/scanner/hooks-scanner.js +5 -28
- package/src/dashboard/scanner/index.js +0 -4
- package/src/dashboard/scanner/portfolio.js +5 -29
- package/src/dashboard/scanner/project-api.js +8 -54
- package/src/dashboard/scanner/rules-scanner.js +1 -2
- package/src/dashboard/scanner/skills-scanner.js +2 -12
- package/src/dashboard/scanner/standards-scanner.js +2 -6
- package/src/dashboard/vite-plugin-morph-data.js +5 -19
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +7 -52
- package/src/lib/ai/ai-project-scan.js +14 -108
- package/src/lib/ai/code-audit.js +7 -63
- package/src/lib/ai/pin-drift.js +8 -51
- package/src/lib/ai/registry-audit.js +11 -90
- package/src/lib/ci/ci-verify.js +4 -19
- package/src/lib/ci/github-ci.js +9 -37
- package/src/lib/ci/workflow-scaffold.js +14 -10
- package/src/lib/detectors/claude-config-detector.js +13 -74
- package/src/lib/detectors/index.js +4 -50
- package/src/lib/e2e/applicability.js +37 -0
- package/src/lib/e2e/compose-env.js +5 -66
- package/src/lib/e2e/stack-teardown.js +6 -40
- package/src/lib/generators/architecture-scanner.js +18 -156
- package/src/lib/generators/handoff-generator.js +9 -53
- package/src/lib/generators/scaffold-generator.js +5 -101
- package/src/lib/generators/settings-generator.js +4 -37
- package/src/lib/git/branch-classifier.js +11 -84
- package/src/lib/git/default-branch.js +14 -169
- package/src/lib/git/evidence-staleness.js +1 -0
- package/src/lib/git/gh.js +2 -17
- package/src/lib/git/pending-prs.js +6 -37
- package/src/lib/git/pr-files.js +3 -28
- package/src/lib/git/pr-index.js +3 -34
- package/src/lib/git/tree-fingerprint.js +3 -15
- package/src/lib/git/tree-state.js +6 -33
- package/src/lib/graph/god-nodes.js +2 -22
- package/src/lib/graph/graph-health.js +3 -24
- package/src/lib/graph/graph-loader.js +5 -56
- package/src/lib/graph/graphify-runtime.js +7 -37
- package/src/lib/graph/graphifyignore.js +8 -74
- package/src/lib/graph/sanitize.js +16 -136
- package/src/lib/heavy/cleanup.js +1 -1
- package/src/lib/installers/effects.js +8 -93
- package/src/lib/installers/mcp-installer.js +9 -169
- package/src/lib/installers/memory-installer.js +4 -27
- package/src/lib/installers/templates-reconcile.js +5 -60
- package/src/lib/mutation/adapters.js +6 -17
- package/src/lib/mutation/dotnet-introspection.js +20 -106
- package/src/lib/mutation/runner.js +23 -75
- package/src/lib/mutation/subset.js +1 -4
- package/src/lib/mutation/trx.js +0 -1
- package/src/lib/plan/artifact-vocabulary.js +5 -25
- package/src/lib/plan/checks.js +34 -33
- package/src/lib/plan/plan-parser.js +7 -8
- package/src/lib/projects/registry.js +7 -44
- package/src/lib/review-reports.js +11 -26
- package/src/lib/risk-detector.js +41 -173
- package/src/lib/specs/adr-index.js +6 -34
- package/src/lib/specs/frontmatter.js +2 -15
- package/src/lib/specs/gfm-table.js +1 -7
- package/src/lib/specs/revocations.js +1 -14
- package/src/lib/specs/screen-checklist.js +47 -124
- package/src/lib/stack/dotnet-build-ignores.js +2 -6
- package/src/lib/stack/stack-profile.js +1 -28
- package/src/lib/stack/stack-scan.js +1 -8
- package/src/lib/stack-filter.js +4 -27
- package/src/lib/standards/registry.js +1 -7
- package/src/lib/standards/sections.js +2 -13
- package/src/lib/standards/verified-against.js +19 -191
- package/src/lib/tasks/eval-node.js +21 -169
- package/src/lib/tasks/linux-runner.js +18 -142
- package/src/lib/tasks/test-nodes.js +43 -240
- package/src/lib/tasks/test-runner.js +43 -341
- package/src/lib/telemetry/metrics.js +7 -36
- package/src/lib/telemetry/telemetry-reader.js +13 -125
- package/src/lib/telemetry/verify-event.js +2 -30
- package/src/lib/tests-audit/audit.js +96 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +4 -44
- package/src/lib/validators/code-budget/index.js +0 -7
- package/src/lib/validators/contracts/contract-compliance-validator.js +2 -24
- package/src/lib/validators/contracts/index.js +0 -6
- package/src/lib/validators/nextjs/index.js +0 -5
- package/src/lib/validators/nextjs/next-component-validator.js +4 -81
- package/src/lib/validators/packages/index.js +0 -6
- package/src/lib/validators/packages/package-validator.js +6 -56
- package/src/lib/validators/reuse/index.js +0 -8
- package/src/lib/validators/reuse/reuse-validator.js +11 -101
- package/src/lib/validators/reuse/symbol-scanner.js +6 -62
- package/src/lib/validators/traceability-check.js +5 -63
- package/src/lib/validators/validation-runner.js +21 -158
- package/src/lib/validators/verify-runner.js +85 -376
- package/src/lib/work-type-detector.js +5 -67
- package/src/lib/worktree/docker-audit.js +17 -136
- package/src/lib/worktree/env-allocator.js +10 -98
- package/src/lib/worktree/feature-truth.js +12 -115
- package/src/lib/worktree/finish-backup.js +32 -242
- package/src/lib/worktree/fleet-scanner.js +13 -95
- package/src/lib/worktree/ignored-triage.js +15 -91
- package/src/lib/worktree/vscode-settings.js +2 -3
- package/src/lib/worktree/worktree-audit.js +21 -213
- package/src/lib/worktree/worktree-include.js +3 -5
- package/src/scripts/global-install.js +3 -9
- package/src/scripts/install-plugin.js +1 -13
- package/src/scripts/setup-infra.js +17 -147
- package/src/utils/agents-installer.js +29 -276
- package/src/utils/banner.js +4 -25
- package/src/utils/claude-md-injector.js +1 -37
- package/src/utils/claude-settings-manager.js +1 -52
- package/src/utils/file-copier.js +11 -95
- package/src/utils/hooks-installer.js +21 -298
- package/src/utils/skills-installer.js +4 -101
- package/src/utils/version-checker.js +4 -83
- package/framework/commands/morph-proposal.md +0 -9
- package/framework/hooks/git/commit-msg/conventional-commits.sh +0 -33
- package/framework/hooks/git/pre-commit/agents.sh +0 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +0 -64
- package/framework/hooks/git/pre-push/run-tests.sh +0 -44
- package/src/commands/utils/index.js +0 -3
- package/src/lib/detectors/design-system-detector.js +0 -188
- package/src/lib/generators/design-system-generator.js +0 -508
- package/src/lib/specs/old-format.js +0 -68
- package/src/utils/agent-id-mapper.js +0 -11
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @fileoverview Design System Detector
|
|
3
|
-
*
|
|
4
|
-
* Detects if a design system exists for the project or feature.
|
|
5
|
-
* Checks multiple locations in priority order.
|
|
6
|
-
*
|
|
7
|
-
* @module design-system-detector
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
11
|
-
import { join } from 'path';
|
|
12
|
-
|
|
13
|
-
const SKIP_DIRS = new Set(['node_modules', '.git', '.morph', 'dist', 'build', '.next', 'obj', 'bin', '.vs']);
|
|
14
|
-
const CSS_SCAN_MAX_DEPTH = 6;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Recursively walk a directory collecting files with given extensions.
|
|
18
|
-
* @param {string} dir - Directory to walk
|
|
19
|
-
* @param {string[]} extensions - File extensions to collect (e.g. ['.css', '.scss'])
|
|
20
|
-
* @param {string[]} files - Accumulator array (mutated in-place)
|
|
21
|
-
* @param {number} depth - Current recursion depth
|
|
22
|
-
* @param {number} maxDepth - Maximum recursion depth
|
|
23
|
-
*/
|
|
24
|
-
function walkDirForCss(dir, extensions, files, depth, maxDepth) {
|
|
25
|
-
if (depth > maxDepth) return;
|
|
26
|
-
let entries;
|
|
27
|
-
try {
|
|
28
|
-
entries = readdirSync(dir, { withFileTypes: true });
|
|
29
|
-
} catch {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
if (entry.isDirectory()) {
|
|
34
|
-
if (!SKIP_DIRS.has(entry.name)) {
|
|
35
|
-
walkDirForCss(join(dir, entry.name), extensions, files, depth + 1, maxDepth);
|
|
36
|
-
}
|
|
37
|
-
} else if (entry.isFile()) {
|
|
38
|
-
const lower = entry.name.toLowerCase();
|
|
39
|
-
if (extensions.some(ext => lower.endsWith(ext))) {
|
|
40
|
-
files.push(join(dir, entry.name));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Detect design system for project or feature
|
|
48
|
-
* @param {string} projectPath - Root path of the project
|
|
49
|
-
* @param {string|null} featureName - Feature name (optional, checks feature-level if provided)
|
|
50
|
-
* @returns {Object} { hasDesignSystem, path, isProjectLevel, isFeatureLevel, source }
|
|
51
|
-
*/
|
|
52
|
-
export function detectDesignSystem(projectPath, featureName = null) {
|
|
53
|
-
const result = {
|
|
54
|
-
hasDesignSystem: false,
|
|
55
|
-
path: null,
|
|
56
|
-
isProjectLevel: false,
|
|
57
|
-
isFeatureLevel: false,
|
|
58
|
-
source: null
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// Priority 1: Feature-level design system (if feature specified)
|
|
62
|
-
if (featureName) {
|
|
63
|
-
const featureDesignSystemPath = join(
|
|
64
|
-
projectPath,
|
|
65
|
-
'.morph/features',
|
|
66
|
-
featureName,
|
|
67
|
-
'1-design',
|
|
68
|
-
'design-system.md'
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (existsSync(featureDesignSystemPath)) {
|
|
72
|
-
return {
|
|
73
|
-
hasDesignSystem: true,
|
|
74
|
-
path: featureDesignSystemPath,
|
|
75
|
-
isProjectLevel: false,
|
|
76
|
-
isFeatureLevel: true,
|
|
77
|
-
source: 'feature-output'
|
|
78
|
-
};
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Priority 2: Project-level design system (markdown)
|
|
83
|
-
const projectDesignSystemPath = join(projectPath, '.morph/context/design-system.md');
|
|
84
|
-
if (existsSync(projectDesignSystemPath)) {
|
|
85
|
-
return {
|
|
86
|
-
hasDesignSystem: true,
|
|
87
|
-
path: projectDesignSystemPath,
|
|
88
|
-
isProjectLevel: true,
|
|
89
|
-
isFeatureLevel: false,
|
|
90
|
-
source: 'project-markdown'
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// Priority 3: Generated CSS design system
|
|
95
|
-
const cssDesignSystemPath = join(projectPath, 'wwwroot/css/design-system.css');
|
|
96
|
-
if (existsSync(cssDesignSystemPath)) {
|
|
97
|
-
return {
|
|
98
|
-
hasDesignSystem: true,
|
|
99
|
-
path: cssDesignSystemPath,
|
|
100
|
-
isProjectLevel: true,
|
|
101
|
-
isFeatureLevel: false,
|
|
102
|
-
source: 'generated-css'
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Priority 4: Recursively scan frontend root directories for CSS/SCSS with design tokens
|
|
107
|
-
const frontendRoots = ['wwwroot', 'styles', 'src/styles', 'src', 'public'];
|
|
108
|
-
const extensions = ['.css', '.scss'];
|
|
109
|
-
const collected = [];
|
|
110
|
-
|
|
111
|
-
for (const root of frontendRoots) {
|
|
112
|
-
const rootPath = join(projectPath, root);
|
|
113
|
-
if (existsSync(rootPath)) {
|
|
114
|
-
walkDirForCss(rootPath, extensions, collected, 0, CSS_SCAN_MAX_DEPTH);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Deduplicate by resolved path
|
|
119
|
-
const seen = new Set();
|
|
120
|
-
const cssFiles = [];
|
|
121
|
-
for (const file of collected) {
|
|
122
|
-
if (!seen.has(file)) {
|
|
123
|
-
seen.add(file);
|
|
124
|
-
cssFiles.push(file);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// Sort for deterministic first-match across platforms
|
|
129
|
-
cssFiles.sort();
|
|
130
|
-
|
|
131
|
-
for (const cssFile of cssFiles) {
|
|
132
|
-
try {
|
|
133
|
-
const content = readFileSync(cssFile, 'utf8');
|
|
134
|
-
const hasDesignTokens = /:root\s*\{[\s\S]*?--/.test(content);
|
|
135
|
-
if (hasDesignTokens) {
|
|
136
|
-
return {
|
|
137
|
-
hasDesignSystem: true,
|
|
138
|
-
path: cssFile,
|
|
139
|
-
isProjectLevel: true,
|
|
140
|
-
isFeatureLevel: false,
|
|
141
|
-
source: 'existing-css-variables'
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
} catch {
|
|
145
|
-
// Continue checking other files
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// No design system found
|
|
150
|
-
return result;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Get recommended design system locations based on project type
|
|
155
|
-
* @param {string} projectPath - Root path of the project
|
|
156
|
-
* @returns {Object} { markdown, css, description }
|
|
157
|
-
*/
|
|
158
|
-
export function getRecommendedLocations(projectPath) {
|
|
159
|
-
try {
|
|
160
|
-
const configPath = join(projectPath, '.morph/config/config.json');
|
|
161
|
-
|
|
162
|
-
let projectType = 'unknown';
|
|
163
|
-
if (existsSync(configPath)) {
|
|
164
|
-
const config = JSON.parse(readFileSync(configPath, 'utf8'));
|
|
165
|
-
projectType = config.project?.type || 'unknown';
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// Default locations
|
|
169
|
-
const locations = {
|
|
170
|
-
markdown: '.morph/context/design-system.md',
|
|
171
|
-
css: 'public/design-system.css',
|
|
172
|
-
description: 'Project-level design system (shared across features)'
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// Adjust based on project type
|
|
176
|
-
if (projectType === 'nextjs' || projectType.includes('nextjs')) {
|
|
177
|
-
locations.css = 'styles/design-system.css';
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return locations;
|
|
181
|
-
} catch {
|
|
182
|
-
return {
|
|
183
|
-
markdown: '.morph/context/design-system.md',
|
|
184
|
-
css: 'wwwroot/css/design-system.css',
|
|
185
|
-
description: 'Project-level design system (shared across features)'
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
@@ -1,508 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MORPH-SPEC Design System Generator Library
|
|
3
|
-
*
|
|
4
|
-
* Reads design-system.md and generates:
|
|
5
|
-
* - CSS variables (design-system.css)
|
|
6
|
-
*
|
|
7
|
-
* Supports BOTH:
|
|
8
|
-
* - New CSS :root format (design-system.md template v2)
|
|
9
|
-
* - Old markdown list format (backward compat)
|
|
10
|
-
*
|
|
11
|
-
* Used both by CLI commands and internal automation.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import { readFileSync, existsSync } from 'fs';
|
|
15
|
-
|
|
16
|
-
// ============================================================================
|
|
17
|
-
// CSS :root Helpers
|
|
18
|
-
// ============================================================================
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Extract all CSS :root blocks from markdown (inside ```css fences)
|
|
22
|
-
* @param {string} markdown
|
|
23
|
-
* @returns {string} Combined CSS content from all :root blocks
|
|
24
|
-
*/
|
|
25
|
-
function extractCSSRootBlocks(markdown) {
|
|
26
|
-
const cssBlocks = [];
|
|
27
|
-
const fenceRegex = /```css\s*\n([\s\S]*?)```/g;
|
|
28
|
-
let match;
|
|
29
|
-
while ((match = fenceRegex.exec(markdown)) !== null) {
|
|
30
|
-
cssBlocks.push(match[1]);
|
|
31
|
-
}
|
|
32
|
-
return cssBlocks.join('\n');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Extract CSS custom properties from :root blocks
|
|
37
|
-
* Returns array of { name, value } for all --xxx: yyy; declarations
|
|
38
|
-
*/
|
|
39
|
-
function extractCSSProperties(cssContent) {
|
|
40
|
-
const props = [];
|
|
41
|
-
const propRegex = /(--[\w-]+)\s*:\s*([^;]+);/g;
|
|
42
|
-
let match;
|
|
43
|
-
while ((match = propRegex.exec(cssContent)) !== null) {
|
|
44
|
-
props.push({ name: match[1].trim(), value: match[2].trim() });
|
|
45
|
-
}
|
|
46
|
-
return props;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// ============================================================================
|
|
50
|
-
// Parsing Functions
|
|
51
|
-
// ============================================================================
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Parse colors from markdown — supports CSS :root format and old markdown format
|
|
55
|
-
* @param {string} markdown - Markdown content
|
|
56
|
-
* @returns {Object} Colors object with primary, accent, secondary, neutral, semantic, surface, text, other
|
|
57
|
-
*/
|
|
58
|
-
export function parseColors(markdown) {
|
|
59
|
-
const colors = {
|
|
60
|
-
primary: {},
|
|
61
|
-
accent: {},
|
|
62
|
-
secondary: {},
|
|
63
|
-
neutral: {},
|
|
64
|
-
semantic: {},
|
|
65
|
-
surface: {},
|
|
66
|
-
text: {},
|
|
67
|
-
other: {}
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
// Try CSS :root format first
|
|
71
|
-
const cssContent = extractCSSRootBlocks(markdown);
|
|
72
|
-
const props = extractCSSProperties(cssContent);
|
|
73
|
-
|
|
74
|
-
let cssColorCount = 0;
|
|
75
|
-
|
|
76
|
-
for (const { name, value } of props) {
|
|
77
|
-
if (!name.startsWith('--color-')) continue;
|
|
78
|
-
|
|
79
|
-
const suffix = name.slice('--color-'.length); // e.g. "primary", "primary-light", "neutral-500"
|
|
80
|
-
|
|
81
|
-
if (suffix.startsWith('primary-rgb')) {
|
|
82
|
-
// Store RGB metadata as-is
|
|
83
|
-
colors.primary['rgb'] = value;
|
|
84
|
-
cssColorCount++;
|
|
85
|
-
} else if (suffix.startsWith('primary')) {
|
|
86
|
-
const variant = suffix.slice('primary'.length).replace(/^-/, '') || 'default';
|
|
87
|
-
colors.primary[variant] = value;
|
|
88
|
-
cssColorCount++;
|
|
89
|
-
} else if (suffix.startsWith('accent')) {
|
|
90
|
-
const variant = suffix.slice('accent'.length).replace(/^-/, '') || 'default';
|
|
91
|
-
colors.accent[variant] = value;
|
|
92
|
-
cssColorCount++;
|
|
93
|
-
} else if (suffix.startsWith('neutral-')) {
|
|
94
|
-
const variant = suffix.slice('neutral-'.length);
|
|
95
|
-
colors.neutral[variant] = value;
|
|
96
|
-
cssColorCount++;
|
|
97
|
-
} else if (suffix.startsWith('surface-')) {
|
|
98
|
-
const variant = suffix.slice('surface-'.length);
|
|
99
|
-
colors.surface[variant] = value;
|
|
100
|
-
cssColorCount++;
|
|
101
|
-
} else if (suffix.startsWith('text-')) {
|
|
102
|
-
const variant = suffix.slice('text-'.length);
|
|
103
|
-
colors.text[variant] = value;
|
|
104
|
-
cssColorCount++;
|
|
105
|
-
} else if (suffix === 'success' || suffix === 'warning' || suffix === 'error' || suffix === 'info') {
|
|
106
|
-
colors.semantic[suffix] = value;
|
|
107
|
-
cssColorCount++;
|
|
108
|
-
} else if (suffix === 'border') {
|
|
109
|
-
colors.other['border'] = value;
|
|
110
|
-
cssColorCount++;
|
|
111
|
-
} else {
|
|
112
|
-
// Any other --color-xxx token
|
|
113
|
-
colors.other[suffix] = value;
|
|
114
|
-
cssColorCount++;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// If CSS format found colors, return them
|
|
119
|
-
if (cssColorCount > 0) {
|
|
120
|
-
return colors;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// FALL BACK to old markdown regex format
|
|
124
|
-
const colorRegex = /(?:^|\n)[-*]\s+\*\*([^*]+)\*\*[^\(]*\(`(#[0-9A-Fa-f]{6})`\)/gm;
|
|
125
|
-
|
|
126
|
-
let match;
|
|
127
|
-
while ((match = colorRegex.exec(markdown)) !== null) {
|
|
128
|
-
const [, name, hex] = match;
|
|
129
|
-
|
|
130
|
-
if (name.includes('Primary')) {
|
|
131
|
-
const variant = name.match(/\d+/)?.[0] || 'default';
|
|
132
|
-
colors.primary[variant] = hex;
|
|
133
|
-
} else if (name.includes('Secondary')) {
|
|
134
|
-
const variant = name.match(/\d+/)?.[0] || 'default';
|
|
135
|
-
colors.secondary[variant] = hex;
|
|
136
|
-
} else if (name.includes('Neutral') || name.includes('Gray')) {
|
|
137
|
-
const variant = name.match(/\d+/)?.[0] || 'default';
|
|
138
|
-
colors.neutral[variant] = hex;
|
|
139
|
-
} else if (name.includes('Success') || name.includes('Error') || name.includes('Warning') || name.includes('Info')) {
|
|
140
|
-
const type = name.toLowerCase().replace(/\s+/g, '-');
|
|
141
|
-
colors.semantic[type] = hex;
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return colors;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Parse typography from markdown — supports CSS :root format and old markdown format
|
|
150
|
-
* @param {string} markdown - Markdown content
|
|
151
|
-
* @returns {Object} Typography with fontFamily, fontSize, fontWeight, lineHeight
|
|
152
|
-
*/
|
|
153
|
-
export function parseTypography(markdown) {
|
|
154
|
-
const typography = {
|
|
155
|
-
fontFamily: {},
|
|
156
|
-
fontSize: {},
|
|
157
|
-
fontWeight: {},
|
|
158
|
-
lineHeight: {}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// Try CSS :root format first
|
|
162
|
-
const cssContent = extractCSSRootBlocks(markdown);
|
|
163
|
-
const props = extractCSSProperties(cssContent);
|
|
164
|
-
|
|
165
|
-
let cssTypoCount = 0;
|
|
166
|
-
|
|
167
|
-
for (const { name, value } of props) {
|
|
168
|
-
if (name === '--font-display') {
|
|
169
|
-
typography.fontFamily.display = value;
|
|
170
|
-
cssTypoCount++;
|
|
171
|
-
} else if (name === '--font-body') {
|
|
172
|
-
typography.fontFamily.body = value;
|
|
173
|
-
cssTypoCount++;
|
|
174
|
-
} else if (name === '--font-mono') {
|
|
175
|
-
typography.fontFamily.mono = value;
|
|
176
|
-
cssTypoCount++;
|
|
177
|
-
} else if (name.startsWith('--text-')) {
|
|
178
|
-
// Font sizes: --text-xs, --text-sm, --text-base, etc.
|
|
179
|
-
const sizeName = name.slice('--text-'.length);
|
|
180
|
-
typography.fontSize[sizeName] = value;
|
|
181
|
-
cssTypoCount++;
|
|
182
|
-
} else if (name.startsWith('--font-weight-')) {
|
|
183
|
-
const weightName = name.slice('--font-weight-'.length);
|
|
184
|
-
typography.fontWeight[weightName] = value;
|
|
185
|
-
cssTypoCount++;
|
|
186
|
-
} else if (name.startsWith('--leading-')) {
|
|
187
|
-
const leadingName = name.slice('--leading-'.length);
|
|
188
|
-
typography.lineHeight[leadingName] = value;
|
|
189
|
-
cssTypoCount++;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// If CSS format found typography, return it
|
|
194
|
-
if (cssTypoCount > 0) {
|
|
195
|
-
return typography;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
// FALL BACK to old markdown format
|
|
199
|
-
// Match font family
|
|
200
|
-
const fontFamilyRegex = /(?:font[- ]?family|typeface)[:\s]+([^\n]+)/gi;
|
|
201
|
-
const fontMatch = fontFamilyRegex.exec(markdown);
|
|
202
|
-
if (fontMatch) {
|
|
203
|
-
typography.fontFamily.primary = fontMatch[1].trim();
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Match font sizes (but exclude spacing-related)
|
|
207
|
-
const fontSizeRegex = /(?:^|\n)[-*]\s+\*\*([^*]+)\*\*[^:]*:\s*(\d+(?:\.\d+)?(?:rem|px|em))/gm;
|
|
208
|
-
let match;
|
|
209
|
-
while ((match = fontSizeRegex.exec(markdown)) !== null) {
|
|
210
|
-
const [, name, size] = match;
|
|
211
|
-
const lowerName = name.toLowerCase();
|
|
212
|
-
// Skip if it's spacing-related
|
|
213
|
-
if (lowerName.includes('spacing') || lowerName.includes('gap') || lowerName.includes('margin') || lowerName.includes('padding')) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
const cleanName = name.trim().toLowerCase().replace(/\s+/g, '-');
|
|
217
|
-
typography.fontSize[cleanName] = size;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return typography;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Parse spacing from markdown — supports CSS :root format and old markdown format
|
|
225
|
-
* @param {string} markdown - Markdown content
|
|
226
|
-
* @returns {Object} Spacing values
|
|
227
|
-
*/
|
|
228
|
-
export function parseSpacing(markdown) {
|
|
229
|
-
const spacing = {};
|
|
230
|
-
|
|
231
|
-
// Try CSS :root format first
|
|
232
|
-
const cssContent = extractCSSRootBlocks(markdown);
|
|
233
|
-
const props = extractCSSProperties(cssContent);
|
|
234
|
-
|
|
235
|
-
let cssSpaceCount = 0;
|
|
236
|
-
|
|
237
|
-
for (const { name, value } of props) {
|
|
238
|
-
if (name.startsWith('--space-')) {
|
|
239
|
-
const spaceName = name.slice('--space-'.length);
|
|
240
|
-
spacing[spaceName] = value;
|
|
241
|
-
cssSpaceCount++;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// If CSS format found spacing, return it
|
|
246
|
-
if (cssSpaceCount > 0) {
|
|
247
|
-
return spacing;
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// FALL BACK to old markdown format
|
|
251
|
-
const spacingRegex = /(?:^|\n)[-*]\s+\*\*([^*]+)\*\*[^:]*:\s*(\d+(?:\.\d+)?(?:rem|px|em))/gm;
|
|
252
|
-
let match;
|
|
253
|
-
while ((match = spacingRegex.exec(markdown)) !== null) {
|
|
254
|
-
const [, name, value] = match;
|
|
255
|
-
if (name.toLowerCase().includes('spacing') || name.toLowerCase().includes('gap') || name.toLowerCase().includes('margin') || name.toLowerCase().includes('padding')) {
|
|
256
|
-
const cleanName = name.trim().toLowerCase().replace(/\s+/g, '-');
|
|
257
|
-
spacing[cleanName] = value;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
return spacing;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Parse motion tokens from markdown (CSS :root format only)
|
|
266
|
-
* @param {string} markdown - Markdown content
|
|
267
|
-
* @returns {Object} { durations, easings, scroll }
|
|
268
|
-
*/
|
|
269
|
-
export function parseMotion(markdown) {
|
|
270
|
-
const motion = {
|
|
271
|
-
durations: {},
|
|
272
|
-
easings: {},
|
|
273
|
-
scroll: {}
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
const cssContent = extractCSSRootBlocks(markdown);
|
|
277
|
-
const props = extractCSSProperties(cssContent);
|
|
278
|
-
|
|
279
|
-
for (const { name, value } of props) {
|
|
280
|
-
if (name.startsWith('--duration-')) {
|
|
281
|
-
const key = name.slice('--duration-'.length);
|
|
282
|
-
motion.durations[key] = value;
|
|
283
|
-
} else if (name.startsWith('--easing-')) {
|
|
284
|
-
const key = name.slice('--easing-'.length);
|
|
285
|
-
motion.easings[key] = value;
|
|
286
|
-
} else if (name.startsWith('--scroll-')) {
|
|
287
|
-
const key = name.slice('--scroll-'.length);
|
|
288
|
-
motion.scroll[key] = value;
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return motion;
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Parse advanced effects tokens from markdown (CSS :root format only)
|
|
297
|
-
* @param {string} markdown - Markdown content
|
|
298
|
-
* @returns {Object} { glass, aurora, perspective, container, viewTransition, noise }
|
|
299
|
-
*/
|
|
300
|
-
export function parseAdvancedEffects(markdown) {
|
|
301
|
-
const effects = {
|
|
302
|
-
glass: {},
|
|
303
|
-
aurora: {},
|
|
304
|
-
perspective: {},
|
|
305
|
-
container: {},
|
|
306
|
-
viewTransition: {},
|
|
307
|
-
noise: {}
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const cssContent = extractCSSRootBlocks(markdown);
|
|
311
|
-
const props = extractCSSProperties(cssContent);
|
|
312
|
-
|
|
313
|
-
for (const { name, value } of props) {
|
|
314
|
-
if (name.startsWith('--glass-')) {
|
|
315
|
-
const key = name.slice('--glass-'.length);
|
|
316
|
-
effects.glass[key] = value;
|
|
317
|
-
} else if (name.startsWith('--aurora-')) {
|
|
318
|
-
const key = name.slice('--aurora-'.length);
|
|
319
|
-
effects.aurora[key] = value;
|
|
320
|
-
} else if (name.startsWith('--noise-')) {
|
|
321
|
-
const key = name.slice('--noise-'.length);
|
|
322
|
-
effects.noise[key] = value;
|
|
323
|
-
} else if (name.startsWith('--perspective-')) {
|
|
324
|
-
const key = name.slice('--perspective-'.length);
|
|
325
|
-
effects.perspective[key] = value;
|
|
326
|
-
} else if (name.startsWith('--container-')) {
|
|
327
|
-
const key = name.slice('--container-'.length);
|
|
328
|
-
effects.container[key] = value;
|
|
329
|
-
} else if (name.startsWith('--view-transition-')) {
|
|
330
|
-
const key = name.slice('--view-transition-'.length);
|
|
331
|
-
effects.viewTransition[key] = value;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
return effects;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/**
|
|
339
|
-
* Parse all design system properties from markdown
|
|
340
|
-
* @param {string} markdownPath - Path to design-system.md file
|
|
341
|
-
* @returns {Object} Parsed design system with colors, typography, spacing, motion, effects
|
|
342
|
-
*/
|
|
343
|
-
export function parseDesignSystem(markdownPath) {
|
|
344
|
-
if (!existsSync(markdownPath)) {
|
|
345
|
-
throw new Error(`File not found: ${markdownPath}`);
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
const markdown = readFileSync(markdownPath, 'utf-8');
|
|
349
|
-
|
|
350
|
-
return {
|
|
351
|
-
colors: parseColors(markdown),
|
|
352
|
-
typography: parseTypography(markdown),
|
|
353
|
-
spacing: parseSpacing(markdown),
|
|
354
|
-
motion: parseMotion(markdown),
|
|
355
|
-
effects: parseAdvancedEffects(markdown)
|
|
356
|
-
};
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// ============================================================================
|
|
360
|
-
// Generation Functions
|
|
361
|
-
// ============================================================================
|
|
362
|
-
|
|
363
|
-
/**
|
|
364
|
-
* Helper to emit CSS variable entries for a color category
|
|
365
|
-
*/
|
|
366
|
-
function emitColorCategory(colors, prefix, label) {
|
|
367
|
-
if (Object.keys(colors).length === 0) return '';
|
|
368
|
-
let css = ` /* ${label} */\n`;
|
|
369
|
-
Object.entries(colors).forEach(([variant, value]) => {
|
|
370
|
-
if (variant === 'rgb') {
|
|
371
|
-
css += ` --color-${prefix}-rgb: ${value};\n`;
|
|
372
|
-
} else if (variant === 'default') {
|
|
373
|
-
css += ` --color-${prefix}: ${value};\n`;
|
|
374
|
-
} else {
|
|
375
|
-
css += ` --color-${prefix}-${variant}: ${value};\n`;
|
|
376
|
-
}
|
|
377
|
-
});
|
|
378
|
-
css += `\n`;
|
|
379
|
-
return css;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* Generate CSS variables file
|
|
384
|
-
* @param {Object} colors - Colors object
|
|
385
|
-
* @param {Object} typography - Typography object
|
|
386
|
-
* @param {Object} spacing - Spacing object
|
|
387
|
-
* @returns {string} CSS content
|
|
388
|
-
*/
|
|
389
|
-
export function generateCSS(colors, typography, spacing) {
|
|
390
|
-
let css = `/* Auto-generated by MORPH-SPEC Design System Generator */\n`;
|
|
391
|
-
css += `/* Do not edit manually - regenerate from design-system.md */\n\n`;
|
|
392
|
-
css += `:root {\n`;
|
|
393
|
-
|
|
394
|
-
// Colors — handle expanded structure
|
|
395
|
-
css += emitColorCategory(colors.primary, 'primary', 'Primary Colors');
|
|
396
|
-
|
|
397
|
-
// Support both accent (new) and secondary (old)
|
|
398
|
-
if (Object.keys(colors.accent || {}).length > 0) {
|
|
399
|
-
css += emitColorCategory(colors.accent, 'accent', 'Accent Colors');
|
|
400
|
-
}
|
|
401
|
-
if (Object.keys(colors.secondary || {}).length > 0) {
|
|
402
|
-
css += emitColorCategory(colors.secondary, 'secondary', 'Secondary Colors');
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
css += emitColorCategory(colors.neutral || {}, 'neutral', 'Neutral Colors');
|
|
406
|
-
|
|
407
|
-
if (Object.keys(colors.surface || {}).length > 0) {
|
|
408
|
-
css += emitColorCategory(colors.surface, 'surface', 'Surface Colors');
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
if (Object.keys(colors.text || {}).length > 0) {
|
|
412
|
-
css += emitColorCategory(colors.text, 'text', 'Text Colors');
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
if (Object.keys(colors.semantic || {}).length > 0) {
|
|
416
|
-
css += ` /* Semantic Colors */\n`;
|
|
417
|
-
Object.entries(colors.semantic).forEach(([type, hex]) => {
|
|
418
|
-
css += ` --color-${type}: ${hex};\n`;
|
|
419
|
-
});
|
|
420
|
-
css += `\n`;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
if (Object.keys(colors.other || {}).length > 0) {
|
|
424
|
-
css += ` /* Other Colors */\n`;
|
|
425
|
-
Object.entries(colors.other).forEach(([key, value]) => {
|
|
426
|
-
css += ` --color-${key}: ${value};\n`;
|
|
427
|
-
});
|
|
428
|
-
css += `\n`;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
// Typography
|
|
432
|
-
const fontFamilies = typography.fontFamily || {};
|
|
433
|
-
if (fontFamilies.display || fontFamilies.body || fontFamilies.mono || fontFamilies.primary) {
|
|
434
|
-
css += ` /* Typography */\n`;
|
|
435
|
-
if (fontFamilies.display) css += ` --font-display: ${fontFamilies.display};\n`;
|
|
436
|
-
if (fontFamilies.body) css += ` --font-body: ${fontFamilies.body};\n`;
|
|
437
|
-
if (fontFamilies.mono) css += ` --font-mono: ${fontFamilies.mono};\n`;
|
|
438
|
-
if (fontFamilies.primary) css += ` --font-family-primary: ${fontFamilies.primary};\n`;
|
|
439
|
-
css += `\n`;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (Object.keys(typography.fontSize).length > 0) {
|
|
443
|
-
css += ` /* Font Sizes */\n`;
|
|
444
|
-
Object.entries(typography.fontSize).forEach(([name, size]) => {
|
|
445
|
-
css += ` --font-size-${name}: ${size};\n`;
|
|
446
|
-
});
|
|
447
|
-
css += `\n`;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// Spacing
|
|
451
|
-
if (Object.keys(spacing).length > 0) {
|
|
452
|
-
css += ` /* Spacing */\n`;
|
|
453
|
-
Object.entries(spacing).forEach(([name, value]) => {
|
|
454
|
-
// If key is numeric (from --space-N), use --space-N format
|
|
455
|
-
if (/^\d+$/.test(name)) {
|
|
456
|
-
css += ` --space-${name}: ${value};\n`;
|
|
457
|
-
} else {
|
|
458
|
-
css += ` --${name}: ${value};\n`;
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
css += `}\n`;
|
|
464
|
-
|
|
465
|
-
return css;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
/**
|
|
469
|
-
* Count non-empty keys in an object
|
|
470
|
-
*/
|
|
471
|
-
function countTokens(obj) {
|
|
472
|
-
return Object.keys(obj || {}).length;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Generate all design system files
|
|
477
|
-
* @param {string} designSystemPath - Path to design-system.md
|
|
478
|
-
* @param {Object} options - Generation options
|
|
479
|
-
* @returns {Object} Generated files content
|
|
480
|
-
*/
|
|
481
|
-
export function generateDesignSystem(designSystemPath, options = {}) {
|
|
482
|
-
// Parse design system
|
|
483
|
-
const { colors, typography, spacing, motion, effects } = parseDesignSystem(designSystemPath);
|
|
484
|
-
|
|
485
|
-
const generated = {
|
|
486
|
-
css: generateCSS(colors, typography, spacing),
|
|
487
|
-
stats: {
|
|
488
|
-
primaryColors: countTokens(colors.primary),
|
|
489
|
-
accentColors: countTokens(colors.accent),
|
|
490
|
-
secondaryColors: countTokens(colors.secondary),
|
|
491
|
-
neutralColors: countTokens(colors.neutral),
|
|
492
|
-
semanticColors: countTokens(colors.semantic),
|
|
493
|
-
surfaceColors: countTokens(colors.surface),
|
|
494
|
-
textColors: countTokens(colors.text),
|
|
495
|
-
fontSizes: countTokens(typography.fontSize),
|
|
496
|
-
fontWeights: countTokens(typography.fontWeight),
|
|
497
|
-
lineHeights: countTokens(typography.lineHeight),
|
|
498
|
-
spacingValues: countTokens(spacing),
|
|
499
|
-
motionDurations: countTokens(motion.durations),
|
|
500
|
-
motionEasings: countTokens(motion.easings),
|
|
501
|
-
effectsGlass: countTokens(effects.glass),
|
|
502
|
-
effectsAurora: countTokens(effects.aurora),
|
|
503
|
-
effectsPerspective: countTokens(effects.perspective)
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
|
|
507
|
-
return generated;
|
|
508
|
-
}
|