contextdevkit 1.8.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/CHANGELOG.md +592 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/docs/AGENT-PACKAGE-FORMAT.md +140 -0
- package/docs/ARCHITECTURE.md +258 -0
- package/docs/CHANGELOG.md +559 -0
- package/docs/CUSTOMIZING.md +211 -0
- package/docs/LEVELS.md +151 -0
- package/docs/ROADMAP.md +385 -0
- package/docs/SQUAD-PIPELINE-FORMAT.md +258 -0
- package/docs/SQUADS/agent-forge.md +65 -0
- package/docs/SQUADS/design-team.md +161 -0
- package/docs/token-economy-plan.md +135 -0
- package/install.mjs +273 -0
- package/instrucoes.md +274 -0
- package/package.json +46 -0
- package/templates/CLAUDE.md.tpl +133 -0
- package/templates/claude/agents/_TEMPLATE.md +52 -0
- package/templates/claude/agents/accessibility.md +36 -0
- package/templates/claude/agents/agent-architect.md +37 -0
- package/templates/claude/agents/architect.md +39 -0
- package/templates/claude/agents/code-reviewer.md +43 -0
- package/templates/claude/agents/code-security.md +59 -0
- package/templates/claude/agents/context-keeper.md +40 -0
- package/templates/claude/agents/devops.md +40 -0
- package/templates/claude/agents/eval-designer.md +40 -0
- package/templates/claude/agents/forge-orchestrator.md +42 -0
- package/templates/claude/agents/governance-officer.md +45 -0
- package/templates/claude/agents/growth.md +92 -0
- package/templates/claude/agents/infra-security.md +53 -0
- package/templates/claude/agents/landing-architect.md +154 -0
- package/templates/claude/agents/model-router.md +34 -0
- package/templates/claude/agents/packager.md +38 -0
- package/templates/claude/agents/privacy-lgpd.md +64 -0
- package/templates/claude/agents/product-owner.md +51 -0
- package/templates/claude/agents/prompt-engineer.md +33 -0
- package/templates/claude/agents/qa-e2e.md +52 -0
- package/templates/claude/agents/qa-fuzzer.md +24 -0
- package/templates/claude/agents/qa-integration.md +21 -0
- package/templates/claude/agents/qa-orchestrator.md +40 -0
- package/templates/claude/agents/qa-perf.md +40 -0
- package/templates/claude/agents/qa-unit.md +39 -0
- package/templates/claude/agents/rag-designer.md +54 -0
- package/templates/claude/agents/retention.md +85 -0
- package/templates/claude/agents/security.md +48 -0
- package/templates/claude/agents/seo-specialist.md +106 -0
- package/templates/claude/agents/test-engineer.md +48 -0
- package/templates/claude/agents/tool-designer.md +32 -0
- package/templates/claude/agents/ui-designer.md +37 -0
- package/templates/claude/agents/ux-designer.md +38 -0
- package/templates/claude/commands/README.md +95 -0
- package/templates/claude/commands/advise.md +80 -0
- package/templates/claude/commands/audit/analyze-code-ia-practices.md +75 -0
- package/templates/claude/commands/audit/audit.md +35 -0
- package/templates/claude/commands/audit/contract-check.md +21 -0
- package/templates/claude/commands/audit/deep-analysis.md +48 -0
- package/templates/claude/commands/audit/deps-audit.md +49 -0
- package/templates/claude/commands/audit/security-setup.md +35 -0
- package/templates/claude/commands/audit/seo-audit.md +63 -0
- package/templates/claude/commands/audit/tech-debt-sweep.md +35 -0
- package/templates/claude/commands/bug-hunt.md +42 -0
- package/templates/claude/commands/claude-md.md +36 -0
- package/templates/claude/commands/close-version.md +25 -0
- package/templates/claude/commands/context-refresh.md +19 -0
- package/templates/claude/commands/context-stats.md +15 -0
- package/templates/claude/commands/dashboard.md +66 -0
- package/templates/claude/commands/distill-apply.md +19 -0
- package/templates/claude/commands/distill-sessions.md +26 -0
- package/templates/claude/commands/fleet.md +47 -0
- package/templates/claude/commands/forge/forge-audit.md +16 -0
- package/templates/claude/commands/forge/forge-budget.md +16 -0
- package/templates/claude/commands/forge/forge-deprecate.md +16 -0
- package/templates/claude/commands/forge/forge-doctor.md +17 -0
- package/templates/claude/commands/forge/forge-eval.md +16 -0
- package/templates/claude/commands/forge/forge-fallback-test.md +17 -0
- package/templates/claude/commands/forge/forge-killswitch.md +17 -0
- package/templates/claude/commands/forge/forge-list.md +17 -0
- package/templates/claude/commands/forge/forge-new.md +41 -0
- package/templates/claude/commands/forge/forge-policy.md +16 -0
- package/templates/claude/commands/forge/forge-redteam.md +17 -0
- package/templates/claude/commands/forge/forge-refresh-matrix.md +20 -0
- package/templates/claude/commands/forge/forge-route.md +17 -0
- package/templates/claude/commands/forge/forge-show.md +16 -0
- package/templates/claude/commands/landing-page.md +71 -0
- package/templates/claude/commands/log-session.md +59 -0
- package/templates/claude/commands/media-gen.md +93 -0
- package/templates/claude/commands/new-adr.md +30 -0
- package/templates/claude/commands/pipeline/dev-start.md +64 -0
- package/templates/claude/commands/pipeline/pipeline.md +36 -0
- package/templates/claude/commands/pipeline/resume.md +70 -0
- package/templates/claude/commands/pipeline/retro.md +34 -0
- package/templates/claude/commands/pipeline/runs.md +63 -0
- package/templates/claude/commands/pipeline/ship.md +54 -0
- package/templates/claude/commands/pipeline/workflow.md +85 -0
- package/templates/claude/commands/playbook.md +27 -0
- package/templates/claude/commands/predictions-review.md +28 -0
- package/templates/claude/commands/qa/qa-signoff.md +24 -0
- package/templates/claude/commands/qa/scaffold-tests.md +27 -0
- package/templates/claude/commands/qa/test-plan.md +26 -0
- package/templates/claude/commands/qa/visual-test.md +42 -0
- package/templates/claude/commands/roadmap.md +48 -0
- package/templates/claude/commands/setup/aidevtool-from0.md +104 -0
- package/templates/claude/commands/setup/context-config.md +25 -0
- package/templates/claude/commands/setup/context-doctor.md +21 -0
- package/templates/claude/commands/setup/context-level.md +17 -0
- package/templates/claude/commands/setup/setupcontextdevkit.md +121 -0
- package/templates/claude/commands/simulate-impact.md +32 -0
- package/templates/claude/commands/squad.md +44 -0
- package/templates/claude/commands/state.md +21 -0
- package/templates/claude/commands/token-report.md +29 -0
- package/templates/claude/commands/tune-agents.md +35 -0
- package/templates/claude/commands/vcs/claim.md +18 -0
- package/templates/claude/commands/vcs/git.md +83 -0
- package/templates/claude/commands/vcs/release.md +15 -0
- package/templates/claude/commands/vcs/worktree-new.md +18 -0
- package/templates/claude/commands/watch.md +47 -0
- package/templates/contextkit/.env.example +36 -0
- package/templates/contextkit/CLAUDE.child.md.tpl +38 -0
- package/templates/contextkit/README.md +74 -0
- package/templates/contextkit/behaviors-examples.md +183 -0
- package/templates/contextkit/behaviors.md +116 -0
- package/templates/contextkit/best-practices.md +323 -0
- package/templates/contextkit/config.json +66 -0
- package/templates/contextkit/detectors/README.md +45 -0
- package/templates/contextkit/detectors/example-detector.mjs.example +25 -0
- package/templates/contextkit/instrucoes.md +114 -0
- package/templates/contextkit/memory/GLOSSARY.md +13 -0
- package/templates/contextkit/memory/SESSIONS.md +9 -0
- package/templates/contextkit/memory/WORKSPACE.md +7 -0
- package/templates/contextkit/memory/business-rules/_TEMPLATE.md +33 -0
- package/templates/contextkit/memory/decisions/0000-record-architecture-decisions.md +34 -0
- package/templates/contextkit/memory/decisions/_TEMPLATE.md +25 -0
- package/templates/contextkit/memory/predictions/.gitkeep +0 -0
- package/templates/contextkit/memory/roadmap.md +28 -0
- package/templates/contextkit/memory/sessions/.gitkeep +0 -0
- package/templates/contextkit/memory/workflows/.gitkeep +0 -0
- package/templates/contextkit/pipeline/backlog/.gitkeep +0 -0
- package/templates/contextkit/pipeline/conclusion/.gitkeep +0 -0
- package/templates/contextkit/pipeline/devpipeline.md +9 -0
- package/templates/contextkit/pipeline/testing/.gitkeep +0 -0
- package/templates/contextkit/pipeline/working/.gitkeep +0 -0
- package/templates/contextkit/review-protocol.md +214 -0
- package/templates/contextkit/runtime/config/defaults.mjs +215 -0
- package/templates/contextkit/runtime/config/levels.mjs +42 -0
- package/templates/contextkit/runtime/config/load.mjs +105 -0
- package/templates/contextkit/runtime/config/paths.mjs +92 -0
- package/templates/contextkit/runtime/config/presets.mjs +47 -0
- package/templates/contextkit/runtime/config/schema.mjs +88 -0
- package/templates/contextkit/runtime/config/settings-compose.mjs +55 -0
- package/templates/contextkit/runtime/git-hooks/commit-msg.mjs +55 -0
- package/templates/contextkit/runtime/git-hooks/pre-commit.mjs +47 -0
- package/templates/contextkit/runtime/git-hooks/pre-push.mjs +102 -0
- package/templates/contextkit/runtime/hooks/boot-context-readers.mjs +111 -0
- package/templates/contextkit/runtime/hooks/boot-signals.mjs +135 -0
- package/templates/contextkit/runtime/hooks/check-registration.mjs +228 -0
- package/templates/contextkit/runtime/hooks/concurrency-guard.mjs +110 -0
- package/templates/contextkit/runtime/hooks/ledger.mjs +231 -0
- package/templates/contextkit/runtime/hooks/md-extract.mjs +65 -0
- package/templates/contextkit/runtime/hooks/path-classification.mjs +62 -0
- package/templates/contextkit/runtime/hooks/safe-io.mjs +84 -0
- package/templates/contextkit/runtime/hooks/session-digest-core.mjs +85 -0
- package/templates/contextkit/runtime/hooks/session-start.mjs +248 -0
- package/templates/contextkit/runtime/hooks/simulate-gate.mjs +108 -0
- package/templates/contextkit/runtime/hooks/track-edits.mjs +154 -0
- package/templates/contextkit/runtime/providers/media/_adapter.mjs +120 -0
- package/templates/contextkit/runtime/providers/media/nano-banana.mjs +110 -0
- package/templates/contextkit/runtime/providers/media/veo.mjs +162 -0
- package/templates/contextkit/runtime/providers/review/_adapter.mjs +71 -0
- package/templates/contextkit/runtime/providers/review/detect.mjs +115 -0
- package/templates/contextkit/runtime/providers/review/gh.mjs +103 -0
- package/templates/contextkit/runtime/state/state-io.mjs +172 -0
- package/templates/contextkit/runtime/statusline.mjs +51 -0
- package/templates/contextkit/squads/README.md +115 -0
- package/templates/contextkit/squads/_BRIEFING.md.tpl +27 -0
- package/templates/contextkit/squads/agent-forge/README.md +69 -0
- package/templates/contextkit/squads/agent-forge/ROADMAP.md +108 -0
- package/templates/contextkit/squads/agent-forge/best-practices.md +89 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-admin.mjs +132 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-eval-cli.mjs +163 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-new.mjs +97 -0
- package/templates/contextkit/squads/agent-forge/cli/forge-ops.mjs +177 -0
- package/templates/contextkit/squads/agent-forge/lib/architect.mjs +112 -0
- package/templates/contextkit/squads/agent-forge/lib/eval-designer.mjs +133 -0
- package/templates/contextkit/squads/agent-forge/lib/eval-runner.mjs +167 -0
- package/templates/contextkit/squads/agent-forge/lib/governance-officer.mjs +178 -0
- package/templates/contextkit/squads/agent-forge/lib/package-ops.mjs +101 -0
- package/templates/contextkit/squads/agent-forge/lib/packager.mjs +219 -0
- package/templates/contextkit/squads/agent-forge/lib/prompt-gen.mjs +122 -0
- package/templates/contextkit/squads/agent-forge/lib/rag-designer.mjs +102 -0
- package/templates/contextkit/squads/agent-forge/lib/router.mjs +165 -0
- package/templates/contextkit/squads/agent-forge/lib/tool-gen.mjs +113 -0
- package/templates/contextkit/squads/agent-forge/lib/yaml.mjs +47 -0
- package/templates/contextkit/squads/agent-forge/pipeline.yaml +65 -0
- package/templates/contextkit/squads/agent-forge/router/capability-matrix.json +112 -0
- package/templates/contextkit/squads/agent-forge/router/decision-rules.json +120 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/.agentforgerc +12 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/CHANGELOG.md +13 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/LICENSE +5 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/README.md +39 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/README.md +10 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/agent.go +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/go/go.mod +3 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/README.md +11 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/index.js +53 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/node/package.json +9 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/README.md +10 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/agent.py +16 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/adapters/python/pyproject.toml +10 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/golden.jsonl +1 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/red-team.jsonl +3 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/rubric.yaml +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/run-eval.md +17 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/evals/thresholds.yaml +18 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/basic.node.md +17 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/with-fallback.node.md +24 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/examples/with-rag.python.md +20 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/audit.schema.json +23 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/compliance.policy.yaml +43 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/cost.policy.yaml +36 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/fallback-chain.yaml +16 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/governance/quality.policy.yaml +43 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/manifest.yaml +91 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.anthropic.md +19 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.canonical.md +25 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.deepseek.md +21 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.google.md +19 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.ollama.md +21 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/prompts/system.openai.md +20 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/config.yaml +17 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/index/.gitkeep +3 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/ingestion/chunker.config.yaml +6 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/ingestion/sources.yaml +8 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/retrieval/query-template.md +16 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/rag/retrieval/rerank.config.yaml +6 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/anthropic.tools.json +11 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/deepseek.tools.json +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/google.tools.json +11 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/ollama.tools.json +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/adapters/openai.tools.json +14 -0
- package/templates/contextkit/squads/agent-forge/templates/agent-package/tools/schemas.canonical.json +25 -0
- package/templates/contextkit/starters/tanstack/README.md +86 -0
- package/templates/contextkit/starters/tanstack/index.html +12 -0
- package/templates/contextkit/starters/tanstack/package.json +25 -0
- package/templates/contextkit/starters/tanstack/src/main.tsx +40 -0
- package/templates/contextkit/starters/tanstack/src/router.tsx +12 -0
- package/templates/contextkit/starters/tanstack/src/routes/__root.tsx +10 -0
- package/templates/contextkit/starters/tanstack/src/routes/index.tsx +17 -0
- package/templates/contextkit/starters/tanstack/tsconfig.json +19 -0
- package/templates/contextkit/starters/tanstack/vite.config.ts +10 -0
- package/templates/contextkit/tools/scripts/adr-digest-core.mjs +42 -0
- package/templates/contextkit/tools/scripts/adr-digest.mjs +78 -0
- package/templates/contextkit/tools/scripts/agent-tuning.mjs +74 -0
- package/templates/contextkit/tools/scripts/aiso-audit.mjs +174 -0
- package/templates/contextkit/tools/scripts/audit-shared.mjs +129 -0
- package/templates/contextkit/tools/scripts/claim.mjs +133 -0
- package/templates/contextkit/tools/scripts/claude-md.mjs +123 -0
- package/templates/contextkit/tools/scripts/clean-drive.mjs +78 -0
- package/templates/contextkit/tools/scripts/context-config.mjs +111 -0
- package/templates/contextkit/tools/scripts/context-level.mjs +98 -0
- package/templates/contextkit/tools/scripts/context-pack.mjs +120 -0
- package/templates/contextkit/tools/scripts/contract-scan.mjs +186 -0
- package/templates/contextkit/tools/scripts/dashboard-data.mjs +198 -0
- package/templates/contextkit/tools/scripts/dashboard-html.mjs +215 -0
- package/templates/contextkit/tools/scripts/dashboard-server.mjs +129 -0
- package/templates/contextkit/tools/scripts/dashboard.mjs +107 -0
- package/templates/contextkit/tools/scripts/deep-analysis.mjs +62 -0
- package/templates/contextkit/tools/scripts/deps-audit.mjs +201 -0
- package/templates/contextkit/tools/scripts/detect-stack.mjs +164 -0
- package/templates/contextkit/tools/scripts/distill-detect.mjs +90 -0
- package/templates/contextkit/tools/scripts/doctor.mjs +165 -0
- package/templates/contextkit/tools/scripts/fleet.mjs +170 -0
- package/templates/contextkit/tools/scripts/generate-context.mjs +142 -0
- package/templates/contextkit/tools/scripts/gh-alerts.mjs +117 -0
- package/templates/contextkit/tools/scripts/git.mjs +97 -0
- package/templates/contextkit/tools/scripts/home.mjs +106 -0
- package/templates/contextkit/tools/scripts/mark-simulation.mjs +78 -0
- package/templates/contextkit/tools/scripts/media-gen.mjs +154 -0
- package/templates/contextkit/tools/scripts/pipeline-board.mjs +74 -0
- package/templates/contextkit/tools/scripts/pipeline-prioritize.mjs +68 -0
- package/templates/contextkit/tools/scripts/pipeline-session.mjs +99 -0
- package/templates/contextkit/tools/scripts/pipeline-validate.mjs +136 -0
- package/templates/contextkit/tools/scripts/pipeline.mjs +302 -0
- package/templates/contextkit/tools/scripts/playbook.mjs +123 -0
- package/templates/contextkit/tools/scripts/predictions-review.mjs +113 -0
- package/templates/contextkit/tools/scripts/release.mjs +60 -0
- package/templates/contextkit/tools/scripts/resume.mjs +114 -0
- package/templates/contextkit/tools/scripts/roadmap.mjs +86 -0
- package/templates/contextkit/tools/scripts/runs.mjs +116 -0
- package/templates/contextkit/tools/scripts/seo-audit.mjs +150 -0
- package/templates/contextkit/tools/scripts/session-digest.mjs +89 -0
- package/templates/contextkit/tools/scripts/session-reindex.mjs +91 -0
- package/templates/contextkit/tools/scripts/setup-complete.mjs +69 -0
- package/templates/contextkit/tools/scripts/squad-meta.mjs +23 -0
- package/templates/contextkit/tools/scripts/squad-pipeline-condition.mjs +192 -0
- package/templates/contextkit/tools/scripts/squad-pipeline.mjs +301 -0
- package/templates/contextkit/tools/scripts/squad.mjs +80 -0
- package/templates/contextkit/tools/scripts/stats.mjs +138 -0
- package/templates/contextkit/tools/scripts/sync-check.mjs +235 -0
- package/templates/contextkit/tools/scripts/tech-debt-detectors.mjs +76 -0
- package/templates/contextkit/tools/scripts/tech-debt-scan.mjs +164 -0
- package/templates/contextkit/tools/scripts/token-report.mjs +153 -0
- package/templates/contextkit/tools/scripts/visual-test.mjs +132 -0
- package/templates/contextkit/tools/scripts/watch.mjs +106 -0
- package/templates/contextkit/tools/scripts/workflow.mjs +136 -0
- package/templates/contextkit/tools/scripts/workspace-sync.mjs +220 -0
- package/templates/contextkit/tools/scripts/worktree-new.mjs +50 -0
- package/templates/contextkit/workflows/L1-static-loading.md +59 -0
- package/templates/contextkit/workflows/L2-session-ledger.md +86 -0
- package/templates/contextkit/workflows/L3-multi-session.md +80 -0
- package/templates/contextkit/workflows/L4-squads.md +68 -0
- package/templates/contextkit/workflows/L5-proactive.md +88 -0
- package/templates/contextkit/workflows/README.md +47 -0
- package/templates/contextkit/workflows/playbooks/distillation-cycle.md +74 -0
- package/templates/contextkit/workflows/playbooks/landing-page.md +197 -0
- package/templates/contextkit/workflows/playbooks/security-batch.md +68 -0
- package/templates/contextkit/workflows/playbooks/seo-aiso.md +288 -0
- package/templates/contextkit/workflows/playbooks/simulate-impact.md +83 -0
- package/templates/contextkit/workflows/playbooks/tanstack.md +164 -0
- package/templates/contextkit/workflows/playbooks/tech-debt-sweep.md +77 -0
- package/templates/docs/CHANGELOG.md.tpl +11 -0
- package/templates/gitattributes +3 -0
- package/templates/github/ISSUE_TEMPLATE/bug_report.md +30 -0
- package/templates/github/ISSUE_TEMPLATE/feature_request.md +22 -0
- package/templates/github/PULL_REQUEST_TEMPLATE.md +27 -0
- package/templates/github/dependabot.yml +27 -0
- package/templates/github/workflows/quality.yml +36 -0
- package/templates/github/workflows/security.yml +54 -0
- package/tools/install/cli.mjs +62 -0
- package/tools/install/fs.mjs +56 -0
- package/tools/install/git.mjs +114 -0
- package/tools/install/project.mjs +51 -0
- package/tools/install/uninstall.mjs +54 -0
- package/tools/integration-test-compozy.mjs +88 -0
- package/tools/integration-test-guards.mjs +269 -0
- package/tools/integration-test-tooling-agent-forge.mjs +189 -0
- package/tools/integration-test-tooling-pipeline.mjs +164 -0
- package/tools/integration-test-tooling.mjs +172 -0
- package/tools/integration-test.mjs +228 -0
- package/tools/it-helpers.mjs +60 -0
- package/tools/selfcheck-agent-forge-ops.mjs +107 -0
- package/tools/selfcheck-agent-forge.mjs +304 -0
- package/tools/selfcheck-config.mjs +80 -0
- package/tools/selfcheck-runtime.mjs +135 -0
- package/tools/selfcheck-source.mjs +326 -0
- package/tools/selfcheck.mjs +268 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nano Banana — image generation via Google AI Studio's Imagen API
|
|
3
|
+
* (ADR-0024).
|
|
4
|
+
*
|
|
5
|
+
* Authentication: a single API key in `GOOGLE_AI_API_KEY` (get one at
|
|
6
|
+
* https://aistudio.google.com/apikey). The adapter shells out to
|
|
7
|
+
* `node:fetch` — no Google SDK.
|
|
8
|
+
*
|
|
9
|
+
* Pricing (dated 2026-06-02 — verify at https://ai.google.dev/pricing):
|
|
10
|
+
* Imagen 3 fast: ~$0.020 per image
|
|
11
|
+
* Imagen 3 standard: ~$0.040 per image
|
|
12
|
+
* The adapter charges the standard floor; the real bill is on Google.
|
|
13
|
+
*
|
|
14
|
+
* Model id is configurable via `options.model`; default is the most
|
|
15
|
+
* capable image model at write time.
|
|
16
|
+
*/
|
|
17
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
18
|
+
import { dirname } from 'node:path';
|
|
19
|
+
import {
|
|
20
|
+
MediaProviderError, MEDIA_ERROR_CODES,
|
|
21
|
+
assertCredentials, noteCostOrThrow,
|
|
22
|
+
} from './_adapter.mjs';
|
|
23
|
+
|
|
24
|
+
export const id = 'nano-banana';
|
|
25
|
+
export const kind = 'image';
|
|
26
|
+
export const envVar = 'GOOGLE_AI_API_KEY';
|
|
27
|
+
export const requiredEnv = ['GOOGLE_AI_API_KEY'];
|
|
28
|
+
|
|
29
|
+
const DEFAULT_MODEL = 'imagen-3.0-generate-002';
|
|
30
|
+
const COST_ESTIMATE_USD = 0.04;
|
|
31
|
+
const ENDPOINT = (model) => `https://generativelanguage.googleapis.com/v1beta/models/${model}:predict`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Generate an image and write it to disk.
|
|
35
|
+
*
|
|
36
|
+
* @param {object} input
|
|
37
|
+
* @param {string} input.prompt required
|
|
38
|
+
* @param {string} input.outPath required — absolute or cwd-relative
|
|
39
|
+
* @param {object} [input.options]
|
|
40
|
+
* @param {string} [input.options.model] default DEFAULT_MODEL
|
|
41
|
+
* @param {string} [input.options.aspectRatio] "1:1" | "16:9" | "9:16" | "3:4" | "4:3"
|
|
42
|
+
* @param {number} [input.options.sampleCount] 1..4 — only the first is written
|
|
43
|
+
* @returns {Promise<{ outPath, durationMs, costEstimateUsd, providerRequestId }>}
|
|
44
|
+
*/
|
|
45
|
+
export async function generate({ prompt, outPath, options = {} }) {
|
|
46
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
47
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.BAD_INPUT, 'generate(): `prompt` is required');
|
|
48
|
+
}
|
|
49
|
+
if (!outPath || typeof outPath !== 'string') {
|
|
50
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.BAD_INPUT, 'generate(): `outPath` is required');
|
|
51
|
+
}
|
|
52
|
+
assertCredentials(requiredEnv);
|
|
53
|
+
noteCostOrThrow(COST_ESTIMATE_USD);
|
|
54
|
+
|
|
55
|
+
const model = options.model || DEFAULT_MODEL;
|
|
56
|
+
const body = {
|
|
57
|
+
instances: [{ prompt }],
|
|
58
|
+
parameters: {
|
|
59
|
+
sampleCount: Math.max(1, Math.min(4, options.sampleCount || 1)),
|
|
60
|
+
...(options.aspectRatio ? { aspectRatio: options.aspectRatio } : {}),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const t0 = Date.now();
|
|
65
|
+
let resp;
|
|
66
|
+
try {
|
|
67
|
+
resp = await fetch(`${ENDPOINT(model)}?key=${encodeURIComponent(process.env[envVar])}`, {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'Content-Type': 'application/json' },
|
|
70
|
+
body: JSON.stringify(body),
|
|
71
|
+
});
|
|
72
|
+
} catch (err) {
|
|
73
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `network error calling Imagen: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
const providerRequestId = resp.headers.get('x-request-id') || undefined;
|
|
76
|
+
|
|
77
|
+
const raw = await resp.text();
|
|
78
|
+
let payload;
|
|
79
|
+
try { payload = JSON.parse(raw); } catch { payload = null; }
|
|
80
|
+
|
|
81
|
+
if (resp.status === 429) {
|
|
82
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.RATE_LIMIT, `Imagen rate-limited: ${raw.slice(0, 200)}`, { providerRequestId });
|
|
83
|
+
}
|
|
84
|
+
if (resp.status === 400 && /safety|policy/i.test(raw)) {
|
|
85
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.CONTENT_POLICY, `Imagen refused prompt for content policy: ${raw.slice(0, 200)}`, { providerRequestId });
|
|
86
|
+
}
|
|
87
|
+
if (!resp.ok) {
|
|
88
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `Imagen returned ${resp.status}: ${raw.slice(0, 200)}`, { providerRequestId });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const pred = payload?.predictions?.[0];
|
|
92
|
+
const b64 = pred?.bytesBase64Encoded;
|
|
93
|
+
if (!b64) {
|
|
94
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, 'Imagen response missing predictions[0].bytesBase64Encoded', { providerRequestId });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
99
|
+
writeFileSync(outPath, Buffer.from(b64, 'base64'));
|
|
100
|
+
} catch (err) {
|
|
101
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.IO, `failed to write image to ${outPath}: ${err.message}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
outPath,
|
|
106
|
+
durationMs: Date.now() - t0,
|
|
107
|
+
costEstimateUsd: COST_ESTIMATE_USD,
|
|
108
|
+
providerRequestId,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Veo — video generation via Google AI Studio's Veo API (ADR-0024).
|
|
3
|
+
*
|
|
4
|
+
* Authentication: same single API key as nano-banana — `GOOGLE_AI_API_KEY`.
|
|
5
|
+
*
|
|
6
|
+
* Pricing (dated 2026-06-02 — verify at https://ai.google.dev/pricing):
|
|
7
|
+
* Veo 3 generate: ~$0.50 per second of video
|
|
8
|
+
* Typical 8 s clip: ~$4.00
|
|
9
|
+
* The adapter charges per requested duration; the real bill is on Google.
|
|
10
|
+
*
|
|
11
|
+
* Veo's generation is *long-running*: the initial POST returns an
|
|
12
|
+
* operation name; the adapter polls until `done: true` then downloads
|
|
13
|
+
* the resulting video.
|
|
14
|
+
*/
|
|
15
|
+
import { writeFileSync, mkdirSync } from 'node:fs';
|
|
16
|
+
import { dirname } from 'node:path';
|
|
17
|
+
import {
|
|
18
|
+
MediaProviderError, MEDIA_ERROR_CODES,
|
|
19
|
+
assertCredentials, noteCostOrThrow,
|
|
20
|
+
} from './_adapter.mjs';
|
|
21
|
+
|
|
22
|
+
export const id = 'veo';
|
|
23
|
+
export const kind = 'video';
|
|
24
|
+
export const envVar = 'GOOGLE_AI_API_KEY';
|
|
25
|
+
export const requiredEnv = ['GOOGLE_AI_API_KEY'];
|
|
26
|
+
|
|
27
|
+
const DEFAULT_MODEL = 'veo-3.0-generate-preview';
|
|
28
|
+
const COST_PER_SECOND_USD = 0.50;
|
|
29
|
+
const POLL_INTERVAL_MS = 5000;
|
|
30
|
+
const POLL_TIMEOUT_MS = 5 * 60 * 1000;
|
|
31
|
+
const PREDICT_ENDPOINT = (model) => `https://generativelanguage.googleapis.com/v1beta/models/${model}:predictLongRunning`;
|
|
32
|
+
const OPERATION_ENDPOINT = (op) => `https://generativelanguage.googleapis.com/v1beta/${op}`;
|
|
33
|
+
|
|
34
|
+
const sleep = (ms) => new Promise((res) => setTimeout(res, ms));
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generate a video and write it to disk.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} input
|
|
40
|
+
* @param {string} input.prompt
|
|
41
|
+
* @param {string} input.outPath
|
|
42
|
+
* @param {object} [input.options]
|
|
43
|
+
* @param {string} [input.options.model] default DEFAULT_MODEL
|
|
44
|
+
* @param {number} [input.options.durationSeconds] default 8 (Veo's typical max)
|
|
45
|
+
* @param {string} [input.options.aspectRatio] "16:9" | "9:16"
|
|
46
|
+
* @returns {Promise<{ outPath, durationMs, costEstimateUsd, providerRequestId }>}
|
|
47
|
+
*/
|
|
48
|
+
export async function generate({ prompt, outPath, options = {} }) {
|
|
49
|
+
if (!prompt || typeof prompt !== 'string') {
|
|
50
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.BAD_INPUT, 'generate(): `prompt` is required');
|
|
51
|
+
}
|
|
52
|
+
if (!outPath || typeof outPath !== 'string') {
|
|
53
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.BAD_INPUT, 'generate(): `outPath` is required');
|
|
54
|
+
}
|
|
55
|
+
assertCredentials(requiredEnv);
|
|
56
|
+
|
|
57
|
+
const duration = Math.max(1, Math.min(60, options.durationSeconds || 8));
|
|
58
|
+
const estimate = duration * COST_PER_SECOND_USD;
|
|
59
|
+
noteCostOrThrow(estimate);
|
|
60
|
+
|
|
61
|
+
const model = options.model || DEFAULT_MODEL;
|
|
62
|
+
const apiKey = encodeURIComponent(process.env[envVar]);
|
|
63
|
+
const body = {
|
|
64
|
+
instances: [{ prompt }],
|
|
65
|
+
parameters: {
|
|
66
|
+
durationSeconds: duration,
|
|
67
|
+
...(options.aspectRatio ? { aspectRatio: options.aspectRatio } : {}),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const t0 = Date.now();
|
|
72
|
+
let initResp;
|
|
73
|
+
try {
|
|
74
|
+
initResp = await fetch(`${PREDICT_ENDPOINT(model)}?key=${apiKey}`, {
|
|
75
|
+
method: 'POST',
|
|
76
|
+
headers: { 'Content-Type': 'application/json' },
|
|
77
|
+
body: JSON.stringify(body),
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `network error calling Veo: ${err.message}`);
|
|
81
|
+
}
|
|
82
|
+
const initRaw = await initResp.text();
|
|
83
|
+
let initPayload;
|
|
84
|
+
try { initPayload = JSON.parse(initRaw); } catch { initPayload = null; }
|
|
85
|
+
const providerRequestId = initResp.headers.get('x-request-id') || undefined;
|
|
86
|
+
|
|
87
|
+
if (initResp.status === 429) {
|
|
88
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.RATE_LIMIT, `Veo rate-limited: ${initRaw.slice(0, 200)}`, { providerRequestId });
|
|
89
|
+
}
|
|
90
|
+
if (initResp.status === 400 && /safety|policy/i.test(initRaw)) {
|
|
91
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.CONTENT_POLICY, `Veo refused prompt for content policy: ${initRaw.slice(0, 200)}`, { providerRequestId });
|
|
92
|
+
}
|
|
93
|
+
if (!initResp.ok) {
|
|
94
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `Veo init returned ${initResp.status}: ${initRaw.slice(0, 200)}`, { providerRequestId });
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const opName = initPayload?.name;
|
|
98
|
+
if (!opName) {
|
|
99
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, 'Veo response missing `name` (operation id)', { providerRequestId });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let opPayload = null;
|
|
103
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS;
|
|
104
|
+
while (Date.now() < deadline) {
|
|
105
|
+
await sleep(POLL_INTERVAL_MS);
|
|
106
|
+
let pollResp;
|
|
107
|
+
try {
|
|
108
|
+
pollResp = await fetch(`${OPERATION_ENDPOINT(opName)}?key=${apiKey}`);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (!pollResp.ok) continue;
|
|
113
|
+
const pollRaw = await pollResp.text();
|
|
114
|
+
try { opPayload = JSON.parse(pollRaw); } catch { continue; }
|
|
115
|
+
if (opPayload?.done) break;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!opPayload?.done) {
|
|
119
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `Veo operation ${opName} did not complete within ${POLL_TIMEOUT_MS / 1000}s`, { providerRequestId });
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (opPayload.error) {
|
|
123
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `Veo operation failed: ${JSON.stringify(opPayload.error).slice(0, 300)}`, { providerRequestId });
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Veo's response shape: either bytesBase64Encoded inline, or a URI to fetch.
|
|
127
|
+
const resp = opPayload?.response;
|
|
128
|
+
const sample = resp?.generatedVideos?.[0] || resp?.videos?.[0] || resp?.predictions?.[0];
|
|
129
|
+
const b64 = sample?.bytesBase64Encoded || sample?.video?.bytesBase64Encoded;
|
|
130
|
+
const uri = sample?.uri || sample?.video?.uri;
|
|
131
|
+
let bytes;
|
|
132
|
+
if (b64) {
|
|
133
|
+
bytes = Buffer.from(b64, 'base64');
|
|
134
|
+
} else if (uri) {
|
|
135
|
+
try {
|
|
136
|
+
const dl = await fetch(`${uri}${uri.includes('?') ? '&' : '?'}key=${apiKey}`);
|
|
137
|
+
if (!dl.ok) {
|
|
138
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `Veo video download returned ${dl.status}`);
|
|
139
|
+
}
|
|
140
|
+
bytes = Buffer.from(await dl.arrayBuffer());
|
|
141
|
+
} catch (err) {
|
|
142
|
+
if (err instanceof MediaProviderError) throw err;
|
|
143
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, `Veo video download error: ${err.message}`);
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.PROVIDER_ERROR, 'Veo response missing video data (no bytesBase64Encoded or uri)');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
151
|
+
writeFileSync(outPath, bytes);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
throw new MediaProviderError(MEDIA_ERROR_CODES.IO, `failed to write video to ${outPath}: ${err.message}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
outPath,
|
|
158
|
+
durationMs: Date.now() - t0,
|
|
159
|
+
costEstimateUsd: estimate,
|
|
160
|
+
providerRequestId,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Review-provider adapter contract — ADR-0021.
|
|
3
|
+
*
|
|
4
|
+
* Each `*.mjs` file in this directory (except this one and `detect.mjs`) is a
|
|
5
|
+
* concrete adapter. An adapter is a thin shell around an external CLI binary
|
|
6
|
+
* the user already has installed (`gh`, `glab`, `bb`, `tea` …). No SDK
|
|
7
|
+
* dependencies. No HTTP clients. No network probes during detection.
|
|
8
|
+
*
|
|
9
|
+
* Five contract points (recorded inline so a new adapter author reads them
|
|
10
|
+
* before opening the file):
|
|
11
|
+
*
|
|
12
|
+
* 1. No SDK dependency. Adapters `child_process.spawn` their CLI.
|
|
13
|
+
* 2. `detectsRemote` is a pure function over the `origin` URL.
|
|
14
|
+
* 3. `createPullRequest` returns `{ url, number }` or throws ProviderError
|
|
15
|
+
* with `code` and a human-readable `message`.
|
|
16
|
+
* 4. A missing CLI is a refusal, not a fallback. No silent degradation.
|
|
17
|
+
* 5. The selected adapter is recorded in `contextkit/config.json` →
|
|
18
|
+
* `providers.review`. `detect.mjs` auto-resolves on first use.
|
|
19
|
+
*
|
|
20
|
+
* Adapter shape (all five fields required):
|
|
21
|
+
*
|
|
22
|
+
* export const id = 'gh';
|
|
23
|
+
* export const cliBinary = 'gh';
|
|
24
|
+
* export const detectsRemote = (remoteUrl) => /github\.com/.test(remoteUrl);
|
|
25
|
+
* export async function createPullRequest({ title, body, baseBranch }) { … }
|
|
26
|
+
* export async function listOpenReviewComments({ prNumber }) { … }
|
|
27
|
+
* export async function postReviewComment({ prNumber, body }) { … }
|
|
28
|
+
*
|
|
29
|
+
* Additions to the contract require an ADR-style paragraph in this file and
|
|
30
|
+
* a matching selfcheck assertion. The current surface is the minimum useful.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Typed error every adapter throws on a failure path.
|
|
35
|
+
*
|
|
36
|
+
* @param {string} code short stable code (e.g. 'CLI_MISSING', 'AUTH', 'REMOTE_REJECTED')
|
|
37
|
+
* @param {string} message human-readable message (no stack-leaking)
|
|
38
|
+
*/
|
|
39
|
+
export class ProviderError extends Error {
|
|
40
|
+
constructor(code, message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'ProviderError';
|
|
43
|
+
this.code = code;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate the shape of an adapter module. Used by selfcheck and by
|
|
49
|
+
* `detect.mjs` before treating a discovered file as a real adapter.
|
|
50
|
+
*
|
|
51
|
+
* @param {object} mod the imported module
|
|
52
|
+
* @returns {{ ok: true } | { ok: false, reasons: string[] }}
|
|
53
|
+
*/
|
|
54
|
+
export function validateAdapter(mod) {
|
|
55
|
+
const reasons = [];
|
|
56
|
+
if (typeof mod.id !== 'string' || mod.id.length === 0) {
|
|
57
|
+
reasons.push('missing or empty `id` export');
|
|
58
|
+
}
|
|
59
|
+
if (typeof mod.cliBinary !== 'string' || mod.cliBinary.length === 0) {
|
|
60
|
+
reasons.push('missing or empty `cliBinary` export');
|
|
61
|
+
}
|
|
62
|
+
if (typeof mod.detectsRemote !== 'function') {
|
|
63
|
+
reasons.push('missing `detectsRemote(remoteUrl)` export');
|
|
64
|
+
}
|
|
65
|
+
for (const fn of ['createPullRequest', 'listOpenReviewComments', 'postReviewComment']) {
|
|
66
|
+
if (typeof mod[fn] !== 'function') {
|
|
67
|
+
reasons.push(`missing async \`${fn}(...)\` export`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return reasons.length === 0 ? { ok: true } : { ok: false, reasons };
|
|
71
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve which review-provider adapter to use for the current repo — ADR-0021.
|
|
3
|
+
*
|
|
4
|
+
* Order:
|
|
5
|
+
* 1. `contextkit/config.json` → `providers.review` (if set, win).
|
|
6
|
+
* 2. Auto-detect from `git remote get-url origin` by asking each adapter's
|
|
7
|
+
* `detectsRemote`. Records the resolution back to `config.json`.
|
|
8
|
+
* 3. Refuse with a clear error if nothing matches. No silent fallback.
|
|
9
|
+
*
|
|
10
|
+
* Zero deps. The adapter modules themselves are zero-dep (they shell out to
|
|
11
|
+
* the user's CLI).
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from 'node:child_process';
|
|
14
|
+
import { readFileSync, writeFileSync, readdirSync } from 'node:fs';
|
|
15
|
+
import { join, dirname } from 'node:path';
|
|
16
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
17
|
+
import { validateAdapter, ProviderError } from './_adapter.mjs';
|
|
18
|
+
import { PLATFORM_DIR } from '../../config/paths.mjs';
|
|
19
|
+
|
|
20
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
|
|
22
|
+
function readOriginUrl(cwd) {
|
|
23
|
+
const result = spawnSync('git', ['remote', 'get-url', 'origin'], {
|
|
24
|
+
cwd,
|
|
25
|
+
encoding: 'utf8',
|
|
26
|
+
shell: false,
|
|
27
|
+
});
|
|
28
|
+
if (result.status !== 0) return null;
|
|
29
|
+
return (result.stdout || '').trim() || null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function loadAdapterModules() {
|
|
33
|
+
const files = readdirSync(here)
|
|
34
|
+
.filter(name => name.endsWith('.mjs'))
|
|
35
|
+
.filter(name => !name.startsWith('_'))
|
|
36
|
+
.filter(name => name !== 'detect.mjs');
|
|
37
|
+
return Promise.all(
|
|
38
|
+
files.map(async (file) => {
|
|
39
|
+
const url = pathToFileURL(join(here, file)).href;
|
|
40
|
+
const mod = await import(url);
|
|
41
|
+
const verdict = validateAdapter(mod);
|
|
42
|
+
if (!verdict.ok) {
|
|
43
|
+
throw new ProviderError(
|
|
44
|
+
'BAD_ADAPTER',
|
|
45
|
+
`adapter ${file} failed contract validation: ${verdict.reasons.join('; ')}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
return { file, mod };
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readConfig(configPath) {
|
|
54
|
+
try {
|
|
55
|
+
const raw = readFileSync(configPath, 'utf8').replace(/^/, '');
|
|
56
|
+
return JSON.parse(raw);
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeConfig(configPath, config) {
|
|
63
|
+
try {
|
|
64
|
+
writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
|
|
65
|
+
} catch {
|
|
66
|
+
/* best-effort — do not break the calling command on a config-write race */
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Resolve the review-provider adapter for `cwd`.
|
|
72
|
+
*
|
|
73
|
+
* @param {object} [opts]
|
|
74
|
+
* @param {string} [opts.cwd] project root (defaults to process.cwd())
|
|
75
|
+
* @param {string} [opts.configPath] override path to contextkit/config.json
|
|
76
|
+
* @returns {Promise<{ id: string, source: 'config' | 'detected', adapter: object }>}
|
|
77
|
+
*/
|
|
78
|
+
export async function resolveAdapter({ cwd = process.cwd(), configPath } = {}) {
|
|
79
|
+
const resolvedConfigPath = configPath || join(cwd, PLATFORM_DIR, 'config.json');
|
|
80
|
+
const config = readConfig(resolvedConfigPath) || {};
|
|
81
|
+
const adapters = await loadAdapterModules();
|
|
82
|
+
|
|
83
|
+
const fromConfig = config?.providers?.review;
|
|
84
|
+
if (fromConfig) {
|
|
85
|
+
const hit = adapters.find(a => a.mod.id === fromConfig);
|
|
86
|
+
if (!hit) {
|
|
87
|
+
throw new ProviderError(
|
|
88
|
+
'CONFIGURED_ADAPTER_NOT_FOUND',
|
|
89
|
+
`contextkit/config.json names review provider "${fromConfig}" but no matching adapter is installed`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
return { id: hit.mod.id, source: 'config', adapter: hit.mod };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const originUrl = readOriginUrl(cwd);
|
|
96
|
+
if (!originUrl) {
|
|
97
|
+
throw new ProviderError(
|
|
98
|
+
'NO_ORIGIN',
|
|
99
|
+
'no `origin` remote configured — cannot auto-detect a review provider. Set `providers.review` in contextkit/config.json.',
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
const detected = adapters.find(a => a.mod.detectsRemote(originUrl));
|
|
103
|
+
if (!detected) {
|
|
104
|
+
throw new ProviderError(
|
|
105
|
+
'NO_MATCHING_ADAPTER',
|
|
106
|
+
`no review adapter matched origin "${originUrl}". Set \`providers.review\` in contextkit/config.json or add an adapter to contextkit/runtime/providers/review/.`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
config.providers = config.providers || {};
|
|
111
|
+
config.providers.review = detected.mod.id;
|
|
112
|
+
writeConfig(resolvedConfigPath, config);
|
|
113
|
+
|
|
114
|
+
return { id: detected.mod.id, source: 'detected', adapter: detected.mod };
|
|
115
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub adapter for the review-provider contract — ADR-0021.
|
|
3
|
+
*
|
|
4
|
+
* Shells out to the `gh` CLI. The user is expected to have authenticated
|
|
5
|
+
* via `gh auth login` before any command that hits the network. We do not
|
|
6
|
+
* store credentials.
|
|
7
|
+
*/
|
|
8
|
+
import { spawnSync } from 'node:child_process';
|
|
9
|
+
import { ProviderError } from './_adapter.mjs';
|
|
10
|
+
|
|
11
|
+
export const id = 'gh';
|
|
12
|
+
export const cliBinary = 'gh';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Pure detection from the `origin` URL — no network call.
|
|
16
|
+
*
|
|
17
|
+
* @param {string} remoteUrl output of `git remote get-url origin`
|
|
18
|
+
* @returns {boolean}
|
|
19
|
+
*/
|
|
20
|
+
export const detectsRemote = (remoteUrl) => {
|
|
21
|
+
if (typeof remoteUrl !== 'string') return false;
|
|
22
|
+
return /github\.com[:/]/.test(remoteUrl);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function runGh(args, { stdin } = {}) {
|
|
26
|
+
const result = spawnSync('gh', args, {
|
|
27
|
+
input: stdin,
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
shell: false,
|
|
30
|
+
});
|
|
31
|
+
if (result.error && result.error.code === 'ENOENT') {
|
|
32
|
+
throw new ProviderError(
|
|
33
|
+
'CLI_MISSING',
|
|
34
|
+
'`gh` CLI not found on PATH. Install from https://cli.github.com/ or switch `providers.review` in contextkit/config.json.',
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
if (result.status !== 0) {
|
|
38
|
+
const stderr = (result.stderr || '').trim();
|
|
39
|
+
const code = /not authenticated/i.test(stderr) ? 'AUTH'
|
|
40
|
+
: /not found/i.test(stderr) ? 'NOT_FOUND'
|
|
41
|
+
: 'REMOTE_REJECTED';
|
|
42
|
+
throw new ProviderError(code, `gh ${args.join(' ')} failed: ${stderr || result.status}`);
|
|
43
|
+
}
|
|
44
|
+
return result.stdout || '';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Create a pull request on GitHub.
|
|
49
|
+
*
|
|
50
|
+
* @param {{ title: string, body: string, baseBranch?: string }} input
|
|
51
|
+
* @returns {Promise<{ url: string, number: number }>}
|
|
52
|
+
*/
|
|
53
|
+
export async function createPullRequest({ title, body, baseBranch }) {
|
|
54
|
+
if (!title) throw new ProviderError('BAD_INPUT', 'createPullRequest: `title` is required');
|
|
55
|
+
const args = ['pr', 'create', '--title', title, '--body-file', '-'];
|
|
56
|
+
if (baseBranch) args.push('--base', baseBranch);
|
|
57
|
+
const stdout = runGh(args, { stdin: body || '' });
|
|
58
|
+
const url = stdout.trim().split('\n').filter(Boolean).pop() || '';
|
|
59
|
+
const match = /\/pull\/(\d+)/.exec(url);
|
|
60
|
+
if (!match) {
|
|
61
|
+
throw new ProviderError('PARSE', `could not parse PR number from gh output: ${stdout}`);
|
|
62
|
+
}
|
|
63
|
+
return { url, number: Number(match[1]) };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List open review comments on a PR.
|
|
68
|
+
*
|
|
69
|
+
* @param {{ prNumber: number }} input
|
|
70
|
+
* @returns {Promise<Array<{ id: number, body: string, path?: string, line?: number }>>}
|
|
71
|
+
*/
|
|
72
|
+
export async function listOpenReviewComments({ prNumber }) {
|
|
73
|
+
if (!Number.isInteger(prNumber)) {
|
|
74
|
+
throw new ProviderError('BAD_INPUT', 'listOpenReviewComments: `prNumber` must be an integer');
|
|
75
|
+
}
|
|
76
|
+
const stdout = runGh(['api', `repos/{owner}/{repo}/pulls/${prNumber}/comments`]);
|
|
77
|
+
try {
|
|
78
|
+
const raw = JSON.parse(stdout);
|
|
79
|
+
return raw.map(c => ({
|
|
80
|
+
id: c.id,
|
|
81
|
+
body: c.body,
|
|
82
|
+
path: c.path,
|
|
83
|
+
line: c.line ?? c.original_line ?? null,
|
|
84
|
+
}));
|
|
85
|
+
} catch (e) {
|
|
86
|
+
throw new ProviderError('PARSE', `gh returned non-JSON for review comments: ${e.message}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Post a top-level review comment on a PR.
|
|
92
|
+
*
|
|
93
|
+
* @param {{ prNumber: number, body: string }} input
|
|
94
|
+
* @returns {Promise<{ ok: true }>}
|
|
95
|
+
*/
|
|
96
|
+
export async function postReviewComment({ prNumber, body }) {
|
|
97
|
+
if (!Number.isInteger(prNumber)) {
|
|
98
|
+
throw new ProviderError('BAD_INPUT', 'postReviewComment: `prNumber` must be an integer');
|
|
99
|
+
}
|
|
100
|
+
if (!body) throw new ProviderError('BAD_INPUT', 'postReviewComment: `body` is required');
|
|
101
|
+
runGh(['pr', 'comment', String(prNumber), '--body-file', '-'], { stdin: body });
|
|
102
|
+
return { ok: true };
|
|
103
|
+
}
|