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,262 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const crypto = require('node:crypto');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
|
|
7
|
+
const LOCKFILE_NAME = 'forge.lock';
|
|
8
|
+
const AUDIT_LOG_PATH = path.join('.forge', 'log.jsonl');
|
|
9
|
+
const REMOTE_SOURCE_PATTERN = /^(?:https?:|gh:|gist:|npm:)/i;
|
|
10
|
+
|
|
11
|
+
function toPosixPath(value) {
|
|
12
|
+
return String(value || '').replaceAll('\\', '/');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeRelativePath(projectRoot, fullPath) {
|
|
16
|
+
return toPosixPath(path.relative(projectRoot, fullPath));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function isInsideRoot(projectRoot, fullPath) {
|
|
20
|
+
const relative = path.relative(projectRoot, fullPath);
|
|
21
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function lockPath(projectRoot) {
|
|
25
|
+
return path.join(projectRoot, LOCKFILE_NAME);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function defaultLock() {
|
|
29
|
+
return {
|
|
30
|
+
version: 1,
|
|
31
|
+
generatedBy: 'forge',
|
|
32
|
+
extensions: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function readForgeLock(projectRoot) {
|
|
37
|
+
const filePath = lockPath(projectRoot);
|
|
38
|
+
if (!fs.existsSync(filePath)) return defaultLock();
|
|
39
|
+
const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
40
|
+
return {
|
|
41
|
+
...defaultLock(),
|
|
42
|
+
...parsed,
|
|
43
|
+
extensions: Array.isArray(parsed.extensions) ? parsed.extensions : [],
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeForgeLock(projectRoot, lock) {
|
|
48
|
+
fs.writeFileSync(lockPath(projectRoot), `${JSON.stringify(lock, null, 2)}\n`, 'utf8');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function appendAuditEvent(projectRoot, event) {
|
|
52
|
+
const logPath = path.join(projectRoot, AUDIT_LOG_PATH);
|
|
53
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
54
|
+
fs.appendFileSync(logPath, `${JSON.stringify({
|
|
55
|
+
kind: 'forge.lock',
|
|
56
|
+
schemaVersion: 1,
|
|
57
|
+
recordedAt: new Date().toISOString(),
|
|
58
|
+
...event,
|
|
59
|
+
})}\n`, 'utf8');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isRemoteSource(source) {
|
|
63
|
+
return REMOTE_SOURCE_PATTERN.test(String(source || ''));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function resolveLocalSource(projectRoot, source) {
|
|
67
|
+
const root = path.resolve(projectRoot);
|
|
68
|
+
const resolved = path.resolve(root, source);
|
|
69
|
+
if (!isInsideRoot(root, resolved)) {
|
|
70
|
+
throw new Error(`Source must stay inside project root: ${source}`);
|
|
71
|
+
}
|
|
72
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
|
|
73
|
+
throw new Error(`Local source does not exist: ${source}`);
|
|
74
|
+
}
|
|
75
|
+
const realRoot = fs.realpathSync(root);
|
|
76
|
+
const realResolved = fs.realpathSync(resolved);
|
|
77
|
+
if (!isInsideRoot(realRoot, realResolved)) {
|
|
78
|
+
throw new Error(`Source must stay inside project root: ${source}`);
|
|
79
|
+
}
|
|
80
|
+
if (!fs.statSync(realResolved).isFile()) {
|
|
81
|
+
throw new Error(`Local source does not exist: ${source}`);
|
|
82
|
+
}
|
|
83
|
+
return realResolved;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function sourceIntegrity(filePath) {
|
|
87
|
+
const digest = crypto
|
|
88
|
+
.createHash('sha512')
|
|
89
|
+
.update(fs.readFileSync(filePath))
|
|
90
|
+
.digest('base64');
|
|
91
|
+
return `sha512-${digest}`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function verifyUnsupportedRemoteEntry(entry) {
|
|
95
|
+
if (!isRemoteSource(entry.source)) {
|
|
96
|
+
return {
|
|
97
|
+
name: entry.name,
|
|
98
|
+
status: 'fail',
|
|
99
|
+
reason: 'unsupported remote verification requires a remote source locator',
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (entry.resolvedPath || entry.integrity || entry.trust?.allowUntrusted !== true) {
|
|
103
|
+
return {
|
|
104
|
+
name: entry.name,
|
|
105
|
+
status: 'fail',
|
|
106
|
+
reason: 'unsupported remote entry has inconsistent lock metadata',
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
name: entry.name,
|
|
111
|
+
status: 'warn',
|
|
112
|
+
reason: 'remote source integrity cannot be rechecked by this foundation',
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function classifySource(projectRoot, source, options = {}) {
|
|
117
|
+
if (!source) {
|
|
118
|
+
throw new Error('Source is required');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (isRemoteSource(source)) {
|
|
122
|
+
if (!options.allowUntrusted) {
|
|
123
|
+
throw new Error(`Untrusted source refused: ${source}. Re-run with --allow-untrusted to record it explicitly.`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
trusted: false,
|
|
127
|
+
allowUntrusted: true,
|
|
128
|
+
resolvedPath: null,
|
|
129
|
+
integrity: null,
|
|
130
|
+
verification: 'unsupported-remote',
|
|
131
|
+
reason: 'remote locator requires explicit --allow-untrusted',
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const resolvedPath = resolveLocalSource(projectRoot, source);
|
|
136
|
+
return {
|
|
137
|
+
trusted: true,
|
|
138
|
+
allowUntrusted: false,
|
|
139
|
+
resolvedPath,
|
|
140
|
+
integrity: sourceIntegrity(resolvedPath),
|
|
141
|
+
verification: 'sri',
|
|
142
|
+
reason: 'local file inside project root',
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function upsertExtension(extensions, entry) {
|
|
147
|
+
const next = extensions.filter(existing => existing.name !== entry.name);
|
|
148
|
+
next.push(entry);
|
|
149
|
+
return next.sort((a, b) => a.name.localeCompare(b.name));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function addLockEntry(projectRoot, options) {
|
|
153
|
+
const root = path.resolve(projectRoot);
|
|
154
|
+
const realRoot = fs.realpathSync(root);
|
|
155
|
+
const sourceInfo = classifySource(root, options.source, {
|
|
156
|
+
allowUntrusted: Boolean(options.allowUntrusted),
|
|
157
|
+
});
|
|
158
|
+
const lock = readForgeLock(root);
|
|
159
|
+
const entry = {
|
|
160
|
+
name: options.name,
|
|
161
|
+
source: options.source,
|
|
162
|
+
resolvedPath: sourceInfo.resolvedPath ? normalizeRelativePath(realRoot, sourceInfo.resolvedPath) : null,
|
|
163
|
+
integrity: sourceInfo.integrity,
|
|
164
|
+
verification: sourceInfo.verification,
|
|
165
|
+
trust: {
|
|
166
|
+
trusted: sourceInfo.trusted,
|
|
167
|
+
allowUntrusted: sourceInfo.allowUntrusted,
|
|
168
|
+
reason: sourceInfo.reason,
|
|
169
|
+
},
|
|
170
|
+
lockedAt: new Date().toISOString(),
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
lock.extensions = upsertExtension(lock.extensions, entry);
|
|
174
|
+
writeForgeLock(root, lock);
|
|
175
|
+
appendAuditEvent(root, {
|
|
176
|
+
action: 'add',
|
|
177
|
+
name: entry.name,
|
|
178
|
+
source: entry.source,
|
|
179
|
+
trusted: entry.trust.trusted,
|
|
180
|
+
verification: entry.verification,
|
|
181
|
+
integrity: entry.integrity,
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
return { lock, entry };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function verifyEntry(projectRoot, entry) {
|
|
188
|
+
if (entry.verification === 'unsupported-remote') {
|
|
189
|
+
return verifyUnsupportedRemoteEntry(entry);
|
|
190
|
+
}
|
|
191
|
+
if (isRemoteSource(entry.source) || isRemoteSource(entry.resolvedPath)) {
|
|
192
|
+
return {
|
|
193
|
+
name: entry.name,
|
|
194
|
+
status: 'fail',
|
|
195
|
+
reason: 'remote source locator requires unsupported-remote verification policy',
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const relativePath = entry.resolvedPath || entry.source;
|
|
200
|
+
const root = path.resolve(projectRoot);
|
|
201
|
+
const realRoot = fs.realpathSync(root);
|
|
202
|
+
const fullPath = path.resolve(realRoot, relativePath);
|
|
203
|
+
if (!isInsideRoot(realRoot, fullPath)) {
|
|
204
|
+
return {
|
|
205
|
+
name: entry.name,
|
|
206
|
+
status: 'fail',
|
|
207
|
+
reason: `source escapes project root: ${relativePath}`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
|
|
211
|
+
return {
|
|
212
|
+
name: entry.name,
|
|
213
|
+
status: 'fail',
|
|
214
|
+
reason: `source missing: ${relativePath}`,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const realFullPath = fs.realpathSync(fullPath);
|
|
218
|
+
if (!isInsideRoot(realRoot, realFullPath)) {
|
|
219
|
+
return {
|
|
220
|
+
name: entry.name,
|
|
221
|
+
status: 'fail',
|
|
222
|
+
reason: `source escapes project root: ${relativePath}`,
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const actual = sourceIntegrity(realFullPath);
|
|
227
|
+
if (actual !== entry.integrity) {
|
|
228
|
+
return {
|
|
229
|
+
name: entry.name,
|
|
230
|
+
status: 'fail',
|
|
231
|
+
reason: `integrity mismatch: expected ${entry.integrity}, got ${actual}`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return {
|
|
236
|
+
name: entry.name,
|
|
237
|
+
status: 'pass',
|
|
238
|
+
reason: 'integrity verified',
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function verifyForgeLock(projectRoot) {
|
|
243
|
+
const lock = readForgeLock(projectRoot);
|
|
244
|
+
const results = lock.extensions.map(entry => verifyEntry(projectRoot, entry));
|
|
245
|
+
return {
|
|
246
|
+
ok: results.every(result => result.status !== 'fail'),
|
|
247
|
+
path: LOCKFILE_NAME,
|
|
248
|
+
results,
|
|
249
|
+
lock,
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = {
|
|
254
|
+
LOCKFILE_NAME,
|
|
255
|
+
AUDIT_LOG_PATH,
|
|
256
|
+
addLockEntry,
|
|
257
|
+
classifySource,
|
|
258
|
+
readForgeLock,
|
|
259
|
+
sourceIntegrity,
|
|
260
|
+
verifyForgeLock,
|
|
261
|
+
writeForgeLock,
|
|
262
|
+
};
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @module gate-events
|
|
5
|
+
*
|
|
6
|
+
* Human gates enforced by kernel EVENTS, not skill prose (Fable's key insight,
|
|
7
|
+
* locked in docs/work/2026-07-04-kernel-native-skills/decisions.md). A gate
|
|
8
|
+
* approval is a durable, queryable, resume-safe kernel event on the ISSUE:
|
|
9
|
+
* `gate.approved` / `gate.rejected` by an actor. `forge gate check` (and, later,
|
|
10
|
+
* the ship stage) refuses to proceed without the required `gate.approved` event
|
|
11
|
+
* UNLESS the gate is toggled off — giving evidence-checked HARD-GATEs and
|
|
12
|
+
* resume-from-kernel-state after a compaction/crash for free.
|
|
13
|
+
*
|
|
14
|
+
* Mechanism: a PURE APPEND to the issue's event stream via the driver primitives
|
|
15
|
+
* (`insertKernelEvent` + `listKernelEvents`), NOT the guarded issue-mutation
|
|
16
|
+
* pipeline. Gate events do not mutate the issue, so they must not participate in
|
|
17
|
+
* the issue-revision CAS (`lib/kernel/evaluators.js` would otherwise quarantine an
|
|
18
|
+
* `expected_revision:0` append against a non-zero issue revision as `stale_revision`).
|
|
19
|
+
* Idempotency is enforced two ways: a pre-check on the idempotency key, plus the
|
|
20
|
+
* unique index `idx_kernel_events_idempotency` catching a concurrent race.
|
|
21
|
+
*
|
|
22
|
+
* Config-surface note: which gates EXIST (and whether each is enabled) lives in the
|
|
23
|
+
* runtime graph + `.forge/config.yaml`; this module only records/reads the events.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
const { buildMigratedKernelIssueDeps } = require('./kernel/cli-broker-factory');
|
|
27
|
+
const { resolveIssueActor } = require('./forge-issues');
|
|
28
|
+
|
|
29
|
+
const GATE_APPROVED_EVENT = 'gate.approved';
|
|
30
|
+
const GATE_REJECTED_EVENT = 'gate.rejected';
|
|
31
|
+
|
|
32
|
+
const GATE_EVENT_TYPES = {
|
|
33
|
+
approved: GATE_APPROVED_EVENT,
|
|
34
|
+
rejected: GATE_REJECTED_EVENT,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const ISSUE_ENTITY_TYPE = 'issue';
|
|
38
|
+
const GATE_EVENT_ORIGIN = 'cli';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Resolve the kernel broker + driver + config. Tests (and the orchestrator) inject
|
|
42
|
+
* a shared, already-migrated kernel via `deps`; the CLI path builds a fresh one for
|
|
43
|
+
* the (short-lived) process.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} projectRoot
|
|
46
|
+
* @param {{ kernelBroker?: Object, kernelDriver?: Object }} [deps]
|
|
47
|
+
* @returns {Promise<{ broker: Object, driver: Object, config: Object }>}
|
|
48
|
+
*/
|
|
49
|
+
async function resolveGateKernel(projectRoot, deps = {}) {
|
|
50
|
+
if (deps.kernelBroker && deps.kernelDriver) {
|
|
51
|
+
return { broker: deps.kernelBroker, driver: deps.kernelDriver, config: deps.kernelBroker.config };
|
|
52
|
+
}
|
|
53
|
+
const built = await buildMigratedKernelIssueDeps({ projectRoot });
|
|
54
|
+
return { broker: built.kernelBroker, driver: built.kernelDriver, config: built.kernelBroker.config };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Idempotency key for a gate event. Scoped to issue + gate + actor + decision so a
|
|
59
|
+
* SAME-actor re-approval mints no duplicate, while a later reject (different
|
|
60
|
+
* decision) or a different actor is recorded as its own event.
|
|
61
|
+
*/
|
|
62
|
+
function gateIdempotencyKey(eventType, issueId, gateId, actor) {
|
|
63
|
+
return `${eventType}:${issueId}:${gateId}:${actor}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Shape a stored kernel_events row into the gate-event view callers consume. */
|
|
67
|
+
function parseGateEvent(row) {
|
|
68
|
+
let payload;
|
|
69
|
+
try {
|
|
70
|
+
payload = row.payload_json ? JSON.parse(row.payload_json) : {};
|
|
71
|
+
} catch {
|
|
72
|
+
payload = {};
|
|
73
|
+
}
|
|
74
|
+
const view = {
|
|
75
|
+
event_type: row.event_type,
|
|
76
|
+
gate: payload.gate,
|
|
77
|
+
actor: row.actor,
|
|
78
|
+
created_at: row.created_at,
|
|
79
|
+
};
|
|
80
|
+
if (payload.reason !== undefined) view.reason = payload.reason;
|
|
81
|
+
return view;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isIdempotencyRace(error) {
|
|
85
|
+
const message = error && error.message ? String(error.message) : '';
|
|
86
|
+
return /UNIQUE constraint failed/i.test(message) && /idempotency_key/i.test(message);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Record a gate decision as a kernel event on the issue. Idempotent per
|
|
91
|
+
* issue+gate+actor+decision. Validates the issue exists (the caller validates the
|
|
92
|
+
* gate id against the known-gate set before calling).
|
|
93
|
+
*
|
|
94
|
+
* @param {string} projectRoot
|
|
95
|
+
* @param {Object} params
|
|
96
|
+
* @param {string} params.issueId
|
|
97
|
+
* @param {string} params.gateId
|
|
98
|
+
* @param {'approved'|'rejected'} params.decision
|
|
99
|
+
* @param {string} [params.reason]
|
|
100
|
+
* @param {Object} [params.env] - env source for actor resolution (FORGE_ACTOR → …).
|
|
101
|
+
* @param {Object} [params.deps] - injected { kernelBroker, kernelDriver }.
|
|
102
|
+
* @param {string} [params.now] - ISO timestamp (defaults to now).
|
|
103
|
+
* @returns {Promise<{ ok: boolean, duplicate?: boolean, issueMissing?: boolean, event?: Object, actor?: string }>}
|
|
104
|
+
*/
|
|
105
|
+
async function recordGateEvent(projectRoot, params = {}) {
|
|
106
|
+
const { issueId, gateId, decision, reason, env, deps, now } = params;
|
|
107
|
+
const eventType = GATE_EVENT_TYPES[decision];
|
|
108
|
+
if (!eventType) {
|
|
109
|
+
throw new Error(`Unknown gate decision '${decision}' (expected 'approved' or 'rejected').`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const actor = resolveIssueActor(env || process.env) || 'forge';
|
|
113
|
+
const { driver, config } = await resolveGateKernel(projectRoot, deps);
|
|
114
|
+
|
|
115
|
+
const entity = await driver.loadKernelEntity(ISSUE_ENTITY_TYPE, issueId, {}, config);
|
|
116
|
+
if (!entity) {
|
|
117
|
+
return { ok: false, issueMissing: true, actor };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const idempotencyKey = gateIdempotencyKey(eventType, issueId, gateId, actor);
|
|
121
|
+
|
|
122
|
+
const existing = await driver.loadKernelEventByIdempotencyKey(idempotencyKey, {}, config);
|
|
123
|
+
if (existing) {
|
|
124
|
+
return { ok: true, duplicate: true, event: parseGateEvent(existing), actor };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const payload = { gate: gateId, actor };
|
|
128
|
+
if (typeof reason === 'string' && reason.length > 0) payload.reason = reason;
|
|
129
|
+
|
|
130
|
+
const event = {
|
|
131
|
+
entity_type: ISSUE_ENTITY_TYPE,
|
|
132
|
+
entity_id: issueId,
|
|
133
|
+
event_type: eventType,
|
|
134
|
+
idempotency_key: idempotencyKey,
|
|
135
|
+
expected_revision: 0,
|
|
136
|
+
actor,
|
|
137
|
+
origin: GATE_EVENT_ORIGIN,
|
|
138
|
+
payload,
|
|
139
|
+
created_at: now || new Date().toISOString(),
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const inserted = await driver.insertKernelEvent(event, {}, config);
|
|
144
|
+
return { ok: true, duplicate: false, event: parseGateEvent(inserted), actor };
|
|
145
|
+
} catch (error) {
|
|
146
|
+
if (isIdempotencyRace(error)) {
|
|
147
|
+
const winner = await driver.loadKernelEventByIdempotencyKey(idempotencyKey, {}, config);
|
|
148
|
+
return { ok: true, duplicate: true, event: winner ? parseGateEvent(winner) : parseGateEvent(event), actor };
|
|
149
|
+
}
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* List every gate event on an issue, oldest first.
|
|
156
|
+
*
|
|
157
|
+
* @param {string} projectRoot
|
|
158
|
+
* @param {string} issueId
|
|
159
|
+
* @param {{ deps?: Object }} [options]
|
|
160
|
+
* @returns {Promise<Array<{ event_type: string, gate: string, actor: string, created_at: string, reason?: string }>>}
|
|
161
|
+
*/
|
|
162
|
+
async function listGateEvents(projectRoot, issueId, options = {}) {
|
|
163
|
+
const { driver, config } = await resolveGateKernel(projectRoot, options.deps);
|
|
164
|
+
const rows = await driver.listKernelEvents(ISSUE_ENTITY_TYPE, issueId, {}, config);
|
|
165
|
+
return (rows || [])
|
|
166
|
+
.filter(row => typeof row.event_type === 'string' && row.event_type.startsWith('gate.'))
|
|
167
|
+
.map(parseGateEvent);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* True iff a `gate.approved` event exists for this issue + gate.
|
|
172
|
+
*
|
|
173
|
+
* @param {string} projectRoot
|
|
174
|
+
* @param {string} issueId
|
|
175
|
+
* @param {string} gateId
|
|
176
|
+
* @param {{ deps?: Object }} [options]
|
|
177
|
+
* @returns {Promise<boolean>}
|
|
178
|
+
*/
|
|
179
|
+
async function isGateApproved(projectRoot, issueId, gateId, options = {}) {
|
|
180
|
+
const events = await listGateEvents(projectRoot, issueId, options);
|
|
181
|
+
return events.some(event => event.event_type === GATE_APPROVED_EVENT && event.gate === gateId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
module.exports = {
|
|
185
|
+
GATE_APPROVED_EVENT,
|
|
186
|
+
GATE_REJECTED_EVENT,
|
|
187
|
+
GATE_EVENT_TYPES,
|
|
188
|
+
gateIdempotencyKey,
|
|
189
|
+
parseGateEvent,
|
|
190
|
+
recordGateEvent,
|
|
191
|
+
listGateEvents,
|
|
192
|
+
isGateApproved,
|
|
193
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Global CLI flags recognized by bin/forge.js parseGlobalFlags().
|
|
5
|
+
*
|
|
6
|
+
* bin/forge.js parses these into the `flags` object but leaves them inside the
|
|
7
|
+
* positional `args` array it hands to command handlers. Free-text commands
|
|
8
|
+
* (remember, recall) that join positionals into content must strip them first,
|
|
9
|
+
* or `forge remember "note" -p <dir>` stores the literal `note -p <dir>`
|
|
10
|
+
* (kernel issue c1e090ff).
|
|
11
|
+
*
|
|
12
|
+
* Keep these sets in sync with parseGlobalFlags() in bin/forge.js.
|
|
13
|
+
* @module lib/global-flags
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
// Flags that consume a following value token (also accepted as --flag=value).
|
|
17
|
+
const GLOBAL_VALUE_FLAGS = new Set([
|
|
18
|
+
'--path', '-p',
|
|
19
|
+
'--agents',
|
|
20
|
+
'--merge',
|
|
21
|
+
'--type',
|
|
22
|
+
'--budget',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const GLOBAL_VALUE_FLAG_PREFIXES = ['--path=', '--agents=', '--merge=', '--type=', '--budget='];
|
|
26
|
+
|
|
27
|
+
// Boolean flags that take no value.
|
|
28
|
+
const GLOBAL_BOOLEAN_FLAGS = new Set([
|
|
29
|
+
'--quick', '-q',
|
|
30
|
+
'--skip-external', '--skip-services',
|
|
31
|
+
'--all',
|
|
32
|
+
'--help', '-h',
|
|
33
|
+
'--version', '-V',
|
|
34
|
+
'--yes', '-y',
|
|
35
|
+
'--non-interactive',
|
|
36
|
+
'--force',
|
|
37
|
+
'--verbose',
|
|
38
|
+
'--dry-run',
|
|
39
|
+
'--symlink',
|
|
40
|
+
'--sync',
|
|
41
|
+
'--interview',
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Remove recognized global flags (and their values) from a positional args
|
|
46
|
+
* array. Plain words that merely resemble flag names (no leading dash) are
|
|
47
|
+
* always kept.
|
|
48
|
+
*
|
|
49
|
+
* @param {string[]} args - Raw command arguments.
|
|
50
|
+
* @returns {string[]} Arguments with global flags stripped.
|
|
51
|
+
*/
|
|
52
|
+
function stripGlobalFlags(args) {
|
|
53
|
+
const kept = [];
|
|
54
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
55
|
+
const arg = args[index];
|
|
56
|
+
if (GLOBAL_BOOLEAN_FLAGS.has(arg)) continue;
|
|
57
|
+
if (GLOBAL_VALUE_FLAG_PREFIXES.some((prefix) => arg.startsWith(prefix))) continue;
|
|
58
|
+
if (GLOBAL_VALUE_FLAGS.has(arg)) {
|
|
59
|
+
const next = args[index + 1];
|
|
60
|
+
// Mirror parsePathFlag() in bin/forge.js: the value is consumed only
|
|
61
|
+
// when it exists and is not itself a flag.
|
|
62
|
+
if (next !== undefined && !next.startsWith('-')) index += 1;
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
kept.push(arg);
|
|
66
|
+
}
|
|
67
|
+
return kept;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = {
|
|
71
|
+
GLOBAL_BOOLEAN_FLAGS,
|
|
72
|
+
GLOBAL_VALUE_FLAGS,
|
|
73
|
+
stripGlobalFlags,
|
|
74
|
+
};
|
package/lib/greptile-match.js
CHANGED
|
@@ -1,80 +1,24 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const {
|
|
3
|
+
const { GreptileReviewAdapter } = require('./adapters/greptile-review-adapter');
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Match unresolved review threads to recent commits that touched the same files.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* Uses execFileSync (not exec) to prevent shell injection (OWASP A03).
|
|
8
|
+
* This backward-compatible export now delegates to the Greptile review adapter
|
|
9
|
+
* so existing Greptile scripts keep working while new review integrations use
|
|
10
|
+
* the ReviewAdapter SPI.
|
|
13
11
|
*
|
|
14
12
|
* @param {Array<{file: string, line: number}>} threads - Review threads with file paths
|
|
15
13
|
* @param {string} projectRoot - Absolute path to the project root (used with git -C)
|
|
16
14
|
* @param {object} [opts] - Options
|
|
17
|
-
* @param {Function} [opts._exec] - Injected exec function for testing
|
|
15
|
+
* @param {Function} [opts._exec] - Injected exec function for testing
|
|
18
16
|
* @param {string} [opts.sinceCommit] - Merge-base SHA to restrict log to PR commits only
|
|
19
17
|
* @returns {Array<{file: string, line: number, resolved: boolean, sha?: string, reason?: string}>}
|
|
20
18
|
*/
|
|
21
19
|
function matchThreadsToCommits(threads, projectRoot, opts = {}) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const exec = opts._exec || ((cmd, args) => {
|
|
27
|
-
return execFileSync(cmd, args, { encoding: 'utf8' });
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// Determine commit range — restrict to PR commits when sinceCommit provided
|
|
31
|
-
let sinceCommit = opts.sinceCommit;
|
|
32
|
-
if (!sinceCommit) {
|
|
33
|
-
try {
|
|
34
|
-
sinceCommit = exec('git', ['-C', projectRoot, 'merge-base', 'HEAD', 'main']).trim();
|
|
35
|
-
} catch (_e) { // NOSONAR S2486
|
|
36
|
-
/* intentional: main branch not found, try master instead */
|
|
37
|
-
try {
|
|
38
|
-
sinceCommit = exec('git', ['-C', projectRoot, 'merge-base', 'HEAD', 'master']).trim();
|
|
39
|
-
} catch (_e2) { /* intentional: no merge-base available, fall back to unbounded log */ // NOSONAR S2486
|
|
40
|
-
sinceCommit = null;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return threads.map((thread) => {
|
|
46
|
-
const { file, line } = thread;
|
|
47
|
-
|
|
48
|
-
try {
|
|
49
|
-
const logArgs = [
|
|
50
|
-
'-C', projectRoot,
|
|
51
|
-
'log',
|
|
52
|
-
'--oneline',
|
|
53
|
-
'--follow',
|
|
54
|
-
];
|
|
55
|
-
if (sinceCommit) {
|
|
56
|
-
logArgs.push(`${sinceCommit}..HEAD`);
|
|
57
|
-
}
|
|
58
|
-
logArgs.push('--', file);
|
|
59
|
-
|
|
60
|
-
const output = exec('git', logArgs);
|
|
61
|
-
|
|
62
|
-
const trimmed = (output || '').trim();
|
|
63
|
-
|
|
64
|
-
if (!trimmed) {
|
|
65
|
-
return { file, line, resolved: false, reason: 'no matching commit' };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// git log --oneline format: "<sha> <message>"
|
|
69
|
-
// Take the first (most recent) line
|
|
70
|
-
const firstLine = trimmed.split('\n')[0];
|
|
71
|
-
const sha = firstLine.split(' ')[0];
|
|
72
|
-
|
|
73
|
-
return { file, line, resolved: true, sha };
|
|
74
|
-
} catch (_err) { /* intentional: git log failed (file missing or git unavailable), mark unresolved */ // NOSONAR S2486
|
|
75
|
-
return { file, line, resolved: false, reason: 'no matching commit' };
|
|
76
|
-
}
|
|
77
|
-
});
|
|
20
|
+
const adapter = new GreptileReviewAdapter();
|
|
21
|
+
return adapter.score(threads, { ...opts, projectRoot });
|
|
78
22
|
}
|
|
79
23
|
|
|
80
24
|
module.exports = { matchThreadsToCommits };
|