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/impact.js
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Impact Analysis
|
|
3
|
+
*
|
|
4
|
+
* Given a change to an artifact, identify what code is affected.
|
|
5
|
+
* Uses the linkage map to propagate forward.
|
|
6
|
+
*
|
|
7
|
+
* Public API:
|
|
8
|
+
* forArtifactDiff(projectRoot, artifactType, oldContent, newContent)
|
|
9
|
+
* -> { changes, affectedFiles, severity }
|
|
10
|
+
* forIdSet(projectRoot, addedIds, removedIds, changedIds)
|
|
11
|
+
* -> { addedAffects, removedAffects, changedAffects }
|
|
12
|
+
* transitive(projectRoot, fileSet, depth) -> Set
|
|
13
|
+
* forDesign(projectRoot, oldContent, newContent)
|
|
14
|
+
* -> { tokenChanges, affectedFiles, severity }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const linkage = require('./linkage');
|
|
21
|
+
const designSpec = require('./design-spec');
|
|
22
|
+
const artifactDiff = require('./artifact-diff');
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// ID extraction from artifact content
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Extract stable IDs declared in artifact content.
|
|
30
|
+
* Different per artifact type.
|
|
31
|
+
*/
|
|
32
|
+
function extractIds(artifactType, content) {
|
|
33
|
+
const ids = new Set();
|
|
34
|
+
if (!content) return ids;
|
|
35
|
+
|
|
36
|
+
if (artifactType === 'prd') {
|
|
37
|
+
// P-MUST-NN, P-SHOULD-NN, P-COULD-NN appearing anywhere
|
|
38
|
+
const regex = /\bP-(?:MUST|SHOULD|COULD)-\d+\b/g;
|
|
39
|
+
let m;
|
|
40
|
+
while ((m = regex.exec(content)) !== null) ids.add(m[0]);
|
|
41
|
+
} else if (artifactType === 'arch') {
|
|
42
|
+
const adrRegex = /\bADR-\d+\b/g;
|
|
43
|
+
const containerRegex = /\bC-[\w-]+\b/g;
|
|
44
|
+
let m;
|
|
45
|
+
while ((m = adrRegex.exec(content)) !== null) ids.add(m[0]);
|
|
46
|
+
while ((m = containerRegex.exec(content)) !== null) ids.add(m[0]);
|
|
47
|
+
} else if (artifactType === 'roadmap') {
|
|
48
|
+
const regex = /\bM-[\w-]+\b/g;
|
|
49
|
+
let m;
|
|
50
|
+
while ((m = regex.exec(content)) !== null) ids.add(m[0]);
|
|
51
|
+
} else if (artifactType === 'stack') {
|
|
52
|
+
const regex = /\bS-[\w-]+\b/g;
|
|
53
|
+
let m;
|
|
54
|
+
while ((m = regex.exec(content)) !== null) ids.add(m[0]);
|
|
55
|
+
} else if (artifactType === 'design') {
|
|
56
|
+
const parsed = designSpec.parse(content);
|
|
57
|
+
if (parsed.frontmatter) {
|
|
58
|
+
// Token paths: walk colors / typography / spacing / rounded
|
|
59
|
+
walkTokens(parsed.frontmatter, '', ids);
|
|
60
|
+
// Component IDs: D-{slug}
|
|
61
|
+
if (parsed.frontmatter.components) {
|
|
62
|
+
for (const compName of Object.keys(parsed.frontmatter.components)) {
|
|
63
|
+
ids.add(`D-${compName}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return ids;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function walkTokens(obj, prefix, set) {
|
|
72
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
73
|
+
if (k === 'name' || k === 'description' || k === 'components' || k === 'version') continue;
|
|
74
|
+
const here = prefix ? `${prefix}.${k}` : k;
|
|
75
|
+
if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
|
76
|
+
walkTokens(v, here, set);
|
|
77
|
+
} else {
|
|
78
|
+
// Only token leaves (values that are colors/dimensions)
|
|
79
|
+
set.add(here);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ============================================================================
|
|
85
|
+
// Diff at the ID level
|
|
86
|
+
// ============================================================================
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Compute added / removed / unchanged IDs from old vs new artifact content.
|
|
90
|
+
*/
|
|
91
|
+
function diffIds(artifactType, oldContent, newContent) {
|
|
92
|
+
const oldIds = extractIds(artifactType, oldContent);
|
|
93
|
+
const newIds = extractIds(artifactType, newContent);
|
|
94
|
+
const added = [...newIds].filter(id => !oldIds.has(id));
|
|
95
|
+
const removed = [...oldIds].filter(id => !newIds.has(id));
|
|
96
|
+
const kept = [...newIds].filter(id => oldIds.has(id));
|
|
97
|
+
return { added, removed, kept };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ============================================================================
|
|
101
|
+
// Impact analysis
|
|
102
|
+
// ============================================================================
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Given added / removed / changed IDs, return affected files for each.
|
|
106
|
+
*/
|
|
107
|
+
function forIdSet(projectRoot, { added = [], removed = [], changed = [] } = {}) {
|
|
108
|
+
function lookup(ids) {
|
|
109
|
+
const result = {};
|
|
110
|
+
for (const id of ids) {
|
|
111
|
+
const files = linkage.queryByArtifact(projectRoot, id);
|
|
112
|
+
if (files.length > 0) result[id] = files;
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
addedAffects: lookup(added),
|
|
118
|
+
removedAffects: lookup(removed),
|
|
119
|
+
changedAffects: lookup(changed)
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* High-level analysis from old/new artifact content.
|
|
125
|
+
* Severity:
|
|
126
|
+
* error if any ID was removed and code still references it
|
|
127
|
+
* warning if any ID was changed (kept but content differs)
|
|
128
|
+
* info if only additions
|
|
129
|
+
*/
|
|
130
|
+
function forArtifactDiff(projectRoot, artifactType, oldContent, newContent) {
|
|
131
|
+
const idDiff = diffIds(artifactType, oldContent, newContent);
|
|
132
|
+
const { addedAffects, removedAffects } = forIdSet(projectRoot, {
|
|
133
|
+
added: idDiff.added,
|
|
134
|
+
removed: idDiff.removed
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Also compute artifact-level diff for changed sections
|
|
138
|
+
const sectionDiff = artifactDiff.diffArtifacts(oldContent || '', newContent || '');
|
|
139
|
+
|
|
140
|
+
const affectedFiles = new Set();
|
|
141
|
+
for (const files of Object.values(addedAffects)) {
|
|
142
|
+
for (const f of files) affectedFiles.add(f);
|
|
143
|
+
}
|
|
144
|
+
for (const files of Object.values(removedAffects)) {
|
|
145
|
+
for (const f of files) affectedFiles.add(f);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let severity = 'info';
|
|
149
|
+
if (Object.keys(removedAffects).length > 0) severity = 'error';
|
|
150
|
+
else if (sectionDiff.regression) severity = 'warning';
|
|
151
|
+
else if (Object.keys(addedAffects).length > 0) severity = 'info';
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
artifactType,
|
|
155
|
+
idDiff,
|
|
156
|
+
addedAffects,
|
|
157
|
+
removedAffects,
|
|
158
|
+
sectionChanges: sectionDiff.changes,
|
|
159
|
+
affectedFiles: [...affectedFiles].sort(),
|
|
160
|
+
severity
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Specialized DESIGN.md impact: token + component changes.
|
|
166
|
+
*/
|
|
167
|
+
function forDesign(projectRoot, oldContent, newContent) {
|
|
168
|
+
const oldParsed = oldContent ? designSpec.parse(oldContent) : { frontmatter: {} };
|
|
169
|
+
const newParsed = newContent ? designSpec.parse(newContent) : { frontmatter: {} };
|
|
170
|
+
|
|
171
|
+
const tokenChanges = computeTokenChanges(
|
|
172
|
+
oldParsed.frontmatter || {},
|
|
173
|
+
newParsed.frontmatter || {}
|
|
174
|
+
);
|
|
175
|
+
const componentChanges = computeComponentChanges(
|
|
176
|
+
oldParsed.frontmatter || {},
|
|
177
|
+
newParsed.frontmatter || {}
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
// Affected files = files linked to any changed token or component
|
|
181
|
+
const affectedFiles = new Set();
|
|
182
|
+
for (const t of tokenChanges) {
|
|
183
|
+
const files = linkage.queryByArtifact(projectRoot, t.path);
|
|
184
|
+
for (const f of files) affectedFiles.add(f);
|
|
185
|
+
}
|
|
186
|
+
for (const c of componentChanges) {
|
|
187
|
+
const files = linkage.queryByArtifact(projectRoot, `D-${c.name}`);
|
|
188
|
+
for (const f of files) affectedFiles.add(f);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const severity = tokenChanges.some(t => t.kind === 'removed') || componentChanges.some(c => c.kind === 'removed')
|
|
192
|
+
? 'error'
|
|
193
|
+
: (tokenChanges.length || componentChanges.length) ? 'warning' : 'info';
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
tokenChanges,
|
|
197
|
+
componentChanges,
|
|
198
|
+
affectedFiles: [...affectedFiles].sort(),
|
|
199
|
+
severity
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function computeTokenChanges(oldFm, newFm) {
|
|
204
|
+
const changes = [];
|
|
205
|
+
const oldTokens = new Map();
|
|
206
|
+
const newTokens = new Map();
|
|
207
|
+
walkTokenValues(oldFm, '', oldTokens);
|
|
208
|
+
walkTokenValues(newFm, '', newTokens);
|
|
209
|
+
for (const [path, val] of oldTokens) {
|
|
210
|
+
if (!newTokens.has(path)) {
|
|
211
|
+
changes.push({ path, kind: 'removed', oldValue: val });
|
|
212
|
+
} else if (newTokens.get(path) !== val) {
|
|
213
|
+
changes.push({ path, kind: 'modified', oldValue: val, newValue: newTokens.get(path) });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const [path, val] of newTokens) {
|
|
217
|
+
if (!oldTokens.has(path)) {
|
|
218
|
+
changes.push({ path, kind: 'added', newValue: val });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return changes;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function walkTokenValues(obj, prefix, map) {
|
|
225
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
226
|
+
if (k === 'name' || k === 'description' || k === 'components' || k === 'version') continue;
|
|
227
|
+
const here = prefix ? `${prefix}.${k}` : k;
|
|
228
|
+
if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
|
|
229
|
+
walkTokenValues(v, here, map);
|
|
230
|
+
} else {
|
|
231
|
+
map.set(here, String(v));
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function computeComponentChanges(oldFm, newFm) {
|
|
237
|
+
const changes = [];
|
|
238
|
+
const oldComps = oldFm.components || {};
|
|
239
|
+
const newComps = newFm.components || {};
|
|
240
|
+
for (const name of Object.keys(oldComps)) {
|
|
241
|
+
if (!(name in newComps)) {
|
|
242
|
+
changes.push({ name, kind: 'removed' });
|
|
243
|
+
} else if (JSON.stringify(oldComps[name]) !== JSON.stringify(newComps[name])) {
|
|
244
|
+
changes.push({ name, kind: 'modified' });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
for (const name of Object.keys(newComps)) {
|
|
248
|
+
if (!(name in oldComps)) {
|
|
249
|
+
changes.push({ name, kind: 'added' });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return changes;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// ============================================================================
|
|
256
|
+
// Transitive expansion (depth-bounded)
|
|
257
|
+
// ============================================================================
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Expand affected file set transitively via simple import analysis.
|
|
261
|
+
* Default depth = 2 (matches plan's recommendation).
|
|
262
|
+
*/
|
|
263
|
+
function transitive(projectRoot, fileSet, depth = 2) {
|
|
264
|
+
const visited = new Set();
|
|
265
|
+
let frontier = new Set(fileSet);
|
|
266
|
+
for (let d = 0; d < depth; d++) {
|
|
267
|
+
const next = new Set();
|
|
268
|
+
for (const f of frontier) {
|
|
269
|
+
if (visited.has(f)) continue;
|
|
270
|
+
visited.add(f);
|
|
271
|
+
const dependents = findDependents(projectRoot, f);
|
|
272
|
+
for (const dep of dependents) {
|
|
273
|
+
if (!visited.has(dep)) next.add(dep);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
frontier = next;
|
|
277
|
+
if (frontier.size === 0) break;
|
|
278
|
+
}
|
|
279
|
+
return [...visited].sort();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function findDependents(projectRoot, targetFile) {
|
|
283
|
+
// Heuristic: scan known files for imports matching the target.
|
|
284
|
+
// For tests / fast V1: scan only files in the linkage reverse map.
|
|
285
|
+
const rev = linkage.readReverse(projectRoot);
|
|
286
|
+
const candidates = Object.keys(rev);
|
|
287
|
+
const targetBase = path.basename(targetFile, path.extname(targetFile));
|
|
288
|
+
const dependents = [];
|
|
289
|
+
for (const cand of candidates) {
|
|
290
|
+
const full = path.join(projectRoot, cand);
|
|
291
|
+
if (!fs.existsSync(full)) continue;
|
|
292
|
+
const ext = path.extname(cand);
|
|
293
|
+
if (!['.js', '.jsx', '.ts', '.tsx', '.mjs', '.css', '.scss'].includes(ext)) continue;
|
|
294
|
+
let content;
|
|
295
|
+
try {
|
|
296
|
+
content = fs.readFileSync(full, 'utf8');
|
|
297
|
+
} catch (e) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (content.includes(targetBase) && cand !== targetFile) {
|
|
301
|
+
dependents.push(cand);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return dependents;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
extractIds,
|
|
309
|
+
diffIds,
|
|
310
|
+
forIdSet,
|
|
311
|
+
forArtifactDiff,
|
|
312
|
+
forDesign,
|
|
313
|
+
transitive
|
|
314
|
+
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Impeccable Bridge
|
|
3
|
+
*
|
|
4
|
+
* Thin invocation layer between godpowers and the impeccable design skill.
|
|
5
|
+
* Detects impeccable presence; dispatches commands; captures findings.
|
|
6
|
+
*
|
|
7
|
+
* IMPORTANT: Impeccable is never vendored. This module only detects and
|
|
8
|
+
* delegates. If impeccable is not installed, command dispatch returns a
|
|
9
|
+
* graceful "not-installed" result that callers can act on.
|
|
10
|
+
*
|
|
11
|
+
* Public API:
|
|
12
|
+
* isInstalled(projectRoot) -> bool
|
|
13
|
+
* getInstallation(projectRoot) -> { installed, locations, primaryTool }
|
|
14
|
+
* runDetect(targetPath, opts) -> { findings, error } // shells out to npx impeccable
|
|
15
|
+
* describeBridge() -> { commands, sourcedFrom } // metadata
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const { execSync } = require('child_process');
|
|
21
|
+
|
|
22
|
+
const designDetector = require('./design-detector');
|
|
23
|
+
|
|
24
|
+
const IMPECCABLE_COMMANDS = [
|
|
25
|
+
'craft', 'teach', 'document', 'extract', 'shape',
|
|
26
|
+
'critique', 'audit', 'polish',
|
|
27
|
+
'bolder', 'quieter', 'distill', 'harden', 'onboard',
|
|
28
|
+
'animate', 'colorize', 'typeset', 'layout',
|
|
29
|
+
'delight', 'overdrive', 'clarify', 'adapt', 'optimize', 'live'
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Quick presence check. Delegates to design-detector for consistency.
|
|
34
|
+
*/
|
|
35
|
+
function isInstalled(projectRoot) {
|
|
36
|
+
return designDetector.isImpeccableInstalled(projectRoot).installed;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Detailed installation info: where impeccable is found.
|
|
41
|
+
* Identifies the primary AI tool from the first matched skill location.
|
|
42
|
+
*/
|
|
43
|
+
function getInstallation(projectRoot) {
|
|
44
|
+
const result = designDetector.isImpeccableInstalled(projectRoot);
|
|
45
|
+
let primaryTool = null;
|
|
46
|
+
for (const loc of result.locations) {
|
|
47
|
+
if (loc.includes('.claude')) { primaryTool = 'claude-code'; break; }
|
|
48
|
+
if (loc.includes('.cursor')) { primaryTool = 'cursor'; break; }
|
|
49
|
+
if (loc.includes('.gemini')) { primaryTool = 'gemini'; break; }
|
|
50
|
+
if (loc.includes('.opencode')) { primaryTool = 'opencode'; break; }
|
|
51
|
+
if (loc.includes('.kiro')) { primaryTool = 'kiro'; break; }
|
|
52
|
+
if (loc.includes('.qoder')) { primaryTool = 'qoder'; break; }
|
|
53
|
+
if (loc.includes('.rovodev')) { primaryTool = 'rovodev'; break; }
|
|
54
|
+
if (loc.includes('.trae')) { primaryTool = 'trae'; break; }
|
|
55
|
+
if (loc.includes('.agents')) { primaryTool = 'codex'; break; }
|
|
56
|
+
if (loc.includes('.github')) { primaryTool = 'copilot'; break; }
|
|
57
|
+
if (loc.includes('node_modules')) { primaryTool = 'npm'; break; }
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
installed: result.installed,
|
|
61
|
+
locations: result.locations,
|
|
62
|
+
primaryTool
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Run `npx impeccable detect <target>` and parse the JSON output.
|
|
68
|
+
* Returns { findings, error }.
|
|
69
|
+
*
|
|
70
|
+
* If impeccable CLI is not available, returns
|
|
71
|
+
* { findings: [], error: 'not-installed' }
|
|
72
|
+
*
|
|
73
|
+
* Callers should pass `--fast --json` or rely on this function's defaults.
|
|
74
|
+
*/
|
|
75
|
+
function runDetect(targetPath, opts = {}) {
|
|
76
|
+
const cwd = opts.cwd || process.cwd();
|
|
77
|
+
if (!isInstalled(cwd) && !opts.forceRun) {
|
|
78
|
+
return { findings: [], error: 'not-installed' };
|
|
79
|
+
}
|
|
80
|
+
const args = ['impeccable', 'detect', '--fast', '--json'];
|
|
81
|
+
if (targetPath) args.push(targetPath);
|
|
82
|
+
try {
|
|
83
|
+
// Use execFileSync with array args (no shell, no injection risk).
|
|
84
|
+
const { execFileSync } = require('child_process');
|
|
85
|
+
const output = execFileSync('npx', args, {
|
|
86
|
+
cwd,
|
|
87
|
+
encoding: 'utf8',
|
|
88
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
89
|
+
timeout: 30_000
|
|
90
|
+
});
|
|
91
|
+
let parsed;
|
|
92
|
+
try {
|
|
93
|
+
parsed = JSON.parse(output);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
return { findings: [], error: 'parse-error', raw: output };
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
findings: parsed.findings || [],
|
|
99
|
+
summary: parsed.summary || null,
|
|
100
|
+
error: null
|
|
101
|
+
};
|
|
102
|
+
} catch (e) {
|
|
103
|
+
return { findings: [], error: 'execution-error', message: e.message };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Metadata about the bridge: which commands are bridged, where they delegate.
|
|
109
|
+
*/
|
|
110
|
+
function describeBridge() {
|
|
111
|
+
return {
|
|
112
|
+
commands: IMPECCABLE_COMMANDS,
|
|
113
|
+
sourcedFrom: 'https://github.com/pbakaus/impeccable',
|
|
114
|
+
integrationModel: 'detect-and-delegate; never vendored',
|
|
115
|
+
bridgeFunctions: ['isInstalled', 'getInstallation', 'runDetect']
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Build a god-design subcommand -> impeccable command mapping.
|
|
121
|
+
*/
|
|
122
|
+
function commandMap() {
|
|
123
|
+
const map = {};
|
|
124
|
+
for (const cmd of IMPECCABLE_COMMANDS) {
|
|
125
|
+
map[`/god-design ${cmd}`] = `/impeccable ${cmd}`;
|
|
126
|
+
}
|
|
127
|
+
// Aliases for godpowers-natural names
|
|
128
|
+
map['/god-design refresh'] = '/impeccable document';
|
|
129
|
+
return map;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
module.exports = {
|
|
133
|
+
isInstalled,
|
|
134
|
+
getInstallation,
|
|
135
|
+
runDetect,
|
|
136
|
+
describeBridge,
|
|
137
|
+
commandMap,
|
|
138
|
+
IMPECCABLE_COMMANDS
|
|
139
|
+
};
|
package/lib/intent.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intent Manager
|
|
3
|
+
*
|
|
4
|
+
* Read .godpowers/intent.yaml. Validate basic structure.
|
|
5
|
+
*
|
|
6
|
+
* Note: this is a minimal YAML reader, intentionally avoiding a YAML
|
|
7
|
+
* dependency. Handles the subset of YAML our intent files use.
|
|
8
|
+
* For complex YAML, agents read the file directly.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
function intentPath(projectRoot) {
|
|
15
|
+
return path.join(projectRoot, '.godpowers', 'intent.yaml');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read intent.yaml. Returns parsed object or null if not found.
|
|
20
|
+
*
|
|
21
|
+
* Minimal YAML parser: handles the subset our schema uses
|
|
22
|
+
* (key: value, nested objects, arrays of strings, booleans).
|
|
23
|
+
* For full YAML, agents should use a real parser.
|
|
24
|
+
*/
|
|
25
|
+
function read(projectRoot) {
|
|
26
|
+
const file = intentPath(projectRoot);
|
|
27
|
+
if (!fs.existsSync(file)) return null;
|
|
28
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
29
|
+
return parseSimpleYaml(content);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parse a simple YAML subset. Just enough for intent.yaml structure.
|
|
34
|
+
* Real-world: replace with `yaml` npm package when we add deps.
|
|
35
|
+
*/
|
|
36
|
+
function parseSimpleYaml(content) {
|
|
37
|
+
const lines = content.split('\n');
|
|
38
|
+
const result = {};
|
|
39
|
+
const stack = [{ obj: result, indent: -1, key: null, isArray: false, parent: null }];
|
|
40
|
+
|
|
41
|
+
for (let line of lines) {
|
|
42
|
+
if (!line.trim() || line.trim().startsWith('#')) continue;
|
|
43
|
+
// Strip inline comments (but not # inside quotes)
|
|
44
|
+
const hashIdx = line.indexOf(' #');
|
|
45
|
+
if (hashIdx !== -1) {
|
|
46
|
+
// Make sure the # isn't inside a quoted string
|
|
47
|
+
const before = line.slice(0, hashIdx);
|
|
48
|
+
const dquoteCount = (before.match(/"/g) || []).length;
|
|
49
|
+
const squoteCount = (before.match(/'/g) || []).length;
|
|
50
|
+
if (dquoteCount % 2 === 0 && squoteCount % 2 === 0) {
|
|
51
|
+
line = before;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const indent = line.length - line.trimStart().length;
|
|
56
|
+
const trimmed = line.trim();
|
|
57
|
+
|
|
58
|
+
// Pop stack to the right indent level
|
|
59
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
60
|
+
stack.pop();
|
|
61
|
+
}
|
|
62
|
+
const parent = stack[stack.length - 1].obj;
|
|
63
|
+
const parentMeta = stack[stack.length - 1];
|
|
64
|
+
|
|
65
|
+
// List item: "- key: value" or "- value"
|
|
66
|
+
if (trimmed.startsWith('- ')) {
|
|
67
|
+
const rest = trimmed.slice(2);
|
|
68
|
+
// If the rest is a quoted string, treat as simple value (don't split on colon)
|
|
69
|
+
const isQuotedSimple = /^"[^"]*"$|^'[^']*'$/.test(rest.trim());
|
|
70
|
+
const restColonIdx = isQuotedSimple ? -1 : rest.indexOf(':');
|
|
71
|
+
|
|
72
|
+
// Ensure parent is array
|
|
73
|
+
if (!Array.isArray(parent.__items__)) {
|
|
74
|
+
parent.__items__ = [];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (restColonIdx === -1) {
|
|
78
|
+
// Simple list value: "- value"
|
|
79
|
+
parent.__items__.push(parseValue(rest.trim()));
|
|
80
|
+
} else {
|
|
81
|
+
// List of objects: "- key: value"
|
|
82
|
+
const itemKey = rest.slice(0, restColonIdx).trim();
|
|
83
|
+
const itemVal = rest.slice(restColonIdx + 1).trim();
|
|
84
|
+
const newObj = {};
|
|
85
|
+
if (itemVal) {
|
|
86
|
+
newObj[itemKey] = parseValue(itemVal);
|
|
87
|
+
} else {
|
|
88
|
+
newObj[itemKey] = {};
|
|
89
|
+
// Inner object's properties start at indent+4 (2 for "- " + 2 for key indentation)
|
|
90
|
+
stack.push({ obj: newObj[itemKey], indent: indent + 3, key: itemKey, parent: newObj });
|
|
91
|
+
}
|
|
92
|
+
parent.__items__.push(newObj);
|
|
93
|
+
// Push the new object onto the stack at indent+1 so subsequent properties
|
|
94
|
+
// at indent+2 (the "- " offset) go into it. The +1 ensures it's NOT popped
|
|
95
|
+
// when we encounter properties at the same visual indent as the "-".
|
|
96
|
+
stack.push({ obj: newObj, indent: indent + 1, key: '__item__', parent });
|
|
97
|
+
}
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const colonIdx = trimmed.indexOf(':');
|
|
102
|
+
if (colonIdx === -1) continue;
|
|
103
|
+
const key = trimmed.slice(0, colonIdx).trim();
|
|
104
|
+
const valueStr = trimmed.slice(colonIdx + 1).trim();
|
|
105
|
+
|
|
106
|
+
if (!valueStr) {
|
|
107
|
+
const child = {};
|
|
108
|
+
parent[key] = child;
|
|
109
|
+
stack.push({ obj: child, indent, key, parent });
|
|
110
|
+
} else if (valueStr === '|' || valueStr === '>') {
|
|
111
|
+
parent[key] = '';
|
|
112
|
+
} else {
|
|
113
|
+
parent[key] = parseValue(valueStr);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return cleanArrays(result);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function parseValue(str) {
|
|
121
|
+
if (str === 'true') return true;
|
|
122
|
+
if (str === 'false') return false;
|
|
123
|
+
if (str === 'null' || str === '~') return null;
|
|
124
|
+
if (/^-?\d+$/.test(str)) return parseInt(str, 10);
|
|
125
|
+
if (/^-?\d+\.\d+$/.test(str)) return parseFloat(str);
|
|
126
|
+
if (/^".*"$/.test(str) || /^'.*'$/.test(str)) return str.slice(1, -1);
|
|
127
|
+
// Inline array: [/god-mode, /god-foo]
|
|
128
|
+
if (/^\[.*\]$/.test(str)) {
|
|
129
|
+
const inner = str.slice(1, -1).trim();
|
|
130
|
+
if (!inner) return [];
|
|
131
|
+
return inner.split(',').map(s => parseValue(s.trim()));
|
|
132
|
+
}
|
|
133
|
+
return str;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function cleanArrays(obj) {
|
|
137
|
+
if (Array.isArray(obj)) return obj.map(cleanArrays);
|
|
138
|
+
if (obj && typeof obj === 'object') {
|
|
139
|
+
// Detect array container (legacy or new)
|
|
140
|
+
if (obj.__items__) return obj.__items__.map(cleanArrays);
|
|
141
|
+
if (obj.__pending_array__) return obj.__pending_array__;
|
|
142
|
+
const cleaned = {};
|
|
143
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
144
|
+
cleaned[k] = cleanArrays(v);
|
|
145
|
+
}
|
|
146
|
+
return cleaned;
|
|
147
|
+
}
|
|
148
|
+
return obj;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get a setting from intent.yaml using dot notation.
|
|
153
|
+
* Example: get(root, 'config.yolo')
|
|
154
|
+
*/
|
|
155
|
+
function get(intent, key) {
|
|
156
|
+
if (!intent) return undefined;
|
|
157
|
+
return key.split('.').reduce((acc, k) => (acc && acc[k] !== undefined ? acc[k] : undefined), intent);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Validate the structure of an intent object against expected shape.
|
|
162
|
+
* Returns array of error messages (empty if valid).
|
|
163
|
+
*/
|
|
164
|
+
function validate(intent) {
|
|
165
|
+
const errors = [];
|
|
166
|
+
if (!intent) return ['intent is null'];
|
|
167
|
+
if (intent.apiVersion !== 'godpowers/v1') errors.push('apiVersion must be godpowers/v1');
|
|
168
|
+
if (intent.kind !== 'Project') errors.push('kind must be Project');
|
|
169
|
+
if (!intent.metadata || !intent.metadata.name) errors.push('metadata.name required');
|
|
170
|
+
if (!['A', 'B', 'C', 'E'].includes(intent.mode)) errors.push('mode must be A, B, C, or E (D is a suite-membership boolean, mode-d-suite, not a primary mode)');
|
|
171
|
+
if (!['trivial', 'small', 'medium', 'large', 'enterprise'].includes(intent.scale)) {
|
|
172
|
+
errors.push('scale must be trivial/small/medium/large/enterprise');
|
|
173
|
+
}
|
|
174
|
+
return errors;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
module.exports = { read, get, validate, intentPath, parseSimpleYaml };
|