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,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drift Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects divergence between artifact decisions and actual code.
|
|
5
|
+
* Uses the linkage map to know which files are linked to which artifacts.
|
|
6
|
+
*
|
|
7
|
+
* Drift types:
|
|
8
|
+
* - DESIGN token drift: code uses a value that doesn't match the
|
|
9
|
+
* declared token value
|
|
10
|
+
* - DESIGN component drift: implementing file's actual styling differs
|
|
11
|
+
* from component property declarations
|
|
12
|
+
* - ARCH container drift: file is in container A but its imports
|
|
13
|
+
* reach container B in a way ARCH says shouldn't happen
|
|
14
|
+
* - STACK version drift: package.json has a version different from
|
|
15
|
+
* what STACK/DECISION.md declares
|
|
16
|
+
*
|
|
17
|
+
* Public API:
|
|
18
|
+
* detectAll(projectRoot) -> { findings, summary }
|
|
19
|
+
* detectDesignTokenDrift(projectRoot, designContent) -> findings
|
|
20
|
+
* detectStackVersionDrift(projectRoot, stackContent) -> findings
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
const linkage = require('./linkage');
|
|
27
|
+
const designSpec = require('./design-spec');
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Detect drift between DESIGN.md tokens and code that references them.
|
|
31
|
+
* If a file references a token that no longer exists in DESIGN.md, drift.
|
|
32
|
+
* If a file declares a token override (CSS var) with a different value, drift.
|
|
33
|
+
*/
|
|
34
|
+
function detectDesignTokenDrift(projectRoot, designContent) {
|
|
35
|
+
const findings = [];
|
|
36
|
+
if (!designContent) {
|
|
37
|
+
const designPath = path.join(projectRoot, 'DESIGN.md');
|
|
38
|
+
if (!fs.existsSync(designPath)) return findings;
|
|
39
|
+
designContent = fs.readFileSync(designPath, 'utf8');
|
|
40
|
+
}
|
|
41
|
+
const parsed = designSpec.parse(designContent);
|
|
42
|
+
if (!parsed.frontmatter) return findings;
|
|
43
|
+
const fm = parsed.frontmatter;
|
|
44
|
+
|
|
45
|
+
// Collect all valid token paths
|
|
46
|
+
const validTokens = new Set();
|
|
47
|
+
function walk(obj, prefix) {
|
|
48
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
49
|
+
const here = prefix ? `${prefix}.${k}` : k;
|
|
50
|
+
if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
|
51
|
+
walk(v, here);
|
|
52
|
+
} else {
|
|
53
|
+
validTokens.add(here);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
walk(fm, '');
|
|
58
|
+
|
|
59
|
+
// Read reverse linkage to find files referencing tokens
|
|
60
|
+
const rev = linkage.readReverse(projectRoot);
|
|
61
|
+
for (const [filePath, ids] of Object.entries(rev)) {
|
|
62
|
+
for (const id of ids) {
|
|
63
|
+
if (linkage.classifyId(id) === 'token') {
|
|
64
|
+
// Check the token still exists
|
|
65
|
+
if (!validTokens.has(id) && !hasTokenInTree(fm, id)) {
|
|
66
|
+
findings.push({
|
|
67
|
+
kind: 'design-token-drift',
|
|
68
|
+
severity: 'error',
|
|
69
|
+
file: filePath,
|
|
70
|
+
artifactId: id,
|
|
71
|
+
message: `File references token "${id}" that no longer exists in DESIGN.md.`
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return findings;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Detect drift between STACK/DECISION.md declared versions and actual
|
|
82
|
+
* package.json (or pyproject.toml etc.) versions.
|
|
83
|
+
*/
|
|
84
|
+
function detectStackVersionDrift(projectRoot, stackContent) {
|
|
85
|
+
const findings = [];
|
|
86
|
+
const stackPath = path.join(projectRoot, '.godpowers', 'stack', 'DECISION.md');
|
|
87
|
+
if (!stackContent && fs.existsSync(stackPath)) {
|
|
88
|
+
stackContent = fs.readFileSync(stackPath, 'utf8');
|
|
89
|
+
}
|
|
90
|
+
if (!stackContent) return findings;
|
|
91
|
+
|
|
92
|
+
// Extract declared deps from stack table: "| Foo | Choice | Lock-in |"
|
|
93
|
+
// and from "Selected Stack" rows. Heuristic: capture lines like
|
|
94
|
+
// "Node 20 LTS" or "Next.js 15" or "Go 1.23"
|
|
95
|
+
const versionDeclared = {};
|
|
96
|
+
const tableLineRegex = /\|\s*[\w\s]+\s*\|\s*([\w.-]+)\s+(\d+(?:\.\d+)*(?:[\w-]*)?)\s*\|/g;
|
|
97
|
+
let m;
|
|
98
|
+
while ((m = tableLineRegex.exec(stackContent)) !== null) {
|
|
99
|
+
versionDeclared[m[1].toLowerCase()] = m[2];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Read package.json
|
|
103
|
+
const pkgPath = path.join(projectRoot, 'package.json');
|
|
104
|
+
if (fs.existsSync(pkgPath)) {
|
|
105
|
+
try {
|
|
106
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
107
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
108
|
+
for (const [decl, expectedVer] of Object.entries(versionDeclared)) {
|
|
109
|
+
// Match against a dep name fragment
|
|
110
|
+
for (const [depName, actualSpec] of Object.entries(deps)) {
|
|
111
|
+
if (depName.toLowerCase().includes(decl) || decl.includes(depName.toLowerCase())) {
|
|
112
|
+
// Strip version specifier characters
|
|
113
|
+
const actualVer = String(actualSpec).replace(/^[\^~>=<]+/, '');
|
|
114
|
+
const majorActual = actualVer.split('.')[0];
|
|
115
|
+
const majorExpected = expectedVer.split('.')[0];
|
|
116
|
+
if (majorActual && majorExpected && majorActual !== majorExpected) {
|
|
117
|
+
findings.push({
|
|
118
|
+
kind: 'stack-version-drift',
|
|
119
|
+
severity: 'warning',
|
|
120
|
+
file: 'package.json',
|
|
121
|
+
artifactId: `S-${decl}`,
|
|
122
|
+
message: `STACK declares ${decl} ${expectedVer}; package.json has ${depName} ${actualSpec}.`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (e) {
|
|
129
|
+
// ignore parse errors
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return findings;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Check if a token path exists in the parsed frontmatter tree.
|
|
137
|
+
*/
|
|
138
|
+
function hasTokenInTree(fm, tokenPath) {
|
|
139
|
+
return tokenPath.split('.').reduce((acc, k) => (acc ? acc[k] : undefined), fm) !== undefined;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Detect ARCH container drift: cross-container imports that violate ARCH.
|
|
144
|
+
* Heuristic V1: if a file is linked to C-foo but imports from a path that
|
|
145
|
+
* looks like C-bar (e.g., src/bar/), flag.
|
|
146
|
+
*/
|
|
147
|
+
function detectArchContainerDrift(projectRoot) {
|
|
148
|
+
const findings = [];
|
|
149
|
+
const rev = linkage.readReverse(projectRoot);
|
|
150
|
+
for (const [filePath, ids] of Object.entries(rev)) {
|
|
151
|
+
const containerIds = ids.filter(id => linkage.classifyId(id) === 'container');
|
|
152
|
+
if (containerIds.length === 0) continue;
|
|
153
|
+
const fullPath = path.join(projectRoot, filePath);
|
|
154
|
+
if (!fs.existsSync(fullPath)) continue;
|
|
155
|
+
const ext = path.extname(filePath);
|
|
156
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs'].includes(ext)) continue;
|
|
157
|
+
|
|
158
|
+
const content = fs.readFileSync(fullPath, 'utf8');
|
|
159
|
+
const importRegex = /(?:import\s+[^'"]*from\s+|require\s*\(\s*)['"]([^'"]+)['"]/g;
|
|
160
|
+
let m;
|
|
161
|
+
while ((m = importRegex.exec(content)) !== null) {
|
|
162
|
+
const importPath = m[1];
|
|
163
|
+
if (importPath.startsWith('.')) continue; // relative, same package
|
|
164
|
+
// Look for cross-container: something like "src/bar/" or "@/services/bar/"
|
|
165
|
+
const crossMatch = importPath.match(/(?:src|services?|lib|app)[\/\\]([\w-]+)/);
|
|
166
|
+
if (crossMatch) {
|
|
167
|
+
const otherSlug = crossMatch[1];
|
|
168
|
+
const otherContainerId = `C-${otherSlug}`;
|
|
169
|
+
if (otherContainerId !== containerIds[0] && containerIds.indexOf(otherContainerId) === -1) {
|
|
170
|
+
// It's a cross-container import. Whether this is drift depends on ARCH;
|
|
171
|
+
// V1 reports as info (suggestion to verify), not error.
|
|
172
|
+
findings.push({
|
|
173
|
+
kind: 'arch-container-cross-import',
|
|
174
|
+
severity: 'info',
|
|
175
|
+
file: filePath,
|
|
176
|
+
artifactId: containerIds[0],
|
|
177
|
+
message: `File in ${containerIds[0]} imports from ${otherContainerId} (verify ARCH allows this).`
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return findings;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Detect all known drift types.
|
|
188
|
+
*/
|
|
189
|
+
function detectAll(projectRoot) {
|
|
190
|
+
const findings = [
|
|
191
|
+
...detectDesignTokenDrift(projectRoot),
|
|
192
|
+
...detectStackVersionDrift(projectRoot),
|
|
193
|
+
...detectArchContainerDrift(projectRoot)
|
|
194
|
+
];
|
|
195
|
+
const summary = {
|
|
196
|
+
errors: findings.filter(f => f.severity === 'error').length,
|
|
197
|
+
warnings: findings.filter(f => f.severity === 'warning').length,
|
|
198
|
+
infos: findings.filter(f => f.severity === 'info').length,
|
|
199
|
+
byKind: {}
|
|
200
|
+
};
|
|
201
|
+
for (const f of findings) {
|
|
202
|
+
summary.byKind[f.kind] = (summary.byKind[f.kind] || 0) + 1;
|
|
203
|
+
}
|
|
204
|
+
return { findings, summary };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
module.exports = {
|
|
208
|
+
detectAll,
|
|
209
|
+
detectDesignTokenDrift,
|
|
210
|
+
detectStackVersionDrift,
|
|
211
|
+
detectArchContainerDrift
|
|
212
|
+
};
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Reader
|
|
3
|
+
*
|
|
4
|
+
* Reads events.jsonl files and produces three views:
|
|
5
|
+
* - timeline(runId): chronological readable summary
|
|
6
|
+
* - metrics(runId | runIds): per-tier durations, pauses, retries
|
|
7
|
+
* - trace(runId, tier): deep dive on one tier's events
|
|
8
|
+
*
|
|
9
|
+
* Companion to lib/events.js which is the writer.
|
|
10
|
+
*
|
|
11
|
+
* Public API:
|
|
12
|
+
* readAll(projectRoot, runId) -> Event[]
|
|
13
|
+
* timeline(projectRoot, runId, opts) -> string (formatted) or rows array
|
|
14
|
+
* metrics(projectRoot, runIds | null) -> { perTier, totals }
|
|
15
|
+
* trace(projectRoot, runId, tier) -> Event[] (filtered)
|
|
16
|
+
* summarize(events) -> { agentCount, pauseCount, errorCount, durationMs }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const events = require('./events');
|
|
23
|
+
|
|
24
|
+
function readAll(projectRoot, runId) {
|
|
25
|
+
return events.readRun(projectRoot, runId);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Render events as a readable timeline.
|
|
30
|
+
*
|
|
31
|
+
* opts: { limit, since (ISO), filter (event-name regex) }
|
|
32
|
+
*
|
|
33
|
+
* Returns an array of { ts, name, attrs, durationMs? } rows.
|
|
34
|
+
*/
|
|
35
|
+
function timeline(projectRoot, runId, opts = {}) {
|
|
36
|
+
const all = readAll(projectRoot, runId);
|
|
37
|
+
let rows = all;
|
|
38
|
+
if (opts.since) {
|
|
39
|
+
rows = rows.filter(e => e.ts >= opts.since);
|
|
40
|
+
}
|
|
41
|
+
if (opts.filter) {
|
|
42
|
+
const re = new RegExp(opts.filter);
|
|
43
|
+
rows = rows.filter(e => re.test(e.name));
|
|
44
|
+
}
|
|
45
|
+
if (opts.limit) rows = rows.slice(-opts.limit);
|
|
46
|
+
|
|
47
|
+
// Pair agent.start / agent.end to compute durations
|
|
48
|
+
const startMap = new Map();
|
|
49
|
+
const result = rows.map(e => {
|
|
50
|
+
const row = { ts: e.ts, name: e.name, attrs: e.attrs || {} };
|
|
51
|
+
if (e.name === 'agent.start') {
|
|
52
|
+
startMap.set(e.span_id, new Date(e.ts).getTime());
|
|
53
|
+
}
|
|
54
|
+
if (e.name === 'agent.end') {
|
|
55
|
+
const start = startMap.get(e.span_id);
|
|
56
|
+
if (start) row.durationMs = new Date(e.ts).getTime() - start;
|
|
57
|
+
}
|
|
58
|
+
return row;
|
|
59
|
+
});
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Format a timeline as a human-readable string.
|
|
65
|
+
*/
|
|
66
|
+
function formatTimeline(rows) {
|
|
67
|
+
return rows.map(r => {
|
|
68
|
+
const dur = r.durationMs != null ? ` (${(r.durationMs / 1000).toFixed(2)}s)` : '';
|
|
69
|
+
const tier = r.attrs.tier ? ` [${r.attrs.tier}]` : '';
|
|
70
|
+
const agent = r.attrs.agent ? ` ${r.attrs.agent}` : '';
|
|
71
|
+
return `${r.ts} ${r.name}${tier}${agent}${dur}`;
|
|
72
|
+
}).join('\n');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Compute per-tier metrics across one or all runs.
|
|
77
|
+
*
|
|
78
|
+
* If runIds is null, walks every run in the project.
|
|
79
|
+
*
|
|
80
|
+
* Returns:
|
|
81
|
+
* {
|
|
82
|
+
* perTier: {
|
|
83
|
+
* 'tier-1': { count, totalMs, avgMs, pauseCount, errorCount },
|
|
84
|
+
* ...
|
|
85
|
+
* },
|
|
86
|
+
* totals: { runs, agents, pauses, errors, totalMs }
|
|
87
|
+
* }
|
|
88
|
+
*/
|
|
89
|
+
function metrics(projectRoot, runIds) {
|
|
90
|
+
if (!runIds) runIds = events.listRuns(projectRoot);
|
|
91
|
+
if (!Array.isArray(runIds)) runIds = [runIds];
|
|
92
|
+
|
|
93
|
+
const perTier = {};
|
|
94
|
+
const totals = { runs: runIds.length, agents: 0, pauses: 0, errors: 0, totalMs: 0 };
|
|
95
|
+
|
|
96
|
+
for (const runId of runIds) {
|
|
97
|
+
const all = readAll(projectRoot, runId);
|
|
98
|
+
const starts = new Map();
|
|
99
|
+
for (const e of all) {
|
|
100
|
+
const tier = (e.attrs && e.attrs.tier) || 'unknown';
|
|
101
|
+
if (!perTier[tier]) {
|
|
102
|
+
perTier[tier] = { count: 0, totalMs: 0, pauseCount: 0, errorCount: 0 };
|
|
103
|
+
}
|
|
104
|
+
if (e.name === 'agent.start') {
|
|
105
|
+
starts.set(e.span_id, { tier, ts: new Date(e.ts).getTime() });
|
|
106
|
+
}
|
|
107
|
+
if (e.name === 'agent.end') {
|
|
108
|
+
const s = starts.get(e.span_id);
|
|
109
|
+
if (s) {
|
|
110
|
+
const dur = new Date(e.ts).getTime() - s.ts;
|
|
111
|
+
perTier[s.tier].count += 1;
|
|
112
|
+
perTier[s.tier].totalMs += dur;
|
|
113
|
+
totals.agents += 1;
|
|
114
|
+
totals.totalMs += dur;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (e.name === 'agent.pause') {
|
|
118
|
+
perTier[tier].pauseCount += 1;
|
|
119
|
+
totals.pauses += 1;
|
|
120
|
+
}
|
|
121
|
+
if (e.name === 'error') {
|
|
122
|
+
perTier[tier].errorCount += 1;
|
|
123
|
+
totals.errors += 1;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const t of Object.keys(perTier)) {
|
|
129
|
+
const p = perTier[t];
|
|
130
|
+
p.avgMs = p.count > 0 ? Math.round(p.totalMs / p.count) : 0;
|
|
131
|
+
}
|
|
132
|
+
return { perTier, totals };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Filter events for one tier across a run.
|
|
137
|
+
*/
|
|
138
|
+
function trace(projectRoot, runId, tier) {
|
|
139
|
+
return readAll(projectRoot, runId).filter(e =>
|
|
140
|
+
e.attrs && e.attrs.tier === tier
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function summarize(eventList) {
|
|
145
|
+
const summary = { agentCount: 0, pauseCount: 0, errorCount: 0, durationMs: 0 };
|
|
146
|
+
const starts = new Map();
|
|
147
|
+
let firstTs = null, lastTs = null;
|
|
148
|
+
for (const e of eventList) {
|
|
149
|
+
if (!firstTs || e.ts < firstTs) firstTs = e.ts;
|
|
150
|
+
if (!lastTs || e.ts > lastTs) lastTs = e.ts;
|
|
151
|
+
if (e.name === 'agent.start') {
|
|
152
|
+
starts.set(e.span_id, new Date(e.ts).getTime());
|
|
153
|
+
}
|
|
154
|
+
if (e.name === 'agent.end') {
|
|
155
|
+
const s = starts.get(e.span_id);
|
|
156
|
+
if (s) summary.agentCount += 1;
|
|
157
|
+
}
|
|
158
|
+
if (e.name === 'agent.pause') summary.pauseCount += 1;
|
|
159
|
+
if (e.name === 'error') summary.errorCount += 1;
|
|
160
|
+
}
|
|
161
|
+
if (firstTs && lastTs) {
|
|
162
|
+
summary.durationMs = new Date(lastTs).getTime() - new Date(firstTs).getTime();
|
|
163
|
+
}
|
|
164
|
+
return summary;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
module.exports = {
|
|
168
|
+
readAll,
|
|
169
|
+
timeline,
|
|
170
|
+
formatTimeline,
|
|
171
|
+
metrics,
|
|
172
|
+
trace,
|
|
173
|
+
summarize
|
|
174
|
+
};
|
package/lib/events.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events Manager
|
|
3
|
+
*
|
|
4
|
+
* Append OpenTelemetry-shape events to .godpowers/runs/<run-id>/events.jsonl.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const crypto = require('crypto');
|
|
10
|
+
|
|
11
|
+
const VALID_EVENT_NAMES = new Set([
|
|
12
|
+
'workflow.run', 'workflow.complete',
|
|
13
|
+
'agent.start', 'agent.end', 'agent.pause', 'agent.yolo-resolve',
|
|
14
|
+
'user.resolve',
|
|
15
|
+
'tool.call', 'tool.result',
|
|
16
|
+
'model.call',
|
|
17
|
+
'decision.route',
|
|
18
|
+
'artifact.created', 'artifact.updated', 'artifact.hash',
|
|
19
|
+
'have-nots.check',
|
|
20
|
+
'gate.fail', 'gate.pass',
|
|
21
|
+
'tier.skip',
|
|
22
|
+
'state.repair', 'state.rollback',
|
|
23
|
+
'extension.install', 'extension.activate',
|
|
24
|
+
// Cost / cache / budget (v0.14 token cost saver)
|
|
25
|
+
'cost.recorded', 'cache.hit', 'cache.miss', 'budget.exceeded',
|
|
26
|
+
'error', 'warn'
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
function generateTraceId() {
|
|
30
|
+
return crypto.randomBytes(16).toString('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function generateSpanId() {
|
|
34
|
+
return crypto.randomBytes(8).toString('hex');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function eventsPath(projectRoot, runId) {
|
|
38
|
+
return path.join(projectRoot, '.godpowers', 'runs', runId, 'events.jsonl');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Start a new run. Returns a run handle with trace_id and write functions.
|
|
43
|
+
*/
|
|
44
|
+
function startRun(projectRoot, attrs = {}) {
|
|
45
|
+
const traceId = generateTraceId();
|
|
46
|
+
const runId = `${new Date().toISOString().replace(/[:.]/g, '-')}-${traceId.slice(0, 8)}`;
|
|
47
|
+
const spanId = generateSpanId();
|
|
48
|
+
|
|
49
|
+
const file = eventsPath(projectRoot, runId);
|
|
50
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
51
|
+
|
|
52
|
+
const handle = {
|
|
53
|
+
traceId,
|
|
54
|
+
runId,
|
|
55
|
+
rootSpanId: spanId,
|
|
56
|
+
file,
|
|
57
|
+
emit: (event) => emit(file, { trace_id: traceId, ...event }),
|
|
58
|
+
spawn: (parentSpanId) => spawnSpan(traceId, parentSpanId || spanId, file)
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
handle.emit({
|
|
62
|
+
span_id: spanId,
|
|
63
|
+
ts: new Date().toISOString(),
|
|
64
|
+
name: 'workflow.run',
|
|
65
|
+
attrs
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return handle;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function spawnSpan(traceId, parentSpanId, file) {
|
|
72
|
+
const spanId = generateSpanId();
|
|
73
|
+
return {
|
|
74
|
+
traceId,
|
|
75
|
+
spanId,
|
|
76
|
+
parentSpanId,
|
|
77
|
+
emit: (event) =>
|
|
78
|
+
emit(file, {
|
|
79
|
+
trace_id: traceId,
|
|
80
|
+
span_id: spanId,
|
|
81
|
+
parent: parentSpanId,
|
|
82
|
+
...event
|
|
83
|
+
})
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Append a single event to the events.jsonl file. Includes a hash chain:
|
|
89
|
+
* each event carries `prev` = sha256 of the previous event line (or
|
|
90
|
+
* 'genesis' for the first). Any truncation, reordering, or tampering
|
|
91
|
+
* breaks the chain at that point and is detectable by
|
|
92
|
+
* verifyChain(file).
|
|
93
|
+
*/
|
|
94
|
+
function emit(file, event) {
|
|
95
|
+
if (!event.trace_id) throw new Error('event.trace_id required');
|
|
96
|
+
if (!event.span_id) throw new Error('event.span_id required');
|
|
97
|
+
if (!event.ts) event.ts = new Date().toISOString();
|
|
98
|
+
if (!event.name) throw new Error('event.name required');
|
|
99
|
+
if (!VALID_EVENT_NAMES.has(event.name)) {
|
|
100
|
+
throw new Error(`Invalid event name: ${event.name}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Compute prev hash from the last line of the file, if any.
|
|
104
|
+
let prev = 'genesis';
|
|
105
|
+
if (fs.existsSync(file)) {
|
|
106
|
+
const stat = fs.statSync(file);
|
|
107
|
+
if (stat.size > 0) {
|
|
108
|
+
// Read just the last 4KB to find the last full line.
|
|
109
|
+
const fd = fs.openSync(file, 'r');
|
|
110
|
+
const chunkSize = Math.min(4096, stat.size);
|
|
111
|
+
const buf = Buffer.alloc(chunkSize);
|
|
112
|
+
fs.readSync(fd, buf, 0, chunkSize, stat.size - chunkSize);
|
|
113
|
+
fs.closeSync(fd);
|
|
114
|
+
const tail = buf.toString('utf8');
|
|
115
|
+
const lines = tail.split('\n').filter(l => l.trim());
|
|
116
|
+
if (lines.length > 0) {
|
|
117
|
+
prev = 'sha256:' + crypto.createHash('sha256')
|
|
118
|
+
.update(lines[lines.length - 1])
|
|
119
|
+
.digest('hex');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
event.prev = prev;
|
|
124
|
+
|
|
125
|
+
fs.appendFileSync(file, JSON.stringify(event) + '\n');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Verify the hash chain in an events.jsonl file. Returns
|
|
130
|
+
* { valid: bool, breakAt: number | null, expected, actual }.
|
|
131
|
+
*
|
|
132
|
+
* The chain is broken if any event's `prev` doesn't match the sha256
|
|
133
|
+
* of the previous line's exact bytes. Genesis line must have
|
|
134
|
+
* prev === 'genesis'.
|
|
135
|
+
*/
|
|
136
|
+
function verifyChain(file) {
|
|
137
|
+
if (!fs.existsSync(file)) return { valid: true, lines: 0 };
|
|
138
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(l => l.trim());
|
|
139
|
+
let prev = 'genesis';
|
|
140
|
+
for (let i = 0; i < lines.length; i++) {
|
|
141
|
+
let ev;
|
|
142
|
+
try { ev = JSON.parse(lines[i]); }
|
|
143
|
+
catch (e) { return { valid: false, breakAt: i, error: 'parse-error' }; }
|
|
144
|
+
if (ev.prev !== prev) {
|
|
145
|
+
return { valid: false, breakAt: i, expected: prev, actual: ev.prev };
|
|
146
|
+
}
|
|
147
|
+
prev = 'sha256:' + crypto.createHash('sha256').update(lines[i]).digest('hex');
|
|
148
|
+
}
|
|
149
|
+
return { valid: true, lines: lines.length };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Read all events for a run.
|
|
154
|
+
*/
|
|
155
|
+
function readRun(projectRoot, runId) {
|
|
156
|
+
const file = eventsPath(projectRoot, runId);
|
|
157
|
+
if (!fs.existsSync(file)) return [];
|
|
158
|
+
return fs.readFileSync(file, 'utf8')
|
|
159
|
+
.split('\n')
|
|
160
|
+
.filter(Boolean)
|
|
161
|
+
.map(line => JSON.parse(line));
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* List all runs in a project.
|
|
166
|
+
*/
|
|
167
|
+
function listRuns(projectRoot) {
|
|
168
|
+
const runsDir = path.join(projectRoot, '.godpowers', 'runs');
|
|
169
|
+
if (!fs.existsSync(runsDir)) return [];
|
|
170
|
+
return fs.readdirSync(runsDir).sort();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = {
|
|
174
|
+
startRun,
|
|
175
|
+
emit,
|
|
176
|
+
readRun,
|
|
177
|
+
listRuns,
|
|
178
|
+
verifyChain,
|
|
179
|
+
generateTraceId,
|
|
180
|
+
generateSpanId,
|
|
181
|
+
eventsPath,
|
|
182
|
+
VALID_EVENT_NAMES
|
|
183
|
+
};
|