forge-workflow 0.0.10 → 0.1.0-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +150 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -118
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +461 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/forge/TOOLCHAIN.md +670 -0
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +121 -0
- package/lib/beads-sync-scaffold.js +25 -101
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +741 -77
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +17 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +0 -1
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +838 -972
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +158 -21
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +4 -1
- package/lib/commands/test.js +43 -27
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +43 -18
- package/lib/commands/worktree.js +307 -100
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +2 -2
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +35 -9
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +382 -11
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +3 -2
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +99 -497
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +241 -20
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/scoring.js +17 -3
- package/lib/status/beads-snapshot.js +45 -2
- package/lib/status/presenter.js +169 -18
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +296 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +11 -22
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +37 -35
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +81 -57
- package/scripts/beads-upgrade-smoke.sh +24 -3
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard.sh +22 -3
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +49 -84
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +50 -83
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +21 -94
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status.sh +60 -57
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +13 -6
- package/scripts/test.js +95 -12
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -89
- package/.github/workflows/github-to-beads.yml +0 -100
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -281
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-bootstrap.js +0 -225
- package/lib/beads-health-check.js +0 -188
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -584
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -356
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -132
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
|
@@ -1,17 +1,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).
|
|
@@ -87,45 +87,6 @@ _extract_issue_number_from_url() {
|
|
|
87
87
|
printf '%s' "$suffix"
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
_extract_issue_title_from_show() {
|
|
91
|
-
local input="${1:-}"
|
|
92
|
-
|
|
93
|
-
printf '%s' "$input" | node -e '
|
|
94
|
-
const fs = require("node:fs");
|
|
95
|
-
const input = fs.readFileSync(0, "utf8");
|
|
96
|
-
const lines = input.split(/\r?\n/);
|
|
97
|
-
const titledLine = lines.find((line) => /^Title:\s*/.test(line));
|
|
98
|
-
|
|
99
|
-
if (titledLine) {
|
|
100
|
-
process.stdout.write(titledLine.replace(/^Title:\s*/, "").trim());
|
|
101
|
-
process.exit(0);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const summaryLine = lines.find((line) => line.trim().length > 0);
|
|
105
|
-
if (!summaryLine) {
|
|
106
|
-
process.exit(0);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const separators = [" - ", " \u00B7 ", " \u2022 "];
|
|
110
|
-
for (const separator of separators) {
|
|
111
|
-
const separatorIndex = summaryLine.indexOf(separator);
|
|
112
|
-
if (separatorIndex === -1) {
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const candidate = summaryLine
|
|
117
|
-
.slice(separatorIndex + separator.length)
|
|
118
|
-
.replace(/\s+\[[^\]]*\]\s*$/u, "")
|
|
119
|
-
.trim();
|
|
120
|
-
|
|
121
|
-
if (candidate.length > 0) {
|
|
122
|
-
process.stdout.write(candidate);
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
'
|
|
127
|
-
}
|
|
128
|
-
|
|
129
90
|
_sync_mapping_file() {
|
|
130
91
|
local root="${TEAM_MAP_ROOT:-.}"
|
|
131
92
|
printf '%s' "${SYNC_MAPPING_FILE:-$root/.github/beads-mapping.json}"
|
|
@@ -196,41 +157,30 @@ NODE
|
|
|
196
157
|
}
|
|
197
158
|
|
|
198
159
|
# ── _get_github_issue_number ─────────────────────────────────────────────
|
|
199
|
-
# Extracts the canonical GitHub issue number from
|
|
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.
|
|
200
162
|
# Returns the number or empty string (exit 1 if not found).
|
|
201
163
|
_get_github_issue_number() {
|
|
202
164
|
local beads_id="${1:-}"
|
|
203
165
|
if [[ -z "$beads_id" ]]; then
|
|
204
|
-
_sync_error "
|
|
166
|
+
_sync_error "issue id required"
|
|
205
167
|
return 1
|
|
206
168
|
fi
|
|
207
169
|
|
|
208
|
-
local
|
|
170
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
209
171
|
local show_output
|
|
210
|
-
show_output="$("$
|
|
211
|
-
_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"
|
|
212
174
|
return 1
|
|
213
175
|
}
|
|
214
176
|
|
|
215
177
|
local issue_num
|
|
216
|
-
issue_num="$(printf '%s' "$show_output" |
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
const value = issue?.github?.number
|
|
223
|
-
?? issue?.githubNumber
|
|
224
|
-
?? issue?.issueNumber
|
|
225
|
-
?? issue?.github_issue
|
|
226
|
-
?? null;
|
|
227
|
-
if (value != null && value !== "") {
|
|
228
|
-
process.stdout.write(String(value));
|
|
229
|
-
}
|
|
230
|
-
} catch (_err) {
|
|
231
|
-
process.exitCode = 0;
|
|
232
|
-
}
|
|
233
|
-
')"
|
|
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)"
|
|
234
184
|
|
|
235
185
|
if [[ -z "$issue_num" ]]; then
|
|
236
186
|
issue_num="$(_get_issue_number_from_mapping "$beads_id" 2>/dev/null || true)"
|
|
@@ -246,16 +196,16 @@ _get_github_issue_number() {
|
|
|
246
196
|
}
|
|
247
197
|
|
|
248
198
|
# ── _get_issue_title ─────────────────────────────────────────────────────
|
|
249
|
-
# Extracts issue title from `
|
|
250
|
-
#
|
|
199
|
+
# Extracts the issue title from `issue show <id> --json`. Falls back to the
|
|
200
|
+
# issue id when the title is empty.
|
|
251
201
|
_get_issue_title() {
|
|
252
202
|
local beads_id="${1:-}"
|
|
253
|
-
local
|
|
203
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
254
204
|
local show_output
|
|
255
|
-
show_output="$("$
|
|
205
|
+
show_output="$("$forge_cmd" issue show "$beads_id" --json 2>/dev/null)" || return 1
|
|
256
206
|
|
|
257
207
|
local title
|
|
258
|
-
title="$(
|
|
208
|
+
title="$(printf '%s' "$show_output" | jq -r '(.data.issue // .data // {}) | (.title // "")' 2>/dev/null)"
|
|
259
209
|
|
|
260
210
|
if [[ -z "$title" ]]; then
|
|
261
211
|
title="$beads_id"
|
|
@@ -266,10 +216,10 @@ _get_issue_title() {
|
|
|
266
216
|
|
|
267
217
|
# ── Public API ───────────────────────────────────────────────────────────
|
|
268
218
|
|
|
269
|
-
# sync_issue_create <
|
|
270
|
-
# Creates GitHub issue from
|
|
271
|
-
# Persists both the canonical mapping file and the
|
|
272
|
-
#
|
|
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.
|
|
273
223
|
# Returns 0 on success, 1 on failure.
|
|
274
224
|
sync_issue_create() {
|
|
275
225
|
local beads_id="${1:-}"
|
|
@@ -279,9 +229,24 @@ sync_issue_create() {
|
|
|
279
229
|
fi
|
|
280
230
|
|
|
281
231
|
local gh_cmd="${GH_CMD:-gh}"
|
|
282
|
-
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
|
|
283
248
|
|
|
284
|
-
# Get title from
|
|
249
|
+
# Get title from the issue backend
|
|
285
250
|
local raw_title
|
|
286
251
|
raw_title="$(_get_issue_title "$beads_id")" || {
|
|
287
252
|
_sync_error "Failed to get title for $beads_id"
|
|
@@ -313,8 +278,8 @@ sync_issue_create() {
|
|
|
313
278
|
return 1
|
|
314
279
|
fi
|
|
315
280
|
|
|
316
|
-
if ! "$
|
|
317
|
-
_sync_error "Failed to store github_issue
|
|
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"
|
|
318
283
|
return 1
|
|
319
284
|
fi
|
|
320
285
|
|
|
@@ -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
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# cmd_workload — Main entry point for `forge team workload`
|
|
8
8
|
#
|
|
9
9
|
# Env overrides (for testing):
|
|
10
|
-
#
|
|
10
|
+
# FORGE_CMD — Path to forge binary
|
|
11
11
|
# GH_CMD — Path to gh binary
|
|
12
12
|
# WORKLOAD_NOW — Override "now" timestamp for testing (ISO 8601)
|
|
13
13
|
#
|
|
@@ -88,70 +88,46 @@ _json_escape() {
|
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
# _collect_issues — Gather all open/in_progress issues with details
|
|
91
|
-
# Outputs lines: id
|
|
91
|
+
# Outputs tab-delimited (jq @tsv) lines: id<TAB>title<TAB>status<TAB>owner<TAB>updated<TAB>depends_on
|
|
92
|
+
# @tsv escapes any tab/newline/CR inside fields, so user-controlled titles that
|
|
93
|
+
# contain '|', tabs, or newlines never shift the downstream columns.
|
|
94
|
+
#
|
|
95
|
+
# The Kernel list filter does not accept comma-joined statuses, so query each
|
|
96
|
+
# active status separately and concatenate the JSON issue arrays. Every field
|
|
97
|
+
# the workload view needs (status, assignee, updated_at, dependencies) is
|
|
98
|
+
# present in `issue list --json`, so no per-issue lookup is required.
|
|
92
99
|
_collect_issues() {
|
|
93
|
-
local
|
|
94
|
-
local
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
# Get details via bd show
|
|
125
|
-
local show_output
|
|
126
|
-
show_output="$("$bd_cmd" show "$issue_id" 2>/dev/null)" || continue
|
|
127
|
-
|
|
128
|
-
# Parse owner
|
|
129
|
-
local owner
|
|
130
|
-
owner="$(echo "$show_output" | grep -E '^Owner:' | sed 's/^Owner:[[:space:]]*//')"
|
|
131
|
-
[[ -z "$owner" ]] && owner="unassigned"
|
|
132
|
-
|
|
133
|
-
# Parse status from the header line [● Px · STATUS]
|
|
134
|
-
local status
|
|
135
|
-
status="$(echo "$show_output" | head -1 | grep -oE '(IN_PROGRESS|OPEN|BLOCKED|CLOSED)' | head -1)"
|
|
136
|
-
[[ -z "$status" ]] && status="OPEN"
|
|
137
|
-
|
|
138
|
-
# Parse title — text after "forge-xxx · " on first line
|
|
139
|
-
local title
|
|
140
|
-
title="$(echo "$show_output" | head -1 | sed -E 's/^[^·]*·[[:space:]]*//' | sed -E 's/[[:space:]]*\[.*//')"
|
|
141
|
-
|
|
142
|
-
# Parse updated timestamp
|
|
143
|
-
local updated
|
|
144
|
-
updated="$(echo "$show_output" | grep -E '^Updated:' | sed 's/^Updated:[[:space:]]*//')"
|
|
145
|
-
[[ -z "$updated" ]] && updated=""
|
|
146
|
-
|
|
147
|
-
# Parse dependencies (DEPENDS ON section)
|
|
148
|
-
local depends_on=""
|
|
149
|
-
if echo "$show_output" | grep -q "DEPENDS ON"; then
|
|
150
|
-
depends_on="$(echo "$show_output" | grep -E '^\s*→' | sed -E 's/^\s*→\s*//' | sed -E 's/:.*//' | tr '\n' ',' | sed 's/,$//')"
|
|
151
|
-
fi
|
|
152
|
-
|
|
153
|
-
echo "${issue_id}|${title}|${status}|${owner}|${updated}|${depends_on}"
|
|
154
|
-
done <<< "$list_output"
|
|
100
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
101
|
+
local open_json inprog_json issues_json
|
|
102
|
+
|
|
103
|
+
open_json="$("$forge_cmd" issue list --status=open --json 2>/dev/null)" || open_json=""
|
|
104
|
+
inprog_json="$("$forge_cmd" issue list --status=in_progress --json 2>/dev/null)" || inprog_json=""
|
|
105
|
+
|
|
106
|
+
issues_json="$(jq -n \
|
|
107
|
+
--argjson a "${open_json:-null}" \
|
|
108
|
+
--argjson b "${inprog_json:-null}" \
|
|
109
|
+
'((($a.data.issues) // []) + (($b.data.issues) // []))' 2>/dev/null)" || issues_json="[]"
|
|
110
|
+
|
|
111
|
+
# A Kernel issue's `.dependencies` lists the issues it BLOCKS (outgoing), so
|
|
112
|
+
# depends_on (the issues that block THIS one) is a reverse scan: issues within
|
|
113
|
+
# the active set whose `.dependencies` include this issue.
|
|
114
|
+
printf '%s' "$issues_json" | jq -r '
|
|
115
|
+
. as $all
|
|
116
|
+
| $all[]
|
|
117
|
+
| . as $x
|
|
118
|
+
| [ $x.id,
|
|
119
|
+
($x.title // ""),
|
|
120
|
+
(($x.status // "open") | ascii_upcase),
|
|
121
|
+
($x.assignee // $x.claimed_by // "unassigned"),
|
|
122
|
+
(($x.updated_at // "") | sub("\\.[0-9]+Z$"; "Z")),
|
|
123
|
+
( [ $all[]
|
|
124
|
+
| select( (.dependencies // [])
|
|
125
|
+
| map(if type == "object" then (.id // .to // "") else . end)
|
|
126
|
+
| any(. == $x.id) )
|
|
127
|
+
| .id ]
|
|
128
|
+
| join(",") )
|
|
129
|
+
]
|
|
130
|
+
| @tsv' | tr -d '\r'
|
|
155
131
|
}
|
|
156
132
|
|
|
157
133
|
# ── Public API ───────────────────────────────────────────────────────────
|
|
@@ -213,7 +189,7 @@ cmd_workload() {
|
|
|
213
189
|
local work_dir
|
|
214
190
|
work_dir="$(mktemp -d)"
|
|
215
191
|
|
|
216
|
-
while IFS
|
|
192
|
+
while IFS=$'\t' read -r issue_id title status owner updated depends_on; do
|
|
217
193
|
[[ -z "$issue_id" ]] && continue
|
|
218
194
|
|
|
219
195
|
# Apply developer filter
|
|
@@ -7,7 +7,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
|
7
7
|
TEST_TMP="$(mktemp -d)"
|
|
8
8
|
trap 'rm -rf "$TEST_TMP"' EXIT
|
|
9
9
|
|
|
10
|
-
# Override TEAM_MAP_ROOT so tests don't touch real .
|
|
10
|
+
# Override TEAM_MAP_ROOT so tests don't touch real .forge/
|
|
11
11
|
export TEAM_MAP_ROOT="$TEST_TMP"
|
|
12
12
|
|
|
13
13
|
# ── Create mock gh ───────────────────────────────────────────────────────
|
|
@@ -52,26 +52,32 @@ exit 1
|
|
|
52
52
|
MOCK
|
|
53
53
|
chmod +x "$mock_dir/gh-fail"
|
|
54
54
|
|
|
55
|
-
# ── Create mock
|
|
56
|
-
|
|
55
|
+
# ── Create mock forge ─────────────────────────────────────────────────────
|
|
56
|
+
# `issue show --json` carries the github_issue:<n> association as a label.
|
|
57
|
+
cat > "$mock_dir/forge" << 'MOCK'
|
|
57
58
|
#!/usr/bin/env bash
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
forge-
|
|
64
|
-
forge-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
sub="$1 $2"
|
|
60
|
+
id="$3"
|
|
61
|
+
case "$sub" in
|
|
62
|
+
"issue show")
|
|
63
|
+
case "$id" in
|
|
64
|
+
forge-assigned) labels='["github_issue:42"]' ;;
|
|
65
|
+
forge-unassigned) labels='["github_issue:43"]' ;;
|
|
66
|
+
forge-mine) labels='["github_issue:44"]' ;;
|
|
67
|
+
forge-nogithub) labels='[]' ;;
|
|
68
|
+
*) printf 'unexpected issue id: %s\n' "$id" >&2; exit 64 ;;
|
|
69
|
+
esac
|
|
70
|
+
printf '{"data":{"issue":{"id":"%s","labels":%s}}}\n' "$id" "$labels"
|
|
71
|
+
;;
|
|
72
|
+
"issue claim") printf '{"data":{"id":"%s"}}\n' "$id" ;;
|
|
73
|
+
"issue comment") printf '{"data":{"id":"%s"}}\n' "$id" ;;
|
|
74
|
+
*) printf 'unexpected forge command: %s\n' "$*" >&2; exit 64 ;;
|
|
69
75
|
esac
|
|
70
76
|
MOCK
|
|
71
|
-
chmod +x "$mock_dir/
|
|
77
|
+
chmod +x "$mock_dir/forge"
|
|
72
78
|
|
|
73
79
|
export GH_CMD="$mock_dir/gh"
|
|
74
|
-
export
|
|
80
|
+
export FORGE_CMD="$mock_dir/forge"
|
|
75
81
|
|
|
76
82
|
# Source the library under test
|
|
77
83
|
source "$SCRIPT_DIR/lib/claim.sh"
|
|
@@ -109,7 +115,7 @@ assert_exit() {
|
|
|
109
115
|
# ── Test 1: Pre-claim on unassigned issue → returns 0 ───────────────────
|
|
110
116
|
echo "── Test 1: Pre-claim on unassigned issue ──"
|
|
111
117
|
unset _GITHUB_USER_CACHE
|
|
112
|
-
mkdir -p "$TEST_TMP/.
|
|
118
|
+
mkdir -p "$TEST_TMP/.forge"
|
|
113
119
|
rc=0
|
|
114
120
|
pre_claim_check "forge-unassigned" 2>/dev/null || rc=$?
|
|
115
121
|
assert_exit "unassigned issue returns 0" 0 "$rc"
|
|
@@ -136,7 +142,7 @@ assert_exit "assigned to self returns 0" 0 "$rc"
|
|
|
136
142
|
echo ""
|
|
137
143
|
echo "── Test 4: --force overrides pre-claim check ──"
|
|
138
144
|
unset _GITHUB_USER_CACHE
|
|
139
|
-
mkdir -p "$TEST_TMP/.
|
|
145
|
+
mkdir -p "$TEST_TMP/.forge"
|
|
140
146
|
rc=0
|
|
141
147
|
output="$(forge_team_claim "forge-assigned" "--force" 2>&1)" || rc=$?
|
|
142
148
|
assert_exit "force claim succeeds" 0 "$rc"
|
|
@@ -146,7 +152,7 @@ assert_contains "confirmation output" "claimed" "$output"
|
|
|
146
152
|
echo ""
|
|
147
153
|
echo "── Test 5: Claim with lock succeeds ──"
|
|
148
154
|
unset _GITHUB_USER_CACHE
|
|
149
|
-
mkdir -p "$TEST_TMP/.
|
|
155
|
+
mkdir -p "$TEST_TMP/.forge"
|
|
150
156
|
# Use the unassigned issue so pre-claim passes
|
|
151
157
|
rc=0
|
|
152
158
|
output="$(forge_team_claim "forge-unassigned" 2>&1)" || rc=$?
|