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,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Browser Driver
|
|
3
|
+
*
|
|
4
|
+
* Wraps the vercel-labs/agent-browser CLI for use as a browser backend
|
|
5
|
+
* in the runtime verification pipeline. Native Rust binary, headless by
|
|
6
|
+
* default, accessibility-tree-first interaction model.
|
|
7
|
+
*
|
|
8
|
+
* Source: https://github.com/vercel-labs/agent-browser (MIT)
|
|
9
|
+
*
|
|
10
|
+
* The driver presents a uniform interface (goto, click, type, expect,
|
|
11
|
+
* styles, screenshot, close) that runtime-audit and runtime-test consume
|
|
12
|
+
* regardless of which backend is active.
|
|
13
|
+
*
|
|
14
|
+
* Headless contract: agent-browser is headless by default. We never pass
|
|
15
|
+
* any flag that would change that.
|
|
16
|
+
*
|
|
17
|
+
* Public API:
|
|
18
|
+
* openSession(opts) -> Promise<void>
|
|
19
|
+
* newPage() -> Promise<page-handle> // Driver acts as both browser and page
|
|
20
|
+
* goto(url) -> Promise<void>
|
|
21
|
+
* click(selector) -> Promise<void>
|
|
22
|
+
* type(selector, text) -> Promise<void>
|
|
23
|
+
* fill(selector, text) -> Promise<void>
|
|
24
|
+
* isVisible(selector) -> Promise<boolean>
|
|
25
|
+
* getStyles(selector) -> Promise<object>
|
|
26
|
+
* screenshot(filePath, opts) -> Promise<string>
|
|
27
|
+
* evaluate(fn, ...args) -> Promise<any> // limited support via `eval`
|
|
28
|
+
* snapshot() -> Promise<string> // accessibility tree
|
|
29
|
+
* close() -> Promise<void>
|
|
30
|
+
* isInstalled() -> bool
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
const { execSync } = require('child_process');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Detect agent-browser presence (mirror of bridge.isAgentBrowserInstalled).
|
|
38
|
+
*/
|
|
39
|
+
function isInstalled() {
|
|
40
|
+
try {
|
|
41
|
+
execSync('agent-browser --version', {
|
|
42
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
43
|
+
timeout: 5000
|
|
44
|
+
});
|
|
45
|
+
return true;
|
|
46
|
+
} catch (e1) {
|
|
47
|
+
try {
|
|
48
|
+
execSync('npx --no-install agent-browser --version', {
|
|
49
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
50
|
+
timeout: 5000
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
} catch (e2) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Run an agent-browser CLI command and return stdout.
|
|
61
|
+
* Throws on non-zero exit. Times out after 30s default.
|
|
62
|
+
*/
|
|
63
|
+
function run(args, opts = {}) {
|
|
64
|
+
const cmd = Array.isArray(args) ? args : [args];
|
|
65
|
+
// Quote arguments containing spaces (best-effort)
|
|
66
|
+
const argString = cmd.map(a => {
|
|
67
|
+
const s = String(a);
|
|
68
|
+
if (/\s/.test(s) && !s.startsWith('"')) return `"${s.replace(/"/g, '\\"')}"`;
|
|
69
|
+
return s;
|
|
70
|
+
}).join(' ');
|
|
71
|
+
const fullCmd = `agent-browser ${argString}`;
|
|
72
|
+
return execSync(fullCmd, {
|
|
73
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
74
|
+
timeout: opts.timeout || 30000,
|
|
75
|
+
cwd: opts.cwd || process.cwd(),
|
|
76
|
+
encoding: 'utf8'
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let activeSession = null;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Open a session. agent-browser maintains the browser process between commands.
|
|
84
|
+
* We mark the driver as having an active session.
|
|
85
|
+
*/
|
|
86
|
+
async function openSession(opts = {}) {
|
|
87
|
+
if (activeSession) return; // already open
|
|
88
|
+
// `agent-browser open` with no args launches without navigation
|
|
89
|
+
try {
|
|
90
|
+
run(['open'], { cwd: opts.cwd });
|
|
91
|
+
activeSession = { startedAt: Date.now(), opts };
|
|
92
|
+
} catch (e) {
|
|
93
|
+
activeSession = null;
|
|
94
|
+
throw new Error(`agent-browser open failed: ${e.message}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The driver itself acts as the page handle (single session model).
|
|
100
|
+
*/
|
|
101
|
+
async function newPage() {
|
|
102
|
+
return module.exports;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function goto(url) {
|
|
106
|
+
run(['open', url]);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function click(selector) {
|
|
110
|
+
// If selector starts with @ (snapshot ref), use as-is. Otherwise try as CSS.
|
|
111
|
+
run(['click', selector]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async function type(selector, text) {
|
|
115
|
+
run(['type', selector, text]);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
async function fill(selector, text) {
|
|
119
|
+
run(['fill', selector, text]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function isVisible(selector) {
|
|
123
|
+
try {
|
|
124
|
+
const out = run(['is', 'visible', selector]);
|
|
125
|
+
return /true/i.test(out);
|
|
126
|
+
} catch (e) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function getStyles(selector) {
|
|
132
|
+
try {
|
|
133
|
+
const out = run(['get', 'styles', selector]);
|
|
134
|
+
try {
|
|
135
|
+
return JSON.parse(out);
|
|
136
|
+
} catch (parseErr) {
|
|
137
|
+
// CLI returns a key:value lines format on some versions; best-effort parse
|
|
138
|
+
const result = {};
|
|
139
|
+
for (const line of out.split('\n')) {
|
|
140
|
+
const m = line.match(/^([\w-]+):\s*(.+)$/);
|
|
141
|
+
if (m) result[m[1]] = m[2].trim();
|
|
142
|
+
}
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function screenshot(filePath, opts = {}) {
|
|
151
|
+
const args = ['screenshot', filePath];
|
|
152
|
+
if (opts.fullPage) args.push('--full');
|
|
153
|
+
run(args);
|
|
154
|
+
return filePath;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async function evaluate(fn, ...args) {
|
|
158
|
+
// CLI mode: serialize JS expression. agent-browser supports `eval <js>`.
|
|
159
|
+
// We can't pass functions directly; only JS expression strings.
|
|
160
|
+
let expr;
|
|
161
|
+
if (typeof fn === 'string') {
|
|
162
|
+
expr = fn;
|
|
163
|
+
} else if (typeof fn === 'function') {
|
|
164
|
+
// Best-effort: stringify the function and immediately invoke
|
|
165
|
+
const fnStr = fn.toString();
|
|
166
|
+
expr = `(${fnStr})(${args.map(a => JSON.stringify(a)).join(', ')})`;
|
|
167
|
+
} else {
|
|
168
|
+
throw new Error('evaluate expects a string or function');
|
|
169
|
+
}
|
|
170
|
+
const out = run(['eval', expr]);
|
|
171
|
+
try {
|
|
172
|
+
return JSON.parse(out);
|
|
173
|
+
} catch (e) {
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function snapshot() {
|
|
179
|
+
return run(['snapshot']);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function close() {
|
|
183
|
+
try {
|
|
184
|
+
run(['close']);
|
|
185
|
+
} catch (e) {
|
|
186
|
+
// Already closed
|
|
187
|
+
}
|
|
188
|
+
activeSession = null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Match the bridge's expected `browser.newPage` and `browser.close`
|
|
193
|
+
* interface so the higher-level audit/test code can treat agent-browser
|
|
194
|
+
* the same as Playwright.
|
|
195
|
+
*/
|
|
196
|
+
module.exports = {
|
|
197
|
+
isInstalled,
|
|
198
|
+
openSession,
|
|
199
|
+
newPage,
|
|
200
|
+
goto,
|
|
201
|
+
click,
|
|
202
|
+
type,
|
|
203
|
+
fill,
|
|
204
|
+
isVisible,
|
|
205
|
+
getStyles,
|
|
206
|
+
screenshot,
|
|
207
|
+
evaluate,
|
|
208
|
+
snapshot,
|
|
209
|
+
close,
|
|
210
|
+
// Expose run for testing
|
|
211
|
+
_run: run,
|
|
212
|
+
// Expose session state
|
|
213
|
+
_session: () => activeSession,
|
|
214
|
+
_setSession: (s) => { activeSession = s; }
|
|
215
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Cache - opt-in cache of agent outputs keyed by deterministic
|
|
3
|
+
* input hash. Cache hit = no spawn, no tokens spent.
|
|
4
|
+
*
|
|
5
|
+
* Cache key = sha256(agent_name + agent_version + sorted-inputs JSON +
|
|
6
|
+
* state_hash + project_mode)
|
|
7
|
+
*
|
|
8
|
+
* Storage: .godpowers/cache/<keyPrefix>/<fullKey>.json
|
|
9
|
+
* Each entry: {
|
|
10
|
+
* key, agent, agent_version, model, ts,
|
|
11
|
+
* inputs_hash, state_hash,
|
|
12
|
+
* output: <artifact text or structured result>,
|
|
13
|
+
* tokens: { in, out },
|
|
14
|
+
* ttl_ms,
|
|
15
|
+
* expires
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* Cache is OPT-IN. The orchestrator only consults it when
|
|
19
|
+
* intent.yaml.budgets.cache === true, or `/god-mode --cache` is set.
|
|
20
|
+
*
|
|
21
|
+
* Invalidation:
|
|
22
|
+
* - TTL expiration (default 24 hours)
|
|
23
|
+
* - state.json hash mismatch (downstream input changed)
|
|
24
|
+
* - agent version mismatch (agent was updated)
|
|
25
|
+
* - manual: /god-cache-clear (all) or by agent name
|
|
26
|
+
*
|
|
27
|
+
* Public API:
|
|
28
|
+
* cacheDir(projectRoot) -> string
|
|
29
|
+
* key(agent, version, inputs, stateHash) -> string
|
|
30
|
+
* get(projectRoot, key) -> entry | null
|
|
31
|
+
* put(projectRoot, key, entry) -> path
|
|
32
|
+
* has(projectRoot, key) -> bool (and not expired)
|
|
33
|
+
* clear(projectRoot, opts) -> { removed: number }
|
|
34
|
+
* stats(projectRoot) -> { count, totalBytes, oldestTs }
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
const fs = require('fs');
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const crypto = require('crypto');
|
|
40
|
+
|
|
41
|
+
const DEFAULT_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
42
|
+
|
|
43
|
+
function cacheDir(projectRoot) {
|
|
44
|
+
return path.join(projectRoot, '.godpowers', 'cache');
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Deterministically compute a cache key.
|
|
49
|
+
*
|
|
50
|
+
* inputs is normalized: top-level keys sorted, nested objects sorted
|
|
51
|
+
* recursively, undefined dropped. Same inputs => same key.
|
|
52
|
+
*/
|
|
53
|
+
function key(agent, agent_version, inputs, stateHash) {
|
|
54
|
+
const stable = sortKeys(inputs || {});
|
|
55
|
+
const payload = JSON.stringify({
|
|
56
|
+
agent: agent || '',
|
|
57
|
+
agent_version: agent_version || '',
|
|
58
|
+
state_hash: stateHash || '',
|
|
59
|
+
inputs: stable
|
|
60
|
+
});
|
|
61
|
+
return crypto.createHash('sha256').update(payload).digest('hex');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function sortKeys(obj) {
|
|
65
|
+
if (Array.isArray(obj)) return obj.map(sortKeys);
|
|
66
|
+
if (obj && typeof obj === 'object') {
|
|
67
|
+
const out = {};
|
|
68
|
+
for (const k of Object.keys(obj).sort()) {
|
|
69
|
+
if (obj[k] === undefined) continue;
|
|
70
|
+
out[k] = sortKeys(obj[k]);
|
|
71
|
+
}
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
return obj;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function entryPath(projectRoot, k) {
|
|
78
|
+
// Shard by first 2 chars of key to keep dirs flat
|
|
79
|
+
return path.join(cacheDir(projectRoot), k.slice(0, 2), `${k}.json`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isExpired(entry, now) {
|
|
83
|
+
if (!entry || !entry.expires) return true;
|
|
84
|
+
return (now || Date.now()) > Date.parse(entry.expires);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read a cache entry. Returns null on miss or expired.
|
|
89
|
+
*/
|
|
90
|
+
function get(projectRoot, k) {
|
|
91
|
+
const p = entryPath(projectRoot, k);
|
|
92
|
+
if (!fs.existsSync(p)) return null;
|
|
93
|
+
let entry;
|
|
94
|
+
try { entry = JSON.parse(fs.readFileSync(p, 'utf8')); }
|
|
95
|
+
catch (e) { return null; }
|
|
96
|
+
if (isExpired(entry)) return null;
|
|
97
|
+
return entry;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function has(projectRoot, k) {
|
|
101
|
+
return get(projectRoot, k) != null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Write a cache entry. Returns the file path.
|
|
106
|
+
*/
|
|
107
|
+
function put(projectRoot, k, entry) {
|
|
108
|
+
const p = entryPath(projectRoot, k);
|
|
109
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
110
|
+
const ttl = entry.ttl_ms || DEFAULT_TTL_MS;
|
|
111
|
+
const full = {
|
|
112
|
+
key: k,
|
|
113
|
+
ts: new Date().toISOString(),
|
|
114
|
+
expires: new Date(Date.now() + ttl).toISOString(),
|
|
115
|
+
ttl_ms: ttl,
|
|
116
|
+
...entry
|
|
117
|
+
};
|
|
118
|
+
fs.writeFileSync(p, JSON.stringify(full, null, 2));
|
|
119
|
+
return p;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Clear cache. opts:
|
|
124
|
+
* { all: bool, agent: string, expiredOnly: bool, olderThanMs: number }
|
|
125
|
+
*
|
|
126
|
+
* Returns { removed: <count>, kept: <count> }.
|
|
127
|
+
*/
|
|
128
|
+
function clear(projectRoot, opts = {}) {
|
|
129
|
+
const dir = cacheDir(projectRoot);
|
|
130
|
+
if (!fs.existsSync(dir)) return { removed: 0, kept: 0 };
|
|
131
|
+
|
|
132
|
+
let removed = 0;
|
|
133
|
+
let kept = 0;
|
|
134
|
+
const now = Date.now();
|
|
135
|
+
for (const shard of fs.readdirSync(dir)) {
|
|
136
|
+
const shardPath = path.join(dir, shard);
|
|
137
|
+
if (!fs.statSync(shardPath).isDirectory()) continue;
|
|
138
|
+
for (const fname of fs.readdirSync(shardPath)) {
|
|
139
|
+
const fpath = path.join(shardPath, fname);
|
|
140
|
+
let entry;
|
|
141
|
+
try { entry = JSON.parse(fs.readFileSync(fpath, 'utf8')); }
|
|
142
|
+
catch (e) { fs.unlinkSync(fpath); removed++; continue; }
|
|
143
|
+
let shouldRemove = false;
|
|
144
|
+
if (opts.all) shouldRemove = true;
|
|
145
|
+
else if (opts.agent && entry.agent === opts.agent) shouldRemove = true;
|
|
146
|
+
else if (opts.expiredOnly && isExpired(entry, now)) shouldRemove = true;
|
|
147
|
+
else if (opts.olderThanMs && (now - Date.parse(entry.ts)) > opts.olderThanMs) shouldRemove = true;
|
|
148
|
+
if (shouldRemove) { fs.unlinkSync(fpath); removed++; }
|
|
149
|
+
else kept++;
|
|
150
|
+
}
|
|
151
|
+
// Remove empty shard dirs
|
|
152
|
+
if (fs.readdirSync(shardPath).length === 0) fs.rmdirSync(shardPath);
|
|
153
|
+
}
|
|
154
|
+
return { removed, kept };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Stats: count, total bytes, oldest entry timestamp.
|
|
159
|
+
*/
|
|
160
|
+
function stats(projectRoot) {
|
|
161
|
+
const dir = cacheDir(projectRoot);
|
|
162
|
+
if (!fs.existsSync(dir)) return { count: 0, totalBytes: 0, oldestTs: null };
|
|
163
|
+
|
|
164
|
+
let count = 0;
|
|
165
|
+
let totalBytes = 0;
|
|
166
|
+
let oldestTs = null;
|
|
167
|
+
for (const shard of fs.readdirSync(dir)) {
|
|
168
|
+
const shardPath = path.join(dir, shard);
|
|
169
|
+
if (!fs.statSync(shardPath).isDirectory()) continue;
|
|
170
|
+
for (const fname of fs.readdirSync(shardPath)) {
|
|
171
|
+
const fpath = path.join(shardPath, fname);
|
|
172
|
+
const stat = fs.statSync(fpath);
|
|
173
|
+
totalBytes += stat.size;
|
|
174
|
+
count += 1;
|
|
175
|
+
try {
|
|
176
|
+
const entry = JSON.parse(fs.readFileSync(fpath, 'utf8'));
|
|
177
|
+
if (!oldestTs || entry.ts < oldestTs) oldestTs = entry.ts;
|
|
178
|
+
} catch (e) { /* skip */ }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return { count, totalBytes, oldestTs };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
cacheDir,
|
|
186
|
+
key,
|
|
187
|
+
get,
|
|
188
|
+
put,
|
|
189
|
+
has,
|
|
190
|
+
clear,
|
|
191
|
+
stats,
|
|
192
|
+
isExpired,
|
|
193
|
+
DEFAULT_TTL_MS
|
|
194
|
+
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent Validator
|
|
3
|
+
*
|
|
4
|
+
* Parses each `agents/*.md` and validates contract structure.
|
|
5
|
+
*
|
|
6
|
+
* Backward-compatible by design: most issues surface as `warning` so
|
|
7
|
+
* existing agents pass on day one. Only structural breakage (broken
|
|
8
|
+
* hand-off targets, dual-ownership of output paths) escalates to
|
|
9
|
+
* `error`.
|
|
10
|
+
*
|
|
11
|
+
* Public API:
|
|
12
|
+
* parseAgentFile(filePath) -> { name, frontmatter, sections, raw }
|
|
13
|
+
* validateAgent(agent, opts) -> [findings]
|
|
14
|
+
* findHandoffTargets(agent) -> [...names]
|
|
15
|
+
* findOutputPaths(agent) -> [...paths]
|
|
16
|
+
* auditAll(projectRoot, opts) -> { results, summary }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const REQUIRED_FRONTMATTER = ['name', 'description'];
|
|
23
|
+
const RECOMMENDED_FRONTMATTER = ['tools'];
|
|
24
|
+
const RECOMMENDED_SECTIONS = ['Have-Nots', 'Inputs', 'Outputs', 'Handoff'];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Parse a single agent .md file. Extract YAML frontmatter and section
|
|
28
|
+
* headings (# / ## / ###).
|
|
29
|
+
*/
|
|
30
|
+
function parseAgentFile(filePath) {
|
|
31
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
32
|
+
const result = {
|
|
33
|
+
path: filePath,
|
|
34
|
+
name: path.basename(filePath, '.md'),
|
|
35
|
+
frontmatter: {},
|
|
36
|
+
sections: {}, // heading -> body string
|
|
37
|
+
raw
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Frontmatter block: --- ... ---
|
|
41
|
+
if (raw.startsWith('---')) {
|
|
42
|
+
const end = raw.indexOf('\n---', 3);
|
|
43
|
+
if (end > 0) {
|
|
44
|
+
const fmBlock = raw.slice(3, end).trim();
|
|
45
|
+
// Parse simple YAML (key: value, key: |, etc.)
|
|
46
|
+
let currentMultiline = null;
|
|
47
|
+
for (const line of fmBlock.split('\n')) {
|
|
48
|
+
if (currentMultiline) {
|
|
49
|
+
if (line.startsWith(' ') || line.startsWith('\t')) {
|
|
50
|
+
result.frontmatter[currentMultiline] += '\n' + line.trim();
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
currentMultiline = null;
|
|
54
|
+
}
|
|
55
|
+
const m = line.match(/^([\w-]+):\s*(.*)$/);
|
|
56
|
+
if (m) {
|
|
57
|
+
if (m[2] === '|' || m[2] === '>') {
|
|
58
|
+
currentMultiline = m[1];
|
|
59
|
+
result.frontmatter[m[1]] = '';
|
|
60
|
+
} else {
|
|
61
|
+
result.frontmatter[m[1]] = m[2].trim();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Sections: lines starting with # / ## / ###
|
|
69
|
+
const lines = raw.split('\n');
|
|
70
|
+
let currentHeading = null;
|
|
71
|
+
let currentBody = [];
|
|
72
|
+
for (const line of lines) {
|
|
73
|
+
const m = line.match(/^(#{1,4})\s+(.+?)\s*$/);
|
|
74
|
+
if (m) {
|
|
75
|
+
if (currentHeading) {
|
|
76
|
+
result.sections[currentHeading] = currentBody.join('\n').trim();
|
|
77
|
+
}
|
|
78
|
+
currentHeading = m[2].trim();
|
|
79
|
+
currentBody = [];
|
|
80
|
+
} else if (currentHeading) {
|
|
81
|
+
currentBody.push(line);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (currentHeading) {
|
|
85
|
+
result.sections[currentHeading] = currentBody.join('\n').trim();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Validate one parsed agent. Returns findings.
|
|
93
|
+
*/
|
|
94
|
+
function validateAgent(agent, opts = {}) {
|
|
95
|
+
const findings = [];
|
|
96
|
+
|
|
97
|
+
// Required frontmatter
|
|
98
|
+
for (const field of REQUIRED_FRONTMATTER) {
|
|
99
|
+
if (!agent.frontmatter[field]) {
|
|
100
|
+
findings.push({
|
|
101
|
+
severity: 'error',
|
|
102
|
+
kind: 'missing-required-frontmatter',
|
|
103
|
+
agent: agent.name,
|
|
104
|
+
message: `Missing required frontmatter field: \`${field}\``
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Recommended frontmatter
|
|
110
|
+
for (const field of RECOMMENDED_FRONTMATTER) {
|
|
111
|
+
if (!agent.frontmatter[field]) {
|
|
112
|
+
findings.push({
|
|
113
|
+
severity: 'warning',
|
|
114
|
+
kind: 'missing-recommended-frontmatter',
|
|
115
|
+
agent: agent.name,
|
|
116
|
+
message: `Missing recommended frontmatter field: \`${field}\``
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Recommended sections (case-insensitive substring match)
|
|
122
|
+
const sectionTitles = Object.keys(agent.sections).map(s => s.toLowerCase());
|
|
123
|
+
for (const section of RECOMMENDED_SECTIONS) {
|
|
124
|
+
const lower = section.toLowerCase();
|
|
125
|
+
const found = sectionTitles.some(t =>
|
|
126
|
+
t === lower || t.includes(lower) || (lower === 'handoff' && t.includes('hand'))
|
|
127
|
+
);
|
|
128
|
+
if (!found) {
|
|
129
|
+
findings.push({
|
|
130
|
+
severity: 'info',
|
|
131
|
+
kind: 'missing-recommended-section',
|
|
132
|
+
agent: agent.name,
|
|
133
|
+
message: `Missing recommended section: \`## ${section}\``,
|
|
134
|
+
suggestion: `Add a "${section}" section documenting the contract.`
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return findings;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Extract claimed hand-off targets (other agents this agent spawns).
|
|
144
|
+
*/
|
|
145
|
+
function findHandoffTargets(agent) {
|
|
146
|
+
const targets = new Set();
|
|
147
|
+
// Look for "spawn god-x", "Spawned by:", "spawns: [god-y, god-z]"
|
|
148
|
+
const text = agent.raw;
|
|
149
|
+
|
|
150
|
+
// "Spawned by:" indicates UPSTREAM (who spawns me); skip
|
|
151
|
+
// We want DOWNSTREAM: who do I spawn?
|
|
152
|
+
|
|
153
|
+
const spawnRegex = /spawn(?:s|ed|ing)?\s+(?:a\s+)?(?:fresh\s+)?`?(god-[\w-]+)`?/gi;
|
|
154
|
+
let m;
|
|
155
|
+
while ((m = spawnRegex.exec(text)) !== null) {
|
|
156
|
+
targets.add(m[1]);
|
|
157
|
+
}
|
|
158
|
+
return [...targets];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Extract output paths the agent claims to write.
|
|
163
|
+
*/
|
|
164
|
+
function findOutputPaths(agent) {
|
|
165
|
+
const paths = new Set();
|
|
166
|
+
const text = agent.raw;
|
|
167
|
+
|
|
168
|
+
// .godpowers/*.md or .godpowers/*.json
|
|
169
|
+
const dotPathRegex = /(?:writes?|appends?|updates?)[\s\S]{0,80}?(\.godpowers\/[\w\/.-]+\.(?:md|json|jsonl|yaml))/gi;
|
|
170
|
+
let m;
|
|
171
|
+
while ((m = dotPathRegex.exec(text)) !== null) {
|
|
172
|
+
paths.add(m[1]);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Project-root files (DESIGN.md, PRODUCT.md, AGENTS.md, etc.)
|
|
176
|
+
const rootRegex = /(?:writes?|appends?|updates?)[\s\S]{0,80}?\b([A-Z]+\.md)\b/g;
|
|
177
|
+
while ((m = rootRegex.exec(text)) !== null) {
|
|
178
|
+
paths.add(m[1]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return [...paths];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Cross-agent validation: dual-ownership of output paths, unresolved
|
|
186
|
+
* hand-off targets.
|
|
187
|
+
*/
|
|
188
|
+
function crossValidate(agents, opts = {}) {
|
|
189
|
+
const findings = [];
|
|
190
|
+
const validAgentNames = new Set(agents.map(a => a.name));
|
|
191
|
+
const outputOwners = {}; // path -> [agent.name, ...]
|
|
192
|
+
|
|
193
|
+
for (const agent of agents) {
|
|
194
|
+
// Hand-off targets must exist
|
|
195
|
+
for (const target of findHandoffTargets(agent)) {
|
|
196
|
+
if (!validAgentNames.has(target)) {
|
|
197
|
+
findings.push({
|
|
198
|
+
severity: 'warning',
|
|
199
|
+
kind: 'unresolved-handoff',
|
|
200
|
+
agent: agent.name,
|
|
201
|
+
message: `Agent claims to spawn \`${target}\` but no such agent file exists.`
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Track output paths
|
|
207
|
+
for (const out of findOutputPaths(agent)) {
|
|
208
|
+
if (!outputOwners[out]) outputOwners[out] = [];
|
|
209
|
+
if (!outputOwners[out].includes(agent.name)) {
|
|
210
|
+
outputOwners[out].push(agent.name);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Dual-ownership check (warn for paths claimed by 3+ agents; the
|
|
216
|
+
// orchestrator + reconciler + author triumvirate is normal pattern)
|
|
217
|
+
for (const [outPath, owners] of Object.entries(outputOwners)) {
|
|
218
|
+
if (owners.length >= 4) {
|
|
219
|
+
findings.push({
|
|
220
|
+
severity: 'warning',
|
|
221
|
+
kind: 'multi-ownership',
|
|
222
|
+
path: outPath,
|
|
223
|
+
owners,
|
|
224
|
+
message: `Output \`${outPath}\` is claimed by ${owners.length} agents: ${owners.join(', ')}. Verify the boundaries are clean.`
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return findings;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Walk agents/ and audit all.
|
|
234
|
+
*/
|
|
235
|
+
function auditAll(projectRoot, opts = {}) {
|
|
236
|
+
const agentsDir = path.join(projectRoot, 'agents');
|
|
237
|
+
if (!fs.existsSync(agentsDir)) {
|
|
238
|
+
return { results: [], summary: { errors: 0, warnings: 0, infos: 0 } };
|
|
239
|
+
}
|
|
240
|
+
const files = fs.readdirSync(agentsDir).filter(f => f.endsWith('.md'));
|
|
241
|
+
const agents = files.map(f => parseAgentFile(path.join(agentsDir, f)));
|
|
242
|
+
|
|
243
|
+
const allFindings = [];
|
|
244
|
+
const results = [];
|
|
245
|
+
for (const agent of agents) {
|
|
246
|
+
const f = validateAgent(agent, opts);
|
|
247
|
+
results.push({ agent: agent.name, findings: f });
|
|
248
|
+
allFindings.push(...f);
|
|
249
|
+
}
|
|
250
|
+
// Cross-agent
|
|
251
|
+
const crossFindings = crossValidate(agents, opts);
|
|
252
|
+
allFindings.push(...crossFindings);
|
|
253
|
+
|
|
254
|
+
const summary = {
|
|
255
|
+
errors: allFindings.filter(f => f.severity === 'error').length,
|
|
256
|
+
warnings: allFindings.filter(f => f.severity === 'warning').length,
|
|
257
|
+
infos: allFindings.filter(f => f.severity === 'info').length,
|
|
258
|
+
crossFindings,
|
|
259
|
+
agentCount: agents.length
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
return { results, summary, allFindings };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
module.exports = {
|
|
266
|
+
parseAgentFile,
|
|
267
|
+
validateAgent,
|
|
268
|
+
findHandoffTargets,
|
|
269
|
+
findOutputPaths,
|
|
270
|
+
crossValidate,
|
|
271
|
+
auditAll,
|
|
272
|
+
REQUIRED_FRONTMATTER,
|
|
273
|
+
RECOMMENDED_FRONTMATTER,
|
|
274
|
+
RECOMMENDED_SECTIONS
|
|
275
|
+
};
|