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
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Env overrides (for testing):
|
|
10
10
|
# GH_CMD — Path to gh binary (default: gh)
|
|
11
|
-
#
|
|
12
|
-
#
|
|
11
|
+
# FORGE_CMD — Path to forge binary (default: forge)
|
|
12
|
+
# FORGE_ROOT — Path to .forge/ parent directory (default: .)
|
|
13
13
|
#
|
|
14
14
|
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
15
15
|
|
|
@@ -56,11 +56,11 @@ _hooks_warn() {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
# ── _check_auto_sync_enabled ─────────────────────────────────────────────
|
|
59
|
-
# Reads .
|
|
59
|
+
# Reads .forge/config.yaml for the team.auto-sync setting.
|
|
60
60
|
# Returns 0 if enabled (default), 1 if disabled.
|
|
61
61
|
_check_auto_sync_enabled() {
|
|
62
|
-
local
|
|
63
|
-
local config_file="$
|
|
62
|
+
local forge_root="${FORGE_ROOT:-.}"
|
|
63
|
+
local config_file="$forge_root/.forge/config.yaml"
|
|
64
64
|
|
|
65
65
|
if [[ ! -f "$config_file" ]]; then
|
|
66
66
|
# Default: enabled
|
|
@@ -108,7 +108,7 @@ forge_team_sync() {
|
|
|
108
108
|
done
|
|
109
109
|
|
|
110
110
|
local gh_cmd="${GH_CMD:-gh}"
|
|
111
|
-
local
|
|
111
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
112
112
|
|
|
113
113
|
# 1. Check gh auth status
|
|
114
114
|
if ! "$gh_cmd" auth status >/dev/null 2>&1; then
|
|
@@ -122,44 +122,42 @@ forge_team_sync() {
|
|
|
122
122
|
return 0
|
|
123
123
|
fi
|
|
124
124
|
|
|
125
|
-
# 3. Get all in_progress issues
|
|
126
|
-
local
|
|
127
|
-
|
|
125
|
+
# 3. Get all in_progress issues as JSON
|
|
126
|
+
local list_json
|
|
127
|
+
list_json="$("$forge_cmd" issue list --status=in_progress --json 2>/dev/null)" || {
|
|
128
128
|
if [[ "$quiet" == "false" ]]; then
|
|
129
129
|
_hooks_warn "Failed to list issues, sync skipped"
|
|
130
130
|
fi
|
|
131
131
|
return 0
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
#
|
|
135
|
-
|
|
134
|
+
# Build "id<TAB>has_github" lines (has_github is true when a github_issue:<n>
|
|
135
|
+
# label is present on the issue).
|
|
136
|
+
local issue_lines
|
|
137
|
+
issue_lines="$(printf '%s' "$list_json" | jq -r '
|
|
138
|
+
(.data.issues // [])[]
|
|
139
|
+
| [ .id, (((.labels // []) | any(test("^github_issue:"))) | tostring) ]
|
|
140
|
+
| join("\t")' 2>/dev/null | tr -d '\r')"
|
|
141
|
+
|
|
142
|
+
if [[ -z "$issue_lines" ]] || [[ -z "$(printf '%s' "$issue_lines" | tr -d '[:space:]')" ]]; then
|
|
136
143
|
if [[ "$quiet" == "false" ]]; then
|
|
137
144
|
_hooks_info "Nothing to sync"
|
|
138
145
|
fi
|
|
139
146
|
return 0
|
|
140
147
|
fi
|
|
141
148
|
|
|
142
|
-
# 4. For each issue
|
|
149
|
+
# 4. For each issue with a github_issue label, sync its status to GitHub
|
|
143
150
|
local synced=0
|
|
144
151
|
local warnings=0
|
|
145
152
|
|
|
146
|
-
while IFS
|
|
147
|
-
[[ -z "$line" ]] && continue
|
|
148
|
-
|
|
149
|
-
# Extract issue id
|
|
150
|
-
local issue_id
|
|
151
|
-
issue_id="$(echo "$line" | grep -oE 'forge-[a-zA-Z0-9]+(\.[a-zA-Z0-9]+)*' | head -1)"
|
|
153
|
+
while IFS=$'\t' read -r issue_id has_github; do
|
|
152
154
|
[[ -z "$issue_id" ]] && continue
|
|
153
155
|
|
|
154
|
-
#
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
local gh_issue_num
|
|
160
|
-
gh_issue_num="$(printf '%s' "$show_output" | grep -oP 'github_issue:\K[0-9]+' | head -1 || true)"
|
|
161
|
-
|
|
162
|
-
if [[ -z "$gh_issue_num" ]]; then
|
|
156
|
+
# Skip only when there is neither a github_issue:<n> label nor a legacy
|
|
157
|
+
# mapping-file association. _get_github_issue_number (from sync-github.sh)
|
|
158
|
+
# checks the label first, then the mapping-file fallback, so mapping-only
|
|
159
|
+
# linked issues are still synced instead of being skipped here.
|
|
160
|
+
if [[ "$has_github" != "true" ]] && ! _get_github_issue_number "$issue_id" >/dev/null 2>&1; then
|
|
163
161
|
# No GitHub issue linked, skip but warn
|
|
164
162
|
warnings=$((warnings + 1))
|
|
165
163
|
if [[ "$quiet" == "false" ]]; then
|
|
@@ -177,7 +175,7 @@ forge_team_sync() {
|
|
|
177
175
|
_hooks_warn "Failed to sync $issue_id to GitHub"
|
|
178
176
|
fi
|
|
179
177
|
fi
|
|
180
|
-
done <<< "$
|
|
178
|
+
done <<< "$issue_lines"
|
|
181
179
|
|
|
182
180
|
# 5. Report
|
|
183
181
|
if [[ "$quiet" == "false" ]]; then
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# scripts/forge-team/lib/identity.sh — GitHub-username-only identity mapping
|
|
3
3
|
#
|
|
4
4
|
# Auto-detects developer via `gh api user`. Stores identity in
|
|
5
|
-
# .
|
|
5
|
+
# .forge/team-map.jsonl (append-only, LWW per GitHub username).
|
|
6
6
|
#
|
|
7
7
|
# Functions:
|
|
8
8
|
# get_github_user — Current dev's GitHub username (cached)
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Env overrides:
|
|
16
16
|
# GH_CMD — Path to gh binary (for testing)
|
|
17
|
-
# TEAM_MAP_ROOT — Root dir for .
|
|
17
|
+
# TEAM_MAP_ROOT — Root dir for .forge/team-map.jsonl (default: .)
|
|
18
18
|
#
|
|
19
19
|
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
20
20
|
|
|
@@ -69,7 +69,7 @@ _identity_prompt() {
|
|
|
69
69
|
# _team_map_file — Returns the path to team-map.jsonl
|
|
70
70
|
_team_map_file() {
|
|
71
71
|
local root="${TEAM_MAP_ROOT:-.}"
|
|
72
|
-
printf '%s' "$root/.
|
|
72
|
+
printf '%s' "$root/.forge/team-map.jsonl"
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
# _validate_github_username <username>
|
|
@@ -124,7 +124,7 @@ get_github_user() {
|
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
# team_map_add <github-user> [display-name]
|
|
127
|
-
# Adds/updates entry in .
|
|
127
|
+
# Adds/updates entry in .forge/team-map.jsonl.
|
|
128
128
|
# Schema: {"github":"<user>","display_name":"<name>","updated_at":"<ts>","is_bot":false}
|
|
129
129
|
team_map_add() {
|
|
130
130
|
local github_user="${1:-}"
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# scripts/forge-team/lib/sync-github.sh — Bidirectional sync between
|
|
2
|
+
# scripts/forge-team/lib/sync-github.sh — Bidirectional sync between Forge issues and GitHub
|
|
3
3
|
#
|
|
4
4
|
# Functions:
|
|
5
|
-
# sync_issue_create <
|
|
6
|
-
# sync_issue_claim <
|
|
7
|
-
# sync_issue_status <
|
|
8
|
-
# sync_issue_close <
|
|
9
|
-
# sync_issue_deps <
|
|
10
|
-
# _get_github_issue_number <
|
|
5
|
+
# sync_issue_create <issue-id> — Create GitHub issue from a Forge issue
|
|
6
|
+
# sync_issue_claim <issue-id> — Assign current dev on GitHub
|
|
7
|
+
# sync_issue_status <issue-id> <status> — Update status label on GitHub
|
|
8
|
+
# sync_issue_close <issue-id> — Close GitHub issue
|
|
9
|
+
# sync_issue_deps <issue-id-a> <issue-id-b> — Add "Blocked by" comment
|
|
10
|
+
# _get_github_issue_number <issue-id> — Extract canonical GitHub issue from issue show
|
|
11
11
|
#
|
|
12
12
|
# Env overrides (for testing):
|
|
13
|
-
# GH_CMD
|
|
14
|
-
#
|
|
13
|
+
# GH_CMD — Path to gh binary (default: gh)
|
|
14
|
+
# FORGE_CMD — Path to forge binary (default: forge)
|
|
15
15
|
#
|
|
16
16
|
# All GitHub-sourced strings are sanitized via sanitize_for_agent() before
|
|
17
17
|
# use in shell commands (OWASP A03).
|
|
@@ -71,29 +71,123 @@ _safe_sanitize() {
|
|
|
71
71
|
printf '%s' "$input"
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
_extract_issue_number_from_url() {
|
|
75
|
+
local input="${1:-}"
|
|
76
|
+
local suffix="${input##*/issues/}"
|
|
77
|
+
|
|
78
|
+
if [[ "$suffix" == "$input" ]]; then
|
|
79
|
+
return 1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
suffix="${suffix%%[^0-9]*}"
|
|
83
|
+
if [[ -z "$suffix" ]]; then
|
|
84
|
+
return 1
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
printf '%s' "$suffix"
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_sync_mapping_file() {
|
|
91
|
+
local root="${TEAM_MAP_ROOT:-.}"
|
|
92
|
+
printf '%s' "${SYNC_MAPPING_FILE:-$root/.github/beads-mapping.json}"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
_persist_issue_mapping() {
|
|
96
|
+
local beads_id="${1:-}"
|
|
97
|
+
local issue_num="${2:-}"
|
|
98
|
+
local mapping_file
|
|
99
|
+
mapping_file="$(_sync_mapping_file)"
|
|
100
|
+
|
|
101
|
+
node - "$mapping_file" "$issue_num" "$beads_id" <<'NODE'
|
|
102
|
+
const fs = require('node:fs');
|
|
103
|
+
const path = require('node:path');
|
|
104
|
+
|
|
105
|
+
const [mappingPath, issueNum, beadsId] = process.argv.slice(2);
|
|
106
|
+
let data = {};
|
|
107
|
+
|
|
108
|
+
if (fs.existsSync(mappingPath)) {
|
|
109
|
+
const raw = fs.readFileSync(mappingPath, 'utf8').trim();
|
|
110
|
+
if (raw) {
|
|
111
|
+
data = JSON.parse(raw);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
fs.mkdirSync(path.dirname(mappingPath), { recursive: true });
|
|
116
|
+
data[String(issueNum)] = beadsId;
|
|
117
|
+
fs.writeFileSync(mappingPath, `${JSON.stringify(data, null, 2)}\n`, 'utf8');
|
|
118
|
+
NODE
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_get_issue_number_from_mapping() {
|
|
122
|
+
local beads_id="${1:-}"
|
|
123
|
+
local mapping_file
|
|
124
|
+
mapping_file="$(_sync_mapping_file)"
|
|
125
|
+
|
|
126
|
+
node - "$mapping_file" "$beads_id" <<'NODE'
|
|
127
|
+
const fs = require('node:fs');
|
|
128
|
+
|
|
129
|
+
const [mappingPath, beadsId] = process.argv.slice(2);
|
|
130
|
+
if (!fs.existsSync(mappingPath)) {
|
|
131
|
+
process.exit(0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const raw = fs.readFileSync(mappingPath, 'utf8').trim();
|
|
135
|
+
if (!raw) {
|
|
136
|
+
process.exit(0);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const data = JSON.parse(raw);
|
|
140
|
+
let newestIssueNum = null;
|
|
141
|
+
for (const [issueNum, mappedBeadsId] of Object.entries(data)) {
|
|
142
|
+
if (mappedBeadsId !== beadsId) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const parsedIssueNum = Number.parseInt(issueNum, 10);
|
|
146
|
+
if (!Number.isFinite(parsedIssueNum)) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (newestIssueNum == null || parsedIssueNum > newestIssueNum) {
|
|
150
|
+
newestIssueNum = parsedIssueNum;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (newestIssueNum != null) {
|
|
154
|
+
process.stdout.write(String(newestIssueNum));
|
|
155
|
+
}
|
|
156
|
+
NODE
|
|
157
|
+
}
|
|
158
|
+
|
|
74
159
|
# ── _get_github_issue_number ─────────────────────────────────────────────
|
|
75
|
-
# Extracts
|
|
76
|
-
#
|
|
160
|
+
# Extracts the canonical GitHub issue number from the issue's github_issue:<n>
|
|
161
|
+
# label (via `issue show <id> --json`), falling back to the local mapping file.
|
|
77
162
|
# Returns the number or empty string (exit 1 if not found).
|
|
78
163
|
_get_github_issue_number() {
|
|
79
164
|
local beads_id="${1:-}"
|
|
80
165
|
if [[ -z "$beads_id" ]]; then
|
|
81
|
-
_sync_error "
|
|
166
|
+
_sync_error "issue id required"
|
|
82
167
|
return 1
|
|
83
168
|
fi
|
|
84
169
|
|
|
85
|
-
local
|
|
170
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
86
171
|
local show_output
|
|
87
|
-
show_output="$("$
|
|
88
|
-
_sync_error "Failed to get
|
|
172
|
+
show_output="$("$forge_cmd" issue show "$beads_id" --json 2>/dev/null)" || {
|
|
173
|
+
_sync_error "Failed to get issue info for $beads_id"
|
|
89
174
|
return 1
|
|
90
175
|
}
|
|
91
176
|
|
|
92
177
|
local issue_num
|
|
93
|
-
issue_num="$(printf '%s' "$show_output" |
|
|
178
|
+
issue_num="$(printf '%s' "$show_output" | jq -r '
|
|
179
|
+
(.data.issue // .data // {})
|
|
180
|
+
| (.labels // [])
|
|
181
|
+
| map(select(startswith("github_issue:")))
|
|
182
|
+
| (.[0] // "")
|
|
183
|
+
| ltrimstr("github_issue:")' 2>/dev/null)"
|
|
94
184
|
|
|
95
185
|
if [[ -z "$issue_num" ]]; then
|
|
96
|
-
|
|
186
|
+
issue_num="$(_get_issue_number_from_mapping "$beads_id" 2>/dev/null || true)"
|
|
187
|
+
fi
|
|
188
|
+
|
|
189
|
+
if [[ -z "$issue_num" ]]; then
|
|
190
|
+
_sync_error "No canonical GitHub number found for $beads_id"
|
|
97
191
|
return 1
|
|
98
192
|
fi
|
|
99
193
|
|
|
@@ -102,22 +196,16 @@ _get_github_issue_number() {
|
|
|
102
196
|
}
|
|
103
197
|
|
|
104
198
|
# ── _get_issue_title ─────────────────────────────────────────────────────
|
|
105
|
-
# Extracts issue title from `
|
|
106
|
-
#
|
|
199
|
+
# Extracts the issue title from `issue show <id> --json`. Falls back to the
|
|
200
|
+
# issue id when the title is empty.
|
|
107
201
|
_get_issue_title() {
|
|
108
202
|
local beads_id="${1:-}"
|
|
109
|
-
local
|
|
203
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
110
204
|
local show_output
|
|
111
|
-
show_output="$("$
|
|
205
|
+
show_output="$("$forge_cmd" issue show "$beads_id" --json 2>/dev/null)" || return 1
|
|
112
206
|
|
|
113
207
|
local title
|
|
114
|
-
|
|
115
|
-
title="$(printf '%s' "$show_output" | grep -oP '^Title:\s*\K.*' | head -1)"
|
|
116
|
-
|
|
117
|
-
if [[ -z "$title" ]]; then
|
|
118
|
-
# Fallback: extract from summary line "○ <id> · <title> [...]"
|
|
119
|
-
title="$(printf '%s' "$show_output" | grep -oP '·\s*\K[^\[]+' | head -1 | sed 's/[[:space:]]*$//')"
|
|
120
|
-
fi
|
|
208
|
+
title="$(printf '%s' "$show_output" | jq -r '(.data.issue // .data // {}) | (.title // "")' 2>/dev/null)"
|
|
121
209
|
|
|
122
210
|
if [[ -z "$title" ]]; then
|
|
123
211
|
title="$beads_id"
|
|
@@ -128,9 +216,10 @@ _get_issue_title() {
|
|
|
128
216
|
|
|
129
217
|
# ── Public API ───────────────────────────────────────────────────────────
|
|
130
218
|
|
|
131
|
-
# sync_issue_create <
|
|
132
|
-
# Creates GitHub issue from
|
|
133
|
-
#
|
|
219
|
+
# sync_issue_create <issue-id>
|
|
220
|
+
# Creates a GitHub issue from the Forge issue data.
|
|
221
|
+
# Persists both the canonical mapping file and the github_issue:<n> label so
|
|
222
|
+
# that downstream hook and verify flows can resolve the GitHub association.
|
|
134
223
|
# Returns 0 on success, 1 on failure.
|
|
135
224
|
sync_issue_create() {
|
|
136
225
|
local beads_id="${1:-}"
|
|
@@ -140,9 +229,24 @@ sync_issue_create() {
|
|
|
140
229
|
fi
|
|
141
230
|
|
|
142
231
|
local gh_cmd="${GH_CMD:-gh}"
|
|
143
|
-
local
|
|
232
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
233
|
+
|
|
234
|
+
# Retry-safety / idempotency: the mapping file is persisted *before* the
|
|
235
|
+
# github_issue:<n> label is stored, so a previous run that created the GitHub
|
|
236
|
+
# issue and mapping but failed on the label leaves a recoverable mapping entry.
|
|
237
|
+
# On retry, repair the missing label from the mapping instead of calling
|
|
238
|
+
# `gh issue create` again and creating a duplicate GitHub issue.
|
|
239
|
+
local existing_num
|
|
240
|
+
existing_num="$(_get_issue_number_from_mapping "$beads_id" 2>/dev/null || true)"
|
|
241
|
+
if [[ -n "$existing_num" ]]; then
|
|
242
|
+
if ! "$forge_cmd" issue update "$beads_id" --label "github_issue:$existing_num" >/dev/null 2>&1; then
|
|
243
|
+
_sync_error "Failed to store github_issue:$existing_num for $beads_id"
|
|
244
|
+
return 1
|
|
245
|
+
fi
|
|
246
|
+
return 0
|
|
247
|
+
fi
|
|
144
248
|
|
|
145
|
-
# Get title from
|
|
249
|
+
# Get title from the issue backend
|
|
146
250
|
local raw_title
|
|
147
251
|
raw_title="$(_get_issue_title "$beads_id")" || {
|
|
148
252
|
_sync_error "Failed to get title for $beads_id"
|
|
@@ -162,28 +266,21 @@ sync_issue_create() {
|
|
|
162
266
|
|
|
163
267
|
# Extract issue number from URL (e.g., https://github.com/.../issues/42)
|
|
164
268
|
local issue_num
|
|
165
|
-
issue_num="$(
|
|
269
|
+
issue_num="$(_extract_issue_number_from_url "$gh_output")"
|
|
166
270
|
|
|
167
271
|
if [[ -z "$issue_num" ]]; then
|
|
168
272
|
_sync_error "Could not extract issue number from: $gh_output"
|
|
169
273
|
return 1
|
|
170
274
|
fi
|
|
171
275
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
_sync_error "Failed to store github_issue=$issue_num for $beads_id"
|
|
276
|
+
if ! _persist_issue_mapping "$beads_id" "$issue_num"; then
|
|
277
|
+
_sync_error "Failed to persist GitHub link for $beads_id"
|
|
175
278
|
return 1
|
|
176
|
-
|
|
279
|
+
fi
|
|
177
280
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
local updated
|
|
182
|
-
updated="$(jq --arg num "$issue_num" --arg id "$beads_id" '.[$num] = $id' "$mapping_file")"
|
|
183
|
-
printf '%s\n' "$updated" > "$mapping_file"
|
|
184
|
-
else
|
|
185
|
-
mkdir -p .github
|
|
186
|
-
printf '%s\n' "{\"$issue_num\":\"$beads_id\"}" > "$mapping_file"
|
|
281
|
+
if ! "$forge_cmd" issue update "$beads_id" --label "github_issue:$issue_num" >/dev/null 2>&1; then
|
|
282
|
+
_sync_error "Failed to store github_issue:$issue_num for $beads_id"
|
|
283
|
+
return 1
|
|
187
284
|
fi
|
|
188
285
|
|
|
189
286
|
return 0
|
|
@@ -7,15 +7,15 @@
|
|
|
7
7
|
# Checks:
|
|
8
8
|
# 1. gh auth status (GitHub CLI authenticated)
|
|
9
9
|
# 2. Identity mapped in team-map.jsonl
|
|
10
|
-
# 3. Orphan
|
|
11
|
-
# 4. Orphan GitHub issues (
|
|
12
|
-
# 5. Assignee consistency between
|
|
10
|
+
# 3. Orphan tracked issues (no GitHub counterpart)
|
|
11
|
+
# 4. Orphan GitHub issues (no tracked counterpart)
|
|
12
|
+
# 5. Assignee consistency between tracked issues and GitHub
|
|
13
13
|
#
|
|
14
14
|
# Env overrides (for testing):
|
|
15
15
|
# GH_CMD — Path to gh binary (default: gh)
|
|
16
|
-
#
|
|
17
|
-
# TEAM_MAP_ROOT — Root dir for .
|
|
18
|
-
# VERIFY_MAPPING_FILE — Path to
|
|
16
|
+
# FORGE_CMD — Path to forge binary (default: forge)
|
|
17
|
+
# TEAM_MAP_ROOT — Root dir for .forge/ and .github/ (default: .)
|
|
18
|
+
# VERIFY_MAPPING_FILE — Path to the GitHub issue mapping (default: $TEAM_MAP_ROOT/.github/beads-mapping.json)
|
|
19
19
|
#
|
|
20
20
|
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
21
21
|
|
|
@@ -66,16 +66,19 @@ _mapping_file() {
|
|
|
66
66
|
printf '%s' "${VERIFY_MAPPING_FILE:-$root/.github/beads-mapping.json}"
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
# ──
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
local
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
69
|
+
# ── _verify_active_issues_json ─────────────────────────────────────────────
|
|
70
|
+
# Merged JSON array of open + in_progress issues. The Kernel list filter does
|
|
71
|
+
# not accept comma-joined statuses, so query each status separately and
|
|
72
|
+
# concatenate the issue arrays.
|
|
73
|
+
_verify_active_issues_json() {
|
|
74
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
75
|
+
local open_json inprog_json
|
|
76
|
+
open_json="$("$forge_cmd" issue list --status=open --json 2>/dev/null)" || open_json=""
|
|
77
|
+
inprog_json="$("$forge_cmd" issue list --status=in_progress --json 2>/dev/null)" || inprog_json=""
|
|
78
|
+
jq -n \
|
|
79
|
+
--argjson a "${open_json:-null}" \
|
|
80
|
+
--argjson b "${inprog_json:-null}" \
|
|
81
|
+
'((($a.data.issues) // []) + (($b.data.issues) // []))' 2>/dev/null || echo "[]"
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
# ── Public API ───────────────────────────────────────────────────────────
|
|
@@ -84,7 +87,6 @@ _extract_beads_ids() {
|
|
|
84
87
|
# Exit 0 if all clean, exit 1 if any issues found.
|
|
85
88
|
cmd_verify() {
|
|
86
89
|
local gh_cmd="${GH_CMD:-gh}"
|
|
87
|
-
local bd_cmd="${BD_CMD:-bd}"
|
|
88
90
|
local issues_found=0
|
|
89
91
|
|
|
90
92
|
echo "forge team verify"
|
|
@@ -130,47 +132,75 @@ cmd_verify() {
|
|
|
130
132
|
issues_found=$((issues_found + 1))
|
|
131
133
|
fi
|
|
132
134
|
|
|
133
|
-
# ──
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
local
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
135
|
+
# ── Association set: labels ∪ mapping (shared by Check 3 and Check 4) ─
|
|
136
|
+
# A GitHub↔Forge association can live in either the issue's `github_issue:<n>`
|
|
137
|
+
# label OR the local mapping file. Build a single set from BOTH so Check 3
|
|
138
|
+
# (Forge→GitHub) and Check 4 (GitHub→Forge) agree: a label-only or
|
|
139
|
+
# mapping-only association must never be misreported as an orphan.
|
|
140
|
+
local mapping_file
|
|
141
|
+
mapping_file="$(_mapping_file)"
|
|
142
|
+
local mapping_content="{}"
|
|
143
|
+
[[ -f "$mapping_file" ]] && mapping_content="$(cat "$mapping_file" 2>/dev/null || echo '{}')"
|
|
144
|
+
|
|
145
|
+
local issues_json
|
|
146
|
+
issues_json="$(_verify_active_issues_json)"
|
|
147
|
+
|
|
148
|
+
local -A assoc_gh_num=() # associated GitHub numbers (mapping keys ∪ label numbers)
|
|
149
|
+
local -A assoc_beads=() # associated Forge ids (mapping values ∪ labelled ids)
|
|
150
|
+
|
|
151
|
+
# From the mapping file: keys are GitHub numbers, values are Forge ids. The
|
|
152
|
+
# value is a string today; tolerate a legacy/forward object form ({beads_id}).
|
|
153
|
+
local _m_num _m_bid
|
|
154
|
+
while IFS=$'\t' read -r _m_num _m_bid; do
|
|
155
|
+
[[ -n "$_m_num" ]] && assoc_gh_num["$_m_num"]=1
|
|
156
|
+
[[ -n "$_m_bid" && "$_m_bid" != "null" ]] && assoc_beads["$_m_bid"]=1
|
|
157
|
+
done <<< "$(printf '%s' "$mapping_content" | jq -r '
|
|
158
|
+
to_entries[]
|
|
159
|
+
| [ (.key | tostring),
|
|
160
|
+
(if (.value | type) == "object" then (.value.beads_id // "") else (.value | tostring) end) ]
|
|
161
|
+
| @tsv' 2>/dev/null | tr -d '\r')"
|
|
162
|
+
|
|
163
|
+
# From active issues: a github_issue:<n> label links the issue id to number n.
|
|
164
|
+
local _l_id _l_num
|
|
165
|
+
while IFS=$'\t' read -r _l_id _l_num; do
|
|
166
|
+
[[ -z "$_l_id" ]] && continue
|
|
167
|
+
assoc_beads["$_l_id"]=1
|
|
168
|
+
[[ -n "$_l_num" ]] && assoc_gh_num["$_l_num"]=1
|
|
169
|
+
done <<< "$(printf '%s' "$issues_json" | jq -r '
|
|
170
|
+
.[]
|
|
171
|
+
| . as $x
|
|
172
|
+
| ((($x.labels // []) | map(select(startswith("github_issue:"))) | .[0] // "") | ltrimstr("github_issue:")) as $num
|
|
173
|
+
| select($num != "")
|
|
174
|
+
| [ $x.id, $num ] | @tsv' 2>/dev/null | tr -d '\r')"
|
|
175
|
+
|
|
176
|
+
# ── Check 3: Orphan tracked issues (no GitHub counterpart) ──────────
|
|
177
|
+
local issue_total issue_synced=0
|
|
178
|
+
issue_total="$(printf '%s' "$issues_json" | jq 'length' 2>/dev/null || echo 0)"
|
|
179
|
+
|
|
180
|
+
local issue_orphans=()
|
|
181
|
+
local _aid
|
|
182
|
+
while IFS= read -r _aid; do
|
|
183
|
+
[[ -z "$_aid" ]] && continue
|
|
184
|
+
if [[ -n "${assoc_beads[$_aid]:-}" ]]; then
|
|
185
|
+
issue_synced=$((issue_synced + 1))
|
|
186
|
+
else
|
|
187
|
+
issue_orphans+=("$_aid")
|
|
188
|
+
fi
|
|
189
|
+
done <<< "$(printf '%s' "$issues_json" | jq -r '.[].id' 2>/dev/null | tr -d '\r')"
|
|
159
190
|
|
|
160
|
-
if [[ ${#
|
|
161
|
-
echo "✓
|
|
191
|
+
if [[ ${#issue_orphans[@]} -eq 0 ]]; then
|
|
192
|
+
echo "✓ Forge→GitHub: ${issue_synced}/${issue_total} issues synced"
|
|
162
193
|
else
|
|
163
|
-
echo "✗
|
|
164
|
-
for orphan in "${
|
|
194
|
+
echo "✗ Forge→GitHub: ${#issue_orphans[@]} orphan issues found"
|
|
195
|
+
for orphan in "${issue_orphans[@]}"; do
|
|
165
196
|
_verify_prompt "${orphan} has no GitHub issue. Run: forge team sync-issue ${orphan}"
|
|
166
197
|
done
|
|
167
|
-
issues_found=$((issues_found + ${#
|
|
198
|
+
issues_found=$((issues_found + ${#issue_orphans[@]}))
|
|
168
199
|
fi
|
|
169
200
|
|
|
170
|
-
# ── Check 4: Orphan GitHub issues (
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
201
|
+
# ── Check 4: Orphan GitHub issues (no tracked counterpart) ──────────
|
|
202
|
+
# Uses the same association set: a GitHub number is mapped when it appears in
|
|
203
|
+
# the mapping file OR as a github_issue:<n> label on an active issue.
|
|
174
204
|
local gh_issues_json
|
|
175
205
|
gh_issues_json="$("$gh_cmd" issue list --state open --json number,title --limit 100 2>/dev/null)" || gh_issues_json="[]"
|
|
176
206
|
|
|
@@ -178,43 +208,23 @@ cmd_verify() {
|
|
|
178
208
|
gh_issue_count="$(printf '%s' "$gh_issues_json" | jq 'length')"
|
|
179
209
|
|
|
180
210
|
local gh_orphans=()
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
local
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
local i=0
|
|
187
|
-
while [[ $i -lt $gh_issue_count ]]; do
|
|
188
|
-
local issue_num
|
|
189
|
-
issue_num="$(printf '%s' "$gh_issues_json" | jq -r ".[$i].number")"
|
|
190
|
-
|
|
191
|
-
# Check if this GitHub issue number is a key in the mapping
|
|
192
|
-
local mapped
|
|
193
|
-
mapped="$(printf '%s' "$mapping_content" | jq -r --arg n "$issue_num" '.[$n] // empty')"
|
|
194
|
-
|
|
195
|
-
if [[ -z "$mapped" ]]; then
|
|
196
|
-
gh_orphans+=("$issue_num")
|
|
197
|
-
fi
|
|
198
|
-
i=$((i + 1))
|
|
199
|
-
done
|
|
200
|
-
elif [[ "$gh_issue_count" -gt 0 ]] && [[ ! -f "$mapping_file" ]]; then
|
|
201
|
-
# No mapping file — all GitHub issues are orphans
|
|
202
|
-
local i=0
|
|
203
|
-
while [[ $i -lt $gh_issue_count ]]; do
|
|
204
|
-
local issue_num
|
|
205
|
-
issue_num="$(printf '%s' "$gh_issues_json" | jq -r ".[$i].number")"
|
|
211
|
+
local i=0
|
|
212
|
+
while [[ $i -lt $gh_issue_count ]]; do
|
|
213
|
+
local issue_num
|
|
214
|
+
issue_num="$(printf '%s' "$gh_issues_json" | jq -r ".[$i].number")"
|
|
215
|
+
if [[ -z "${assoc_gh_num[$issue_num]:-}" ]]; then
|
|
206
216
|
gh_orphans+=("$issue_num")
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
217
|
+
fi
|
|
218
|
+
i=$((i + 1))
|
|
219
|
+
done
|
|
210
220
|
|
|
211
221
|
if [[ ${#gh_orphans[@]} -eq 0 ]]; then
|
|
212
222
|
local mapped_count=$((gh_issue_count))
|
|
213
|
-
echo "✓ GitHub→
|
|
223
|
+
echo "✓ GitHub→Forge: ${mapped_count}/${mapped_count} issues mapped"
|
|
214
224
|
else
|
|
215
|
-
echo "✗ GitHub→
|
|
225
|
+
echo "✗ GitHub→Forge: ${#gh_orphans[@]} orphan issues found"
|
|
216
226
|
for orphan_num in "${gh_orphans[@]}"; do
|
|
217
|
-
_verify_prompt "GitHub #${orphan_num} has no
|
|
227
|
+
_verify_prompt "GitHub #${orphan_num} has no tracked issue. Run: forge team import #${orphan_num}"
|
|
218
228
|
done
|
|
219
229
|
issues_found=$((issues_found + ${#gh_orphans[@]}))
|
|
220
230
|
fi
|