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
package/lib/linkage.js
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Linkage Map Manager
|
|
3
|
+
*
|
|
4
|
+
* Bidirectional linkage between artifact elements (PRD requirements,
|
|
5
|
+
* ADRs, ARCH containers, ROADMAP milestones, STACK decisions, DESIGN
|
|
6
|
+
* tokens/components) and code files.
|
|
7
|
+
*
|
|
8
|
+
* Storage:
|
|
9
|
+
* .godpowers/links/artifact-to-code.json - forward map
|
|
10
|
+
* .godpowers/links/code-to-artifact.json - reverse map
|
|
11
|
+
* .godpowers/links/LINKAGE-LOG.md - append-only history
|
|
12
|
+
*
|
|
13
|
+
* Stable ID format:
|
|
14
|
+
* PRD requirement: P-{MUST,SHOULD,COULD}-NN (e.g., P-MUST-01)
|
|
15
|
+
* ADR: ADR-NNN
|
|
16
|
+
* ARCH container: C-{slug} (e.g., C-auth-service)
|
|
17
|
+
* ROADMAP milestone: M-{slug} (e.g., M-launch-v1)
|
|
18
|
+
* STACK decision: S-{slug} (e.g., S-postgres-15)
|
|
19
|
+
* DESIGN token: YAML path (e.g., colors.primary)
|
|
20
|
+
* DESIGN component: D-{slug} (e.g., D-button-primary)
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
const fs = require('fs');
|
|
24
|
+
const path = require('path');
|
|
25
|
+
|
|
26
|
+
const ID_PATTERNS = {
|
|
27
|
+
prd: /^P-(MUST|SHOULD|COULD)-\d+$/,
|
|
28
|
+
adr: /^ADR-\d+$/,
|
|
29
|
+
container: /^C-[\w-]+$/,
|
|
30
|
+
milestone: /^M-[\w-]+$/,
|
|
31
|
+
stack: /^S-[\w-]+$/,
|
|
32
|
+
story: /^STORY-[\w-]+-\d+$/,
|
|
33
|
+
design: /^D-[\w-]+$/,
|
|
34
|
+
token: /^[a-z][\w-]*\.[\w.-]+$/ // colors.primary, typography.display, etc.
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function classifyId(id) {
|
|
38
|
+
for (const [kind, regex] of Object.entries(ID_PATTERNS)) {
|
|
39
|
+
if (regex.test(id)) return kind;
|
|
40
|
+
}
|
|
41
|
+
return 'unknown';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function linksDir(projectRoot) {
|
|
45
|
+
return path.join(projectRoot, '.godpowers', 'links');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function forwardPath(projectRoot) {
|
|
49
|
+
return path.join(linksDir(projectRoot), 'artifact-to-code.json');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function reversePath(projectRoot) {
|
|
53
|
+
return path.join(linksDir(projectRoot), 'code-to-artifact.json');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function logPath(projectRoot) {
|
|
57
|
+
return path.join(linksDir(projectRoot), 'LINKAGE-LOG.md');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function ensureDir(projectRoot) {
|
|
61
|
+
const dir = linksDir(projectRoot);
|
|
62
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function readMap(filePath) {
|
|
66
|
+
if (!fs.existsSync(filePath)) return {};
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
69
|
+
} catch (e) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function writeMap(filePath, data) {
|
|
75
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2) + '\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Read forward map: artifact ID -> array of file paths.
|
|
80
|
+
*/
|
|
81
|
+
function readForward(projectRoot) {
|
|
82
|
+
return readMap(forwardPath(projectRoot));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Read reverse map: file path -> array of artifact IDs.
|
|
87
|
+
*/
|
|
88
|
+
function readReverse(projectRoot) {
|
|
89
|
+
return readMap(reversePath(projectRoot));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Add a link: artifact ID <-> file path. Bidirectional, idempotent.
|
|
94
|
+
* Returns { added: bool, ... }.
|
|
95
|
+
*/
|
|
96
|
+
function addLink(projectRoot, artifactId, filePath, opts = {}) {
|
|
97
|
+
ensureDir(projectRoot);
|
|
98
|
+
const fwd = readForward(projectRoot);
|
|
99
|
+
const rev = readReverse(projectRoot);
|
|
100
|
+
|
|
101
|
+
const normFile = path.relative(projectRoot, path.resolve(projectRoot, filePath));
|
|
102
|
+
if (!fwd[artifactId]) fwd[artifactId] = [];
|
|
103
|
+
if (!rev[normFile]) rev[normFile] = [];
|
|
104
|
+
|
|
105
|
+
const fwdHad = fwd[artifactId].includes(normFile);
|
|
106
|
+
const revHad = rev[normFile].includes(artifactId);
|
|
107
|
+
|
|
108
|
+
if (!fwdHad) fwd[artifactId].push(normFile);
|
|
109
|
+
if (!revHad) rev[normFile].push(artifactId);
|
|
110
|
+
|
|
111
|
+
fwd[artifactId].sort();
|
|
112
|
+
rev[normFile].sort();
|
|
113
|
+
|
|
114
|
+
writeMap(forwardPath(projectRoot), fwd);
|
|
115
|
+
writeMap(reversePath(projectRoot), rev);
|
|
116
|
+
|
|
117
|
+
if (!fwdHad || !revHad) {
|
|
118
|
+
appendLog(projectRoot, `+ link: ${artifactId} <-> ${normFile}` + (opts.source ? ` (via ${opts.source})` : ''));
|
|
119
|
+
}
|
|
120
|
+
return { added: !fwdHad || !revHad, artifactId, file: normFile };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Remove a link.
|
|
125
|
+
*/
|
|
126
|
+
function removeLink(projectRoot, artifactId, filePath) {
|
|
127
|
+
ensureDir(projectRoot);
|
|
128
|
+
const fwd = readForward(projectRoot);
|
|
129
|
+
const rev = readReverse(projectRoot);
|
|
130
|
+
const normFile = path.relative(projectRoot, path.resolve(projectRoot, filePath));
|
|
131
|
+
|
|
132
|
+
let removed = false;
|
|
133
|
+
if (fwd[artifactId]) {
|
|
134
|
+
const before = fwd[artifactId].length;
|
|
135
|
+
fwd[artifactId] = fwd[artifactId].filter(f => f !== normFile);
|
|
136
|
+
if (fwd[artifactId].length === 0) delete fwd[artifactId];
|
|
137
|
+
if (before !== (fwd[artifactId] ? fwd[artifactId].length : 0)) removed = true;
|
|
138
|
+
}
|
|
139
|
+
if (rev[normFile]) {
|
|
140
|
+
const before = rev[normFile].length;
|
|
141
|
+
rev[normFile] = rev[normFile].filter(id => id !== artifactId);
|
|
142
|
+
if (rev[normFile].length === 0) delete rev[normFile];
|
|
143
|
+
if (before !== (rev[normFile] ? rev[normFile].length : 0)) removed = true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
writeMap(forwardPath(projectRoot), fwd);
|
|
147
|
+
writeMap(reversePath(projectRoot), rev);
|
|
148
|
+
|
|
149
|
+
if (removed) {
|
|
150
|
+
appendLog(projectRoot, `- link: ${artifactId} <-> ${normFile}`);
|
|
151
|
+
}
|
|
152
|
+
return { removed };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Query: given an artifact ID, return linked files.
|
|
157
|
+
*/
|
|
158
|
+
function queryByArtifact(projectRoot, artifactId) {
|
|
159
|
+
const fwd = readForward(projectRoot);
|
|
160
|
+
return fwd[artifactId] || [];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Query: given a file path, return linked artifact IDs.
|
|
165
|
+
*/
|
|
166
|
+
function queryByFile(projectRoot, filePath) {
|
|
167
|
+
const rev = readReverse(projectRoot);
|
|
168
|
+
const normFile = path.relative(projectRoot, path.resolve(projectRoot, filePath));
|
|
169
|
+
return rev[normFile] || [];
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* List orphan artifact IDs (no implementing file).
|
|
174
|
+
* `knownIds` is an array of artifact IDs the user wants to check (typically
|
|
175
|
+
* sourced by parsing PRD/ARCH/etc. for declared IDs).
|
|
176
|
+
*/
|
|
177
|
+
function listOrphans(projectRoot, knownIds) {
|
|
178
|
+
const fwd = readForward(projectRoot);
|
|
179
|
+
return knownIds.filter(id => !fwd[id] || fwd[id].length === 0);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Compute coverage: percentage of known IDs that have at least one link.
|
|
184
|
+
*/
|
|
185
|
+
function coverage(projectRoot, knownIds) {
|
|
186
|
+
if (knownIds.length === 0) return 1;
|
|
187
|
+
const fwd = readForward(projectRoot);
|
|
188
|
+
const linked = knownIds.filter(id => fwd[id] && fwd[id].length > 0).length;
|
|
189
|
+
return linked / knownIds.length;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Append to LINKAGE-LOG.md.
|
|
194
|
+
*/
|
|
195
|
+
function appendLog(projectRoot, message) {
|
|
196
|
+
ensureDir(projectRoot);
|
|
197
|
+
const ts = new Date().toISOString();
|
|
198
|
+
const line = `${ts} ${message}\n`;
|
|
199
|
+
fs.appendFileSync(logPath(projectRoot), line);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Replace all links from a source (used by code-scanner to do bulk updates).
|
|
204
|
+
*/
|
|
205
|
+
function bulkReplaceFromSource(projectRoot, source, links) {
|
|
206
|
+
ensureDir(projectRoot);
|
|
207
|
+
const log = [];
|
|
208
|
+
for (const { artifactId, file } of links) {
|
|
209
|
+
const r = addLink(projectRoot, artifactId, file, { source });
|
|
210
|
+
if (r.added) log.push(`from ${source}: ${artifactId} -> ${file}`);
|
|
211
|
+
}
|
|
212
|
+
return { count: links.length, added: log.length };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
module.exports = {
|
|
216
|
+
classifyId,
|
|
217
|
+
ID_PATTERNS,
|
|
218
|
+
readForward,
|
|
219
|
+
readReverse,
|
|
220
|
+
addLink,
|
|
221
|
+
removeLink,
|
|
222
|
+
queryByArtifact,
|
|
223
|
+
queryByFile,
|
|
224
|
+
listOrphans,
|
|
225
|
+
coverage,
|
|
226
|
+
appendLog,
|
|
227
|
+
bulkReplaceFromSource,
|
|
228
|
+
forwardPath,
|
|
229
|
+
reversePath,
|
|
230
|
+
logPath,
|
|
231
|
+
linksDir
|
|
232
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Meta-Linter
|
|
3
|
+
*
|
|
4
|
+
* Cross-repo invariant checks for Mode D suites. Validates:
|
|
5
|
+
* - Byte-identical files across repos (LICENSE, .editorconfig, etc.)
|
|
6
|
+
* - Version table consistency (declared versions match package.json)
|
|
7
|
+
* - Shared standards drift (linter, formatter, Node version)
|
|
8
|
+
*
|
|
9
|
+
* Per locked plan answer Q2: byte-identical drift surfaces as warnings
|
|
10
|
+
* by default; hard gate via `strict: true` opt-in.
|
|
11
|
+
*
|
|
12
|
+
* Public API:
|
|
13
|
+
* checkByteIdentical(hubPath, opts) -> findings
|
|
14
|
+
* checkVersionTable(hubPath, opts) -> findings
|
|
15
|
+
* checkSharedStandards(hubPath, opts) -> findings
|
|
16
|
+
* runAll(hubPath, opts) -> { findings, summary }
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const crypto = require('crypto');
|
|
22
|
+
|
|
23
|
+
const detector = require('./multi-repo-detector');
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* SHA-256 of file content. Returns null if file missing.
|
|
27
|
+
*/
|
|
28
|
+
function fileHash(filePath) {
|
|
29
|
+
if (!fs.existsSync(filePath)) return null;
|
|
30
|
+
const content = fs.readFileSync(filePath);
|
|
31
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* For each declared byte-identical file, hash its content in each
|
|
36
|
+
* sibling repo and report mismatches.
|
|
37
|
+
*/
|
|
38
|
+
function checkByteIdentical(hubPath, opts = {}) {
|
|
39
|
+
const findings = [];
|
|
40
|
+
const config = detector.readSuiteConfig(hubPath);
|
|
41
|
+
if (!config) return findings;
|
|
42
|
+
|
|
43
|
+
const files = detector.getByteIdenticalFiles(hubPath);
|
|
44
|
+
if (files.length === 0) return findings;
|
|
45
|
+
|
|
46
|
+
const siblings = (config.siblings || []).map(sib => {
|
|
47
|
+
if (typeof sib === 'string') return { name: sib, path: path.resolve(hubPath, sib) };
|
|
48
|
+
return { name: sib.name, path: path.resolve(hubPath, sib.path || sib.name) };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const severity = opts.strict ? 'error' : 'warning';
|
|
52
|
+
|
|
53
|
+
for (const fileSpec of files) {
|
|
54
|
+
const filePath = fileSpec.path;
|
|
55
|
+
const hashes = {};
|
|
56
|
+
for (const sib of siblings) {
|
|
57
|
+
const full = path.join(sib.path, filePath);
|
|
58
|
+
hashes[sib.name] = fileHash(full);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const uniqueHashes = new Set(Object.values(hashes).filter(h => h !== null));
|
|
62
|
+
if (uniqueHashes.size > 1) {
|
|
63
|
+
findings.push({
|
|
64
|
+
severity,
|
|
65
|
+
kind: 'byte-identical-drift',
|
|
66
|
+
file: filePath,
|
|
67
|
+
message: `File "${filePath}" differs across repos. Hashes:`,
|
|
68
|
+
hashes
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
// Missing in some siblings is also a drift
|
|
72
|
+
for (const [repo, hash] of Object.entries(hashes)) {
|
|
73
|
+
if (hash === null) {
|
|
74
|
+
findings.push({
|
|
75
|
+
severity,
|
|
76
|
+
kind: 'byte-identical-missing',
|
|
77
|
+
file: filePath,
|
|
78
|
+
repo,
|
|
79
|
+
message: `Byte-identical file "${filePath}" is missing in repo "${repo}".`
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return findings;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Verify the suite-level version table matches each repo's package.json.
|
|
90
|
+
* Strictness setting same as byte-identical.
|
|
91
|
+
*/
|
|
92
|
+
function checkVersionTable(hubPath, opts = {}) {
|
|
93
|
+
const findings = [];
|
|
94
|
+
const config = detector.readSuiteConfig(hubPath);
|
|
95
|
+
if (!config) return findings;
|
|
96
|
+
|
|
97
|
+
const versionTable = detector.getVersionTable(hubPath);
|
|
98
|
+
if (Object.keys(versionTable).length === 0) return findings;
|
|
99
|
+
|
|
100
|
+
const severity = opts.strict ? 'error' : 'warning';
|
|
101
|
+
|
|
102
|
+
const siblings = (config.siblings || []).map(sib => {
|
|
103
|
+
if (typeof sib === 'string') return { name: sib, path: path.resolve(hubPath, sib) };
|
|
104
|
+
return { name: sib.name, path: path.resolve(hubPath, sib.path || sib.name) };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
for (const [packageName, perRepoVersions] of Object.entries(versionTable)) {
|
|
108
|
+
for (const [repoName, declaredVersion] of Object.entries(perRepoVersions)) {
|
|
109
|
+
const sib = siblings.find(s => s.name === repoName);
|
|
110
|
+
if (!sib) continue;
|
|
111
|
+
const pkgPath = path.join(sib.path, 'package.json');
|
|
112
|
+
if (!fs.existsSync(pkgPath)) continue;
|
|
113
|
+
try {
|
|
114
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
115
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
116
|
+
if (packageName === repoName) {
|
|
117
|
+
// The repo's own version
|
|
118
|
+
if (pkg.version !== declaredVersion) {
|
|
119
|
+
findings.push({
|
|
120
|
+
severity,
|
|
121
|
+
kind: 'version-table-drift',
|
|
122
|
+
repo: repoName,
|
|
123
|
+
package: packageName,
|
|
124
|
+
declared: declaredVersion,
|
|
125
|
+
actual: pkg.version,
|
|
126
|
+
message: `Suite declares ${repoName} at ${declaredVersion}; package.json has ${pkg.version}.`
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
} else if (allDeps[packageName]) {
|
|
130
|
+
// A sibling's dependency on packageName
|
|
131
|
+
const actualSpec = String(allDeps[packageName]).replace(/^[\^~>=<]+/, '');
|
|
132
|
+
if (actualSpec.split('.')[0] !== declaredVersion.split('.')[0]) {
|
|
133
|
+
findings.push({
|
|
134
|
+
severity,
|
|
135
|
+
kind: 'version-table-drift',
|
|
136
|
+
repo: repoName,
|
|
137
|
+
package: packageName,
|
|
138
|
+
declared: declaredVersion,
|
|
139
|
+
actual: allDeps[packageName],
|
|
140
|
+
message: `Suite declares ${packageName} at ${declaredVersion} for ${repoName}; package.json has ${allDeps[packageName]}.`
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
} catch (e) {
|
|
145
|
+
// ignore parse errors
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return findings;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Validate shared standards: all repos use the same linter, formatter,
|
|
155
|
+
* Node version, etc. Declared in suite-config under `shared-standards`.
|
|
156
|
+
*/
|
|
157
|
+
function checkSharedStandards(hubPath, opts = {}) {
|
|
158
|
+
const findings = [];
|
|
159
|
+
const config = detector.readSuiteConfig(hubPath);
|
|
160
|
+
if (!config) return findings;
|
|
161
|
+
const standards = config['shared-standards'] || config.sharedStandards;
|
|
162
|
+
if (!standards) return findings;
|
|
163
|
+
|
|
164
|
+
const severity = opts.strict ? 'error' : 'warning';
|
|
165
|
+
|
|
166
|
+
const siblings = (config.siblings || []).map(sib => {
|
|
167
|
+
if (typeof sib === 'string') return { name: sib, path: path.resolve(hubPath, sib) };
|
|
168
|
+
return { name: sib.name, path: path.resolve(hubPath, sib.path || sib.name) };
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Node version (from .nvmrc or engines.node)
|
|
172
|
+
if (standards['node-version']) {
|
|
173
|
+
const expected = standards['node-version'];
|
|
174
|
+
for (const sib of siblings) {
|
|
175
|
+
const nvmrc = path.join(sib.path, '.nvmrc');
|
|
176
|
+
const pkg = path.join(sib.path, 'package.json');
|
|
177
|
+
let actual = null;
|
|
178
|
+
if (fs.existsSync(nvmrc)) {
|
|
179
|
+
actual = fs.readFileSync(nvmrc, 'utf8').trim();
|
|
180
|
+
} else if (fs.existsSync(pkg)) {
|
|
181
|
+
try {
|
|
182
|
+
const j = JSON.parse(fs.readFileSync(pkg, 'utf8'));
|
|
183
|
+
actual = j.engines && j.engines.node;
|
|
184
|
+
} catch (e) {
|
|
185
|
+
// ignore
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
if (actual && !String(actual).includes(String(expected).split('.')[0])) {
|
|
189
|
+
findings.push({
|
|
190
|
+
severity,
|
|
191
|
+
kind: 'shared-standard-drift',
|
|
192
|
+
repo: sib.name,
|
|
193
|
+
standard: 'node-version',
|
|
194
|
+
declared: expected,
|
|
195
|
+
actual,
|
|
196
|
+
message: `Repo ${sib.name} uses Node ${actual}; suite declares ${expected}.`
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Linter
|
|
203
|
+
if (standards.linter) {
|
|
204
|
+
const expected = standards.linter; // 'biome', 'eslint', etc.
|
|
205
|
+
for (const sib of siblings) {
|
|
206
|
+
const pkg = path.join(sib.path, 'package.json');
|
|
207
|
+
if (!fs.existsSync(pkg)) continue;
|
|
208
|
+
try {
|
|
209
|
+
const j = JSON.parse(fs.readFileSync(pkg, 'utf8'));
|
|
210
|
+
const allDeps = { ...j.dependencies, ...j.devDependencies };
|
|
211
|
+
const usesBiome = allDeps['@biomejs/biome'] || allDeps.biome;
|
|
212
|
+
const usesEslint = allDeps.eslint || Object.keys(allDeps).some(k => k.startsWith('eslint-'));
|
|
213
|
+
let detected = null;
|
|
214
|
+
if (usesBiome) detected = 'biome';
|
|
215
|
+
else if (usesEslint) detected = 'eslint';
|
|
216
|
+
if (detected && detected !== expected) {
|
|
217
|
+
findings.push({
|
|
218
|
+
severity,
|
|
219
|
+
kind: 'shared-standard-drift',
|
|
220
|
+
repo: sib.name,
|
|
221
|
+
standard: 'linter',
|
|
222
|
+
declared: expected,
|
|
223
|
+
actual: detected,
|
|
224
|
+
message: `Repo ${sib.name} uses ${detected}; suite declares ${expected}.`
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
} catch (e) {
|
|
228
|
+
// ignore
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return findings;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Run all meta-lint checks.
|
|
238
|
+
*/
|
|
239
|
+
function runAll(hubPath, opts = {}) {
|
|
240
|
+
const findings = [
|
|
241
|
+
...checkByteIdentical(hubPath, opts),
|
|
242
|
+
...checkVersionTable(hubPath, opts),
|
|
243
|
+
...checkSharedStandards(hubPath, opts)
|
|
244
|
+
];
|
|
245
|
+
const summary = {
|
|
246
|
+
errors: findings.filter(f => f.severity === 'error').length,
|
|
247
|
+
warnings: findings.filter(f => f.severity === 'warning').length,
|
|
248
|
+
infos: findings.filter(f => f.severity === 'info').length,
|
|
249
|
+
byKind: {}
|
|
250
|
+
};
|
|
251
|
+
for (const f of findings) {
|
|
252
|
+
summary.byKind[f.kind] = (summary.byKind[f.kind] || 0) + 1;
|
|
253
|
+
}
|
|
254
|
+
return { findings, summary };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
module.exports = {
|
|
258
|
+
checkByteIdentical,
|
|
259
|
+
checkVersionTable,
|
|
260
|
+
checkSharedStandards,
|
|
261
|
+
runAll,
|
|
262
|
+
fileHash
|
|
263
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-Repo Detector
|
|
3
|
+
*
|
|
4
|
+
* Detects whether the working directory is part of a multi-repo suite
|
|
5
|
+
* (Mode D in the repo-ready taxonomy).
|
|
6
|
+
*
|
|
7
|
+
* Detection signals (any one is enough):
|
|
8
|
+
* 1. `.godpowers/suite-config.yaml` declares siblings (canonical)
|
|
9
|
+
* 2. Multiple `.godpowers/` dirs under a common parent that user
|
|
10
|
+
* explicitly registered (via /god-suite-init)
|
|
11
|
+
* 3. Hub indicator: directory contains a suite-config.yaml AND has
|
|
12
|
+
* no source code of its own (pure coordination repo)
|
|
13
|
+
*
|
|
14
|
+
* Per the locked plan answer Q1: explicit declaration only. We do NOT
|
|
15
|
+
* auto-walk parent dirs to find siblings. Users must register them.
|
|
16
|
+
*
|
|
17
|
+
* Public API:
|
|
18
|
+
* detect(projectRoot) -> { isMultiRepo, role, siblings, hubPath }
|
|
19
|
+
* readSuiteConfig(projectRoot) -> { siblings, byteIdentical, ... } | null
|
|
20
|
+
* isHub(projectRoot) -> bool
|
|
21
|
+
* findHub(projectRoot) -> hubPath | null
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
const path = require('path');
|
|
26
|
+
const { parseSimpleYaml } = require('./intent');
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Look for .godpowers/suite-config.yaml in projectRoot or any
|
|
30
|
+
* registered parent. Returns parsed content or null.
|
|
31
|
+
*/
|
|
32
|
+
function readSuiteConfig(projectRoot) {
|
|
33
|
+
const localConfig = path.join(projectRoot, '.godpowers', 'suite-config.yaml');
|
|
34
|
+
if (fs.existsSync(localConfig)) {
|
|
35
|
+
try {
|
|
36
|
+
const content = fs.readFileSync(localConfig, 'utf8');
|
|
37
|
+
return parseSimpleYaml(content);
|
|
38
|
+
} catch (e) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// Check if state.json points to a hub
|
|
43
|
+
const statePath = path.join(projectRoot, '.godpowers', 'state.json');
|
|
44
|
+
if (fs.existsSync(statePath)) {
|
|
45
|
+
try {
|
|
46
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
47
|
+
if (state.suite && state.suite.hubPath) {
|
|
48
|
+
const hubConfigPath = path.join(state.suite.hubPath, '.godpowers', 'suite-config.yaml');
|
|
49
|
+
if (fs.existsSync(hubConfigPath)) {
|
|
50
|
+
return parseSimpleYaml(fs.readFileSync(hubConfigPath, 'utf8'));
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
} catch (e) {
|
|
54
|
+
// ignore
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Is this directory the hub of a suite?
|
|
62
|
+
* Heuristic: has suite-config.yaml AND declares siblings AND is not
|
|
63
|
+
* itself listed in those siblings.
|
|
64
|
+
*/
|
|
65
|
+
function isHub(projectRoot) {
|
|
66
|
+
const config = readSuiteConfig(projectRoot);
|
|
67
|
+
if (!config) return false;
|
|
68
|
+
const siblings = (config.siblings || []).map(s => normalize(s));
|
|
69
|
+
const myName = normalize(path.basename(projectRoot));
|
|
70
|
+
return siblings.length > 0 && !siblings.includes(myName);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Find the hub from a sibling repo. Walks up via state.json.suite.hubPath.
|
|
75
|
+
* Returns hub absolute path or null.
|
|
76
|
+
*/
|
|
77
|
+
function findHub(projectRoot) {
|
|
78
|
+
const statePath = path.join(projectRoot, '.godpowers', 'state.json');
|
|
79
|
+
if (!fs.existsSync(statePath)) return null;
|
|
80
|
+
try {
|
|
81
|
+
const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
82
|
+
if (state.suite && state.suite.hubPath) {
|
|
83
|
+
return state.suite.hubPath;
|
|
84
|
+
}
|
|
85
|
+
} catch (e) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Detect this directory's role in a suite.
|
|
93
|
+
*
|
|
94
|
+
* Returns:
|
|
95
|
+
* {
|
|
96
|
+
* isMultiRepo: bool,
|
|
97
|
+
* role: 'hub' | 'sibling' | null,
|
|
98
|
+
* siblings: [{ name, path }], // populated when isMultiRepo
|
|
99
|
+
* hubPath: string | null
|
|
100
|
+
* }
|
|
101
|
+
*/
|
|
102
|
+
function detect(projectRoot) {
|
|
103
|
+
const config = readSuiteConfig(projectRoot);
|
|
104
|
+
if (!config) {
|
|
105
|
+
return { isMultiRepo: false, role: null, siblings: [], hubPath: null };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const myName = path.basename(projectRoot);
|
|
109
|
+
const declaredSiblings = config.siblings || [];
|
|
110
|
+
|
|
111
|
+
// Resolve sibling paths. Per Q1, paths are explicit (relative or absolute)
|
|
112
|
+
// in suite-config.yaml.
|
|
113
|
+
const siblingsResolved = declaredSiblings.map(sib => {
|
|
114
|
+
if (typeof sib === 'string') {
|
|
115
|
+
return { name: sib, path: path.resolve(projectRoot, sib) };
|
|
116
|
+
}
|
|
117
|
+
if (typeof sib === 'object' && sib.name) {
|
|
118
|
+
return {
|
|
119
|
+
name: sib.name,
|
|
120
|
+
path: sib.path ? path.resolve(projectRoot, sib.path) : null
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
return null;
|
|
124
|
+
}).filter(Boolean);
|
|
125
|
+
|
|
126
|
+
const isCurrentHub = isHub(projectRoot);
|
|
127
|
+
|
|
128
|
+
if (isCurrentHub) {
|
|
129
|
+
return {
|
|
130
|
+
isMultiRepo: true,
|
|
131
|
+
role: 'hub',
|
|
132
|
+
siblings: siblingsResolved,
|
|
133
|
+
hubPath: projectRoot
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// We're a sibling. Find the hub.
|
|
138
|
+
const hubPath = findHub(projectRoot);
|
|
139
|
+
return {
|
|
140
|
+
isMultiRepo: !!hubPath,
|
|
141
|
+
role: hubPath ? 'sibling' : null,
|
|
142
|
+
siblings: siblingsResolved,
|
|
143
|
+
hubPath
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* List byte-identical files declared in the suite config.
|
|
149
|
+
* Returns [{ path, description }].
|
|
150
|
+
*/
|
|
151
|
+
function getByteIdenticalFiles(projectRoot) {
|
|
152
|
+
const config = readSuiteConfig(projectRoot);
|
|
153
|
+
if (!config) return [];
|
|
154
|
+
const list = config['byte-identical'] || config.byteIdentical || [];
|
|
155
|
+
return list.map(item => {
|
|
156
|
+
if (typeof item === 'string') return { path: item, description: '' };
|
|
157
|
+
return { path: item.path, description: item.description || '' };
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Get the version table from suite-config.
|
|
163
|
+
* Format: { 'package-name': { 'repo-name': '1.2.3' } }
|
|
164
|
+
*/
|
|
165
|
+
function getVersionTable(projectRoot) {
|
|
166
|
+
const config = readSuiteConfig(projectRoot);
|
|
167
|
+
if (!config) return {};
|
|
168
|
+
return config['version-table'] || config.versionTable || {};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function normalize(name) {
|
|
172
|
+
return String(name || '').toLowerCase().trim();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
module.exports = {
|
|
176
|
+
detect,
|
|
177
|
+
readSuiteConfig,
|
|
178
|
+
isHub,
|
|
179
|
+
findHub,
|
|
180
|
+
getByteIdenticalFiles,
|
|
181
|
+
getVersionTable
|
|
182
|
+
};
|