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,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cost Tracker - token + dollar accounting for godpowers runs.
|
|
3
|
+
*
|
|
4
|
+
* The model.call event already exists in the vocabulary. This module
|
|
5
|
+
* adds a typed recorder, per-tier / per-agent / per-model aggregation,
|
|
6
|
+
* and a savings counter (cache hits = tokens that would have been
|
|
7
|
+
* spent but weren't).
|
|
8
|
+
*
|
|
9
|
+
* Token costs surface in two places:
|
|
10
|
+
* 1. Real spend: every LLM call records tokens_in / tokens_out / cost_usd
|
|
11
|
+
* 2. Avoided spend: cache.hit events record savings_tokens / savings_usd
|
|
12
|
+
*
|
|
13
|
+
* Public API:
|
|
14
|
+
* recordCost(handle, attrs) - emit a cost.recorded event
|
|
15
|
+
* recordCacheHit(handle, attrs) - emit a cache.hit event with savings
|
|
16
|
+
* recordCacheMiss(handle, attrs) - emit a cache.miss event
|
|
17
|
+
* aggregate(projectRoot, runIds?) -> { perTier, perAgent, perModel, totals }
|
|
18
|
+
* priceTokens({model, in, out}) -> usd estimate
|
|
19
|
+
*
|
|
20
|
+
* Pricing table is approximate, in USD per 1M tokens, May 2026 ballpark.
|
|
21
|
+
* Overridable via opts.pricing in aggregate().
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const events = require('./events');
|
|
25
|
+
|
|
26
|
+
const DEFAULT_PRICING = {
|
|
27
|
+
// Per 1M tokens (input, output). Keep approximate; user can override.
|
|
28
|
+
'claude-3-5-sonnet': { in: 3.00, out: 15.00 },
|
|
29
|
+
'claude-3-5-haiku': { in: 0.80, out: 4.00 },
|
|
30
|
+
'claude-3-opus': { in: 15.00, out: 75.00 },
|
|
31
|
+
'claude-4': { in: 5.00, out: 25.00 }, // estimate
|
|
32
|
+
'gpt-4o': { in: 2.50, out: 10.00 },
|
|
33
|
+
'gpt-4o-mini': { in: 0.15, out: 0.60 },
|
|
34
|
+
'gpt-4-turbo': { in: 10.00, out: 30.00 },
|
|
35
|
+
'gpt-5': { in: 5.00, out: 20.00 }, // estimate
|
|
36
|
+
'gemini-1.5-pro': { in: 3.50, out: 10.50 },
|
|
37
|
+
'gemini-1.5-flash': { in: 0.075, out: 0.30 },
|
|
38
|
+
'o1': { in: 15.00, out: 60.00 },
|
|
39
|
+
'o3-mini': { in: 3.00, out: 12.00 },
|
|
40
|
+
// Fallback bucket
|
|
41
|
+
'_unknown': { in: 5.00, out: 15.00 }
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Compute USD cost from token counts.
|
|
46
|
+
*/
|
|
47
|
+
function priceTokens({ model, in: inTok, out: outTok, pricing }) {
|
|
48
|
+
const table = pricing || DEFAULT_PRICING;
|
|
49
|
+
const p = table[model] || table[normalizeModel(model)] || table._unknown;
|
|
50
|
+
const inCost = (inTok || 0) * p.in / 1_000_000;
|
|
51
|
+
const outCost = (outTok || 0) * p.out / 1_000_000;
|
|
52
|
+
return Number((inCost + outCost).toFixed(6));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeModel(m) {
|
|
56
|
+
if (!m) return '_unknown';
|
|
57
|
+
const s = String(m).toLowerCase();
|
|
58
|
+
// Order matters: "gemini" contains "mini" as a substring, and "claude" can
|
|
59
|
+
// contain "o" etc. Check the most specific tokens first.
|
|
60
|
+
if (s.includes('haiku')) return 'claude-3-5-haiku';
|
|
61
|
+
if (s.includes('sonnet')) return 'claude-3-5-sonnet';
|
|
62
|
+
if (s.includes('opus')) return 'claude-3-opus';
|
|
63
|
+
if (s.includes('flash')) return 'gemini-1.5-flash';
|
|
64
|
+
if (s.includes('gemini')) return 'gemini-1.5-pro';
|
|
65
|
+
if (s.includes('claude')) return 'claude-4';
|
|
66
|
+
if (s.includes('4o-mini') || s.endsWith('-mini')) return 'gpt-4o-mini';
|
|
67
|
+
if (s.includes('4o')) return 'gpt-4o';
|
|
68
|
+
if (s.includes('gpt-5') || s === 'gpt5') return 'gpt-5';
|
|
69
|
+
if (s.includes('gpt-4') || s === 'gpt4') return 'gpt-4-turbo';
|
|
70
|
+
if (s.includes('o3')) return 'o3-mini';
|
|
71
|
+
if (s.includes('o1')) return 'o1';
|
|
72
|
+
return '_unknown';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Record a real cost (spend that happened). The caller has a handle
|
|
77
|
+
* from events.startRun.
|
|
78
|
+
*
|
|
79
|
+
* attrs: {
|
|
80
|
+
* model, tokens_in, tokens_out,
|
|
81
|
+
* agent?, tier?,
|
|
82
|
+
* cost_usd? (optional explicit override),
|
|
83
|
+
* source? ('live' | 'estimated', default 'estimated')
|
|
84
|
+
* }
|
|
85
|
+
*
|
|
86
|
+
* The `source` field distinguishes real per-call token counts reported
|
|
87
|
+
* by the AI tool ('live') from heuristic byte-based estimates the
|
|
88
|
+
* orchestrator computes itself ('estimated'). `/god-cost --strict`
|
|
89
|
+
* uses this to fail loudly when any record is estimated.
|
|
90
|
+
*/
|
|
91
|
+
function recordCost(handle, attrs) {
|
|
92
|
+
const source = attrs.source === 'live' ? 'live' : 'estimated';
|
|
93
|
+
const cost_usd = attrs.cost_usd != null
|
|
94
|
+
? attrs.cost_usd
|
|
95
|
+
: priceTokens({ model: attrs.model, in: attrs.tokens_in, out: attrs.tokens_out, pricing: attrs.pricing });
|
|
96
|
+
handle.emit({
|
|
97
|
+
span_id: attrs.span_id || handle.rootSpanId,
|
|
98
|
+
name: 'cost.recorded',
|
|
99
|
+
attrs: {
|
|
100
|
+
model: attrs.model,
|
|
101
|
+
tokens_in: attrs.tokens_in || 0,
|
|
102
|
+
tokens_out: attrs.tokens_out || 0,
|
|
103
|
+
cost_usd,
|
|
104
|
+
agent: attrs.agent,
|
|
105
|
+
tier: attrs.tier,
|
|
106
|
+
source
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
return cost_usd;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Canonical entry point for AI tools that have actual per-call token
|
|
114
|
+
* counts from the provider's API response. Records a 'live' cost.
|
|
115
|
+
*
|
|
116
|
+
* attrs: { model, tokens_in, tokens_out, agent?, tier?, cost_usd? }
|
|
117
|
+
*
|
|
118
|
+
* Use this rather than recordCost() directly when you have real
|
|
119
|
+
* usage numbers; the `source: 'live'` tag is what lets /god-cost
|
|
120
|
+
* --strict trust the totals.
|
|
121
|
+
*/
|
|
122
|
+
function recordModelCall(handle, attrs) {
|
|
123
|
+
return recordCost(handle, { ...attrs, source: 'live' });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Record a cache hit. Estimates the savings as what the spend would
|
|
128
|
+
* have been if the agent had been spawned.
|
|
129
|
+
*/
|
|
130
|
+
function recordCacheHit(handle, attrs) {
|
|
131
|
+
const savings_usd = attrs.savings_usd != null
|
|
132
|
+
? attrs.savings_usd
|
|
133
|
+
: priceTokens({
|
|
134
|
+
model: attrs.model,
|
|
135
|
+
in: attrs.would_have_spent_in || 0,
|
|
136
|
+
out: attrs.would_have_spent_out || 0,
|
|
137
|
+
pricing: attrs.pricing
|
|
138
|
+
});
|
|
139
|
+
handle.emit({
|
|
140
|
+
span_id: attrs.span_id || handle.rootSpanId,
|
|
141
|
+
name: 'cache.hit',
|
|
142
|
+
attrs: {
|
|
143
|
+
cache_key: attrs.cache_key,
|
|
144
|
+
agent: attrs.agent,
|
|
145
|
+
tier: attrs.tier,
|
|
146
|
+
model: attrs.model,
|
|
147
|
+
savings_tokens: (attrs.would_have_spent_in || 0) + (attrs.would_have_spent_out || 0),
|
|
148
|
+
savings_usd
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
return savings_usd;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function recordCacheMiss(handle, attrs) {
|
|
155
|
+
handle.emit({
|
|
156
|
+
span_id: attrs.span_id || handle.rootSpanId,
|
|
157
|
+
name: 'cache.miss',
|
|
158
|
+
attrs: {
|
|
159
|
+
cache_key: attrs.cache_key,
|
|
160
|
+
agent: attrs.agent,
|
|
161
|
+
tier: attrs.tier,
|
|
162
|
+
reason: attrs.reason || 'no-entry'
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Aggregate cost events across one or all runs.
|
|
169
|
+
*
|
|
170
|
+
* Returns:
|
|
171
|
+
* {
|
|
172
|
+
* totals: {
|
|
173
|
+
* spent_usd, spent_tokens, saved_usd, saved_tokens,
|
|
174
|
+
* cache_hits, cache_misses, hit_rate, calls
|
|
175
|
+
* },
|
|
176
|
+
* perTier: { 'tier-1': {spent_usd, spent_tokens, calls, ...}, ... },
|
|
177
|
+
* perAgent: { 'god-pm': {...}, ... },
|
|
178
|
+
* perModel: { 'claude-3-5-sonnet': {...}, ... }
|
|
179
|
+
* }
|
|
180
|
+
*/
|
|
181
|
+
function aggregate(projectRoot, runIds) {
|
|
182
|
+
if (!runIds) runIds = events.listRuns(projectRoot);
|
|
183
|
+
if (!Array.isArray(runIds)) runIds = [runIds];
|
|
184
|
+
|
|
185
|
+
const totals = {
|
|
186
|
+
spent_usd: 0, spent_tokens: 0,
|
|
187
|
+
saved_usd: 0, saved_tokens: 0,
|
|
188
|
+
cache_hits: 0, cache_misses: 0, hit_rate: 0,
|
|
189
|
+
calls: 0,
|
|
190
|
+
// Split by data source. live = AI tool reported real token counts;
|
|
191
|
+
// estimated = orchestrator's heuristic byte-based estimate.
|
|
192
|
+
live_calls: 0, live_usd: 0, live_tokens: 0,
|
|
193
|
+
estimated_calls: 0, estimated_usd: 0, estimated_tokens: 0
|
|
194
|
+
};
|
|
195
|
+
const perTier = {};
|
|
196
|
+
const perAgent = {};
|
|
197
|
+
const perModel = {};
|
|
198
|
+
|
|
199
|
+
function bump(bucket, key, fields) {
|
|
200
|
+
if (!bucket[key]) {
|
|
201
|
+
bucket[key] = { spent_usd: 0, spent_tokens: 0, saved_usd: 0,
|
|
202
|
+
saved_tokens: 0, calls: 0, cache_hits: 0 };
|
|
203
|
+
}
|
|
204
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
205
|
+
bucket[key][k] = (bucket[key][k] || 0) + v;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for (const runId of runIds) {
|
|
210
|
+
for (const e of events.readRun(projectRoot, runId)) {
|
|
211
|
+
if (e.name === 'cost.recorded') {
|
|
212
|
+
const a = e.attrs || {};
|
|
213
|
+
const usd = a.cost_usd || 0;
|
|
214
|
+
const tok = (a.tokens_in || 0) + (a.tokens_out || 0);
|
|
215
|
+
const source = a.source === 'live' ? 'live' : 'estimated';
|
|
216
|
+
totals.spent_usd += usd;
|
|
217
|
+
totals.spent_tokens += tok;
|
|
218
|
+
totals.calls += 1;
|
|
219
|
+
if (source === 'live') {
|
|
220
|
+
totals.live_calls += 1;
|
|
221
|
+
totals.live_usd += usd;
|
|
222
|
+
totals.live_tokens += tok;
|
|
223
|
+
} else {
|
|
224
|
+
totals.estimated_calls += 1;
|
|
225
|
+
totals.estimated_usd += usd;
|
|
226
|
+
totals.estimated_tokens += tok;
|
|
227
|
+
}
|
|
228
|
+
if (a.tier) bump(perTier, a.tier, { spent_usd: usd, spent_tokens: tok, calls: 1 });
|
|
229
|
+
if (a.agent) bump(perAgent, a.agent, { spent_usd: usd, spent_tokens: tok, calls: 1 });
|
|
230
|
+
if (a.model) bump(perModel, a.model, { spent_usd: usd, spent_tokens: tok, calls: 1 });
|
|
231
|
+
} else if (e.name === 'cache.hit') {
|
|
232
|
+
const a = e.attrs || {};
|
|
233
|
+
const usd = a.savings_usd || 0;
|
|
234
|
+
const tok = a.savings_tokens || 0;
|
|
235
|
+
totals.saved_usd += usd;
|
|
236
|
+
totals.saved_tokens += tok;
|
|
237
|
+
totals.cache_hits += 1;
|
|
238
|
+
if (a.tier) bump(perTier, a.tier, { saved_usd: usd, saved_tokens: tok, cache_hits: 1 });
|
|
239
|
+
if (a.agent) bump(perAgent, a.agent, { saved_usd: usd, saved_tokens: tok, cache_hits: 1 });
|
|
240
|
+
} else if (e.name === 'cache.miss') {
|
|
241
|
+
totals.cache_misses += 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const attempts = totals.cache_hits + totals.cache_misses;
|
|
247
|
+
totals.hit_rate = attempts > 0 ? totals.cache_hits / attempts : 0;
|
|
248
|
+
for (const m of ['spent_usd', 'saved_usd', 'live_usd', 'estimated_usd']) {
|
|
249
|
+
totals[m] = Number(totals[m].toFixed(6));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { totals, perTier, perAgent, perModel };
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Check whether every cost.recorded event has source: 'live'.
|
|
257
|
+
*
|
|
258
|
+
* Returns { strict: bool, live_calls, estimated_calls, total_calls }.
|
|
259
|
+
* Used by /god-cost --strict to fail when any record is estimated.
|
|
260
|
+
*/
|
|
261
|
+
function isStrictLive(projectRoot, runIds) {
|
|
262
|
+
const agg = aggregate(projectRoot, runIds);
|
|
263
|
+
const t = agg.totals;
|
|
264
|
+
return {
|
|
265
|
+
strict: t.calls > 0 && t.estimated_calls === 0,
|
|
266
|
+
live_calls: t.live_calls,
|
|
267
|
+
estimated_calls: t.estimated_calls,
|
|
268
|
+
total_calls: t.calls
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Format an aggregate as a readable report.
|
|
274
|
+
*/
|
|
275
|
+
function formatReport(agg) {
|
|
276
|
+
const t = agg.totals;
|
|
277
|
+
const lines = [];
|
|
278
|
+
lines.push('GODPOWERS COST REPORT');
|
|
279
|
+
lines.push('');
|
|
280
|
+
lines.push(`Spent: $${t.spent_usd.toFixed(4)} across ${t.calls} model calls (${t.spent_tokens} tokens)`);
|
|
281
|
+
if (t.live_calls > 0 || t.estimated_calls > 0) {
|
|
282
|
+
lines.push(` Live: $${t.live_usd.toFixed(4)} (${t.live_calls} calls, ${t.live_tokens} tokens)`);
|
|
283
|
+
lines.push(` Estimated: $${t.estimated_usd.toFixed(4)} (${t.estimated_calls} calls, ${t.estimated_tokens} tokens)`);
|
|
284
|
+
}
|
|
285
|
+
lines.push(`Saved: $${t.saved_usd.toFixed(4)} via ${t.cache_hits} cache hits (${t.saved_tokens} tokens)`);
|
|
286
|
+
lines.push(`Cache hit rate: ${(t.hit_rate * 100).toFixed(1)}% (${t.cache_hits}/${t.cache_hits + t.cache_misses})`);
|
|
287
|
+
lines.push('');
|
|
288
|
+
if (Object.keys(agg.perTier).length > 0) {
|
|
289
|
+
lines.push('Per tier:');
|
|
290
|
+
for (const [k, v] of Object.entries(agg.perTier).sort()) {
|
|
291
|
+
lines.push(` ${k}: $${v.spent_usd.toFixed(4)} (${v.calls} calls, ${v.cache_hits} hits, $${v.saved_usd.toFixed(4)} saved)`);
|
|
292
|
+
}
|
|
293
|
+
lines.push('');
|
|
294
|
+
}
|
|
295
|
+
if (Object.keys(agg.perAgent).length > 0) {
|
|
296
|
+
lines.push('Per agent (top 10 by spend):');
|
|
297
|
+
const sorted = Object.entries(agg.perAgent)
|
|
298
|
+
.sort((a, b) => b[1].spent_usd - a[1].spent_usd)
|
|
299
|
+
.slice(0, 10);
|
|
300
|
+
for (const [k, v] of sorted) {
|
|
301
|
+
lines.push(` ${k}: $${v.spent_usd.toFixed(4)} (${v.calls} calls)`);
|
|
302
|
+
}
|
|
303
|
+
lines.push('');
|
|
304
|
+
}
|
|
305
|
+
if (Object.keys(agg.perModel).length > 0) {
|
|
306
|
+
lines.push('Per model:');
|
|
307
|
+
for (const [k, v] of Object.entries(agg.perModel).sort()) {
|
|
308
|
+
lines.push(` ${k}: $${v.spent_usd.toFixed(4)} (${v.calls} calls)`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return lines.join('\n');
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
recordCost,
|
|
316
|
+
recordModelCall,
|
|
317
|
+
recordCacheHit,
|
|
318
|
+
recordCacheMiss,
|
|
319
|
+
aggregate,
|
|
320
|
+
isStrictLive,
|
|
321
|
+
formatReport,
|
|
322
|
+
priceTokens,
|
|
323
|
+
normalizeModel,
|
|
324
|
+
DEFAULT_PRICING
|
|
325
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Artifact Impact
|
|
3
|
+
*
|
|
4
|
+
* Generalizes the per-pair impact pattern (DESIGN -> code) to
|
|
5
|
+
* artifact-to-artifact relationships:
|
|
6
|
+
* PRD -> ARCH (requirement removal may leave containers over-spec'd)
|
|
7
|
+
* ARCH -> DESIGN (container split may need DESIGN component re-binding)
|
|
8
|
+
* ARCH -> ROADMAP (container removal may invalidate milestones)
|
|
9
|
+
* STACK -> ARCH (dep change may force ADR flip-point review)
|
|
10
|
+
* STACK -> DESIGN (UI lib change may force token review)
|
|
11
|
+
* PRD -> ROADMAP (requirement change may invalidate milestone gates)
|
|
12
|
+
*
|
|
13
|
+
* Public API:
|
|
14
|
+
* forArtifactPair(projectRoot, sourceType, oldContent, newContent, targetType, targetContent)
|
|
15
|
+
* -> { impacts, severity }
|
|
16
|
+
* suggestArtifactReviews(projectRoot, sourceType, oldContent, newContent)
|
|
17
|
+
* -> [{ targetType, reason, severity }]
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const impact = require('./impact');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Define which target artifacts may be impacted by a source artifact change.
|
|
24
|
+
*/
|
|
25
|
+
const IMPACT_RULES = {
|
|
26
|
+
prd: [
|
|
27
|
+
{
|
|
28
|
+
target: 'arch',
|
|
29
|
+
check: (idDiff) => idDiff.removed.length > 0,
|
|
30
|
+
reason: (idDiff) => `${idDiff.removed.length} PRD requirement(s) removed; ARCH containers may be over-spec'd`,
|
|
31
|
+
severity: 'warning'
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
target: 'roadmap',
|
|
35
|
+
check: (idDiff) => idDiff.removed.length > 0 || idDiff.added.length > 0,
|
|
36
|
+
reason: (idDiff) => `PRD requirements changed; ROADMAP milestone gates may need updating`,
|
|
37
|
+
severity: 'warning'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
target: 'design',
|
|
41
|
+
check: (idDiff, sectionChanges) => sectionChanges.some(c => c.section && c.section.match(/Target Users|Brand|Register/i)),
|
|
42
|
+
reason: () => `PRD target users / register changed; DESIGN PRODUCT.md may need re-aligning`,
|
|
43
|
+
severity: 'info'
|
|
44
|
+
}
|
|
45
|
+
],
|
|
46
|
+
arch: [
|
|
47
|
+
{
|
|
48
|
+
target: 'design',
|
|
49
|
+
check: (idDiff) => idDiff.removed.some(id => id.startsWith('C-')),
|
|
50
|
+
reason: (idDiff) => `ARCH container removed; DESIGN components bound to it may need re-binding`,
|
|
51
|
+
severity: 'warning'
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
target: 'roadmap',
|
|
55
|
+
check: (idDiff) => idDiff.removed.length > 0,
|
|
56
|
+
reason: () => `ARCH structure changed; ROADMAP milestone gates may reference removed elements`,
|
|
57
|
+
severity: 'info'
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
stack: [
|
|
61
|
+
{
|
|
62
|
+
target: 'arch',
|
|
63
|
+
check: (idDiff) => idDiff.removed.length > 0 || idDiff.added.length > 0,
|
|
64
|
+
reason: () => `STACK changes; ADR flip-points may need review`,
|
|
65
|
+
severity: 'warning'
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
target: 'design',
|
|
69
|
+
check: (idDiff, sectionChanges, oldContent, newContent) => {
|
|
70
|
+
// Check if a UI framework changed
|
|
71
|
+
const oldUi = /react|vue|svelte|next|nuxt|angular|solid|qwik|astro|tailwind|shadcn|mui|chakra/i;
|
|
72
|
+
const newUi = oldUi;
|
|
73
|
+
const hadUi = oldContent && oldUi.test(oldContent);
|
|
74
|
+
const hasUi = newContent && newUi.test(newContent);
|
|
75
|
+
return hadUi !== hasUi || (hadUi && hasUi && oldContent !== newContent);
|
|
76
|
+
},
|
|
77
|
+
reason: () => `STACK UI library may have changed; DESIGN tokens may need recomputation`,
|
|
78
|
+
severity: 'info'
|
|
79
|
+
}
|
|
80
|
+
],
|
|
81
|
+
design: [
|
|
82
|
+
{
|
|
83
|
+
target: 'arch',
|
|
84
|
+
check: (idDiff) => idDiff.added.some(id => id.startsWith('D-')) || idDiff.removed.some(id => id.startsWith('D-')),
|
|
85
|
+
reason: () => `DESIGN components changed; ARCH UI surface descriptions may need updating`,
|
|
86
|
+
severity: 'info'
|
|
87
|
+
}
|
|
88
|
+
],
|
|
89
|
+
roadmap: [
|
|
90
|
+
// Roadmap changes are usually informational; rarely break upstream
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* For a given source artifact change, suggest which downstream artifacts
|
|
96
|
+
* to review.
|
|
97
|
+
*
|
|
98
|
+
* Returns: [{ targetType, reason, severity }]
|
|
99
|
+
*/
|
|
100
|
+
function suggestArtifactReviews(projectRoot, sourceType, oldContent, newContent) {
|
|
101
|
+
const rules = IMPACT_RULES[sourceType] || [];
|
|
102
|
+
const idDiff = impact.diffIds(sourceType, oldContent, newContent);
|
|
103
|
+
const results = [];
|
|
104
|
+
|
|
105
|
+
// Compute section diff for prose-driven rules
|
|
106
|
+
const artifactDiff = require('./artifact-diff');
|
|
107
|
+
const sectionDiffResult = artifactDiff.diffArtifacts(oldContent || '', newContent || '');
|
|
108
|
+
const sectionChanges = sectionDiffResult.changes || [];
|
|
109
|
+
|
|
110
|
+
for (const rule of rules) {
|
|
111
|
+
let triggered;
|
|
112
|
+
try {
|
|
113
|
+
triggered = rule.check(idDiff, sectionChanges, oldContent, newContent);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
triggered = false;
|
|
116
|
+
}
|
|
117
|
+
if (triggered) {
|
|
118
|
+
results.push({
|
|
119
|
+
targetType: rule.target,
|
|
120
|
+
reason: rule.reason(idDiff, sectionChanges),
|
|
121
|
+
severity: rule.severity
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return results;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Specific pairwise impact: given a source change AND a target artifact
|
|
130
|
+
* content, report what specifically might need to change in the target.
|
|
131
|
+
*/
|
|
132
|
+
function forArtifactPair(projectRoot, sourceType, oldContent, newContent, targetType, targetContent) {
|
|
133
|
+
const suggestions = suggestArtifactReviews(projectRoot, sourceType, oldContent, newContent);
|
|
134
|
+
const matched = suggestions.find(s => s.targetType === targetType);
|
|
135
|
+
if (!matched) {
|
|
136
|
+
return { impacts: [], severity: 'info' };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Extract IDs from both source and target
|
|
140
|
+
const sourceDiff = impact.diffIds(sourceType, oldContent, newContent);
|
|
141
|
+
const targetIds = impact.extractIds(targetType, targetContent || '');
|
|
142
|
+
|
|
143
|
+
// Find target IDs that mention removed source IDs
|
|
144
|
+
const impacts = [];
|
|
145
|
+
for (const removed of sourceDiff.removed) {
|
|
146
|
+
if (targetContent && targetContent.includes(removed)) {
|
|
147
|
+
impacts.push({
|
|
148
|
+
kind: 'cross-reference',
|
|
149
|
+
sourceId: removed,
|
|
150
|
+
targetReference: 'mentioned in target',
|
|
151
|
+
message: `Source ${removed} was removed; target ${targetType} still mentions it.`
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { impacts, severity: matched.severity, summary: matched.reason };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = {
|
|
159
|
+
IMPACT_RULES,
|
|
160
|
+
suggestArtifactReviews,
|
|
161
|
+
forArtifactPair
|
|
162
|
+
};
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-Repo Linkage
|
|
3
|
+
*
|
|
4
|
+
* Extends the per-repo linkage map (lib/linkage.js) to span multiple
|
|
5
|
+
* repos in a Mode D suite. IDs are repo-qualified to prevent collisions:
|
|
6
|
+
* <repo-name>:<id> e.g., shared-libs:C-auth-service
|
|
7
|
+
*
|
|
8
|
+
* Public API:
|
|
9
|
+
* qualifyId(repoName, id) -> string
|
|
10
|
+
* parseQualifiedId(qid) -> { repo, id } | null
|
|
11
|
+
* readForwardSuite(hubPath) -> { 'qualified-id': [files] }
|
|
12
|
+
* crossRepoOrphans(hubPath, knownIds) -> [...]
|
|
13
|
+
* crossRepoImpact(hubPath, qualifiedId) -> [{ repo, files }]
|
|
14
|
+
* collectAllIds(hubPath) -> { id, repo, declared, implemented }[]
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const linkage = require('./linkage');
|
|
21
|
+
const detector = require('./multi-repo-detector');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Qualify a stable ID with its repo name.
|
|
25
|
+
*/
|
|
26
|
+
function qualifyId(repoName, id) {
|
|
27
|
+
return `${repoName}:${id}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Parse a qualified ID. Returns null if not qualified.
|
|
32
|
+
*/
|
|
33
|
+
function parseQualifiedId(qid) {
|
|
34
|
+
if (!qid || typeof qid !== 'string') return null;
|
|
35
|
+
const idx = qid.indexOf(':');
|
|
36
|
+
if (idx === -1) return null;
|
|
37
|
+
return {
|
|
38
|
+
repo: qid.slice(0, idx),
|
|
39
|
+
id: qid.slice(idx + 1)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Read each sibling's forward map and build a qualified map for the suite.
|
|
45
|
+
*/
|
|
46
|
+
function readForwardSuite(hubPath) {
|
|
47
|
+
const config = detector.readSuiteConfig(hubPath);
|
|
48
|
+
if (!config) return {};
|
|
49
|
+
const out = {};
|
|
50
|
+
const siblings = (config.siblings || []).map(sib => {
|
|
51
|
+
if (typeof sib === 'string') return { name: sib, path: path.resolve(hubPath, sib) };
|
|
52
|
+
return { name: sib.name, path: path.resolve(hubPath, sib.path || sib.name) };
|
|
53
|
+
});
|
|
54
|
+
for (const sib of siblings) {
|
|
55
|
+
if (!fs.existsSync(sib.path)) continue;
|
|
56
|
+
const fwd = linkage.readForward(sib.path);
|
|
57
|
+
for (const [id, files] of Object.entries(fwd)) {
|
|
58
|
+
const qid = qualifyId(sib.name, id);
|
|
59
|
+
out[qid] = files.map(f => `${sib.name}/${f}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Find IDs that are declared but have no implementing file across all
|
|
67
|
+
* repos.
|
|
68
|
+
*
|
|
69
|
+
* `knownIds` is a list of qualified IDs (e.g., ['shared:C-auth', 'app:P-MUST-01']).
|
|
70
|
+
*/
|
|
71
|
+
function crossRepoOrphans(hubPath, knownIds) {
|
|
72
|
+
const fwd = readForwardSuite(hubPath);
|
|
73
|
+
return knownIds.filter(qid => !fwd[qid] || fwd[qid].length === 0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Given a qualified ID, find files across the suite that depend on it.
|
|
78
|
+
* Useful when removing a token from one repo: scans all sibling repos
|
|
79
|
+
* for usage of the qualified token reference.
|
|
80
|
+
*/
|
|
81
|
+
function crossRepoImpact(hubPath, qualifiedId) {
|
|
82
|
+
const config = detector.readSuiteConfig(hubPath);
|
|
83
|
+
if (!config) return [];
|
|
84
|
+
const parsed = parseQualifiedId(qualifiedId);
|
|
85
|
+
if (!parsed) return [];
|
|
86
|
+
|
|
87
|
+
const siblings = (config.siblings || []).map(sib => {
|
|
88
|
+
if (typeof sib === 'string') return { name: sib, path: path.resolve(hubPath, sib) };
|
|
89
|
+
return { name: sib.name, path: path.resolve(hubPath, sib.path || sib.name) };
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const result = [];
|
|
93
|
+
for (const sib of siblings) {
|
|
94
|
+
if (!fs.existsSync(sib.path)) continue;
|
|
95
|
+
const reverse = linkage.readReverse(sib.path);
|
|
96
|
+
const matches = [];
|
|
97
|
+
for (const [file, ids] of Object.entries(reverse)) {
|
|
98
|
+
if (ids.includes(qualifiedId) || ids.includes(parsed.id)) {
|
|
99
|
+
matches.push(file);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (matches.length > 0) {
|
|
103
|
+
result.push({ repo: sib.name, files: matches });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Walk all repos and collect every linked ID with its repo origin.
|
|
111
|
+
* Returns deduplicated list:
|
|
112
|
+
* [{ qualifiedId, repo, id, fileCount }, ...]
|
|
113
|
+
*/
|
|
114
|
+
function collectAllIds(hubPath) {
|
|
115
|
+
const fwd = readForwardSuite(hubPath);
|
|
116
|
+
const result = [];
|
|
117
|
+
for (const [qid, files] of Object.entries(fwd)) {
|
|
118
|
+
const parsed = parseQualifiedId(qid);
|
|
119
|
+
if (parsed) {
|
|
120
|
+
result.push({
|
|
121
|
+
qualifiedId: qid,
|
|
122
|
+
repo: parsed.repo,
|
|
123
|
+
id: parsed.id,
|
|
124
|
+
fileCount: files.length
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return result.sort((a, b) => a.qualifiedId.localeCompare(b.qualifiedId));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Compute coverage across the suite: percent of declared IDs that are
|
|
133
|
+
* implemented in at least one repo.
|
|
134
|
+
*/
|
|
135
|
+
function suiteCoverage(hubPath, knownIds) {
|
|
136
|
+
if (knownIds.length === 0) return 1;
|
|
137
|
+
const fwd = readForwardSuite(hubPath);
|
|
138
|
+
const linked = knownIds.filter(qid => fwd[qid] && fwd[qid].length > 0).length;
|
|
139
|
+
return linked / knownIds.length;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = {
|
|
143
|
+
qualifyId,
|
|
144
|
+
parseQualifiedId,
|
|
145
|
+
readForwardSuite,
|
|
146
|
+
crossRepoOrphans,
|
|
147
|
+
crossRepoImpact,
|
|
148
|
+
collectAllIds,
|
|
149
|
+
suiteCoverage
|
|
150
|
+
};
|