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,309 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime Test
|
|
3
|
+
*
|
|
4
|
+
* Run PRD acceptance criteria against the rendered app via headless
|
|
5
|
+
* browser. Each P-MUST/SHOULD/COULD requirement may include an
|
|
6
|
+
* acceptance criterion phrased as a user-flow that the runtime tester
|
|
7
|
+
* will execute.
|
|
8
|
+
*
|
|
9
|
+
* Public API:
|
|
10
|
+
* extractAcceptanceCriteria(prdContent) -> [{ id, text, parsedFlow }]
|
|
11
|
+
* parseFlow(text) -> { steps, expectedOutcome } | null
|
|
12
|
+
* runFlow(page, flow) -> { passed, steps, error?, screenshots }
|
|
13
|
+
* verifyRequirement(page, requirement, opts) -> structured result
|
|
14
|
+
* runAllForUrl(url, prdContent, opts) -> { results, summary, runId }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const browserBridge = require('./browser-bridge');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Extract acceptance criteria from PRD content. Looks for bullet items
|
|
24
|
+
* that mention a P-MUST/SHOULD/COULD ID and "Acceptance:" pattern.
|
|
25
|
+
*/
|
|
26
|
+
function extractAcceptanceCriteria(prdContent) {
|
|
27
|
+
const results = [];
|
|
28
|
+
if (!prdContent) return results;
|
|
29
|
+
|
|
30
|
+
// Walk lines collecting multi-line bullets
|
|
31
|
+
const lines = prdContent.split('\n');
|
|
32
|
+
let current = null;
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
const trimmed = line.trim();
|
|
35
|
+
if (trimmed.startsWith('-') || trimmed.startsWith('*')) {
|
|
36
|
+
if (current) results.push(current);
|
|
37
|
+
current = { rawText: trimmed.replace(/^[-*]\s*/, '') };
|
|
38
|
+
} else if (current && trimmed && /^\s/.test(line)) {
|
|
39
|
+
current.rawText += ' ' + trimmed;
|
|
40
|
+
} else if (!trimmed) {
|
|
41
|
+
if (current) {
|
|
42
|
+
results.push(current);
|
|
43
|
+
current = null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (current) results.push(current);
|
|
48
|
+
|
|
49
|
+
// Filter to those mentioning a stable ID + acceptance
|
|
50
|
+
return results
|
|
51
|
+
.filter(r => /\bP-(MUST|SHOULD|COULD)-\d+\b/.test(r.rawText) || /Acceptance\s*:/i.test(r.rawText))
|
|
52
|
+
.map(r => {
|
|
53
|
+
const idMatch = r.rawText.match(/\bP-(MUST|SHOULD|COULD)-\d+\b/);
|
|
54
|
+
const acceptMatch = r.rawText.match(/Acceptance\s*:\s*(.+)$/i);
|
|
55
|
+
return {
|
|
56
|
+
id: idMatch ? idMatch[0] : null,
|
|
57
|
+
text: r.rawText,
|
|
58
|
+
acceptanceText: acceptMatch ? acceptMatch[1].trim() : null,
|
|
59
|
+
parsedFlow: parseFlow(acceptMatch ? acceptMatch[1] : r.rawText)
|
|
60
|
+
};
|
|
61
|
+
})
|
|
62
|
+
.filter(r => r.id);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Parse acceptance text into a runnable flow.
|
|
67
|
+
*
|
|
68
|
+
* Phase 15 expansion: 8+ verb forms, sequential expectations, negative
|
|
69
|
+
* expectations, multi-action sentences. Falls back to null if no
|
|
70
|
+
* recognizable steps found.
|
|
71
|
+
*
|
|
72
|
+
* Verb classes:
|
|
73
|
+
* navigate: navigate to | visits | opens | goes to | arrives at
|
|
74
|
+
* click: clicks | taps | presses | hits (+ on)
|
|
75
|
+
* type: types | enters | fills | inputs (+ in/into)
|
|
76
|
+
* expect: sees | expects | displays | shows | observes |
|
|
77
|
+
* can see | should see
|
|
78
|
+
* not-expect: does not see | should not see | cannot see
|
|
79
|
+
*/
|
|
80
|
+
function parseFlow(text) {
|
|
81
|
+
if (!text) return null;
|
|
82
|
+
const steps = [];
|
|
83
|
+
|
|
84
|
+
// Split on commas/semicolons/then to find distinct steps
|
|
85
|
+
const segments = text.split(/[,;]\s+|\s+then\s+/i);
|
|
86
|
+
|
|
87
|
+
for (const segment of segments) {
|
|
88
|
+
const seg = segment.trim();
|
|
89
|
+
|
|
90
|
+
// Negative expect: "does not see", "should not see", "cannot see"
|
|
91
|
+
const notExpectMatch = seg.match(/(?:does\s+not|should\s+not|cannot)\s+(?:see|find|view)\s+["']?([^."']+)["']?/i);
|
|
92
|
+
if (notExpectMatch) {
|
|
93
|
+
steps.push({ kind: 'not-expect', target: notExpectMatch[1].trim() });
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Navigation: navigate(s) to | visits | opens | goes to | arrives at
|
|
98
|
+
const navMatch = seg.match(/(?:navigates?\s+to|visits?|opens?|goes?\s+to|arrives?\s+at|loads?)\s+([\w\/\.\-:?=&]+)/i);
|
|
99
|
+
if (navMatch) {
|
|
100
|
+
steps.push({ kind: 'navigate', target: navMatch[1] });
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Click: clicks | taps | presses | hits + on
|
|
105
|
+
const clickMatch = seg.match(/(?:clicks?(?:\s+on)?|taps?(?:\s+on)?|presses?(?:\s+the)?|hits?(?:\s+the)?)\s+["']?([^."']+?)["']?(?:\s|$)/i);
|
|
106
|
+
if (clickMatch) {
|
|
107
|
+
steps.push({ kind: 'click', target: clickMatch[1].trim() });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Type with quoted text: types "hello world"
|
|
112
|
+
const typeQuotedMatch = seg.match(/(?:types?|enters?|fills?(?:\s+in)?|inputs?)\s+["']([^"']+)["']/i);
|
|
113
|
+
if (typeQuotedMatch) {
|
|
114
|
+
steps.push({ kind: 'type', text: typeQuotedMatch[1] });
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Type into field: enters X in field
|
|
119
|
+
const typeFieldMatch = seg.match(/(?:types?|enters?|fills?|inputs?)\s+([\w@.-]+)\s+(?:in|into)\s+(?:the\s+)?["']?([\w\s-]+)["']?\s+(?:field|input|box)/i);
|
|
120
|
+
if (typeFieldMatch) {
|
|
121
|
+
steps.push({ kind: 'type', text: typeFieldMatch[1], field: typeFieldMatch[2].trim() });
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Positive expect: sees | expects | displays | shows | observes |
|
|
126
|
+
// can see | should see | should display | lands on | arrives on | completes
|
|
127
|
+
const seeMatch = seg.match(/(?:can\s+see|should\s+see|should\s+display|sees?|expects?|displays?|shows?|observes?|lands?\s+on|arrives?\s+on|completes?)\s+["']?([^."']+?)["']?(?:\s|$)/i);
|
|
128
|
+
if (seeMatch) {
|
|
129
|
+
steps.push({ kind: 'expect', target: seeMatch[1].trim() });
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (steps.length === 0) return null;
|
|
135
|
+
return { steps, raw: text };
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Run a parsed flow. Backend-aware:
|
|
140
|
+
* - agent-browser: uses driver methods (goto, click, type, isVisible)
|
|
141
|
+
* that map cleanly to its CLI semantics.
|
|
142
|
+
* - Playwright/Vercel: uses traditional page.click("text=..."), page.keyboard.type, etc.
|
|
143
|
+
*
|
|
144
|
+
* Returns { passed, steps, error?, screenshots }.
|
|
145
|
+
*/
|
|
146
|
+
async function runFlow(page, flow, opts = {}) {
|
|
147
|
+
if (opts.mockResult) return opts.mockResult;
|
|
148
|
+
if (!flow || !flow.steps) {
|
|
149
|
+
return { passed: false, steps: [], error: 'no-flow-parsed' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Detect agent-browser driver via interface shape
|
|
153
|
+
const isAgentBrowser = page && typeof page.goto === 'function' &&
|
|
154
|
+
typeof page.snapshot === 'function' &&
|
|
155
|
+
typeof page.$ !== 'function';
|
|
156
|
+
|
|
157
|
+
const stepResults = [];
|
|
158
|
+
for (const step of flow.steps) {
|
|
159
|
+
try {
|
|
160
|
+
if (isAgentBrowser) {
|
|
161
|
+
// agent-browser path: semantic locators (find text "...")
|
|
162
|
+
if (step.kind === 'navigate') {
|
|
163
|
+
await page.goto(step.target);
|
|
164
|
+
stepResults.push({ ...step, passed: true });
|
|
165
|
+
} else if (step.kind === 'click') {
|
|
166
|
+
// agent-browser supports `find text "Submit" click` natively
|
|
167
|
+
// Use that via the driver's click; falls back to selector
|
|
168
|
+
await page.click(step.target);
|
|
169
|
+
stepResults.push({ ...step, passed: true });
|
|
170
|
+
} else if (step.kind === 'type') {
|
|
171
|
+
// agent-browser: type without selector hits current focus
|
|
172
|
+
await page.type('', step.text).catch(() =>
|
|
173
|
+
page._run(['keyboard', 'type', step.text])
|
|
174
|
+
);
|
|
175
|
+
stepResults.push({ ...step, passed: true });
|
|
176
|
+
} else if (step.kind === 'expect') {
|
|
177
|
+
const visible = await page.isVisible(step.target);
|
|
178
|
+
stepResults.push({ ...step, passed: visible });
|
|
179
|
+
if (!visible) {
|
|
180
|
+
return { passed: false, steps: stepResults, error: `expected "${step.target}" not visible` };
|
|
181
|
+
}
|
|
182
|
+
} else {
|
|
183
|
+
stepResults.push({ ...step, passed: false, error: 'unknown-step-kind' });
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (step.kind === 'navigate') {
|
|
189
|
+
await page.goto(step.target);
|
|
190
|
+
stepResults.push({ ...step, passed: true });
|
|
191
|
+
} else if (step.kind === 'click') {
|
|
192
|
+
// Try to click by visible text or selector
|
|
193
|
+
await page.click(`text="${step.target}"`).catch(() => page.click(step.target));
|
|
194
|
+
stepResults.push({ ...step, passed: true });
|
|
195
|
+
} else if (step.kind === 'type') {
|
|
196
|
+
await page.keyboard.type(step.text);
|
|
197
|
+
stepResults.push({ ...step, passed: true });
|
|
198
|
+
} else if (step.kind === 'expect') {
|
|
199
|
+
const visible = await page.isVisible(`text="${step.target}"`).catch(() => false);
|
|
200
|
+
stepResults.push({ ...step, passed: visible });
|
|
201
|
+
if (!visible) {
|
|
202
|
+
return { passed: false, steps: stepResults, error: `expected "${step.target}" not visible` };
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
stepResults.push({ ...step, passed: false, error: 'unknown-step-kind' });
|
|
206
|
+
}
|
|
207
|
+
} catch (e) {
|
|
208
|
+
stepResults.push({ ...step, passed: false, error: e.message });
|
|
209
|
+
return { passed: false, steps: stepResults, error: e.message };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return { passed: stepResults.every(s => s.passed), steps: stepResults };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Verify a single requirement.
|
|
217
|
+
*/
|
|
218
|
+
async function verifyRequirement(page, requirement, opts = {}) {
|
|
219
|
+
if (!requirement.parsedFlow) {
|
|
220
|
+
return {
|
|
221
|
+
id: requirement.id,
|
|
222
|
+
passed: false,
|
|
223
|
+
reason: 'no-runnable-flow-parsed-from-acceptance',
|
|
224
|
+
acceptanceText: requirement.acceptanceText
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
const flowResult = await runFlow(page, requirement.parsedFlow, opts);
|
|
228
|
+
return {
|
|
229
|
+
id: requirement.id,
|
|
230
|
+
passed: flowResult.passed,
|
|
231
|
+
steps: flowResult.steps,
|
|
232
|
+
error: flowResult.error
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Top-level: run all PRD-derived flows against a URL.
|
|
238
|
+
*/
|
|
239
|
+
async function runAllForUrl(url, prdContent, opts = {}) {
|
|
240
|
+
const projectRoot = opts.projectRoot || process.cwd();
|
|
241
|
+
const runId = opts.runId || browserBridge.newRunId();
|
|
242
|
+
const outDir = browserBridge.runtimeDir(projectRoot, runId);
|
|
243
|
+
const requirements = extractAcceptanceCriteria(prdContent);
|
|
244
|
+
|
|
245
|
+
if (opts.mockResult) {
|
|
246
|
+
return {
|
|
247
|
+
runId,
|
|
248
|
+
url,
|
|
249
|
+
results: opts.mockResult.results || [],
|
|
250
|
+
summary: opts.mockResult.summary || { passed: 0, failed: 0, total: 0 },
|
|
251
|
+
backend: 'mock'
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (requirements.length === 0) {
|
|
256
|
+
return {
|
|
257
|
+
runId,
|
|
258
|
+
url,
|
|
259
|
+
results: [],
|
|
260
|
+
summary: { passed: 0, failed: 0, total: 0 },
|
|
261
|
+
reason: 'no-requirements-with-runnable-acceptance'
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const launched = await browserBridge.launch({ projectRoot, backend: opts.backend });
|
|
266
|
+
if (launched.error) {
|
|
267
|
+
return {
|
|
268
|
+
runId,
|
|
269
|
+
url,
|
|
270
|
+
error: launched.error,
|
|
271
|
+
results: [],
|
|
272
|
+
summary: { passed: 0, failed: 0, total: requirements.length }
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const results = [];
|
|
277
|
+
try {
|
|
278
|
+
const page = await browserBridge.newPage(launched.browser);
|
|
279
|
+
await page.goto(url);
|
|
280
|
+
for (const req of requirements) {
|
|
281
|
+
const r = await verifyRequirement(page, req, opts);
|
|
282
|
+
results.push(r);
|
|
283
|
+
}
|
|
284
|
+
} finally {
|
|
285
|
+
await browserBridge.close(launched.browser);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const summary = {
|
|
289
|
+
passed: results.filter(r => r.passed).length,
|
|
290
|
+
failed: results.filter(r => !r.passed).length,
|
|
291
|
+
total: results.length
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
295
|
+
fs.writeFileSync(
|
|
296
|
+
path.join(outDir, 'test-report.json'),
|
|
297
|
+
JSON.stringify({ runId, url, results, summary, backend: launched.backend }, null, 2)
|
|
298
|
+
);
|
|
299
|
+
|
|
300
|
+
return { runId, url, results, summary, backend: launched.backend };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
module.exports = {
|
|
304
|
+
extractAcceptanceCriteria,
|
|
305
|
+
parseFlow,
|
|
306
|
+
runFlow,
|
|
307
|
+
verifyRequirement,
|
|
308
|
+
runAllForUrl
|
|
309
|
+
};
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SkillUI Bridge
|
|
3
|
+
*
|
|
4
|
+
* Thin invocation layer between godpowers and the SkillUI CLI
|
|
5
|
+
* (https://www.npmjs.com/package/skillui, MIT license). SkillUI
|
|
6
|
+
* statically analyzes a website / git repo / local directory and
|
|
7
|
+
* extracts a complete design system including a DESIGN.md.
|
|
8
|
+
*
|
|
9
|
+
* We use SkillUI as the fallback when:
|
|
10
|
+
* - User mentions a site that's NOT in the awesome-design-md catalog
|
|
11
|
+
* - User wants to extract a design system from a private project
|
|
12
|
+
* - User has a specific URL or repo in mind
|
|
13
|
+
*
|
|
14
|
+
* Never vendored. Detect, dispatch, capture output. Caches per-project
|
|
15
|
+
* under .godpowers/cache/skillui/<slug>/.
|
|
16
|
+
*
|
|
17
|
+
* Public API:
|
|
18
|
+
* isInstalled() -> bool
|
|
19
|
+
* extract(target, projectRoot, opts) -> Promise<{ designMd, outputDir, error }>
|
|
20
|
+
* detectTargetKind(target) -> 'url' | 'repo' | 'dir'
|
|
21
|
+
* slugifyTarget(target) -> string
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { execSync, spawn } = require('child_process');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Quick presence check. SkillUI is an npm CLI; check for it via npm + npx.
|
|
30
|
+
*/
|
|
31
|
+
function isInstalled() {
|
|
32
|
+
try {
|
|
33
|
+
execSync('npx --no-install skillui --version', {
|
|
34
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
35
|
+
timeout: 5000
|
|
36
|
+
});
|
|
37
|
+
return true;
|
|
38
|
+
} catch (e) {
|
|
39
|
+
// Fall back: check global node_modules
|
|
40
|
+
try {
|
|
41
|
+
execSync('npm ls -g skillui --depth=0', {
|
|
42
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
|
+
timeout: 5000
|
|
44
|
+
});
|
|
45
|
+
return true;
|
|
46
|
+
} catch (e2) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Detect what kind of target was passed.
|
|
54
|
+
*/
|
|
55
|
+
function detectTargetKind(target) {
|
|
56
|
+
if (!target) return null;
|
|
57
|
+
const s = String(target).trim();
|
|
58
|
+
if (s.startsWith('http://') || s.startsWith('https://')) {
|
|
59
|
+
if (/github\.com\/[^\/]+\/[^\/]+/.test(s)) return 'repo';
|
|
60
|
+
return 'url';
|
|
61
|
+
}
|
|
62
|
+
if (s.match(/^[\w-]+\/[\w-]+$/)) return 'repo'; // org/repo shorthand
|
|
63
|
+
if (fs.existsSync(s) && fs.statSync(s).isDirectory()) return 'dir';
|
|
64
|
+
return 'url'; // default to URL with auto-https prepend
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Turn a target into a stable cache slug.
|
|
69
|
+
*/
|
|
70
|
+
function slugifyTarget(target) {
|
|
71
|
+
return String(target)
|
|
72
|
+
.replace(/^https?:\/\//, '')
|
|
73
|
+
.replace(/\/$/, '')
|
|
74
|
+
.replace(/[\/.:]/g, '-')
|
|
75
|
+
.replace(/-+/g, '-')
|
|
76
|
+
.toLowerCase();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function cacheDir(projectRoot, slug) {
|
|
80
|
+
return path.join(projectRoot, '.godpowers', 'cache', 'skillui', slug);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Extract a design system from a target (URL, repo, or dir).
|
|
85
|
+
* Returns the path to the generated DESIGN.md plus the output directory.
|
|
86
|
+
*
|
|
87
|
+
* If skillui is not installed, returns { error: 'not-installed' }.
|
|
88
|
+
*/
|
|
89
|
+
function extract(target, projectRoot, opts = {}) {
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
if (!isInstalled() && !opts.forceRun) {
|
|
92
|
+
return resolve({
|
|
93
|
+
designMd: null,
|
|
94
|
+
outputDir: null,
|
|
95
|
+
error: 'not-installed',
|
|
96
|
+
installInstructions: 'npm install -g skillui'
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const kind = detectTargetKind(target);
|
|
101
|
+
const slug = slugifyTarget(target);
|
|
102
|
+
const outDir = cacheDir(projectRoot, slug);
|
|
103
|
+
fs.mkdirSync(outDir, { recursive: true });
|
|
104
|
+
|
|
105
|
+
const flag = kind === 'repo' ? '--repo' : kind === 'dir' ? '--dir' : '--url';
|
|
106
|
+
const args = ['skillui', flag, target];
|
|
107
|
+
if (opts.ultra) args.push('--mode', 'ultra');
|
|
108
|
+
args.push('--out', outDir);
|
|
109
|
+
|
|
110
|
+
const proc = spawn('npx', args, {
|
|
111
|
+
cwd: projectRoot,
|
|
112
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
113
|
+
timeout: opts.timeout || 300_000 // 5 min default for ultra mode
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
let stdout = '';
|
|
117
|
+
let stderr = '';
|
|
118
|
+
proc.stdout.on('data', d => { stdout += d.toString(); });
|
|
119
|
+
proc.stderr.on('data', d => { stderr += d.toString(); });
|
|
120
|
+
|
|
121
|
+
proc.on('close', (code) => {
|
|
122
|
+
if (code !== 0) {
|
|
123
|
+
return resolve({
|
|
124
|
+
designMd: null,
|
|
125
|
+
outputDir: outDir,
|
|
126
|
+
error: 'execution-error',
|
|
127
|
+
message: stderr || stdout || `exit ${code}`,
|
|
128
|
+
slug
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Look for DESIGN.md anywhere in outDir tree
|
|
132
|
+
const designPath = findFirstDesignMd(outDir);
|
|
133
|
+
resolve({
|
|
134
|
+
designMd: designPath,
|
|
135
|
+
outputDir: outDir,
|
|
136
|
+
error: null,
|
|
137
|
+
slug,
|
|
138
|
+
kind
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
proc.on('error', (err) => {
|
|
143
|
+
resolve({
|
|
144
|
+
designMd: null,
|
|
145
|
+
outputDir: outDir,
|
|
146
|
+
error: 'spawn-error',
|
|
147
|
+
message: err.message,
|
|
148
|
+
slug
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function findFirstDesignMd(dir) {
|
|
155
|
+
if (!fs.existsSync(dir)) return null;
|
|
156
|
+
function walk(d) {
|
|
157
|
+
let entries;
|
|
158
|
+
try {
|
|
159
|
+
entries = fs.readdirSync(d, { withFileTypes: true });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
for (const e of entries) {
|
|
164
|
+
const full = path.join(d, e.name);
|
|
165
|
+
if (e.isDirectory()) {
|
|
166
|
+
const found = walk(full);
|
|
167
|
+
if (found) return found;
|
|
168
|
+
} else if (e.name === 'DESIGN.md') {
|
|
169
|
+
return full;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return walk(dir);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Plan a fallback path: given a site reference that's NOT in
|
|
179
|
+
* awesome-design-md, return the extraction plan.
|
|
180
|
+
*/
|
|
181
|
+
function planFallback(siteName, projectRoot) {
|
|
182
|
+
// Construct a likely URL from the site name
|
|
183
|
+
const url = siteName.startsWith('http') ? siteName : `https://${siteName.replace(/\s+/g, '').toLowerCase()}.com`;
|
|
184
|
+
return {
|
|
185
|
+
target: url,
|
|
186
|
+
kind: 'url',
|
|
187
|
+
slug: slugifyTarget(url),
|
|
188
|
+
outputDir: cacheDir(projectRoot, slugifyTarget(url)),
|
|
189
|
+
requiresInstall: !isInstalled(),
|
|
190
|
+
installCommand: 'npm install -g skillui',
|
|
191
|
+
expectedDesignMd: path.join(cacheDir(projectRoot, slugifyTarget(url)), 'DESIGN.md')
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Read a previously-cached DESIGN.md (no fetch).
|
|
197
|
+
*/
|
|
198
|
+
function readCached(projectRoot, slug) {
|
|
199
|
+
const designPath = findFirstDesignMd(cacheDir(projectRoot, slug));
|
|
200
|
+
if (!designPath) return null;
|
|
201
|
+
return {
|
|
202
|
+
designMd: designPath,
|
|
203
|
+
content: fs.readFileSync(designPath, 'utf8')
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
isInstalled,
|
|
209
|
+
detectTargetKind,
|
|
210
|
+
slugifyTarget,
|
|
211
|
+
extract,
|
|
212
|
+
planFallback,
|
|
213
|
+
readCached,
|
|
214
|
+
cacheDir,
|
|
215
|
+
findFirstDesignMd
|
|
216
|
+
};
|