forge-workflow 0.0.9 → 0.1.0-beta.2
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/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +151 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -106
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +466 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/reference/TOOLCHAIN.md +658 -0
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +225 -28
- package/lib/beads-sync-scaffold.js +36 -107
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +744 -70
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +66 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +22 -2
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +851 -979
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +329 -11
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +15 -2
- package/lib/commands/test.js +58 -7
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +56 -25
- package/lib/commands/worktree.js +308 -128
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +184 -0
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +42 -17
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +697 -0
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +8 -4
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +166 -0
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +332 -23
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +191 -0
- package/lib/status/beads-snapshot.js +145 -0
- package/lib/status/presenter.js +216 -0
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +298 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +159 -14
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +46 -36
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +165 -22
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +284 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +64 -232
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +144 -47
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +183 -79
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +108 -0
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +117 -369
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +251 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +302 -28
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/skills/ship/SKILL.md +251 -0
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/skills/status/SKILL.md +102 -0
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/ship.prompt.md +0 -215
- package/.github/prompts/status.prompt.md +0 -92
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -56
- package/.github/workflows/github-to-beads.yml +0 -97
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -255
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-health-check.js +0 -143
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -567
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -332
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -78
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Per-harness native HOOK config renderer.
|
|
5
|
+
*
|
|
6
|
+
* Projects Forge's lifecycle enforcement — the TDD gate (source changes require
|
|
7
|
+
* tests) and the protected-path guard — onto each harness's REAL native hook
|
|
8
|
+
* surface, using read → merge → write (idempotent, preserves user hooks). This is
|
|
9
|
+
* the hook analogue of lib/mcp-config-renderer.js and closes the honesty gap that
|
|
10
|
+
* #311 recorded: the capability matrix advertised native hooks that no renderer
|
|
11
|
+
* actually wrote.
|
|
12
|
+
*
|
|
13
|
+
* Verified native hook surfaces:
|
|
14
|
+
* - Claude : `.claude/settings.json` → a `hooks` block
|
|
15
|
+
* (PreToolUse matcher groups → { type:'command', command })
|
|
16
|
+
* - Cursor : `.cursor/hooks.json` → { version: 1, hooks: { <event>: [ { command } ] } }
|
|
17
|
+
* (Cursor 1.7+; only `before*` events can DENY — there is NO pre-edit
|
|
18
|
+
* deny event, so protected-path write-blocking + commit gating run on
|
|
19
|
+
* `beforeShellExecution`, and `afterFileEdit` is an observational audit.)
|
|
20
|
+
* - Codex : `.codex/config.toml` → `[hooks]` matcher groups. Codex reads the
|
|
21
|
+
* GLOBAL `~/.codex/config.toml` (per #311 / lib/agents-config.js), so
|
|
22
|
+
* project `forge setup` MUST NOT write it. Rendered + tested here for a
|
|
23
|
+
* global-config follow-up only.
|
|
24
|
+
* - Hermes : `~/.hermes/config.yaml` → a `hooks:` block of shell hooks (matcher +
|
|
25
|
+
* command; JSON-stdin/stdout wire protocol). A `pre_tool_call` hook CAN
|
|
26
|
+
* deny a tool call (it even accepts Claude's {decision:block} shape). Lives
|
|
27
|
+
* in GLOBAL (home) config, so project `forge setup` MUST NOT write it —
|
|
28
|
+
* rendered + tested here for a global-config follow-up only.
|
|
29
|
+
*
|
|
30
|
+
* The rendered `command` invokes Forge's installed native-hook adapter
|
|
31
|
+
* (`.forge/hooks/forge-native-hook.js`), which the setup flow installs alongside
|
|
32
|
+
* `.forge/hooks/check-tdd.js`. The adapter reads the harness's hook stdin, enforces
|
|
33
|
+
* the protected-path set, and delegates the TDD gate to the real `check-tdd.js`.
|
|
34
|
+
*
|
|
35
|
+
* Dependency-free (JSON only; no TOML lib) so it runs under `bun test` and the
|
|
36
|
+
* release gates. Reuses the MCP renderer's `backupFile` for the data-loss guard.
|
|
37
|
+
*
|
|
38
|
+
* @module hook-renderer
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
const fs = require('node:fs');
|
|
42
|
+
const path = require('node:path');
|
|
43
|
+
const { backupFile } = require('./mcp-config-renderer');
|
|
44
|
+
|
|
45
|
+
const HARNESS_HOOK_FILES = {
|
|
46
|
+
claude: '.claude/settings.json',
|
|
47
|
+
cursor: '.cursor/hooks.json',
|
|
48
|
+
codex: '.codex/config.toml',
|
|
49
|
+
hermes: '~/.hermes/config.yaml',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// The adapter Forge installs into every project (see lib/commands/setup.js). The
|
|
53
|
+
// stable `forge-native-hook.js` token also MARKS Forge-owned hook entries so a
|
|
54
|
+
// re-merge replaces them in place instead of duplicating (idempotency) and never
|
|
55
|
+
// clobbers a user's own hooks.
|
|
56
|
+
const FORGE_HOOK_ADAPTER_REL = '.forge/hooks/forge-native-hook.js';
|
|
57
|
+
const FORGE_HOOK_ADAPTER = `node ${FORGE_HOOK_ADAPTER_REL}`;
|
|
58
|
+
const FORGE_HOOK_MARKER = 'forge-native-hook.js';
|
|
59
|
+
|
|
60
|
+
// CONTEXT intents route to the `forge` CLI (which loads lib/), NOT the self-contained
|
|
61
|
+
// adapter — memory injection needs kernel/FTS access and must FAIL OPEN. The marker
|
|
62
|
+
// token below stamps Forge-owned context-hook entries so a re-merge replaces them in
|
|
63
|
+
// place (idempotency), the same role FORGE_HOOK_MARKER plays for enforcement entries.
|
|
64
|
+
//
|
|
65
|
+
// The command is a RESOLVED `node <abs bin/forge.js>` invocation — NOT a bare `forge`.
|
|
66
|
+
// A bare `forge` on a hook's minimal PATH either does not resolve (feature silently
|
|
67
|
+
// never fires) or resolves to the WRONG binary (e.g. a global `forge` that misroutes),
|
|
68
|
+
// whose stdout would then be injected as session context. Resolving the exact CLI that
|
|
69
|
+
// rendered the hook (via __dirname, works in the repo and under node_modules) removes
|
|
70
|
+
// both failure modes. FORGE_CONTEXT_MARKER is the stable idempotency token.
|
|
71
|
+
const FORGE_CLI_BIN = path.join(__dirname, '..', 'bin', 'forge.js');
|
|
72
|
+
const FORGE_CLI = `node "${FORGE_CLI_BIN}"`;
|
|
73
|
+
const FORGE_CONTEXT_MARKER = 'hooks session-start';
|
|
74
|
+
// The inbox-pickup context hook (UserPromptSubmit tier). A SECOND context marker so a
|
|
75
|
+
// re-merge recognizes + replaces the Forge-owned UserPromptSubmit entry in place, exactly
|
|
76
|
+
// as FORGE_CONTEXT_MARKER does for the SessionStart entry.
|
|
77
|
+
const FORGE_INBOX_CONTEXT_MARKER = 'hooks inbox-pickup';
|
|
78
|
+
|
|
79
|
+
// Per-harness SessionStart context-injection capability. Honest capability matrix —
|
|
80
|
+
// only Claude exposes a native session-start surface that can inject additionalContext.
|
|
81
|
+
// Cursor's 1.7 hooks are deny-oriented (no session-start context surface); Codex and
|
|
82
|
+
// Hermes hooks live in GLOBAL home config that project setup never writes. We NEVER
|
|
83
|
+
// fake parity: each non-Claude harness carries an explicit, tested skip reason.
|
|
84
|
+
const SESSION_START_SUPPORT = Object.freeze({
|
|
85
|
+
claude: Object.freeze({ rendered: true }),
|
|
86
|
+
cursor: Object.freeze({ rendered: false, reason: 'no-session-start-surface' }),
|
|
87
|
+
codex: Object.freeze({ rendered: false, reason: 'global-config' }),
|
|
88
|
+
hermes: Object.freeze({ rendered: false, reason: 'global-config' }),
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Per-harness UserPromptSubmit context-injection capability (the near-real-time inbox
|
|
92
|
+
// tier). Only Claude has a verified UserPromptSubmit surface that injects
|
|
93
|
+
// additionalContext alongside the submitted prompt. Same honesty rule as SessionStart:
|
|
94
|
+
// every non-Claude harness carries an explicit, tested skip reason — no faked parity.
|
|
95
|
+
const USER_PROMPT_SUBMIT_SUPPORT = Object.freeze({
|
|
96
|
+
claude: Object.freeze({ rendered: true }),
|
|
97
|
+
cursor: Object.freeze({ rendered: false, reason: 'no-user-prompt-surface' }),
|
|
98
|
+
codex: Object.freeze({ rendered: false, reason: 'global-config' }),
|
|
99
|
+
hermes: Object.freeze({ rendered: false, reason: 'global-config' }),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Claude exposes $CLAUDE_PROJECT_DIR (absolute project root) to hook commands and
|
|
103
|
+
// documents it as THE cwd-independent way to reference project-local hook scripts —
|
|
104
|
+
// a bare relative path breaks whenever Claude runs the hook from another cwd. Cursor
|
|
105
|
+
// and Codex execute hooks from the workspace root, so a repo-relative path resolves.
|
|
106
|
+
function adapterInvocation(harness) {
|
|
107
|
+
if (harness === 'claude') return `node "$CLAUDE_PROJECT_DIR/${FORGE_HOOK_ADAPTER_REL}"`;
|
|
108
|
+
return FORGE_HOOK_ADAPTER;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The frozen Forge hook contract: the two enforcement intents projected onto each
|
|
113
|
+
* harness. Order matters — protected-path (per-write) is listed before tdd-gate
|
|
114
|
+
* (per-commit) so the rendered Claude PreToolUse groups read write-guard first.
|
|
115
|
+
*/
|
|
116
|
+
const FORGE_HOOK_CONTRACT = Object.freeze({
|
|
117
|
+
schemaVersion: '1.1.0',
|
|
118
|
+
kind: 'forge.hookContract',
|
|
119
|
+
adapter: FORGE_HOOK_ADAPTER,
|
|
120
|
+
intents: Object.freeze([
|
|
121
|
+
Object.freeze({
|
|
122
|
+
id: 'protected-path',
|
|
123
|
+
kind: 'enforcement',
|
|
124
|
+
enforces: 'Protected-path guard: block writes/edits to Forge-protected paths (.forge/, .git/, AGENTS.md, secrets, generated artifacts).',
|
|
125
|
+
lifecycle: 'pre-write',
|
|
126
|
+
command: `${FORGE_HOOK_ADAPTER} --intent protected-path`,
|
|
127
|
+
}),
|
|
128
|
+
Object.freeze({
|
|
129
|
+
id: 'tdd-gate',
|
|
130
|
+
kind: 'enforcement',
|
|
131
|
+
enforces: 'TDD gate: source changes must ship with accompanying tests (blocks bare git commit).',
|
|
132
|
+
lifecycle: 'pre-commit',
|
|
133
|
+
command: `${FORGE_HOOK_ADAPTER} --intent tdd-gate`,
|
|
134
|
+
}),
|
|
135
|
+
Object.freeze({
|
|
136
|
+
id: 'memory-inject',
|
|
137
|
+
kind: 'context',
|
|
138
|
+
cliAction: 'session-start',
|
|
139
|
+
enforces: 'Memory push: inject a bounded, token-capped digest (remembered notes + top open issues) at session start. Additive and FAIL-OPEN — a missing digest never blocks a session.',
|
|
140
|
+
lifecycle: 'session-start',
|
|
141
|
+
command: `${FORGE_CLI} hooks session-start`,
|
|
142
|
+
}),
|
|
143
|
+
Object.freeze({
|
|
144
|
+
id: 'inbox-pickup',
|
|
145
|
+
kind: 'context',
|
|
146
|
+
cliAction: 'inbox-pickup',
|
|
147
|
+
// COMPLIANT COMMENT-BACK: surfaces pending targeted dashboard instruction comments
|
|
148
|
+
// (kernel DATA, fenced) on each prompt. Reads the user's own kernel data via a
|
|
149
|
+
// supported hook — NEVER injects into a running session's stdin, never drives the
|
|
150
|
+
// agent programmatically (Anthropic Usage Policy, kernel issue 6d10c1a1).
|
|
151
|
+
enforces: 'Comment-back pickup: surface a compact count+pointer nudge for pending targeted dashboard instruction comments on each UserPromptSubmit (compact to avoid additionalContext accumulation; full fenced digest is at SessionStart + `forge inbox`). Additive and FAIL-OPEN — a missing nudge never blocks a prompt.',
|
|
152
|
+
lifecycle: 'user-prompt-submit',
|
|
153
|
+
command: `${FORGE_CLI} hooks inbox-pickup`,
|
|
154
|
+
}),
|
|
155
|
+
]),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
/** Thrown when an existing hook config cannot be parsed — signals "do not overwrite". */
|
|
159
|
+
class HookConfigParseError extends Error {
|
|
160
|
+
constructor(message, cause) {
|
|
161
|
+
super(message);
|
|
162
|
+
this.name = 'HookConfigParseError';
|
|
163
|
+
this.cause = cause;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function intentById(contract, id) {
|
|
168
|
+
const intent = contract.intents.find(i => i.id === id);
|
|
169
|
+
if (!intent) throw new Error(`Forge hook contract is missing intent '${id}'`);
|
|
170
|
+
return intent;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function harnessCommand(contract, id, harness) {
|
|
174
|
+
const intent = intentById(contract, id);
|
|
175
|
+
// CONTEXT intents route to the `forge` CLI (fail-open memory injection), NOT the
|
|
176
|
+
// self-contained enforcement adapter. Hooks often run with a minimal PATH; if `forge`
|
|
177
|
+
// does not resolve the command simply fails and the harness ignores it (fail-open).
|
|
178
|
+
if (intent.kind === 'context') {
|
|
179
|
+
return `${FORGE_CLI} hooks ${intent.cliAction} --harness ${harness}`;
|
|
180
|
+
}
|
|
181
|
+
// ENFORCEMENT intents build from the harness-specific adapter invocation (Claude →
|
|
182
|
+
// $CLAUDE_PROJECT_DIR; Cursor/Codex → repo-relative). intent.id is the `--intent` value.
|
|
183
|
+
return `${adapterInvocation(harness)} --intent ${intent.id} --harness ${harness}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Report the per-harness SessionStart context-injection capability (the honest matrix).
|
|
188
|
+
* @param {string} harness
|
|
189
|
+
* @returns {{ rendered: boolean, reason?: string }}
|
|
190
|
+
*/
|
|
191
|
+
function sessionStartCapability(harness) {
|
|
192
|
+
return SESSION_START_SUPPORT[harness] || { rendered: false, reason: 'unknown-harness' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Report the per-harness UserPromptSubmit context-injection capability (the honest matrix
|
|
197
|
+
* for the near-real-time inbox tier).
|
|
198
|
+
* @param {string} harness
|
|
199
|
+
* @returns {{ rendered: boolean, reason?: string }}
|
|
200
|
+
*/
|
|
201
|
+
function userPromptSubmitCapability(harness) {
|
|
202
|
+
return USER_PROMPT_SUBMIT_SUPPORT[harness] || { rendered: false, reason: 'unknown-harness' };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Render the Claude `.claude/settings.json` `hooks` block (PreToolUse groups only).
|
|
207
|
+
* Write/Edit/MultiEdit/NotebookEdit → protected-path deny; Bash → TDD gate.
|
|
208
|
+
* @param {object} contract
|
|
209
|
+
* @returns {{ PreToolUse: object[] }}
|
|
210
|
+
*/
|
|
211
|
+
function renderClaudeHooks(contract) {
|
|
212
|
+
return {
|
|
213
|
+
PreToolUse: [
|
|
214
|
+
{
|
|
215
|
+
matcher: 'Write|Edit|MultiEdit|NotebookEdit',
|
|
216
|
+
hooks: [{ type: 'command', command: harnessCommand(contract, 'protected-path', 'claude') }],
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
matcher: 'Bash',
|
|
220
|
+
hooks: [{ type: 'command', command: harnessCommand(contract, 'tdd-gate', 'claude') }],
|
|
221
|
+
},
|
|
222
|
+
],
|
|
223
|
+
// SessionStart context injection (memory push). No matcher → applies to every
|
|
224
|
+
// session source; the command emits { hookSpecificOutput.additionalContext }.
|
|
225
|
+
SessionStart: [
|
|
226
|
+
{ hooks: [{ type: 'command', command: harnessCommand(contract, 'memory-inject', 'claude') }] },
|
|
227
|
+
],
|
|
228
|
+
// UserPromptSubmit context injection (compliant comment-back — near-real-time tier).
|
|
229
|
+
// Surfaces pending targeted dashboard instruction comments (fenced kernel DATA) on each
|
|
230
|
+
// prompt; the command emits { hookSpecificOutput.additionalContext }. Reads the user's
|
|
231
|
+
// own kernel data via a supported hook — NEVER stdin injection (Anthropic Usage Policy).
|
|
232
|
+
UserPromptSubmit: [
|
|
233
|
+
{ hooks: [{ type: 'command', command: harnessCommand(contract, 'inbox-pickup', 'claude') }] },
|
|
234
|
+
],
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Render the Cursor `.cursor/hooks.json` config. Cursor 1.7+ has NO pre-edit deny
|
|
240
|
+
* event, so write-blocking + commit gating run on `beforeShellExecution` (git /
|
|
241
|
+
* redirects), and `afterFileEdit` carries an observational protected-path audit.
|
|
242
|
+
* @param {object} contract
|
|
243
|
+
* @returns {{ version: number, hooks: object }}
|
|
244
|
+
*/
|
|
245
|
+
function renderCursorHooks(contract) {
|
|
246
|
+
return {
|
|
247
|
+
version: 1,
|
|
248
|
+
hooks: {
|
|
249
|
+
beforeShellExecution: [
|
|
250
|
+
{ command: harnessCommand(contract, 'tdd-gate', 'cursor') },
|
|
251
|
+
{ command: harnessCommand(contract, 'protected-path', 'cursor') },
|
|
252
|
+
],
|
|
253
|
+
afterFileEdit: [
|
|
254
|
+
{ command: harnessCommand(contract, 'protected-path', 'cursor') },
|
|
255
|
+
],
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Render a Codex `[hooks]` TOML block (mirrors the Claude matcher-group model).
|
|
262
|
+
* Returned for a GLOBAL-config follow-up ONLY — never written by project setup.
|
|
263
|
+
* @param {object} contract
|
|
264
|
+
* @returns {string}
|
|
265
|
+
*/
|
|
266
|
+
function renderCodexHooksToml(contract) {
|
|
267
|
+
const groups = [
|
|
268
|
+
{ matcher: 'Write|Edit', command: harnessCommand(contract, 'protected-path', 'codex') },
|
|
269
|
+
{ matcher: 'Bash', command: harnessCommand(contract, 'tdd-gate', 'codex') },
|
|
270
|
+
];
|
|
271
|
+
const blocks = groups.map(g =>
|
|
272
|
+
`[[hooks.PreToolUse]]\nmatcher = ${tomlString(g.matcher)}\n\n`
|
|
273
|
+
+ `[[hooks.PreToolUse.hooks]]\ntype = "command"\ncommand = ${tomlString(g.command)}\n`,
|
|
274
|
+
);
|
|
275
|
+
return blocks.join('\n');
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function tomlString(value) {
|
|
279
|
+
return '"' + String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Render a Hermes shell-hooks YAML block (the `hooks:` section of ~/.hermes/config.yaml).
|
|
284
|
+
* Hermes runs shell hooks as subprocesses over a JSON-stdin/stdout wire protocol; a
|
|
285
|
+
* `pre_tool_call` hook can DENY a tool call. Matchers are regexes over the Hermes
|
|
286
|
+
* tool_name (write_file/patch = edits → protected-path; terminal = shell → tdd-gate).
|
|
287
|
+
* Returned for a GLOBAL-config follow-up ONLY — Hermes reads ~/.hermes/config.yaml
|
|
288
|
+
* (home dir), so project setup never writes it (mirrors renderCodexHooksToml).
|
|
289
|
+
* @param {object} contract
|
|
290
|
+
* @returns {string}
|
|
291
|
+
*/
|
|
292
|
+
function renderHermesHooksYaml(contract) {
|
|
293
|
+
const groups = [
|
|
294
|
+
{ matcher: 'write_file|patch', command: harnessCommand(contract, 'protected-path', 'hermes') },
|
|
295
|
+
{ matcher: 'terminal', command: harnessCommand(contract, 'tdd-gate', 'hermes') },
|
|
296
|
+
];
|
|
297
|
+
const lines = ['hooks:', ' pre_tool_call:'];
|
|
298
|
+
for (const g of groups) {
|
|
299
|
+
lines.push(` - matcher: ${yamlString(g.matcher)}`);
|
|
300
|
+
lines.push(` command: ${yamlString(g.command)}`);
|
|
301
|
+
}
|
|
302
|
+
return lines.join('\n') + '\n';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function yamlString(value) {
|
|
306
|
+
return '"' + String(value).replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** True when a command is Forge-owned — the enforcement adapter OR a context CLI hook. */
|
|
310
|
+
function isForgeCommand(command) {
|
|
311
|
+
return typeof command === 'string'
|
|
312
|
+
&& (command.includes(FORGE_HOOK_MARKER)
|
|
313
|
+
|| command.includes(FORGE_CONTEXT_MARKER)
|
|
314
|
+
|| command.includes(FORGE_INBOX_CONTEXT_MARKER));
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/** True when a hook group/entry is Forge-owned (any inner command is Forge-owned). */
|
|
318
|
+
function isForgeClaudeGroup(group) {
|
|
319
|
+
const hooks = Array.isArray(group?.hooks) ? group.hooks : [];
|
|
320
|
+
return hooks.some(h => isForgeCommand(h?.command));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function isForgeCursorEntry(entry) {
|
|
324
|
+
return isForgeCommand(entry?.command);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function parseJsonConfig(existingText) {
|
|
328
|
+
if (!existingText || !existingText.trim()) return {};
|
|
329
|
+
let obj;
|
|
330
|
+
try {
|
|
331
|
+
obj = JSON.parse(existingText);
|
|
332
|
+
} catch (err) {
|
|
333
|
+
// DATA-LOSS GUARD: never silently discard a populated-but-unparseable config
|
|
334
|
+
// (JSONC comments / trailing commas). Signal the caller to back up + skip.
|
|
335
|
+
throw new HookConfigParseError('existing hook config is not valid JSON', err);
|
|
336
|
+
}
|
|
337
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return {};
|
|
338
|
+
return obj;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Merge Forge's hooks into an existing `.claude/settings.json` string.
|
|
343
|
+
* Preserves all other settings keys, all non-Forge events, and the user's own
|
|
344
|
+
* matcher-groups; replaces only Forge-owned groups (idempotent).
|
|
345
|
+
* @param {string} existingText
|
|
346
|
+
* @param {object} contract
|
|
347
|
+
* @returns {string}
|
|
348
|
+
*/
|
|
349
|
+
function mergeClaudeSettings(existingText, contract) {
|
|
350
|
+
const obj = parseJsonConfig(existingText);
|
|
351
|
+
if (!obj.hooks || typeof obj.hooks !== 'object' || Array.isArray(obj.hooks)) obj.hooks = {};
|
|
352
|
+
const rendered = renderClaudeHooks(contract);
|
|
353
|
+
for (const [event, forgeGroups] of Object.entries(rendered)) {
|
|
354
|
+
const existingGroups = Array.isArray(obj.hooks[event]) ? obj.hooks[event] : [];
|
|
355
|
+
const userGroups = existingGroups.filter(group => !isForgeClaudeGroup(group));
|
|
356
|
+
obj.hooks[event] = [...userGroups, ...forgeGroups];
|
|
357
|
+
}
|
|
358
|
+
return JSON.stringify(obj, null, 2) + '\n';
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Merge Forge's hooks into an existing `.cursor/hooks.json` string.
|
|
363
|
+
* Forces `version: 1`, preserves all non-Forge events and the user's own entries;
|
|
364
|
+
* replaces only Forge-owned entries (idempotent).
|
|
365
|
+
* @param {string} existingText
|
|
366
|
+
* @param {object} contract
|
|
367
|
+
* @returns {string}
|
|
368
|
+
*/
|
|
369
|
+
function mergeCursorHooks(existingText, contract) {
|
|
370
|
+
const obj = parseJsonConfig(existingText);
|
|
371
|
+
obj.version = 1;
|
|
372
|
+
if (!obj.hooks || typeof obj.hooks !== 'object' || Array.isArray(obj.hooks)) obj.hooks = {};
|
|
373
|
+
const rendered = renderCursorHooks(contract);
|
|
374
|
+
for (const [event, forgeEntries] of Object.entries(rendered.hooks)) {
|
|
375
|
+
const existingEntries = Array.isArray(obj.hooks[event]) ? obj.hooks[event] : [];
|
|
376
|
+
const userEntries = existingEntries.filter(entry => !isForgeCursorEntry(entry));
|
|
377
|
+
obj.hooks[event] = [...userEntries, ...forgeEntries];
|
|
378
|
+
}
|
|
379
|
+
return JSON.stringify(obj, null, 2) + '\n';
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const MERGERS = {
|
|
383
|
+
claude: mergeClaudeSettings,
|
|
384
|
+
cursor: mergeCursorHooks,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Render (merge) Forge's native hooks into one harness's native config on disk.
|
|
389
|
+
* Read → merge → write. Unparseable existing file → BACKED UP + left untouched
|
|
390
|
+
* (never overwritten), mirroring the MCP renderer's data-loss safety.
|
|
391
|
+
*
|
|
392
|
+
* Codex is GLOBAL-config scope: project setup cannot write it, so this returns a
|
|
393
|
+
* `scope: 'global-config'` skip WITHOUT touching disk (keeps Codex honest).
|
|
394
|
+
*
|
|
395
|
+
* @param {object} params
|
|
396
|
+
* @param {'claude'|'cursor'|'codex'} params.harness
|
|
397
|
+
* @param {string} params.targetRoot - Project root.
|
|
398
|
+
* @param {object} [params.contract] - Forge hook contract (defaults to FORGE_HOOK_CONTRACT).
|
|
399
|
+
* @returns {{ file?: string, existed?: boolean, skipped: boolean, wrote: boolean, backup?: string, scope?: string }}
|
|
400
|
+
*/
|
|
401
|
+
function renderHookConfig({ harness, targetRoot, contract = FORGE_HOOK_CONTRACT }) {
|
|
402
|
+
if (harness === 'codex' || harness === 'hermes') {
|
|
403
|
+
// Honest: Codex (~/.codex/config.toml) and Hermes (~/.hermes/config.yaml) hooks
|
|
404
|
+
// both live in GLOBAL (home) config; project setup must not write global config.
|
|
405
|
+
// Their renderers are kept + tested for a global-config follow-up.
|
|
406
|
+
return { harness, scope: 'global-config', skipped: true, wrote: false };
|
|
407
|
+
}
|
|
408
|
+
const merge = MERGERS[harness];
|
|
409
|
+
const rel = HARNESS_HOOK_FILES[harness];
|
|
410
|
+
if (!merge || !rel) throw new Error(`Unknown hook harness: ${harness}`);
|
|
411
|
+
|
|
412
|
+
const filePath = path.join(targetRoot, rel);
|
|
413
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
414
|
+
const existed = fs.existsSync(filePath);
|
|
415
|
+
const existing = existed ? fs.readFileSync(filePath, 'utf-8') : '';
|
|
416
|
+
|
|
417
|
+
let merged;
|
|
418
|
+
try {
|
|
419
|
+
merged = merge(existing, contract);
|
|
420
|
+
} catch (err) {
|
|
421
|
+
if (err instanceof HookConfigParseError && existed) {
|
|
422
|
+
const backup = backupFile(filePath);
|
|
423
|
+
return { file: filePath, existed, skipped: true, wrote: false, backup };
|
|
424
|
+
}
|
|
425
|
+
throw err;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
fs.writeFileSync(filePath, merged, 'utf-8');
|
|
429
|
+
return { file: filePath, existed, skipped: false, wrote: true };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
module.exports = {
|
|
433
|
+
FORGE_HOOK_CONTRACT,
|
|
434
|
+
HARNESS_HOOK_FILES,
|
|
435
|
+
FORGE_HOOK_ADAPTER,
|
|
436
|
+
FORGE_HOOK_MARKER,
|
|
437
|
+
FORGE_CONTEXT_MARKER,
|
|
438
|
+
FORGE_INBOX_CONTEXT_MARKER,
|
|
439
|
+
SESSION_START_SUPPORT,
|
|
440
|
+
USER_PROMPT_SUBMIT_SUPPORT,
|
|
441
|
+
sessionStartCapability,
|
|
442
|
+
userPromptSubmitCapability,
|
|
443
|
+
HookConfigParseError,
|
|
444
|
+
renderClaudeHooks,
|
|
445
|
+
renderCursorHooks,
|
|
446
|
+
renderCodexHooksToml,
|
|
447
|
+
renderHermesHooksYaml,
|
|
448
|
+
mergeClaudeSettings,
|
|
449
|
+
mergeCursorHooks,
|
|
450
|
+
renderHookConfig,
|
|
451
|
+
};
|