godpowers 0.15.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/AGENTS.md +37 -0
- package/CHANGELOG.md +639 -0
- package/INSPIRATION.md +52 -0
- package/LICENSE +21 -0
- package/README.md +232 -0
- package/SKILL.md +500 -0
- package/agents/god-archaeologist.md +139 -0
- package/agents/god-architect.md +92 -0
- package/agents/god-auditor.md +150 -0
- package/agents/god-browser-tester.md +144 -0
- package/agents/god-context-writer.md +137 -0
- package/agents/god-coordinator.md +138 -0
- package/agents/god-debt-assessor.md +132 -0
- package/agents/god-debugger.md +77 -0
- package/agents/god-deploy-engineer.md +87 -0
- package/agents/god-deps-auditor.md +111 -0
- package/agents/god-design-reviewer.md +137 -0
- package/agents/god-designer.md +171 -0
- package/agents/god-docs-writer.md +102 -0
- package/agents/god-executor.md +76 -0
- package/agents/god-explorer.md +110 -0
- package/agents/god-harden-auditor.md +163 -0
- package/agents/god-incident-investigator.md +144 -0
- package/agents/god-launch-strategist.md +103 -0
- package/agents/god-migration-strategist.md +126 -0
- package/agents/god-observability-engineer.md +76 -0
- package/agents/god-orchestrator.md +728 -0
- package/agents/god-org-context-loader.md +124 -0
- package/agents/god-planner.md +73 -0
- package/agents/god-pm.md +105 -0
- package/agents/god-quality-reviewer.md +74 -0
- package/agents/god-reconciler.md +230 -0
- package/agents/god-reconstructor.md +124 -0
- package/agents/god-repo-scaffolder.md +60 -0
- package/agents/god-retrospective.md +109 -0
- package/agents/god-roadmap-reconciler.md +123 -0
- package/agents/god-roadmap-updater.md +89 -0
- package/agents/god-roadmapper.md +82 -0
- package/agents/god-spec-reviewer.md +70 -0
- package/agents/god-spike-runner.md +119 -0
- package/agents/god-stack-selector.md +93 -0
- package/agents/god-standards-check.md +132 -0
- package/agents/god-storyteller.md +116 -0
- package/agents/god-updater.md +174 -0
- package/bin/install.js +514 -0
- package/extensions/data-pack/README.md +33 -0
- package/extensions/data-pack/agents/god-dashboard-builder.md +66 -0
- package/extensions/data-pack/agents/god-etl-engineer.md +64 -0
- package/extensions/data-pack/agents/god-ml-feature-engineer.md +66 -0
- package/extensions/data-pack/manifest.yaml +39 -0
- package/extensions/data-pack/package.json +42 -0
- package/extensions/data-pack/skills/god-dashboard.md +28 -0
- package/extensions/data-pack/skills/god-etl.md +28 -0
- package/extensions/data-pack/skills/god-ml-feature.md +28 -0
- package/extensions/data-pack/workflows/dashboard-arc.yaml +13 -0
- package/extensions/data-pack/workflows/etl-arc.yaml +13 -0
- package/extensions/data-pack/workflows/ml-feature-arc.yaml +13 -0
- package/extensions/launch-pack/README.md +36 -0
- package/extensions/launch-pack/agents/god-indie-hackers-strategist.md +128 -0
- package/extensions/launch-pack/agents/god-oss-release-strategist.md +125 -0
- package/extensions/launch-pack/agents/god-product-hunt-strategist.md +118 -0
- package/extensions/launch-pack/agents/god-show-hn-strategist.md +113 -0
- package/extensions/launch-pack/manifest.yaml +45 -0
- package/extensions/launch-pack/package.json +41 -0
- package/extensions/launch-pack/skills/god-indie-hackers.md +39 -0
- package/extensions/launch-pack/skills/god-oss-release.md +43 -0
- package/extensions/launch-pack/skills/god-product-hunt.md +41 -0
- package/extensions/launch-pack/skills/god-show-hn.md +40 -0
- package/extensions/launch-pack/workflows/indie-hackers.yaml +13 -0
- package/extensions/launch-pack/workflows/oss-release.yaml +13 -0
- package/extensions/launch-pack/workflows/product-hunt.yaml +13 -0
- package/extensions/launch-pack/workflows/show-hn.yaml +13 -0
- package/extensions/security-pack/README.md +48 -0
- package/extensions/security-pack/agents/god-hipaa-auditor.md +117 -0
- package/extensions/security-pack/agents/god-pci-auditor.md +100 -0
- package/extensions/security-pack/agents/god-soc2-auditor.md +107 -0
- package/extensions/security-pack/manifest.yaml +39 -0
- package/extensions/security-pack/package.json +42 -0
- package/extensions/security-pack/skills/god-hipaa-audit.md +41 -0
- package/extensions/security-pack/skills/god-pci-audit.md +40 -0
- package/extensions/security-pack/skills/god-soc2-audit.md +42 -0
- package/extensions/security-pack/workflows/hipaa-arc.yaml +15 -0
- package/extensions/security-pack/workflows/pci-arc.yaml +15 -0
- package/extensions/security-pack/workflows/soc2-arc.yaml +15 -0
- package/hooks/pre-tool-use.sh +40 -0
- package/hooks/session-start.sh +74 -0
- package/lib/README.md +28 -0
- package/lib/agent-browser-driver.js +215 -0
- package/lib/agent-cache.js +194 -0
- package/lib/agent-validator.js +275 -0
- package/lib/artifact-diff.js +168 -0
- package/lib/artifact-linter.js +142 -0
- package/lib/awesome-design.js +312 -0
- package/lib/browser-bridge.js +209 -0
- package/lib/budget.js +215 -0
- package/lib/checkpoint.js +390 -0
- package/lib/code-scanner.js +262 -0
- package/lib/context-budget.js +170 -0
- package/lib/context-writer.js +348 -0
- package/lib/cost-tracker.js +325 -0
- package/lib/cross-artifact-impact.js +162 -0
- package/lib/cross-repo-linkage.js +150 -0
- package/lib/design-detector.js +167 -0
- package/lib/design-spec.js +348 -0
- package/lib/drift-detector.js +212 -0
- package/lib/event-reader.js +174 -0
- package/lib/events.js +183 -0
- package/lib/extensions.js +257 -0
- package/lib/have-nots-validator.js +647 -0
- package/lib/impact.js +314 -0
- package/lib/impeccable-bridge.js +139 -0
- package/lib/intent.js +177 -0
- package/lib/linkage.js +232 -0
- package/lib/meta-linter.js +263 -0
- package/lib/multi-repo-detector.js +182 -0
- package/lib/otel-exporter.js +308 -0
- package/lib/recipes.js +186 -0
- package/lib/reverse-sync.js +332 -0
- package/lib/review-required.js +224 -0
- package/lib/router.js +278 -0
- package/lib/runtime-audit.js +455 -0
- package/lib/runtime-test.js +309 -0
- package/lib/skillui-bridge.js +216 -0
- package/lib/state-lock.js +201 -0
- package/lib/state.js +142 -0
- package/lib/story-validator.js +301 -0
- package/lib/suite-state.js +220 -0
- package/lib/workflow-parser.js +109 -0
- package/lib/workflow-runner.js +221 -0
- package/package.json +63 -0
- package/references/HAVE-NOTS.md +573 -0
- package/references/building/BUILD-ANTIPATTERNS.md +102 -0
- package/references/building/BUILD-VERTICAL-SLICES.md +75 -0
- package/references/building/BUILD-WAVES.md +61 -0
- package/references/building/README.md +17 -0
- package/references/design/COLOR.md +122 -0
- package/references/design/DESIGN-ANATOMY.md +121 -0
- package/references/design/DESIGN-ANTIPATTERNS.md +108 -0
- package/references/design/INTERACTION.md +148 -0
- package/references/design/MOTION.md +120 -0
- package/references/design/RESPONSIVE.md +157 -0
- package/references/design/SPATIAL.md +109 -0
- package/references/design/TYPOGRAPHY.md +121 -0
- package/references/design/UX-WRITING.md +135 -0
- package/references/orchestration/MODE-DETECTION.md +74 -0
- package/references/orchestration/README.md +18 -0
- package/references/orchestration/SCALE-DETECTION.md +81 -0
- package/references/planning/ARCH-ANATOMY.md +143 -0
- package/references/planning/ARCH-ANTIPATTERNS.md +52 -0
- package/references/planning/PRD-ANATOMY.md +117 -0
- package/references/planning/PRD-ANTIPATTERNS.md +138 -0
- package/references/planning/README.md +16 -0
- package/references/planning/ROADMAP-ANATOMY.md +43 -0
- package/references/planning/ROADMAP-ANTIPATTERNS.md +94 -0
- package/references/planning/STACK-ANATOMY.md +60 -0
- package/references/planning/STACK-ANTIPATTERNS.md +95 -0
- package/references/shared/GLOSSARY.md +80 -0
- package/references/shared/ORCHESTRATORS.md +76 -0
- package/references/shared/README.md +14 -0
- package/references/shipping/DEPLOY-ANTIPATTERNS.md +64 -0
- package/references/shipping/DEPLOY-PATTERNS.md +110 -0
- package/references/shipping/HARDEN-ANTIPATTERNS.md +66 -0
- package/references/shipping/HARDEN-OWASP-WORKSHEETS.md +89 -0
- package/references/shipping/LAUNCH-ANTIPATTERNS.md +68 -0
- package/references/shipping/OBSERVE-ANTIPATTERNS.md +62 -0
- package/references/shipping/OBSERVE-SLO-EXAMPLES.md +107 -0
- package/references/shipping/README.md +18 -0
- package/routing/god-add-backlog.yaml +24 -0
- package/routing/god-add-tests.yaml +27 -0
- package/routing/god-add-todo.yaml +24 -0
- package/routing/god-agent-audit.yaml +24 -0
- package/routing/god-arch.yaml +46 -0
- package/routing/god-archaeology.yaml +28 -0
- package/routing/god-audit.yaml +32 -0
- package/routing/god-budget.yaml +24 -0
- package/routing/god-build-agent.yaml +24 -0
- package/routing/god-build.yaml +46 -0
- package/routing/god-cache-clear.yaml +24 -0
- package/routing/god-check-todos.yaml +24 -0
- package/routing/god-context-scan.yaml +24 -0
- package/routing/god-context.yaml +44 -0
- package/routing/god-cost.yaml +24 -0
- package/routing/god-debug.yaml +28 -0
- package/routing/god-deploy.yaml +34 -0
- package/routing/god-design-impact.yaml +25 -0
- package/routing/god-design.yaml +67 -0
- package/routing/god-discuss.yaml +27 -0
- package/routing/god-docs.yaml +33 -0
- package/routing/god-doctor.yaml +27 -0
- package/routing/god-explore.yaml +27 -0
- package/routing/god-extension-add.yaml +24 -0
- package/routing/god-extension-info.yaml +24 -0
- package/routing/god-extension-list.yaml +24 -0
- package/routing/god-extension-remove.yaml +24 -0
- package/routing/god-extract-learnings.yaml +24 -0
- package/routing/god-fast.yaml +27 -0
- package/routing/god-feature.yaml +34 -0
- package/routing/god-graph.yaml +24 -0
- package/routing/god-harden.yaml +41 -0
- package/routing/god-help.yaml +27 -0
- package/routing/god-hotfix.yaml +34 -0
- package/routing/god-hygiene.yaml +28 -0
- package/routing/god-init.yaml +37 -0
- package/routing/god-intel.yaml +24 -0
- package/routing/god-launch.yaml +41 -0
- package/routing/god-lifecycle.yaml +27 -0
- package/routing/god-link.yaml +24 -0
- package/routing/god-lint.yaml +24 -0
- package/routing/god-list-assumptions.yaml +27 -0
- package/routing/god-locate.yaml +24 -0
- package/routing/god-logs.yaml +24 -0
- package/routing/god-map-codebase.yaml +24 -0
- package/routing/god-metrics.yaml +24 -0
- package/routing/god-mode.yaml +31 -0
- package/routing/god-next.yaml +27 -0
- package/routing/god-note.yaml +24 -0
- package/routing/god-observe.yaml +34 -0
- package/routing/god-org-context.yaml +28 -0
- package/routing/god-party.yaml +24 -0
- package/routing/god-pause-work.yaml +27 -0
- package/routing/god-plant-seed.yaml +24 -0
- package/routing/god-postmortem.yaml +34 -0
- package/routing/god-pr-branch.yaml +25 -0
- package/routing/god-prd.yaml +49 -0
- package/routing/god-quick.yaml +28 -0
- package/routing/god-reconcile.yaml +48 -0
- package/routing/god-reconstruct.yaml +36 -0
- package/routing/god-redo.yaml +27 -0
- package/routing/god-refactor.yaml +36 -0
- package/routing/god-repair.yaml +27 -0
- package/routing/god-repo.yaml +35 -0
- package/routing/god-restore.yaml +27 -0
- package/routing/god-resume-work.yaml +27 -0
- package/routing/god-review-changes.yaml +25 -0
- package/routing/god-review.yaml +28 -0
- package/routing/god-roadmap-check.yaml +39 -0
- package/routing/god-roadmap-update.yaml +37 -0
- package/routing/god-roadmap.yaml +42 -0
- package/routing/god-rollback.yaml +27 -0
- package/routing/god-scan.yaml +24 -0
- package/routing/god-set-profile.yaml +24 -0
- package/routing/god-settings.yaml +24 -0
- package/routing/god-skip.yaml +27 -0
- package/routing/god-smite.yaml +29 -0
- package/routing/god-spike.yaml +35 -0
- package/routing/god-sprint.yaml +25 -0
- package/routing/god-stack.yaml +41 -0
- package/routing/god-standards.yaml +24 -0
- package/routing/god-status.yaml +27 -0
- package/routing/god-stories.yaml +24 -0
- package/routing/god-story-build.yaml +25 -0
- package/routing/god-story-close.yaml +25 -0
- package/routing/god-story-verify.yaml +25 -0
- package/routing/god-story.yaml +24 -0
- package/routing/god-suite-init.yaml +24 -0
- package/routing/god-suite-patch.yaml +25 -0
- package/routing/god-suite-release.yaml +25 -0
- package/routing/god-suite-status.yaml +25 -0
- package/routing/god-suite-sync.yaml +25 -0
- package/routing/god-sync.yaml +33 -0
- package/routing/god-tech-debt.yaml +32 -0
- package/routing/god-test-extension.yaml +24 -0
- package/routing/god-test-runtime.yaml +25 -0
- package/routing/god-thread.yaml +24 -0
- package/routing/god-trace.yaml +24 -0
- package/routing/god-undo.yaml +27 -0
- package/routing/god-update-deps.yaml +39 -0
- package/routing/god-upgrade.yaml +33 -0
- package/routing/god-version.yaml +24 -0
- package/routing/god-workstream.yaml +24 -0
- package/routing/god.yaml +24 -0
- package/routing/recipes/add-feature-defer-current-milestone.yaml +21 -0
- package/routing/recipes/add-feature-future-conditional.yaml +21 -0
- package/routing/recipes/add-feature-mid-arc-pause.yaml +33 -0
- package/routing/recipes/add-feature-next-milestone.yaml +23 -0
- package/routing/recipes/add-feature-parallel.yaml +29 -0
- package/routing/recipes/add-feature-prd-update.yaml +21 -0
- package/routing/recipes/add-feature-small.yaml +24 -0
- package/routing/recipes/add-feature-tiny.yaml +24 -0
- package/routing/recipes/bluefield-org-aware.yaml +27 -0
- package/routing/recipes/broken-install.yaml +22 -0
- package/routing/recipes/brownfield-onboarding.yaml +32 -0
- package/routing/recipes/bug-no-urgency.yaml +21 -0
- package/routing/recipes/capture-idea.yaml +22 -0
- package/routing/recipes/capture-todo.yaml +21 -0
- package/routing/recipes/clean-pr.yaml +21 -0
- package/routing/recipes/code-cleanup.yaml +23 -0
- package/routing/recipes/docs-drift.yaml +21 -0
- package/routing/recipes/existing-codebase-onboarding.yaml +32 -0
- package/routing/recipes/extract-learnings.yaml +22 -0
- package/routing/recipes/greenfield-fast.yaml +25 -0
- package/routing/recipes/greenfield-manual.yaml +32 -0
- package/routing/recipes/greenfield-with-ideation.yaml +29 -0
- package/routing/recipes/incident-postmortem.yaml +24 -0
- package/routing/recipes/major-framework-upgrade.yaml +23 -0
- package/routing/recipes/monthly-deps.yaml +22 -0
- package/routing/recipes/multi-repo-suite.yaml +56 -0
- package/routing/recipes/parallel-engineers.yaml +26 -0
- package/routing/recipes/pause-handoff.yaml +21 -0
- package/routing/recipes/production-broken.yaml +26 -0
- package/routing/recipes/rerun-tier.yaml +21 -0
- package/routing/recipes/returning-after-break.yaml +31 -0
- package/routing/recipes/state-drift.yaml +21 -0
- package/routing/recipes/undo-last.yaml +21 -0
- package/routing/recipes/weekly-health-check.yaml +24 -0
- package/routing/recipes/whats-next.yaml +22 -0
- package/routing/recipes/where-am-i.yaml +21 -0
- package/schema/events.v1.json +63 -0
- package/schema/extension-manifest.v1.json +84 -0
- package/schema/intent.v1.yaml.json +116 -0
- package/schema/recipe.v1.json +120 -0
- package/schema/routing.v1.json +163 -0
- package/schema/state.v1.json +146 -0
- package/schema/workflow.v1.json +96 -0
- package/skills/god-add-backlog.md +40 -0
- package/skills/god-add-tests.md +53 -0
- package/skills/god-add-todo.md +32 -0
- package/skills/god-agent-audit.md +87 -0
- package/skills/god-arch.md +81 -0
- package/skills/god-archaeology.md +48 -0
- package/skills/god-audit.md +65 -0
- package/skills/god-budget.md +103 -0
- package/skills/god-build-agent.md +91 -0
- package/skills/god-build.md +90 -0
- package/skills/god-cache-clear.md +75 -0
- package/skills/god-check-todos.md +42 -0
- package/skills/god-context-scan.md +125 -0
- package/skills/god-context.md +147 -0
- package/skills/god-cost.md +118 -0
- package/skills/god-debug.md +30 -0
- package/skills/god-deploy.md +76 -0
- package/skills/god-design-impact.md +86 -0
- package/skills/god-design.md +275 -0
- package/skills/god-discuss.md +46 -0
- package/skills/god-docs.md +81 -0
- package/skills/god-doctor.md +94 -0
- package/skills/god-explore.md +50 -0
- package/skills/god-export-otel.md +87 -0
- package/skills/god-extension-add.md +79 -0
- package/skills/god-extension-info.md +75 -0
- package/skills/god-extension-list.md +55 -0
- package/skills/god-extension-remove.md +66 -0
- package/skills/god-extract-learnings.md +60 -0
- package/skills/god-fast.md +47 -0
- package/skills/god-feature.md +114 -0
- package/skills/god-graph.md +56 -0
- package/skills/god-harden.md +106 -0
- package/skills/god-help.md +66 -0
- package/skills/god-hotfix.md +139 -0
- package/skills/god-hygiene.md +104 -0
- package/skills/god-init.md +161 -0
- package/skills/god-intel.md +36 -0
- package/skills/god-launch.md +86 -0
- package/skills/god-lifecycle.md +119 -0
- package/skills/god-link.md +90 -0
- package/skills/god-lint.md +128 -0
- package/skills/god-list-assumptions.md +56 -0
- package/skills/god-locate.md +97 -0
- package/skills/god-logs.md +57 -0
- package/skills/god-map-codebase.md +45 -0
- package/skills/god-metrics.md +51 -0
- package/skills/god-mode.md +159 -0
- package/skills/god-next.md +257 -0
- package/skills/god-note.md +39 -0
- package/skills/god-observe.md +76 -0
- package/skills/god-org-context.md +81 -0
- package/skills/god-party.md +87 -0
- package/skills/god-pause-work.md +64 -0
- package/skills/god-plant-seed.md +59 -0
- package/skills/god-postmortem.md +103 -0
- package/skills/god-pr-branch.md +50 -0
- package/skills/god-prd.md +90 -0
- package/skills/god-quick.md +50 -0
- package/skills/god-reconcile.md +90 -0
- package/skills/god-reconstruct.md +72 -0
- package/skills/god-redo.md +73 -0
- package/skills/god-refactor.md +137 -0
- package/skills/god-repair.md +82 -0
- package/skills/god-repo.md +49 -0
- package/skills/god-restore.md +91 -0
- package/skills/god-resume-work.md +42 -0
- package/skills/god-review-changes.md +93 -0
- package/skills/god-review.md +52 -0
- package/skills/god-roadmap-check.md +66 -0
- package/skills/god-roadmap-update.md +64 -0
- package/skills/god-roadmap.md +77 -0
- package/skills/god-rollback.md +88 -0
- package/skills/god-scan.md +106 -0
- package/skills/god-set-profile.md +58 -0
- package/skills/god-settings.md +44 -0
- package/skills/god-skip.md +78 -0
- package/skills/god-smite.md +86 -0
- package/skills/god-spike.md +120 -0
- package/skills/god-sprint.md +77 -0
- package/skills/god-stack.md +74 -0
- package/skills/god-standards.md +62 -0
- package/skills/god-status.md +99 -0
- package/skills/god-stories.md +60 -0
- package/skills/god-story-build.md +76 -0
- package/skills/god-story-close.md +82 -0
- package/skills/god-story-verify.md +71 -0
- package/skills/god-story.md +55 -0
- package/skills/god-suite-init.md +75 -0
- package/skills/god-suite-patch.md +64 -0
- package/skills/god-suite-release.md +58 -0
- package/skills/god-suite-status.md +63 -0
- package/skills/god-suite-sync.md +49 -0
- package/skills/god-sync.md +102 -0
- package/skills/god-tech-debt.md +56 -0
- package/skills/god-test-extension.md +87 -0
- package/skills/god-test-runtime.md +144 -0
- package/skills/god-thread.md +39 -0
- package/skills/god-trace.md +50 -0
- package/skills/god-undo.md +68 -0
- package/skills/god-update-deps.md +134 -0
- package/skills/god-upgrade.md +139 -0
- package/skills/god-version.md +37 -0
- package/skills/god-workstream.md +61 -0
- package/skills/god.md +207 -0
- package/templates/ARCH.md +99 -0
- package/templates/DEPS-AUDIT.md +66 -0
- package/templates/DESIGN.md +71 -0
- package/templates/DOCS-UPDATE-LOG.md +64 -0
- package/templates/HARDEN-FINDINGS.md +69 -0
- package/templates/MIGRATION.md +86 -0
- package/templates/POSTMORTEM.md +88 -0
- package/templates/PRD.md +80 -0
- package/templates/PROGRESS.md +49 -0
- package/templates/ROADMAP.md +47 -0
- package/templates/SPIKE.md +72 -0
- package/templates/STACK-DECISION.md +61 -0
- package/workflows/audit-only.yaml +22 -0
- package/workflows/bluefield-arc.yaml +87 -0
- package/workflows/brownfield-arc.yaml +44 -0
- package/workflows/deps-audit.yaml +56 -0
- package/workflows/docs-arc.yaml +22 -0
- package/workflows/feature-arc.yaml +59 -0
- package/workflows/full-arc.yaml +84 -0
- package/workflows/hotfix-arc.yaml +59 -0
- package/workflows/hygiene.yaml +43 -0
- package/workflows/migration-arc.yaml +73 -0
- package/workflows/postmortem.yaml +31 -0
- package/workflows/refactor-arc.yaml +59 -0
- package/workflows/spike.yaml +23 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design Detector
|
|
3
|
+
*
|
|
4
|
+
* Determines whether a project requires a DESIGN.md (i.e., has a UI surface).
|
|
5
|
+
* Reads .godpowers/stack/DECISION.md and inspects package.json / pyproject.toml
|
|
6
|
+
* / etc. for frontend frameworks.
|
|
7
|
+
*
|
|
8
|
+
* Public API:
|
|
9
|
+
* isUiProject(projectRoot) -> { required, frameworks, signals }
|
|
10
|
+
* isImpeccableInstalled(projectRoot) -> { installed, locations }
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
|
|
16
|
+
const FRONTEND_FRAMEWORKS = [
|
|
17
|
+
// Web
|
|
18
|
+
{ name: 'react', signal: 'react' },
|
|
19
|
+
{ name: 'next.js', signal: 'next' },
|
|
20
|
+
{ name: 'nuxt', signal: 'nuxt' },
|
|
21
|
+
{ name: 'vue', signal: 'vue' },
|
|
22
|
+
{ name: 'svelte', signal: 'svelte' },
|
|
23
|
+
{ name: 'sveltekit', signal: '@sveltejs/kit' },
|
|
24
|
+
{ name: 'angular', signal: '@angular/core' },
|
|
25
|
+
{ name: 'remix', signal: '@remix-run' },
|
|
26
|
+
{ name: 'solid', signal: 'solid-js' },
|
|
27
|
+
{ name: 'solidstart', signal: '@solidjs/start' },
|
|
28
|
+
{ name: 'astro', signal: 'astro' },
|
|
29
|
+
{ name: 'qwik', signal: '@builder.io/qwik' },
|
|
30
|
+
// Mobile
|
|
31
|
+
{ name: 'react-native', signal: 'react-native' },
|
|
32
|
+
{ name: 'flutter', signal: 'flutter' },
|
|
33
|
+
{ name: 'expo', signal: 'expo' },
|
|
34
|
+
// Desktop
|
|
35
|
+
{ name: 'electron', signal: 'electron' },
|
|
36
|
+
{ name: 'tauri', signal: '@tauri-apps' },
|
|
37
|
+
// CSS / Design libs (signal of UI work)
|
|
38
|
+
{ name: 'tailwindcss', signal: 'tailwindcss' },
|
|
39
|
+
{ name: 'styled-components', signal: 'styled-components' },
|
|
40
|
+
{ name: 'emotion', signal: '@emotion/react' },
|
|
41
|
+
{ name: 'shadcn', signal: 'class-variance-authority' },
|
|
42
|
+
{ name: 'mui', signal: '@mui/material' },
|
|
43
|
+
{ name: 'chakra', signal: '@chakra-ui/react' },
|
|
44
|
+
{ name: 'mantine', signal: '@mantine/core' }
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Detect frontend frameworks via package manifests.
|
|
49
|
+
*/
|
|
50
|
+
function detectFrameworks(projectRoot) {
|
|
51
|
+
const detected = [];
|
|
52
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
53
|
+
if (fs.existsSync(pkgPath)) {
|
|
54
|
+
try {
|
|
55
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
56
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
57
|
+
for (const fw of FRONTEND_FRAMEWORKS) {
|
|
58
|
+
if (Object.keys(deps).some(d => d === fw.signal || d.startsWith(fw.signal + '/'))) {
|
|
59
|
+
detected.push(fw.name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (e) {
|
|
63
|
+
// ignore JSON parse errors
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// Flutter via pubspec.yaml
|
|
67
|
+
if (fs.existsSync(path.join(projectRoot, 'pubspec.yaml'))) {
|
|
68
|
+
const content = fs.readFileSync(path.join(projectRoot, 'pubspec.yaml'), 'utf8');
|
|
69
|
+
if (content.includes('flutter:')) detected.push('flutter');
|
|
70
|
+
}
|
|
71
|
+
// Native iOS/Android (heuristic)
|
|
72
|
+
if (fs.existsSync(path.join(projectRoot, 'ios')) && fs.existsSync(path.join(projectRoot, 'android'))) {
|
|
73
|
+
detected.push('mobile-native');
|
|
74
|
+
}
|
|
75
|
+
return [...new Set(detected)];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read STACK/DECISION.md and look for UI framework selections.
|
|
80
|
+
*/
|
|
81
|
+
function detectFromStack(projectRoot) {
|
|
82
|
+
const stackPath = path.join(projectRoot, '.godpowers', 'stack', 'DECISION.md');
|
|
83
|
+
if (!fs.existsSync(stackPath)) return [];
|
|
84
|
+
const content = fs.readFileSync(stackPath, 'utf8').toLowerCase();
|
|
85
|
+
const detected = [];
|
|
86
|
+
for (const fw of FRONTEND_FRAMEWORKS) {
|
|
87
|
+
if (content.includes(fw.name.toLowerCase()) || content.includes(fw.signal.toLowerCase())) {
|
|
88
|
+
detected.push(fw.name);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return [...new Set(detected)];
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Top-level detection: combines manifests + STACK.md + heuristic signals.
|
|
96
|
+
*/
|
|
97
|
+
function isUiProject(projectRoot) {
|
|
98
|
+
const fromManifests = detectFrameworks(projectRoot);
|
|
99
|
+
const fromStack = detectFromStack(projectRoot);
|
|
100
|
+
const all = [...new Set([...fromManifests, ...fromStack])];
|
|
101
|
+
const signals = [];
|
|
102
|
+
if (fromManifests.length > 0) signals.push('package-manifests');
|
|
103
|
+
if (fromStack.length > 0) signals.push('stack-decision');
|
|
104
|
+
if (fs.existsSync(path.join(projectRoot, 'public'))) signals.push('public-dir');
|
|
105
|
+
if (fs.existsSync(path.join(projectRoot, 'src/components'))) signals.push('src-components-dir');
|
|
106
|
+
if (fs.existsSync(path.join(projectRoot, 'app'))) signals.push('app-dir');
|
|
107
|
+
return {
|
|
108
|
+
required: all.length > 0,
|
|
109
|
+
frameworks: all,
|
|
110
|
+
signals
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Detect if impeccable is installed.
|
|
116
|
+
* Checks: node_modules, .claude/skills, .cursor/skills, .gemini/skills, etc.
|
|
117
|
+
*/
|
|
118
|
+
function isImpeccableInstalled(projectRoot) {
|
|
119
|
+
const locations = [];
|
|
120
|
+
const candidates = [
|
|
121
|
+
'node_modules/impeccable',
|
|
122
|
+
'node_modules/@google/design.md',
|
|
123
|
+
'.claude/skills/impeccable',
|
|
124
|
+
'.cursor/skills/impeccable',
|
|
125
|
+
'.gemini/skills/impeccable',
|
|
126
|
+
'.opencode/skills/impeccable',
|
|
127
|
+
'.kiro/skills/impeccable',
|
|
128
|
+
'.qoder/skills/impeccable',
|
|
129
|
+
'.rovodev/skills/impeccable',
|
|
130
|
+
'.trae/skills/impeccable',
|
|
131
|
+
'.trae-cn/skills/impeccable',
|
|
132
|
+
'.agents/skills/impeccable',
|
|
133
|
+
'.github/skills/impeccable'
|
|
134
|
+
];
|
|
135
|
+
for (const c of candidates) {
|
|
136
|
+
if (fs.existsSync(path.join(projectRoot, c))) {
|
|
137
|
+
locations.push(c);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
// Also check user-home installs
|
|
141
|
+
const home = process.env.HOME || '';
|
|
142
|
+
if (home) {
|
|
143
|
+
const homeCandidates = [
|
|
144
|
+
'.claude/skills/impeccable',
|
|
145
|
+
'.cursor/skills/impeccable',
|
|
146
|
+
'.gemini/skills/impeccable',
|
|
147
|
+
'.agents/skills/impeccable'
|
|
148
|
+
];
|
|
149
|
+
for (const c of homeCandidates) {
|
|
150
|
+
if (fs.existsSync(path.join(home, c))) {
|
|
151
|
+
locations.push(`~/${c}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
installed: locations.length > 0,
|
|
157
|
+
locations
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = {
|
|
162
|
+
isUiProject,
|
|
163
|
+
isImpeccableInstalled,
|
|
164
|
+
detectFrameworks,
|
|
165
|
+
detectFromStack,
|
|
166
|
+
FRONTEND_FRAMEWORKS
|
|
167
|
+
};
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Design Spec
|
|
3
|
+
*
|
|
4
|
+
* Parser and validator for the Google Labs design.md format.
|
|
5
|
+
* (https://github.com/google-labs-code/design.md)
|
|
6
|
+
*
|
|
7
|
+
* A DESIGN.md has YAML frontmatter (tokens) + markdown body (rationale).
|
|
8
|
+
*
|
|
9
|
+
* Public API:
|
|
10
|
+
* parse(content) -> { frontmatter, body, errors }
|
|
11
|
+
* validate(parsed) -> { findings }
|
|
12
|
+
* resolveTokens(parsed) -> { findings } // checks {colors.x} references
|
|
13
|
+
* sectionOrder(body) -> { findings } // verifies canonical order
|
|
14
|
+
* contrastCheck(parsed) -> { findings } // basic WCAG contrast on components
|
|
15
|
+
* lint(content) -> { findings, valid } // run all checks
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const intent = require('./intent');
|
|
19
|
+
|
|
20
|
+
const VALID_SECTIONS = [
|
|
21
|
+
'Overview', 'Brand & Style',
|
|
22
|
+
'Colors',
|
|
23
|
+
'Typography',
|
|
24
|
+
'Layout', 'Layout & Spacing',
|
|
25
|
+
'Elevation & Depth', 'Elevation',
|
|
26
|
+
'Shapes',
|
|
27
|
+
'Components',
|
|
28
|
+
"Do's and Don'ts", 'Dos and Donts'
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
const SECTION_ORDER = [
|
|
32
|
+
['Overview', 'Brand & Style'],
|
|
33
|
+
['Colors'],
|
|
34
|
+
['Typography'],
|
|
35
|
+
['Layout', 'Layout & Spacing'],
|
|
36
|
+
['Elevation & Depth', 'Elevation'],
|
|
37
|
+
['Shapes'],
|
|
38
|
+
['Components'],
|
|
39
|
+
["Do's and Don'ts", 'Dos and Donts']
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const COMPONENT_PROPS = [
|
|
43
|
+
'backgroundColor', 'textColor', 'typography',
|
|
44
|
+
'rounded', 'padding', 'size', 'height', 'width'
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Parse a DESIGN.md file: separate YAML frontmatter from markdown body.
|
|
49
|
+
*/
|
|
50
|
+
function parse(content) {
|
|
51
|
+
const errors = [];
|
|
52
|
+
if (!content.startsWith('---')) {
|
|
53
|
+
return { frontmatter: null, body: content, errors: ['Missing YAML frontmatter (file must start with `---`).'] };
|
|
54
|
+
}
|
|
55
|
+
const end = content.indexOf('\n---', 3);
|
|
56
|
+
if (end === -1) {
|
|
57
|
+
return { frontmatter: null, body: content, errors: ['Frontmatter not closed (missing closing `---`).'] };
|
|
58
|
+
}
|
|
59
|
+
const yamlBlock = content.slice(3, end).trim();
|
|
60
|
+
const body = content.slice(end + 4).trim();
|
|
61
|
+
let frontmatter = null;
|
|
62
|
+
try {
|
|
63
|
+
frontmatter = intent.parseSimpleYaml(yamlBlock);
|
|
64
|
+
} catch (e) {
|
|
65
|
+
errors.push(`Frontmatter YAML parse error: ${e.message}`);
|
|
66
|
+
}
|
|
67
|
+
return { frontmatter, body, errors };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validate frontmatter schema: required fields, recognized types.
|
|
72
|
+
*/
|
|
73
|
+
function validate(parsed) {
|
|
74
|
+
const findings = [];
|
|
75
|
+
if (!parsed.frontmatter) {
|
|
76
|
+
findings.push({ severity: 'error', code: 'D-FRONTMATTER', message: 'No frontmatter parsed.' });
|
|
77
|
+
return { findings };
|
|
78
|
+
}
|
|
79
|
+
const fm = parsed.frontmatter;
|
|
80
|
+
if (!fm.name) {
|
|
81
|
+
findings.push({ severity: 'error', code: 'D-NAME', message: 'Missing required `name` in frontmatter.' });
|
|
82
|
+
}
|
|
83
|
+
if (!fm.description) {
|
|
84
|
+
findings.push({ severity: 'warning', code: 'D-DESCRIPTION', message: 'Missing `description` in frontmatter.' });
|
|
85
|
+
}
|
|
86
|
+
// Color values: must be hex sRGB or oklch() or token reference
|
|
87
|
+
if (fm.colors && typeof fm.colors === 'object') {
|
|
88
|
+
for (const [name, val] of Object.entries(fm.colors)) {
|
|
89
|
+
const v = String(val).trim();
|
|
90
|
+
if (!isValidColor(v) && !isTokenRef(v)) {
|
|
91
|
+
findings.push({
|
|
92
|
+
severity: 'warning',
|
|
93
|
+
code: 'D-COLOR-FORMAT',
|
|
94
|
+
message: `Color "${name}" has unusual format: "${v}". Expected hex sRGB or oklch().`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Typography entries should have fontFamily and fontSize
|
|
100
|
+
if (fm.typography && typeof fm.typography === 'object') {
|
|
101
|
+
for (const [name, val] of Object.entries(fm.typography)) {
|
|
102
|
+
if (typeof val !== 'object') {
|
|
103
|
+
findings.push({
|
|
104
|
+
severity: 'error',
|
|
105
|
+
code: 'D-TYPO-OBJECT',
|
|
106
|
+
message: `Typography "${name}" must be an object with fontFamily/fontSize/etc.`
|
|
107
|
+
});
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (!val.fontFamily) {
|
|
111
|
+
findings.push({
|
|
112
|
+
severity: 'warning',
|
|
113
|
+
code: 'D-TYPO-FAMILY',
|
|
114
|
+
message: `Typography "${name}" missing fontFamily.`
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
if (!val.fontSize) {
|
|
118
|
+
findings.push({
|
|
119
|
+
severity: 'warning',
|
|
120
|
+
code: 'D-TYPO-SIZE',
|
|
121
|
+
message: `Typography "${name}" missing fontSize.`
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Component properties: warn on unknown
|
|
127
|
+
if (fm.components && typeof fm.components === 'object') {
|
|
128
|
+
for (const [name, val] of Object.entries(fm.components)) {
|
|
129
|
+
if (typeof val !== 'object') continue;
|
|
130
|
+
for (const propKey of Object.keys(val)) {
|
|
131
|
+
if (!COMPONENT_PROPS.includes(propKey)) {
|
|
132
|
+
findings.push({
|
|
133
|
+
severity: 'info',
|
|
134
|
+
code: 'D-COMP-PROP',
|
|
135
|
+
message: `Component "${name}" has unknown property "${propKey}". Valid: ${COMPONENT_PROPS.join(', ')}.`
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return { findings };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Resolve token references like {colors.primary}.
|
|
146
|
+
* Returns findings for unresolved references.
|
|
147
|
+
*/
|
|
148
|
+
function resolveTokens(parsed) {
|
|
149
|
+
const findings = [];
|
|
150
|
+
if (!parsed.frontmatter) return { findings };
|
|
151
|
+
const fm = parsed.frontmatter;
|
|
152
|
+
const refRegex = /\{([\w.-]+)\}/g;
|
|
153
|
+
function get(p) {
|
|
154
|
+
return p.split('.').reduce((acc, k) => (acc ? acc[k] : undefined), fm);
|
|
155
|
+
}
|
|
156
|
+
function scan(obj, contextPath) {
|
|
157
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
158
|
+
const here = `${contextPath}.${k}`;
|
|
159
|
+
if (typeof v === 'string') {
|
|
160
|
+
let m;
|
|
161
|
+
const r = new RegExp(refRegex.source, 'g');
|
|
162
|
+
while ((m = r.exec(v)) !== null) {
|
|
163
|
+
const target = m[1];
|
|
164
|
+
if (get(target) === undefined) {
|
|
165
|
+
findings.push({
|
|
166
|
+
severity: 'error',
|
|
167
|
+
code: 'D-TOKEN-REF',
|
|
168
|
+
message: `Unresolved token reference "${m[0]}" at ${here}.`
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else if (typeof v === 'object' && v !== null) {
|
|
173
|
+
scan(v, here);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (fm.components) scan(fm.components, 'components');
|
|
178
|
+
return { findings };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Verify section order: sections present must appear in canonical order.
|
|
183
|
+
*/
|
|
184
|
+
function sectionOrder(body) {
|
|
185
|
+
const findings = [];
|
|
186
|
+
const headings = [];
|
|
187
|
+
for (const line of body.split('\n')) {
|
|
188
|
+
const m = line.match(/^##\s+(.+?)\s*$/);
|
|
189
|
+
if (m) headings.push(m[1].trim());
|
|
190
|
+
}
|
|
191
|
+
// Map each heading to its canonical index
|
|
192
|
+
let lastIdx = -1;
|
|
193
|
+
for (const h of headings) {
|
|
194
|
+
let idx = -1;
|
|
195
|
+
for (let i = 0; i < SECTION_ORDER.length; i++) {
|
|
196
|
+
if (SECTION_ORDER[i].some(name => name.toLowerCase() === h.toLowerCase())) {
|
|
197
|
+
idx = i;
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (idx === -1) {
|
|
202
|
+
findings.push({
|
|
203
|
+
severity: 'info',
|
|
204
|
+
code: 'D-SECTION-UNKNOWN',
|
|
205
|
+
message: `Unknown section "${h}". Allowed: ${VALID_SECTIONS.join(', ')}.`
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
if (idx < lastIdx) {
|
|
210
|
+
findings.push({
|
|
211
|
+
severity: 'error',
|
|
212
|
+
code: 'D-SECTION-ORDER',
|
|
213
|
+
message: `Section "${h}" appears out of order.`
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
lastIdx = idx;
|
|
217
|
+
}
|
|
218
|
+
// Duplicate sections = error
|
|
219
|
+
const seen = new Set();
|
|
220
|
+
for (const h of headings) {
|
|
221
|
+
if (seen.has(h.toLowerCase())) {
|
|
222
|
+
findings.push({
|
|
223
|
+
severity: 'error',
|
|
224
|
+
code: 'D-SECTION-DUP',
|
|
225
|
+
message: `Duplicate section "${h}".`
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
seen.add(h.toLowerCase());
|
|
229
|
+
}
|
|
230
|
+
return { findings };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Compute WCAG contrast ratio for hex colors (basic implementation).
|
|
235
|
+
* Returns ratio (1-21) or null if color cannot be parsed.
|
|
236
|
+
*/
|
|
237
|
+
function contrastRatio(fg, bg) {
|
|
238
|
+
function parseHex(c) {
|
|
239
|
+
const m = c.match(/^#([0-9a-f]{6})$/i);
|
|
240
|
+
if (!m) return null;
|
|
241
|
+
return [
|
|
242
|
+
parseInt(m[1].slice(0, 2), 16) / 255,
|
|
243
|
+
parseInt(m[1].slice(2, 4), 16) / 255,
|
|
244
|
+
parseInt(m[1].slice(4, 6), 16) / 255
|
|
245
|
+
];
|
|
246
|
+
}
|
|
247
|
+
function relLum(rgb) {
|
|
248
|
+
const lin = rgb.map(c => c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4));
|
|
249
|
+
return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
|
|
250
|
+
}
|
|
251
|
+
const fgC = parseHex(fg);
|
|
252
|
+
const bgC = parseHex(bg);
|
|
253
|
+
if (!fgC || !bgC) return null;
|
|
254
|
+
const lFg = relLum(fgC);
|
|
255
|
+
const lBg = relLum(bgC);
|
|
256
|
+
const lighter = Math.max(lFg, lBg);
|
|
257
|
+
const darker = Math.min(lFg, lBg);
|
|
258
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Basic WCAG check on text-on-background components.
|
|
263
|
+
* Resolves token refs to hex; OKLCH not supported here (warning).
|
|
264
|
+
*/
|
|
265
|
+
function contrastCheck(parsed) {
|
|
266
|
+
const findings = [];
|
|
267
|
+
if (!parsed.frontmatter || !parsed.frontmatter.components) return { findings };
|
|
268
|
+
const fm = parsed.frontmatter;
|
|
269
|
+
function resolve(value) {
|
|
270
|
+
if (!value) return null;
|
|
271
|
+
const m = String(value).match(/^\{([\w.-]+)\}$/);
|
|
272
|
+
if (!m) return value;
|
|
273
|
+
return m[1].split('.').reduce((acc, k) => (acc ? acc[k] : undefined), fm);
|
|
274
|
+
}
|
|
275
|
+
for (const [name, props] of Object.entries(fm.components)) {
|
|
276
|
+
if (typeof props !== 'object') continue;
|
|
277
|
+
const fg = resolve(props.textColor);
|
|
278
|
+
const bg = resolve(props.backgroundColor);
|
|
279
|
+
if (!fg || !bg) continue;
|
|
280
|
+
if (typeof fg !== 'string' || typeof bg !== 'string') continue;
|
|
281
|
+
if (!fg.startsWith('#') || !bg.startsWith('#')) {
|
|
282
|
+
// OKLCH or unknown; skip (rendering engine handles)
|
|
283
|
+
continue;
|
|
284
|
+
}
|
|
285
|
+
const ratio = contrastRatio(fg, bg);
|
|
286
|
+
if (ratio === null) continue;
|
|
287
|
+
if (ratio < 4.5) {
|
|
288
|
+
findings.push({
|
|
289
|
+
severity: 'error',
|
|
290
|
+
code: 'D-CONTRAST',
|
|
291
|
+
message: `Component "${name}" contrast ${ratio.toFixed(2)}:1 fails WCAG AA (4.5:1).`
|
|
292
|
+
});
|
|
293
|
+
} else if (ratio < 7) {
|
|
294
|
+
findings.push({
|
|
295
|
+
severity: 'info',
|
|
296
|
+
code: 'D-CONTRAST',
|
|
297
|
+
message: `Component "${name}" contrast ${ratio.toFixed(2)}:1 passes AA but not AAA (7:1).`
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return { findings };
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Convenience: run all checks.
|
|
306
|
+
*/
|
|
307
|
+
function lint(content) {
|
|
308
|
+
const parsed = parse(content);
|
|
309
|
+
const findings = [
|
|
310
|
+
...parsed.errors.map(e => ({ severity: 'error', code: 'D-PARSE', message: e })),
|
|
311
|
+
...validate(parsed).findings,
|
|
312
|
+
...sectionOrder(parsed.body || '').findings,
|
|
313
|
+
...resolveTokens(parsed).findings,
|
|
314
|
+
...contrastCheck(parsed).findings
|
|
315
|
+
];
|
|
316
|
+
const valid = !findings.some(f => f.severity === 'error');
|
|
317
|
+
return { findings, valid };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// ============================================================================
|
|
321
|
+
// Helpers
|
|
322
|
+
// ============================================================================
|
|
323
|
+
|
|
324
|
+
function isValidColor(v) {
|
|
325
|
+
if (/^#[0-9a-f]{6}$/i.test(v)) return true;
|
|
326
|
+
if (/^#[0-9a-f]{8}$/i.test(v)) return true;
|
|
327
|
+
if (/^oklch\s*\(/i.test(v)) return true;
|
|
328
|
+
if (/^rgb\s*\(/i.test(v)) return true;
|
|
329
|
+
if (/^hsl\s*\(/i.test(v)) return true;
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function isTokenRef(v) {
|
|
334
|
+
return /^\{[\w.-]+\}$/.test(v);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = {
|
|
338
|
+
parse,
|
|
339
|
+
validate,
|
|
340
|
+
resolveTokens,
|
|
341
|
+
sectionOrder,
|
|
342
|
+
contrastCheck,
|
|
343
|
+
contrastRatio,
|
|
344
|
+
lint,
|
|
345
|
+
VALID_SECTIONS,
|
|
346
|
+
SECTION_ORDER,
|
|
347
|
+
COMPONENT_PROPS
|
|
348
|
+
};
|