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,455 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Audit
|
|
3
|
+
*
|
|
4
|
+
* Verify that the running app matches DESIGN.md. Uses lib/browser-bridge
|
|
5
|
+
* to drive a headless browser; compares computed styles to declared
|
|
6
|
+
* tokens; runs accessibility checks on real DOM.
|
|
7
|
+
*
|
|
8
|
+
* Public API:
|
|
9
|
+
* extractComputedStyles(page, selectors) -> map
|
|
10
|
+
* compareToDesign(rendered, designContent) -> { findings, summary }
|
|
11
|
+
* checkContrastRealDOM(page) -> findings
|
|
12
|
+
* auditPage(url, designContent, opts) -> structured result
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
const designSpec = require('./design-spec');
|
|
19
|
+
const browserBridge = require('./browser-bridge');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Default selectors to probe. Maps a logical name to a CSS selector.
|
|
23
|
+
* Override per-project via opts.selectors.
|
|
24
|
+
*
|
|
25
|
+
* Expanded in Phase 15 from 6 selectors to 20, covering forms,
|
|
26
|
+
* navigation, cards-by-variant, and feedback elements.
|
|
27
|
+
*/
|
|
28
|
+
const DEFAULT_SELECTORS = {
|
|
29
|
+
// Layout
|
|
30
|
+
body: 'body',
|
|
31
|
+
main: 'main, [role="main"]',
|
|
32
|
+
nav: 'nav, [role="navigation"]',
|
|
33
|
+
header: 'header, [role="banner"]',
|
|
34
|
+
footer: 'footer, [role="contentinfo"]',
|
|
35
|
+
|
|
36
|
+
// Typography
|
|
37
|
+
heading1: 'h1',
|
|
38
|
+
heading2: 'h2',
|
|
39
|
+
heading3: 'h3',
|
|
40
|
+
bodyText: 'p',
|
|
41
|
+
link: 'a',
|
|
42
|
+
small: 'small, .text-sm',
|
|
43
|
+
|
|
44
|
+
// Buttons
|
|
45
|
+
primaryButton: 'button[type="submit"], .btn-primary, [data-design-component="button-primary"]',
|
|
46
|
+
secondaryButton: '.btn-secondary, [data-design-component="button-secondary"]',
|
|
47
|
+
iconButton: 'button[aria-label]:has(svg), [data-design-component="button-icon"]',
|
|
48
|
+
|
|
49
|
+
// Surfaces
|
|
50
|
+
card: '.card, [data-design-component="card"]',
|
|
51
|
+
panel: '.panel, [data-design-component="panel"]',
|
|
52
|
+
|
|
53
|
+
// Forms
|
|
54
|
+
inputText: 'input[type="text"], input[type="email"]',
|
|
55
|
+
label: 'label',
|
|
56
|
+
|
|
57
|
+
// Feedback
|
|
58
|
+
errorMessage: '[role="alert"], .error, [data-design-component="error"]',
|
|
59
|
+
successMessage: '[role="status"], .success, [data-design-component="success"]'
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Auto-derive selectors from DESIGN.md components map. For each
|
|
64
|
+
* declared component, check `data-design-component="<name>"`.
|
|
65
|
+
*/
|
|
66
|
+
function deriveSelectorsFromDesign(designContent) {
|
|
67
|
+
if (!designContent) return {};
|
|
68
|
+
try {
|
|
69
|
+
const parsed = designSpec.parse(designContent);
|
|
70
|
+
if (!parsed.frontmatter || !parsed.frontmatter.components) return {};
|
|
71
|
+
const out = {};
|
|
72
|
+
for (const name of Object.keys(parsed.frontmatter.components)) {
|
|
73
|
+
out[`D-${name}`] = `[data-design-component="${name}"]`;
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
} catch (e) {
|
|
77
|
+
return {};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Extract computed styles for a set of selectors. Backend-aware:
|
|
83
|
+
* - agent-browser: uses `page.getStyles(selector)` (CLI dispatch)
|
|
84
|
+
* - Playwright/Vercel: uses page.$ + page.evaluate
|
|
85
|
+
*
|
|
86
|
+
* Optional in tests: opts.mockStyles can stub the result without launching
|
|
87
|
+
* a browser.
|
|
88
|
+
*/
|
|
89
|
+
async function extractComputedStyles(page, selectors, opts = {}) {
|
|
90
|
+
if (opts.mockStyles) return opts.mockStyles;
|
|
91
|
+
|
|
92
|
+
const probe = selectors || DEFAULT_SELECTORS;
|
|
93
|
+
const results = {};
|
|
94
|
+
|
|
95
|
+
// agent-browser path: driver exposes getStyles directly
|
|
96
|
+
if (page && typeof page.getStyles === 'function' && typeof page.$ !== 'function') {
|
|
97
|
+
for (const [name, selector] of Object.entries(probe)) {
|
|
98
|
+
try {
|
|
99
|
+
const styles = await page.getStyles(selector);
|
|
100
|
+
if (!styles) {
|
|
101
|
+
results[name] = { found: false };
|
|
102
|
+
} else {
|
|
103
|
+
results[name] = { found: true, ...styles };
|
|
104
|
+
}
|
|
105
|
+
} catch (e) {
|
|
106
|
+
results[name] = { found: false, error: e.message };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Playwright / Vercel path: legacy evaluate-based extraction
|
|
113
|
+
for (const [name, selector] of Object.entries(probe)) {
|
|
114
|
+
try {
|
|
115
|
+
const handle = await page.$(selector);
|
|
116
|
+
if (!handle) {
|
|
117
|
+
results[name] = { found: false };
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
const computed = await page.evaluate((el) => {
|
|
121
|
+
const cs = window.getComputedStyle(el);
|
|
122
|
+
return {
|
|
123
|
+
color: cs.color,
|
|
124
|
+
backgroundColor: cs.backgroundColor,
|
|
125
|
+
fontFamily: cs.fontFamily,
|
|
126
|
+
fontSize: cs.fontSize,
|
|
127
|
+
fontWeight: cs.fontWeight,
|
|
128
|
+
borderRadius: cs.borderRadius,
|
|
129
|
+
padding: cs.padding,
|
|
130
|
+
margin: cs.margin,
|
|
131
|
+
lineHeight: cs.lineHeight
|
|
132
|
+
};
|
|
133
|
+
}, handle);
|
|
134
|
+
results[name] = { found: true, ...computed };
|
|
135
|
+
} catch (e) {
|
|
136
|
+
results[name] = { found: false, error: e.message };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return results;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Convert any color string to its sRGB hex form (best effort).
|
|
144
|
+
* Handles "rgb(r, g, b)" "rgba(r, g, b, a)" and hex.
|
|
145
|
+
*/
|
|
146
|
+
function normalizeColor(color) {
|
|
147
|
+
if (!color) return null;
|
|
148
|
+
const s = String(color).trim().toLowerCase();
|
|
149
|
+
if (s.startsWith('#')) return s.slice(0, 7); // #RRGGBB or #RRGGBBAA -> #RRGGBB
|
|
150
|
+
const m = s.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)/);
|
|
151
|
+
if (m) {
|
|
152
|
+
const [r, g, b] = [+m[1], +m[2], +m[3]];
|
|
153
|
+
const hex = '#' + [r, g, b].map(n => n.toString(16).padStart(2, '0')).join('');
|
|
154
|
+
return hex;
|
|
155
|
+
}
|
|
156
|
+
return null; // OKLCH, named colors, etc. are returned by the browser only as rgb()
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Compare extracted styles against DESIGN.md tokens.
|
|
161
|
+
* Returns findings with severity.
|
|
162
|
+
*/
|
|
163
|
+
function compareToDesign(rendered, designContent) {
|
|
164
|
+
const findings = [];
|
|
165
|
+
if (!designContent) return { findings, summary: { errors: 0, warnings: 0, infos: 0 } };
|
|
166
|
+
const parsed = designSpec.parse(designContent);
|
|
167
|
+
if (!parsed.frontmatter) return { findings, summary: { errors: 0, warnings: 0, infos: 0 } };
|
|
168
|
+
const fm = parsed.frontmatter;
|
|
169
|
+
|
|
170
|
+
// Check primary button background matches a known color token
|
|
171
|
+
if (rendered.primaryButton && rendered.primaryButton.found) {
|
|
172
|
+
const renderedBg = normalizeColor(rendered.primaryButton.backgroundColor);
|
|
173
|
+
if (renderedBg) {
|
|
174
|
+
const tokens = collectColorTokens(fm);
|
|
175
|
+
const match = tokens.find(t => normalizeColor(t.value) === renderedBg);
|
|
176
|
+
if (!match) {
|
|
177
|
+
findings.push({
|
|
178
|
+
severity: 'warning',
|
|
179
|
+
kind: 'color-no-match',
|
|
180
|
+
selector: 'primaryButton',
|
|
181
|
+
message: `Primary button background "${rendered.primaryButton.backgroundColor}" does not match any DESIGN.md color token.`,
|
|
182
|
+
rendered: renderedBg
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check body font-family contains a declared family
|
|
189
|
+
if (rendered.bodyText && rendered.bodyText.found && fm.typography) {
|
|
190
|
+
const renderedFamily = String(rendered.bodyText.fontFamily || '').toLowerCase();
|
|
191
|
+
const declaredFamilies = collectFontFamilies(fm.typography);
|
|
192
|
+
const matchesAny = declaredFamilies.some(d =>
|
|
193
|
+
d && renderedFamily.includes(String(d).toLowerCase().split(',')[0].replace(/['"]/g, '').trim())
|
|
194
|
+
);
|
|
195
|
+
if (declaredFamilies.length > 0 && !matchesAny) {
|
|
196
|
+
findings.push({
|
|
197
|
+
severity: 'warning',
|
|
198
|
+
kind: 'typography-no-match',
|
|
199
|
+
selector: 'bodyText',
|
|
200
|
+
message: `Body font "${rendered.bodyText.fontFamily}" does not match any DESIGN.md typography family.`,
|
|
201
|
+
rendered: rendered.bodyText.fontFamily,
|
|
202
|
+
declared: declaredFamilies
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Check border-radius on cards matches `rounded` tokens
|
|
208
|
+
if (rendered.card && rendered.card.found && fm.rounded) {
|
|
209
|
+
const renderedRadius = String(rendered.card.borderRadius || '').trim();
|
|
210
|
+
const declared = Object.values(fm.rounded || {}).map(String);
|
|
211
|
+
if (declared.length > 0 && !declared.includes(renderedRadius)) {
|
|
212
|
+
findings.push({
|
|
213
|
+
severity: 'info',
|
|
214
|
+
kind: 'rounded-no-match',
|
|
215
|
+
selector: 'card',
|
|
216
|
+
message: `Card border-radius "${renderedRadius}" does not match any DESIGN.md rounded token.`,
|
|
217
|
+
rendered: renderedRadius,
|
|
218
|
+
declared
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const summary = {
|
|
224
|
+
errors: findings.filter(f => f.severity === 'error').length,
|
|
225
|
+
warnings: findings.filter(f => f.severity === 'warning').length,
|
|
226
|
+
infos: findings.filter(f => f.severity === 'info').length
|
|
227
|
+
};
|
|
228
|
+
return { findings, summary };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Run an axe-core accessibility audit on the rendered page.
|
|
233
|
+
* In tests, opts.mockAxeResults can stub.
|
|
234
|
+
*/
|
|
235
|
+
async function checkContrastRealDOM(page, opts = {}) {
|
|
236
|
+
if (opts.mockAxeResults) return opts.mockAxeResults;
|
|
237
|
+
// Run an axe-like contrast check via direct DOM evaluation. Not a full
|
|
238
|
+
// axe-core integration; that would require pulling axe into the page.
|
|
239
|
+
// V1: extract foreground and background of text elements, compute contrast,
|
|
240
|
+
// flag those below 4.5:1.
|
|
241
|
+
try {
|
|
242
|
+
const violations = await page.evaluate(() => {
|
|
243
|
+
const findings = [];
|
|
244
|
+
const els = document.querySelectorAll('p, h1, h2, h3, h4, h5, h6, span, a, button, label');
|
|
245
|
+
function lum(rgb) {
|
|
246
|
+
const m = rgb.match(/\d+/g);
|
|
247
|
+
if (!m) return null;
|
|
248
|
+
const norm = m.slice(0, 3).map(n => {
|
|
249
|
+
const c = +n / 255;
|
|
250
|
+
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
251
|
+
});
|
|
252
|
+
return 0.2126 * norm[0] + 0.7152 * norm[1] + 0.0722 * norm[2];
|
|
253
|
+
}
|
|
254
|
+
for (const el of els) {
|
|
255
|
+
if (!el.textContent || !el.textContent.trim()) continue;
|
|
256
|
+
const cs = window.getComputedStyle(el);
|
|
257
|
+
const lFg = lum(cs.color);
|
|
258
|
+
let bgEl = el;
|
|
259
|
+
let bgRgb = cs.backgroundColor;
|
|
260
|
+
while (bgEl && (bgRgb === 'rgba(0, 0, 0, 0)' || bgRgb === 'transparent')) {
|
|
261
|
+
bgEl = bgEl.parentElement;
|
|
262
|
+
if (!bgEl) break;
|
|
263
|
+
bgRgb = window.getComputedStyle(bgEl).backgroundColor;
|
|
264
|
+
}
|
|
265
|
+
const lBg = lum(bgRgb);
|
|
266
|
+
if (lFg === null || lBg === null) continue;
|
|
267
|
+
const lighter = Math.max(lFg, lBg);
|
|
268
|
+
const darker = Math.min(lFg, lBg);
|
|
269
|
+
const ratio = (lighter + 0.05) / (darker + 0.05);
|
|
270
|
+
if (ratio < 4.5) {
|
|
271
|
+
findings.push({
|
|
272
|
+
tag: el.tagName.toLowerCase(),
|
|
273
|
+
text: el.textContent.slice(0, 40),
|
|
274
|
+
ratio: ratio.toFixed(2),
|
|
275
|
+
fg: cs.color,
|
|
276
|
+
bg: bgRgb
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return findings;
|
|
281
|
+
});
|
|
282
|
+
return violations.map(v => ({
|
|
283
|
+
severity: 'error',
|
|
284
|
+
kind: 'wcag-contrast',
|
|
285
|
+
message: `WCAG AA contrast fail: ${v.tag} "${v.text}" has ratio ${v.ratio}:1 (need 4.5:1).`,
|
|
286
|
+
...v
|
|
287
|
+
}));
|
|
288
|
+
} catch (e) {
|
|
289
|
+
return [{ severity: 'info', kind: 'audit-error', message: e.message }];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Save a screenshot of the page to the runtime dir.
|
|
295
|
+
*/
|
|
296
|
+
async function screenshot(page, dir, name) {
|
|
297
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
298
|
+
const filePath = path.join(dir, `${name}.png`);
|
|
299
|
+
await page.screenshot({ path: filePath, fullPage: true });
|
|
300
|
+
return filePath;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Visual regression: compare a current screenshot to a stored baseline.
|
|
305
|
+
* On first run, the current screenshot becomes the baseline. On subsequent
|
|
306
|
+
* runs, file size delta acts as a coarse "did anything change" signal.
|
|
307
|
+
*
|
|
308
|
+
* V1 implementation: byte-size + filesize-percentage delta, not pixel
|
|
309
|
+
* diffing. Pixel-level diffing would require a separate dep (pixelmatch).
|
|
310
|
+
*
|
|
311
|
+
* Returns:
|
|
312
|
+
* { baseline: bool, changed: bool, deltaPct: number, baselinePath, currentPath }
|
|
313
|
+
*/
|
|
314
|
+
function visualRegression(currentScreenshotPath, baselineDir) {
|
|
315
|
+
if (!fs.existsSync(baselineDir)) fs.mkdirSync(baselineDir, { recursive: true });
|
|
316
|
+
const name = path.basename(currentScreenshotPath);
|
|
317
|
+
const baselinePath = path.join(baselineDir, name);
|
|
318
|
+
|
|
319
|
+
if (!fs.existsSync(baselinePath)) {
|
|
320
|
+
// First run: capture baseline
|
|
321
|
+
fs.copyFileSync(currentScreenshotPath, baselinePath);
|
|
322
|
+
return { baseline: true, changed: false, deltaPct: 0, baselinePath, currentPath: currentScreenshotPath };
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const baselineSize = fs.statSync(baselinePath).size;
|
|
326
|
+
const currentSize = fs.statSync(currentScreenshotPath).size;
|
|
327
|
+
const deltaPct = baselineSize === 0 ? 100 : Math.abs((currentSize - baselineSize) / baselineSize * 100);
|
|
328
|
+
// Threshold: > 5% size change = visual change worth flagging
|
|
329
|
+
const changed = deltaPct > 5;
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
baseline: false,
|
|
333
|
+
changed,
|
|
334
|
+
deltaPct: Number(deltaPct.toFixed(2)),
|
|
335
|
+
baselinePath,
|
|
336
|
+
currentPath: currentScreenshotPath
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* High-level: audit a URL against DESIGN.md.
|
|
342
|
+
*
|
|
343
|
+
* 1. Launch headless browser
|
|
344
|
+
* 2. Navigate to URL
|
|
345
|
+
* 3. Extract computed styles for selectors
|
|
346
|
+
* 4. Compare to DESIGN.md
|
|
347
|
+
* 5. Run contrast check on real DOM
|
|
348
|
+
* 6. Take screenshot
|
|
349
|
+
* 7. Close browser
|
|
350
|
+
* 8. Return findings + report
|
|
351
|
+
*/
|
|
352
|
+
async function auditPage(url, designContent, opts = {}) {
|
|
353
|
+
const projectRoot = opts.projectRoot || process.cwd();
|
|
354
|
+
const runId = opts.runId || browserBridge.newRunId();
|
|
355
|
+
const outDir = browserBridge.runtimeDir(projectRoot, runId);
|
|
356
|
+
|
|
357
|
+
// Allow injection for tests
|
|
358
|
+
if (opts.mockBrowserResult) {
|
|
359
|
+
return {
|
|
360
|
+
runId,
|
|
361
|
+
url,
|
|
362
|
+
findings: opts.mockBrowserResult.findings || [],
|
|
363
|
+
summary: opts.mockBrowserResult.summary || { errors: 0, warnings: 0, infos: 0 },
|
|
364
|
+
screenshots: [],
|
|
365
|
+
backend: 'mock'
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const launched = await browserBridge.launch({ projectRoot, backend: opts.backend });
|
|
370
|
+
if (launched.error) {
|
|
371
|
+
return {
|
|
372
|
+
runId,
|
|
373
|
+
url,
|
|
374
|
+
error: launched.error,
|
|
375
|
+
findings: [],
|
|
376
|
+
summary: { errors: 0, warnings: 0, infos: 0 }
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const findings = [];
|
|
381
|
+
const screenshots = [];
|
|
382
|
+
try {
|
|
383
|
+
const page = await browserBridge.newPage(launched.browser);
|
|
384
|
+
await page.goto(url);
|
|
385
|
+
|
|
386
|
+
const styles = await extractComputedStyles(page, opts.selectors);
|
|
387
|
+
const compareResult = compareToDesign(styles, designContent);
|
|
388
|
+
findings.push(...compareResult.findings);
|
|
389
|
+
|
|
390
|
+
const contrastFindings = await checkContrastRealDOM(page);
|
|
391
|
+
findings.push(...contrastFindings);
|
|
392
|
+
|
|
393
|
+
const shotPath = await screenshot(page, path.join(outDir, 'screenshots'), 'main');
|
|
394
|
+
screenshots.push(shotPath);
|
|
395
|
+
} catch (e) {
|
|
396
|
+
findings.push({ severity: 'error', kind: 'audit-error', message: e.message });
|
|
397
|
+
} finally {
|
|
398
|
+
await browserBridge.close(launched.browser);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const summary = {
|
|
402
|
+
errors: findings.filter(f => f.severity === 'error').length,
|
|
403
|
+
warnings: findings.filter(f => f.severity === 'warning').length,
|
|
404
|
+
infos: findings.filter(f => f.severity === 'info').length
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Persist report
|
|
408
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
409
|
+
fs.writeFileSync(
|
|
410
|
+
path.join(outDir, 'audit-report.json'),
|
|
411
|
+
JSON.stringify({ runId, url, findings, summary, screenshots, backend: launched.backend }, null, 2)
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
return { runId, url, findings, summary, screenshots, backend: launched.backend };
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ============================================================================
|
|
418
|
+
// Helpers
|
|
419
|
+
// ============================================================================
|
|
420
|
+
|
|
421
|
+
function collectColorTokens(fm) {
|
|
422
|
+
const tokens = [];
|
|
423
|
+
function walk(obj, prefix) {
|
|
424
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
425
|
+
const here = prefix ? `${prefix}.${k}` : k;
|
|
426
|
+
if (typeof v === 'object' && v !== null) walk(v, here);
|
|
427
|
+
else tokens.push({ path: here, value: String(v) });
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (fm.colors) walk(fm.colors, 'colors');
|
|
431
|
+
return tokens;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
function collectFontFamilies(typography) {
|
|
435
|
+
const families = [];
|
|
436
|
+
for (const v of Object.values(typography || {})) {
|
|
437
|
+
if (typeof v === 'object' && v && v.fontFamily) {
|
|
438
|
+
families.push(v.fontFamily);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return families;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
module.exports = {
|
|
445
|
+
DEFAULT_SELECTORS,
|
|
446
|
+
deriveSelectorsFromDesign,
|
|
447
|
+
extractComputedStyles,
|
|
448
|
+
compareToDesign,
|
|
449
|
+
checkContrastRealDOM,
|
|
450
|
+
screenshot,
|
|
451
|
+
visualRegression,
|
|
452
|
+
auditPage,
|
|
453
|
+
normalizeColor,
|
|
454
|
+
collectColorTokens
|
|
455
|
+
};
|