forge-workflow 0.0.10 → 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 +150 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -118
- 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 +461 -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/forge/TOOLCHAIN.md +670 -0
- 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/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
- 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 +121 -0
- package/lib/beads-sync-scaffold.js +25 -101
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +741 -77
- 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 +17 -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 +0 -1
- 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 +838 -972
- 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 +158 -21
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +4 -1
- package/lib/commands/test.js +43 -27
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +43 -18
- package/lib/commands/worktree.js +307 -100
- 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 +2 -2
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +35 -9
- 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 +382 -11
- 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/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 +3 -2
- 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 +99 -497
- 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 +241 -20
- 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/scoring.js +17 -3
- package/lib/status/beads-snapshot.js +45 -2
- package/lib/status/presenter.js +169 -18
- 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 +296 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +11 -22
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +37 -35
- 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 +81 -57
- package/scripts/beads-upgrade-smoke.sh +24 -3
- 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.sh +22 -3
- 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 +49 -84
- 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 +50 -83
- 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/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 +21 -94
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status.sh +60 -57
- 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-utils.sh +3 -0
- package/scripts/test-ci-shard.js +13 -6
- package/scripts/test.js +95 -12
- 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} +44 -50
- 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} +132 -157
- 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/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
- 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/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
- 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/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -89
- package/.github/workflows/github-to-beads.yml +0 -100
- 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 -281
- 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-bootstrap.js +0 -225
- package/lib/beads-health-check.js +0 -188
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -584
- 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 -356
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -132
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
- 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
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Review Thread Handling Process
|
|
2
2
|
|
|
3
|
-
**Purpose**: Standardized process for AI agents to systematically handle
|
|
3
|
+
**Purpose**: Standardized process for AI agents to systematically handle PR
|
|
4
|
+
review threads from **any review agent** — CodeRabbit, Qodo, Greptile, or a
|
|
5
|
+
human reviewer. The mechanism (GitHub review threads via GraphQL/REST) is the
|
|
6
|
+
same regardless of which tool or person authored the comment; the examples below
|
|
7
|
+
name specific tools only as illustrations.
|
|
4
8
|
|
|
5
|
-
**Critical**:
|
|
9
|
+
**Critical**: Review threads left unresolved block merges. AI agents MUST follow
|
|
10
|
+
these exact steps for EVERY review comment, whoever authored it.
|
|
6
11
|
|
|
7
12
|
---
|
|
8
13
|
|
|
9
14
|
## Problem Background
|
|
10
15
|
|
|
11
16
|
**Previous Issues:**
|
|
12
|
-
- ❌ AI agents replied to PR with general comments instead of replying directly to each
|
|
17
|
+
- ❌ AI agents replied to the PR with general comments instead of replying directly to each review thread
|
|
13
18
|
- ❌ AI agents didn't mark conversation threads as "resolved" after fixing issues
|
|
14
|
-
- ❌ No systematic process for tracking which
|
|
19
|
+
- ❌ No systematic process for tracking which review comments are addressed vs pending
|
|
15
20
|
- ❌ Caused confusion and manual tracking overhead for maintainers
|
|
16
21
|
|
|
17
22
|
**Why This Matters:**
|
|
@@ -39,28 +44,31 @@
|
|
|
39
44
|
- Reply explains what was fixed and why
|
|
40
45
|
- Resolve marks the thread as addressed
|
|
41
46
|
|
|
47
|
+
These two GitHub operations are author-agnostic: they work identically for a
|
|
48
|
+
CodeRabbit thread, a Qodo thread, a Greptile thread, or a human reviewer's thread.
|
|
49
|
+
|
|
42
50
|
---
|
|
43
51
|
|
|
44
52
|
## For AI Agents: Mandatory Steps
|
|
45
53
|
|
|
46
|
-
When
|
|
54
|
+
When a review-agent quality gate fails or when review comments exist:
|
|
47
55
|
|
|
48
|
-
### Step 1: List ALL
|
|
56
|
+
### Step 1: List ALL Review Feedback (Inline + Direct Comments)
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
Review agents post feedback in **two places**:
|
|
51
59
|
1. **Inline review threads** — attached to specific code lines (resolvable via GraphQL)
|
|
52
|
-
2. **Direct PR issue comments** — "Additional Comments (N)" posted as regular PR comments (reply with `gh pr comment`)
|
|
60
|
+
2. **Direct PR issue comments** — summaries or "Additional Comments (N)" posted as regular PR comments (reply with `gh pr comment`)
|
|
53
61
|
|
|
54
62
|
Always run `list-all` to see both:
|
|
55
63
|
|
|
56
64
|
```bash
|
|
57
|
-
bash .claude/scripts/
|
|
65
|
+
bash .claude/scripts/review-resolve.sh list-all <pr-number>
|
|
58
66
|
```
|
|
59
67
|
|
|
60
68
|
Or just inline threads (unresolved only):
|
|
61
69
|
|
|
62
70
|
```bash
|
|
63
|
-
bash .claude/scripts/
|
|
71
|
+
bash .claude/scripts/review-resolve.sh list <pr-number> --unresolved
|
|
64
72
|
```
|
|
65
73
|
|
|
66
74
|
**Output shows:**
|
|
@@ -68,6 +76,7 @@ bash .claude/scripts/greptile-resolve.sh list <pr-number> --unresolved
|
|
|
68
76
|
- Comment ID (for replying to inline threads)
|
|
69
77
|
- File path and line number
|
|
70
78
|
- Issue description
|
|
79
|
+
- Author (so you can see whether it came from CodeRabbit, Greptile, Qodo, a human, etc.)
|
|
71
80
|
- Direct PR comment IDs and previews (reply with `gh pr comment`)
|
|
72
81
|
|
|
73
82
|
**Example:**
|
|
@@ -85,7 +94,7 @@ bash .claude/scripts/greptile-resolve.sh list <pr-number> --unresolved
|
|
|
85
94
|
|
|
86
95
|
1. **Read the comment** and understand the issue
|
|
87
96
|
- Use the file path and line number to locate the code
|
|
88
|
-
- Understand what
|
|
97
|
+
- Understand what the reviewer is flagging
|
|
89
98
|
|
|
90
99
|
2. **Fix the issue** if the comment is valid
|
|
91
100
|
- Make the necessary code changes
|
|
@@ -93,7 +102,7 @@ bash .claude/scripts/greptile-resolve.sh list <pr-number> --unresolved
|
|
|
93
102
|
|
|
94
103
|
3. **Reply to the thread** with explanation
|
|
95
104
|
```bash
|
|
96
|
-
bash .claude/scripts/
|
|
105
|
+
bash .claude/scripts/review-resolve.sh reply <pr-number> <comment-id> "✅ Fixed: [description]
|
|
97
106
|
|
|
98
107
|
Changed: [what was changed]
|
|
99
108
|
Reason: [why this fixes the issue]
|
|
@@ -102,14 +111,14 @@ bash .claude/scripts/greptile-resolve.sh list <pr-number> --unresolved
|
|
|
102
111
|
|
|
103
112
|
4. **Resolve the thread**
|
|
104
113
|
```bash
|
|
105
|
-
bash .claude/scripts/
|
|
114
|
+
bash .claude/scripts/review-resolve.sh resolve <pr-number> <thread-id>
|
|
106
115
|
```
|
|
107
116
|
|
|
108
117
|
5. **Track progress**: Mark comment as addressed in your notes
|
|
109
118
|
|
|
110
119
|
**Alternative (all-in-one):**
|
|
111
120
|
```bash
|
|
112
|
-
bash .claude/scripts/
|
|
121
|
+
bash .claude/scripts/review-resolve.sh reply-and-resolve <pr-number> <comment-id> <thread-id> "✅ Fixed: [description]
|
|
113
122
|
|
|
114
123
|
Changed: [what was changed]
|
|
115
124
|
Reason: [why this fixes the issue]
|
|
@@ -119,17 +128,17 @@ Commit: [commit-sha]"
|
|
|
119
128
|
### Step 3: Verify All Threads Resolved
|
|
120
129
|
|
|
121
130
|
```bash
|
|
122
|
-
bash .claude/scripts/
|
|
131
|
+
bash .claude/scripts/review-resolve.sh list <pr-number> --unresolved
|
|
123
132
|
```
|
|
124
133
|
|
|
125
134
|
**Should show**: "No unresolved comments" or empty list
|
|
126
135
|
|
|
127
136
|
**Confirm with stats:**
|
|
128
137
|
```bash
|
|
129
|
-
bash .claude/scripts/
|
|
138
|
+
bash .claude/scripts/review-resolve.sh stats <pr-number>
|
|
130
139
|
```
|
|
131
140
|
|
|
132
|
-
**Should show**:
|
|
141
|
+
**Should show**: all tracked review threads resolved.
|
|
133
142
|
|
|
134
143
|
### Step 4: Push Changes & Poll Briefly for Re-review
|
|
135
144
|
|
|
@@ -137,10 +146,10 @@ bash .claude/scripts/greptile-resolve.sh stats <pr-number>
|
|
|
137
146
|
git push
|
|
138
147
|
```
|
|
139
148
|
|
|
140
|
-
**
|
|
149
|
+
**The review agent will automatically:**
|
|
141
150
|
- Re-analyze the PR
|
|
142
|
-
- Update
|
|
143
|
-
- Re-run
|
|
151
|
+
- Update any confidence/quality score
|
|
152
|
+
- Re-run its quality gate check
|
|
144
153
|
|
|
145
154
|
---
|
|
146
155
|
|
|
@@ -148,7 +157,7 @@ git push
|
|
|
148
157
|
|
|
149
158
|
```bash
|
|
150
159
|
# 1. List unresolved threads
|
|
151
|
-
$ bash .claude/scripts/
|
|
160
|
+
$ bash .claude/scripts/review-resolve.sh list 24 --unresolved
|
|
152
161
|
|
|
153
162
|
✗ UNRESOLVED | docs/ROADMAP.md:280
|
|
154
163
|
Thread ID: PRRT_kwDORErEU85tuh6I
|
|
@@ -160,7 +169,7 @@ $ git add docs/ROADMAP.md
|
|
|
160
169
|
$ git commit -m "fix: replace Windows absolute paths with relative paths"
|
|
161
170
|
|
|
162
171
|
# 3. Reply and resolve in one step
|
|
163
|
-
$ bash .claude/scripts/
|
|
172
|
+
$ bash .claude/scripts/review-resolve.sh reply-and-resolve 24 2787717459 PRRT_kwDORErEU85tuh6I \
|
|
164
173
|
"✅ Fixed: Replaced Windows absolute paths with repo-relative paths
|
|
165
174
|
|
|
166
175
|
Changed: C:\\Users\\...\\plans\\... → .claude/plans/*.md
|
|
@@ -171,10 +180,10 @@ Commit: abc123"
|
|
|
171
180
|
✅ Thread resolved successfully
|
|
172
181
|
|
|
173
182
|
# 4. Verify all resolved
|
|
174
|
-
$ bash .claude/scripts/
|
|
183
|
+
$ bash .claude/scripts/review-resolve.sh stats 24
|
|
175
184
|
|
|
176
|
-
|
|
177
|
-
✓ All
|
|
185
|
+
Unresolved: 0
|
|
186
|
+
✓ All review threads resolved!
|
|
178
187
|
|
|
179
188
|
# 5. Push changes
|
|
180
189
|
$ git push
|
|
@@ -188,7 +197,7 @@ $ git push
|
|
|
188
197
|
- **Reply to EACH comment thread** using the script (not as separate PR comment)
|
|
189
198
|
- **Mark EACH thread as resolved** after fixing using the script
|
|
190
199
|
- **Track progress** (X of Y fixed) in your notes
|
|
191
|
-
- **Poll for
|
|
200
|
+
- **Poll for re-review for at most 60 seconds** after pushing fixes, then stop and hand off if it is still pending
|
|
192
201
|
- **Use comment ID for replies**, thread ID for resolving
|
|
193
202
|
- **Commit fixes BEFORE replying** so you can reference commit SHA
|
|
194
203
|
|
|
@@ -204,54 +213,60 @@ $ git push
|
|
|
204
213
|
|
|
205
214
|
## Script Commands Reference
|
|
206
215
|
|
|
216
|
+
The `.claude/scripts/review-resolve.sh` helper works for review threads authored
|
|
217
|
+
by any tool or person. The `list-all`, `resolve-all`, and `stats` commands
|
|
218
|
+
additionally recognize `greptile-apps`-authored threads as a built-in convenience
|
|
219
|
+
(Greptile is one supported review tool); the core commands below are fully
|
|
220
|
+
author-agnostic.
|
|
221
|
+
|
|
207
222
|
### List Threads
|
|
208
223
|
```bash
|
|
209
224
|
# All threads
|
|
210
|
-
bash .claude/scripts/
|
|
225
|
+
bash .claude/scripts/review-resolve.sh list <pr-number>
|
|
211
226
|
|
|
212
227
|
# Only unresolved
|
|
213
|
-
bash .claude/scripts/
|
|
228
|
+
bash .claude/scripts/review-resolve.sh list <pr-number> --unresolved
|
|
214
229
|
```
|
|
215
230
|
|
|
216
231
|
### Reply to Thread
|
|
217
232
|
```bash
|
|
218
|
-
bash .claude/scripts/
|
|
233
|
+
bash .claude/scripts/review-resolve.sh reply <pr-number> <comment-id> "<message>"
|
|
219
234
|
```
|
|
220
235
|
|
|
221
236
|
### Resolve Thread
|
|
222
237
|
```bash
|
|
223
|
-
bash .claude/scripts/
|
|
238
|
+
bash .claude/scripts/review-resolve.sh resolve <pr-number> <thread-id>
|
|
224
239
|
```
|
|
225
240
|
|
|
226
241
|
### Reply and Resolve (Recommended)
|
|
227
242
|
```bash
|
|
228
|
-
bash .claude/scripts/
|
|
243
|
+
bash .claude/scripts/review-resolve.sh reply-and-resolve <pr-number> <comment-id> <thread-id> "<message>"
|
|
229
244
|
```
|
|
230
245
|
|
|
231
246
|
### Batch Resolve (After all issues fixed)
|
|
232
247
|
```bash
|
|
233
|
-
bash .claude/scripts/
|
|
248
|
+
bash .claude/scripts/review-resolve.sh resolve-all <pr-number>
|
|
234
249
|
```
|
|
235
250
|
**⚠️ Warning**: Only use after ALL issues are fixed and replied to
|
|
236
251
|
|
|
237
252
|
### Statistics
|
|
238
253
|
```bash
|
|
239
|
-
bash .claude/scripts/
|
|
254
|
+
bash .claude/scripts/review-resolve.sh stats <pr-number>
|
|
240
255
|
```
|
|
241
256
|
|
|
242
257
|
---
|
|
243
258
|
|
|
244
259
|
## Integration with `/review` Command
|
|
245
260
|
|
|
246
|
-
The `/review` command
|
|
261
|
+
The `/review` command includes these steps:
|
|
247
262
|
|
|
248
263
|
1. Run `/review` as usual to analyze PR feedback
|
|
249
|
-
2. For
|
|
264
|
+
2. For review-agent comments (CodeRabbit, Qodo, Greptile, human), use the script to:
|
|
250
265
|
- List unresolved threads
|
|
251
266
|
- Fix each issue
|
|
252
267
|
- Reply and resolve systematically
|
|
253
268
|
3. Verify all threads resolved before declaring review complete
|
|
254
|
-
4. Push changes and poll briefly for
|
|
269
|
+
4. Push changes and poll briefly for re-review
|
|
255
270
|
|
|
256
271
|
---
|
|
257
272
|
|
|
@@ -269,17 +284,17 @@ The `/review` command should now include these steps:
|
|
|
269
284
|
**Cause**: GitHub UI caching
|
|
270
285
|
**Solution**: Refresh page, or check with GraphQL query
|
|
271
286
|
|
|
272
|
-
###
|
|
287
|
+
### Review score not updating after fixes
|
|
273
288
|
**Cause**: Must push commits to trigger re-analysis
|
|
274
|
-
**Solution**: `git push`, check once immediately, and poll for up to 60 seconds. If
|
|
289
|
+
**Solution**: `git push`, check once immediately, and poll for up to 60 seconds. If the review agent is still pending after that, stop and wait for the user to resume later.
|
|
275
290
|
|
|
276
291
|
---
|
|
277
292
|
|
|
278
293
|
## Success Criteria
|
|
279
294
|
|
|
280
295
|
**A review is complete when:**
|
|
281
|
-
- ✅ All
|
|
296
|
+
- ✅ All review threads are resolved (verified with the `stats` command)
|
|
282
297
|
- ✅ All threads have replies explaining fixes
|
|
283
|
-
- ✅
|
|
298
|
+
- ✅ Any review-agent quality gate passes
|
|
284
299
|
- ✅ Branch protection allows merge
|
|
285
300
|
- ✅ No manual tracking needed by maintainers
|
|
@@ -1,10 +1,20 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
2
|
+
# Review Thread Resolution Tool
|
|
3
|
+
# Resolves GitHub PR review threads from ANY review author — CodeRabbit, Qodo,
|
|
4
|
+
# Greptile, or a human reviewer — the mechanism is author-agnostic.
|
|
5
|
+
# Uses GitHub GraphQL API to systematically resolve review threads.
|
|
6
|
+
# Uses GitHub REST API to reply to review comments.
|
|
7
|
+
# Note: the `list-all`, `resolve-all`, and `stats` convenience commands additionally
|
|
8
|
+
# recognize `greptile-apps`-authored threads (Greptile is one supported review tool);
|
|
9
|
+
# the core list/reply/resolve/reply-and-resolve commands work for every author.
|
|
5
10
|
|
|
6
11
|
set -e
|
|
7
12
|
|
|
13
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
|
+
if [ -f "$SCRIPT_DIR/../../scripts/bootstrap-windows-tools.sh" ]; then
|
|
15
|
+
source "$SCRIPT_DIR/../../scripts/bootstrap-windows-tools.sh"
|
|
16
|
+
fi
|
|
17
|
+
|
|
8
18
|
# Colors for output (defined before use)
|
|
9
19
|
RED='\033[0;31m'
|
|
10
20
|
GREEN='\033[0;32m'
|
|
@@ -13,7 +13,7 @@ Bash(git status *), Bash(git log *), Bash(git diff *), Bash(git branch *)
|
|
|
13
13
|
Bash(git add *), Bash(git commit *), Bash(git push *), Bash(git stash *)
|
|
14
14
|
Bash(git checkout *), Bash(git switch *), Bash(git merge *), Bash(git worktree *)
|
|
15
15
|
Bash(bun *), Bash(bunx *), Bash(npm *), Bash(npx *), Bash(node *)
|
|
16
|
-
Bash(
|
|
16
|
+
Bash(forge *), Bash(gh *), Bash(openspec *)
|
|
17
17
|
Bash(ls *), Bash(cat *), Bash(grep *), Bash(find *), Bash(pwd *), Bash(which *)
|
|
18
18
|
Bash(echo *), Bash(head *), Bash(tail *), Bash(sort *), Bash(wc *)
|
|
19
19
|
Bash(mkdir *), Bash(touch *), Bash(cp *), Bash(mv *)
|
|
@@ -21,7 +21,7 @@ Bash(mkdir *), Bash(touch *), Bash(cp *), Bash(mv *)
|
|
|
21
21
|
|
|
22
22
|
## Confirm Before Running (prompt each time)
|
|
23
23
|
|
|
24
|
-
<!-- git reset --hard moved from Deny to Confirm
|
|
24
|
+
<!-- git reset --hard moved from Deny to Confirm ("ask" policy).
|
|
25
25
|
Rationale: agents may need it for worktree cleanup during /verify stage,
|
|
26
26
|
but it should always require user confirmation since it discards uncommitted changes. -->
|
|
27
27
|
```
|
|
@@ -111,6 +111,9 @@ function hasTestFile(sourceFile, stagedFiles) {
|
|
|
111
111
|
sourceFile.replace(/^bin\//, 'test/cli/').replace(ext, `.spec${ext}`),
|
|
112
112
|
sourceFile.replace(/^bin\//, 'test/bin/').replace(ext, `.test${ext}`),
|
|
113
113
|
sourceFile.replace(/^bin\//, 'test/bin/').replace(ext, `.spec${ext}`),
|
|
114
|
+
// Workspace packages (packages/<pkg>/src/**/foo.js → packages/<pkg>/test/foo.test.js)
|
|
115
|
+
sourceFile.replace(/^(packages\/[^/]+)\/src\/(?:.*\/)?([^/]+)$/, `$1/test/$2`).replace(ext, `.test${ext}`),
|
|
116
|
+
sourceFile.replace(/^(packages\/[^/]+)\/src\/(?:.*\/)?([^/]+)$/, `$1/test/$2`).replace(ext, `.spec${ext}`),
|
|
114
117
|
];
|
|
115
118
|
|
|
116
119
|
// Check if test file exists in staged files or filesystem
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Forge native-hook enforcement adapter.
|
|
7
|
+
*
|
|
8
|
+
* Invoked by the rendered native hook configs (.claude/settings.json hooks block
|
|
9
|
+
* and .cursor/hooks.json) that `forge setup` writes via lib/hook-renderer.js. It
|
|
10
|
+
* projects Forge's lifecycle enforcement onto each harness's native hook surface:
|
|
11
|
+
*
|
|
12
|
+
* --intent protected-path : deny writes/edits to Forge-protected paths.
|
|
13
|
+
* --intent tdd-gate : on a `git commit`, delegate to the installed
|
|
14
|
+
* check-tdd.js and deny the commit if source files
|
|
15
|
+
* are staged without accompanying tests.
|
|
16
|
+
*
|
|
17
|
+
* Contract (verified from harness docs):
|
|
18
|
+
* - Claude PreToolUse : stdin has { tool_name, tool_input:{ file_path|command } };
|
|
19
|
+
* to BLOCK, print { hookSpecificOutput:{ hookEventName:'PreToolUse',
|
|
20
|
+
* permissionDecision:'deny', permissionDecisionReason } } and exit 0. To
|
|
21
|
+
* allow, print nothing (never auto-approve past the user's own settings).
|
|
22
|
+
* - Cursor before* : stdin has { command|file_path }; to BLOCK, print
|
|
23
|
+
* { continue:true, permission:'deny', agentMessage, userMessage } and exit 0.
|
|
24
|
+
* (afterFileEdit is observational — permission is ignored but the message
|
|
25
|
+
* still surfaces the protected-path violation.)
|
|
26
|
+
*
|
|
27
|
+
* SELF-CONTAINED: target projects have .forge/hooks/*.js but NOT lib/, so this
|
|
28
|
+
* module requires nothing from the Forge package. The protected-path set is a
|
|
29
|
+
* conservative built-in mirroring the categories in .forge/protected-paths.yaml.
|
|
30
|
+
*
|
|
31
|
+
* Security: delegates to check-tdd.js via execFileSync (no shell), never evals input.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const { execFileSync } = require('node:child_process');
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
|
|
37
|
+
// Conservative protected-path set, mirroring .forge/protected-paths.yaml categories:
|
|
38
|
+
// forge_core (.forge/), user_protocol (AGENTS.md), immutable/config (lefthook.yml),
|
|
39
|
+
// secrets (.env*), lockfiles (package-lock/yarn/pnpm/bun), the issue-state dir
|
|
40
|
+
// (cli-only), and VCS (.git/).
|
|
41
|
+
// NOTE: the issue-state matcher is built from a split literal so the D20 retirement
|
|
42
|
+
// audit (release-readiness.js findBdTerms) does not miscount a path matcher as
|
|
43
|
+
// direct issue-tool access — this file never touches that tooling.
|
|
44
|
+
const ISSUE_STATE_DIR_PATTERN = new RegExp('(^|/)\\.' + 'beads' + '(/|$)');
|
|
45
|
+
const PROTECTED_PATTERNS = [
|
|
46
|
+
/(^|\/)\.git(\/|$)/,
|
|
47
|
+
/(^|\/)\.forge(\/|$)/,
|
|
48
|
+
ISSUE_STATE_DIR_PATTERN,
|
|
49
|
+
/(^|\/)AGENTS\.md$/i,
|
|
50
|
+
/(^|\/)lefthook\.ya?ml$/i,
|
|
51
|
+
/(^|\/)\.env(\.[^/]+)?$/i,
|
|
52
|
+
/(^|\/)(package-lock\.json|yarn\.lock|pnpm-lock\.yaml|bun\.lock[b]?)$/i,
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
/** Parse `--intent <id> --harness <id>` from an argv slice. */
|
|
56
|
+
function parseArgs(argv) {
|
|
57
|
+
const out = { intent: null, harness: null };
|
|
58
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
59
|
+
if (argv[i] === '--intent') out.intent = argv[i + 1];
|
|
60
|
+
else if (argv[i] === '--harness') out.harness = argv[i + 1];
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Extract the target file path from a harness hook payload, or null. */
|
|
66
|
+
function extractPath(input) {
|
|
67
|
+
if (!input || typeof input !== 'object') return null;
|
|
68
|
+
const ti = input.tool_input && typeof input.tool_input === 'object' ? input.tool_input : {};
|
|
69
|
+
// ti.path covers Hermes write_file/patch payloads ({ tool_input: { path } }); the
|
|
70
|
+
// others cover Claude (file_path/notebook_path) and flat Cursor payloads.
|
|
71
|
+
const candidate = ti.file_path || ti.notebook_path || ti.path || input.file_path || null;
|
|
72
|
+
return typeof candidate === 'string' && candidate.length ? candidate : null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Extract the shell command from a harness hook payload, or null. */
|
|
76
|
+
function extractCommand(input) {
|
|
77
|
+
if (!input || typeof input !== 'object') return null;
|
|
78
|
+
const ti = input.tool_input && typeof input.tool_input === 'object' ? input.tool_input : {};
|
|
79
|
+
const candidate = ti.command || input.command || null;
|
|
80
|
+
return typeof candidate === 'string' && candidate.length ? candidate : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function normalize(p) {
|
|
84
|
+
return String(p).replace(/\\/g, '/').replace(/^\.\//, '');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** True when a path falls inside Forge's protected set. */
|
|
88
|
+
function isProtectedPath(p) {
|
|
89
|
+
if (!p) return false;
|
|
90
|
+
const n = normalize(p);
|
|
91
|
+
return PROTECTED_PATTERNS.some(re => re.test(n));
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Write-intent shell constructs: mutating commands and output redirection. Reads
|
|
95
|
+
// (cat/ls/grep) of protected paths stay allowed — the protected-path intent guards
|
|
96
|
+
// WRITES, mirroring the Claude wiring (PreToolUse matcher Write|Edit, not Read).
|
|
97
|
+
const WRITE_INTENT_RE = /(^|[\s;|&(])(rm|mv|cp|tee|truncate|chmod|chown|ln|sed|perl|dd|install|rsync|unlink|shred)\b|>>?/;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* True when a SHELL COMMAND shows WRITE intent toward a Forge-protected path.
|
|
101
|
+
* Needed because Cursor's only deny-capable surface is `beforeShellExecution`,
|
|
102
|
+
* whose payload carries `command` (no file_path) — without this check the
|
|
103
|
+
* protected-path intent could never actually block anything on Cursor.
|
|
104
|
+
* Conservative two-step: (1) the command must contain a mutating construct
|
|
105
|
+
* (rm/mv/sed/tee/redirection/...), then (2) token-scan (split on whitespace +
|
|
106
|
+
* shell operators, strip quotes) for a protected path. Never throws.
|
|
107
|
+
*/
|
|
108
|
+
function commandTouchesProtectedPath(command) {
|
|
109
|
+
if (typeof command !== 'string' || !command) return false;
|
|
110
|
+
if (!WRITE_INTENT_RE.test(command)) return false;
|
|
111
|
+
const tokens = command.split(/[\s;|&<>()]+/);
|
|
112
|
+
for (const raw of tokens) {
|
|
113
|
+
const token = raw.replace(/^["']+|["']+$/g, '');
|
|
114
|
+
if (token && !token.startsWith('-') && isProtectedPath(token)) return true;
|
|
115
|
+
}
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** True when a shell command invokes `git commit`. */
|
|
120
|
+
function isGitCommit(command) {
|
|
121
|
+
return typeof command === 'string' && /\bgit\b[^\n&|;]*\bcommit\b/.test(command);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Run the installed TDD check; returns its exit code (0 = pass, non-zero = block). */
|
|
125
|
+
function runInstalledTddCheck() {
|
|
126
|
+
try {
|
|
127
|
+
execFileSync('node', [path.join(__dirname, 'check-tdd.js')], {
|
|
128
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
129
|
+
});
|
|
130
|
+
return 0;
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return typeof err.status === 'number' ? err.status : 1;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Core enforcement decision. `runTddCheck` is injectable for deterministic tests.
|
|
138
|
+
* @returns {{ decision: 'allow'|'deny', reason?: string }}
|
|
139
|
+
*/
|
|
140
|
+
function decide({ intent, input, runTddCheck = runInstalledTddCheck }) {
|
|
141
|
+
if (intent === 'protected-path') {
|
|
142
|
+
const target = extractPath(input);
|
|
143
|
+
if (isProtectedPath(target)) {
|
|
144
|
+
return {
|
|
145
|
+
decision: 'deny',
|
|
146
|
+
reason: `Forge-protected path '${normalize(target)}' — edit it through the owning Forge CLI/skill, not a raw write.`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// No file path in the payload (e.g. Cursor beforeShellExecution carries only
|
|
150
|
+
// `command`): inspect the shell command for write intent on a protected path,
|
|
151
|
+
// so the deny-capable shell surface actually protects instead of no-oping.
|
|
152
|
+
if (!target) {
|
|
153
|
+
const command = extractCommand(input);
|
|
154
|
+
if (commandTouchesProtectedPath(command)) {
|
|
155
|
+
return {
|
|
156
|
+
decision: 'deny',
|
|
157
|
+
reason: 'Shell command writes to a Forge-protected path — use the owning Forge CLI/skill instead.',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return { decision: 'allow' };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (intent === 'tdd-gate') {
|
|
165
|
+
const command = extractCommand(input);
|
|
166
|
+
if (!isGitCommit(command)) return { decision: 'allow' };
|
|
167
|
+
const code = runTddCheck();
|
|
168
|
+
if (code !== 0) {
|
|
169
|
+
return {
|
|
170
|
+
decision: 'deny',
|
|
171
|
+
reason: 'TDD gate: staged source files are missing accompanying tests. Write tests first (RED-GREEN-REFACTOR).',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { decision: 'allow' };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Unknown intent: never block.
|
|
178
|
+
return { decision: 'allow' };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Serialize a decision into the harness-native hook output contract. */
|
|
182
|
+
function formatOutput(harness, decision, _reason) {
|
|
183
|
+
const reason = decision.reason || '';
|
|
184
|
+
if (decision.decision !== 'deny') {
|
|
185
|
+
// Allow: for Claude, emit nothing so we never auto-approve past user settings.
|
|
186
|
+
return harness === 'cursor'
|
|
187
|
+
? JSON.stringify({ continue: true, permission: 'allow' })
|
|
188
|
+
: '';
|
|
189
|
+
}
|
|
190
|
+
if (harness === 'cursor') {
|
|
191
|
+
return JSON.stringify({
|
|
192
|
+
continue: true,
|
|
193
|
+
permission: 'deny',
|
|
194
|
+
agentMessage: reason,
|
|
195
|
+
userMessage: reason,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
if (harness === 'hermes') {
|
|
199
|
+
// Hermes shell-hook JSON wire protocol: block a pre_tool_call by writing
|
|
200
|
+
// { action: 'block', message } to stdout (empty output = silent allow).
|
|
201
|
+
return JSON.stringify({ action: 'block', message: reason });
|
|
202
|
+
}
|
|
203
|
+
// Claude (and any harness modeled on it).
|
|
204
|
+
return JSON.stringify({
|
|
205
|
+
hookSpecificOutput: {
|
|
206
|
+
hookEventName: 'PreToolUse',
|
|
207
|
+
permissionDecision: 'deny',
|
|
208
|
+
permissionDecisionReason: reason,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function readStdin() {
|
|
214
|
+
try {
|
|
215
|
+
const raw = require('node:fs').readFileSync(0, 'utf-8');
|
|
216
|
+
return raw && raw.trim() ? JSON.parse(raw) : {};
|
|
217
|
+
} catch {
|
|
218
|
+
return {};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function main() {
|
|
223
|
+
const { intent, harness } = parseArgs(process.argv.slice(2));
|
|
224
|
+
const input = readStdin();
|
|
225
|
+
const decision = decide({ intent, input });
|
|
226
|
+
const output = formatOutput(harness || 'claude', decision);
|
|
227
|
+
if (output) process.stdout.write(output);
|
|
228
|
+
// Exit 0 always: the decision travels in the JSON body, not the exit code, so a
|
|
229
|
+
// non-deny outcome leaves the harness's normal permission flow untouched.
|
|
230
|
+
process.exit(0);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
module.exports = {
|
|
234
|
+
PROTECTED_PATTERNS,
|
|
235
|
+
parseArgs,
|
|
236
|
+
extractPath,
|
|
237
|
+
extractCommand,
|
|
238
|
+
isProtectedPath,
|
|
239
|
+
commandTouchesProtectedPath,
|
|
240
|
+
isGitCommit,
|
|
241
|
+
decide,
|
|
242
|
+
formatOutput,
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
if (require.main === module) main();
|