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,148 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Config loader for GitHub-Beads issue sync.
|
|
3
|
-
* @module scripts/github-beads-sync/config
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync } from 'node:fs';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Default configuration for GitHub-Beads sync.
|
|
10
|
-
* @type {Readonly<SyncConfig>}
|
|
11
|
-
*/
|
|
12
|
-
export const DEFAULT_CONFIG = Object.freeze({
|
|
13
|
-
labelToType: Object.freeze({
|
|
14
|
-
bug: 'bug',
|
|
15
|
-
enhancement: 'feature',
|
|
16
|
-
documentation: 'task',
|
|
17
|
-
question: 'task',
|
|
18
|
-
}),
|
|
19
|
-
labelToPriority: Object.freeze({
|
|
20
|
-
P0: 0, critical: 0,
|
|
21
|
-
P1: 1, high: 1,
|
|
22
|
-
P2: 2, medium: 2,
|
|
23
|
-
P3: 3, low: 3,
|
|
24
|
-
P4: 4, backlog: 4,
|
|
25
|
-
}),
|
|
26
|
-
defaultType: 'task',
|
|
27
|
-
defaultPriority: 2,
|
|
28
|
-
mapAssignee: true,
|
|
29
|
-
publicRepoGate: 'none',
|
|
30
|
-
gateLabelName: 'beads-track',
|
|
31
|
-
gateAssociations: Object.freeze(['MEMBER', 'COLLABORATOR', 'OWNER']),
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Deep-merge source into target (one level of nesting).
|
|
36
|
-
* Arrays and scalars from source overwrite target.
|
|
37
|
-
* Plain objects are merged key-by-key.
|
|
38
|
-
* @param {object} target
|
|
39
|
-
* @param {object} source
|
|
40
|
-
* @returns {object} Merged result (new object)
|
|
41
|
-
*/
|
|
42
|
-
function deepMerge(target, source) {
|
|
43
|
-
const result = { ...target };
|
|
44
|
-
for (const key of Object.keys(source)) {
|
|
45
|
-
const srcVal = source[key];
|
|
46
|
-
const tgtVal = target[key];
|
|
47
|
-
if (
|
|
48
|
-
srcVal !== null &&
|
|
49
|
-
typeof srcVal === 'object' &&
|
|
50
|
-
!Array.isArray(srcVal) &&
|
|
51
|
-
tgtVal !== null &&
|
|
52
|
-
typeof tgtVal === 'object' &&
|
|
53
|
-
!Array.isArray(tgtVal)
|
|
54
|
-
) {
|
|
55
|
-
result[key] = { ...tgtVal, ...srcVal };
|
|
56
|
-
} else {
|
|
57
|
-
result[key] = srcVal;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Clone DEFAULT_CONFIG into a mutable plain object.
|
|
65
|
-
* @returns {object}
|
|
66
|
-
*/
|
|
67
|
-
function cloneDefaults() {
|
|
68
|
-
return {
|
|
69
|
-
...DEFAULT_CONFIG,
|
|
70
|
-
labelToType: { ...DEFAULT_CONFIG.labelToType },
|
|
71
|
-
labelToPriority: { ...DEFAULT_CONFIG.labelToPriority },
|
|
72
|
-
gateAssociations: [...DEFAULT_CONFIG.gateAssociations],
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Load and validate sync configuration.
|
|
78
|
-
* - No arguments: returns a copy of DEFAULT_CONFIG.
|
|
79
|
-
* - With path: reads JSON file and deep-merges with defaults (user overrides win).
|
|
80
|
-
* - Missing file: returns defaults (no throw).
|
|
81
|
-
* - Invalid JSON: throws with helpful message.
|
|
82
|
-
*
|
|
83
|
-
* @param {string} [configPath] - Path to a JSON config file.
|
|
84
|
-
* @returns {object} Merged configuration object.
|
|
85
|
-
*/
|
|
86
|
-
export function loadConfig(configPath) {
|
|
87
|
-
if (!configPath) {
|
|
88
|
-
return cloneDefaults();
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
let raw;
|
|
92
|
-
try {
|
|
93
|
-
raw = readFileSync(configPath, 'utf-8');
|
|
94
|
-
} catch {
|
|
95
|
-
// File not found or unreadable — return defaults
|
|
96
|
-
return cloneDefaults();
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
let userConfig;
|
|
100
|
-
try {
|
|
101
|
-
userConfig = JSON.parse(raw);
|
|
102
|
-
} catch (parseErr) {
|
|
103
|
-
throw new Error(
|
|
104
|
-
`Invalid JSON in config file: ${configPath}\n` +
|
|
105
|
-
`Parse error: ${parseErr.message}`,
|
|
106
|
-
{ cause: parseErr }
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const merged = deepMerge(cloneDefaults(), userConfig);
|
|
111
|
-
return validateConfig(merged);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Validate and normalize config values.
|
|
116
|
-
* Replaces invalid types with defaults rather than throwing.
|
|
117
|
-
* @param {object} cfg
|
|
118
|
-
* @returns {object} Validated config
|
|
119
|
-
*/
|
|
120
|
-
function validateConfig(cfg) {
|
|
121
|
-
const defaults = cloneDefaults();
|
|
122
|
-
if (!cfg.labelToType || typeof cfg.labelToType !== 'object' || Array.isArray(cfg.labelToType)) {
|
|
123
|
-
cfg.labelToType = defaults.labelToType;
|
|
124
|
-
}
|
|
125
|
-
if (!cfg.labelToPriority || typeof cfg.labelToPriority !== 'object' || Array.isArray(cfg.labelToPriority)) {
|
|
126
|
-
cfg.labelToPriority = defaults.labelToPriority;
|
|
127
|
-
}
|
|
128
|
-
if (typeof cfg.defaultPriority !== 'number') {
|
|
129
|
-
cfg.defaultPriority = defaults.defaultPriority;
|
|
130
|
-
}
|
|
131
|
-
if (typeof cfg.mapAssignee !== 'boolean') {
|
|
132
|
-
cfg.mapAssignee = defaults.mapAssignee;
|
|
133
|
-
}
|
|
134
|
-
if (!Array.isArray(cfg.gateAssociations)) {
|
|
135
|
-
cfg.gateAssociations = defaults.gateAssociations;
|
|
136
|
-
}
|
|
137
|
-
const VALID_GATE_VALUES = ['none', 'author_association', 'label'];
|
|
138
|
-
if (!VALID_GATE_VALUES.includes(cfg.publicRepoGate)) {
|
|
139
|
-
cfg.publicRepoGate = defaults.publicRepoGate;
|
|
140
|
-
}
|
|
141
|
-
if (typeof cfg.defaultType !== 'string' || !cfg.defaultType) {
|
|
142
|
-
cfg.defaultType = defaults.defaultType;
|
|
143
|
-
}
|
|
144
|
-
if (typeof cfg.gateLabelName !== 'string' || !cfg.gateLabelName) {
|
|
145
|
-
cfg.gateLabelName = defaults.gateLabelName;
|
|
146
|
-
}
|
|
147
|
-
return cfg;
|
|
148
|
-
}
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* GitHub API caller module using `gh api` CLI.
|
|
3
|
-
* Builds argument arrays for execFileSync('gh', args) — no shell invocation.
|
|
4
|
-
* @module github-api
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { execFileSync } from 'node:child_process';
|
|
8
|
-
|
|
9
|
-
const SYNC_MARKER = '<!-- beads-sync:';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Builds args for listing all comments on a GitHub issue.
|
|
13
|
-
* @param {string} owner - Repository owner
|
|
14
|
-
* @param {string} repo - Repository name
|
|
15
|
-
* @param {number|string} issueNumber - Issue number
|
|
16
|
-
* @returns {string[]} Args array for execFileSync('gh', args)
|
|
17
|
-
*/
|
|
18
|
-
export function buildFindCommentsArgs(owner, repo, issueNumber) {
|
|
19
|
-
return [
|
|
20
|
-
'api',
|
|
21
|
-
`repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
|
22
|
-
];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Finds the beads-sync comment in a list of comment objects.
|
|
27
|
-
* @param {Array<{id: number, body: string}>} comments - Array of GitHub comment objects
|
|
28
|
-
* @returns {{id: number, body: string}|null} The sync comment or null
|
|
29
|
-
*/
|
|
30
|
-
export function parseFindSyncComment(comments) {
|
|
31
|
-
const match = comments.find((c) => c.body && c.body.includes(SYNC_MARKER));
|
|
32
|
-
if (!match) return null;
|
|
33
|
-
return { id: match.id, body: match.body };
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Builds args for creating a comment on a GitHub issue.
|
|
38
|
-
* @param {string} owner - Repository owner
|
|
39
|
-
* @param {string} repo - Repository name
|
|
40
|
-
* @param {number|string} issueNumber - Issue number
|
|
41
|
-
* @param {string} body - Comment body
|
|
42
|
-
* @returns {string[]} Args array for execFileSync('gh', args)
|
|
43
|
-
*/
|
|
44
|
-
export function buildCreateCommentArgs(owner, repo, issueNumber, body) {
|
|
45
|
-
return [
|
|
46
|
-
'api',
|
|
47
|
-
`repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
|
48
|
-
'-f',
|
|
49
|
-
`body=${body}`,
|
|
50
|
-
];
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Builds args for editing an existing comment.
|
|
55
|
-
* @param {string} owner - Repository owner
|
|
56
|
-
* @param {string} repo - Repository name
|
|
57
|
-
* @param {number|string} commentId - Comment ID
|
|
58
|
-
* @param {string} body - Updated comment body
|
|
59
|
-
* @returns {string[]} Args array for execFileSync('gh', args)
|
|
60
|
-
*/
|
|
61
|
-
export function buildEditCommentArgs(owner, repo, commentId, body) {
|
|
62
|
-
return [
|
|
63
|
-
'api',
|
|
64
|
-
`repos/${owner}/${repo}/issues/comments/${commentId}`,
|
|
65
|
-
'-X',
|
|
66
|
-
'PATCH',
|
|
67
|
-
'-f',
|
|
68
|
-
`body=${body}`,
|
|
69
|
-
];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Builds args for closing a GitHub issue.
|
|
74
|
-
* @param {string} owner - Repository owner
|
|
75
|
-
* @param {string} repo - Repository name
|
|
76
|
-
* @param {number|string} issueNumber - Issue number
|
|
77
|
-
* @returns {string[]} Args array for execFileSync('gh', args)
|
|
78
|
-
*/
|
|
79
|
-
export function buildCloseIssueArgs(owner, repo, issueNumber) {
|
|
80
|
-
return [
|
|
81
|
-
'api',
|
|
82
|
-
`repos/${owner}/${repo}/issues/${issueNumber}`,
|
|
83
|
-
'-X',
|
|
84
|
-
'PATCH',
|
|
85
|
-
'-f',
|
|
86
|
-
'state=closed',
|
|
87
|
-
];
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* Finds the existing beads-sync comment on an issue, or returns null.
|
|
92
|
-
* @param {string} owner - Repository owner
|
|
93
|
-
* @param {string} repo - Repository name
|
|
94
|
-
* @param {number|string} issueNumber - Issue number
|
|
95
|
-
* @returns {{id: number, body: string}|null}
|
|
96
|
-
*/
|
|
97
|
-
export function findSyncComment(owner, repo, issueNumber) {
|
|
98
|
-
const args = buildFindCommentsArgs(owner, repo, issueNumber);
|
|
99
|
-
const raw = execFileSync('gh', args, { encoding: 'utf-8' });
|
|
100
|
-
const comments = JSON.parse(raw);
|
|
101
|
-
return parseFindSyncComment(comments);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Creates or edits the beads-sync comment on an issue.
|
|
106
|
-
* If a sync comment already exists, edits it; otherwise creates a new one.
|
|
107
|
-
* @param {string} owner - Repository owner
|
|
108
|
-
* @param {string} repo - Repository name
|
|
109
|
-
* @param {number|string} issueNumber - Issue number
|
|
110
|
-
* @param {string} body - Comment body (should contain the beads-sync marker)
|
|
111
|
-
* @returns {void}
|
|
112
|
-
*/
|
|
113
|
-
export function createOrEditComment(owner, repo, issueNumber, body) {
|
|
114
|
-
const existing = findSyncComment(owner, repo, issueNumber);
|
|
115
|
-
const args = existing
|
|
116
|
-
? buildEditCommentArgs(owner, repo, existing.id, body)
|
|
117
|
-
: buildCreateCommentArgs(owner, repo, issueNumber, body);
|
|
118
|
-
execFileSync('gh', args, { encoding: 'utf-8' });
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Closes a GitHub issue.
|
|
123
|
-
* @param {string} owner - Repository owner
|
|
124
|
-
* @param {string} repo - Repository name
|
|
125
|
-
* @param {number|string} issueNumber - Issue number
|
|
126
|
-
* @returns {void}
|
|
127
|
-
*/
|
|
128
|
-
export function closeIssue(owner, repo, issueNumber) {
|
|
129
|
-
const args = buildCloseIssueArgs(owner, repo, issueNumber);
|
|
130
|
-
execFileSync('gh', args, { encoding: 'utf-8' });
|
|
131
|
-
}
|
|
@@ -1,356 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main entry point for GitHub-Beads issue sync.
|
|
3
|
-
* Orchestrates all Wave 1 modules to handle GitHub webhook events.
|
|
4
|
-
*
|
|
5
|
-
* @module scripts/github-beads-sync/index
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { readFileSync, appendFileSync } from 'node:fs';
|
|
9
|
-
import { fileURLToPath } from 'node:url';
|
|
10
|
-
import { loadConfig } from './config.mjs';
|
|
11
|
-
import { sanitizeTitle } from './sanitize.mjs';
|
|
12
|
-
import { mapLabels } from './label-mapper.mjs';
|
|
13
|
-
import { resolveCanonicalBeadsLink, upsertCanonicalBeadsLink } from './mapping.mjs';
|
|
14
|
-
import { bdCreate as realBdCreate, bdClose as realBdClose, bdShow as realBdShow } from './run-bd.mjs';
|
|
15
|
-
import { buildComment, parseComment } from './comment.mjs';
|
|
16
|
-
import { createOrEditComment as realCreateOrEditComment, findSyncComment as realFindSyncComment } from './github-api.mjs';
|
|
17
|
-
|
|
18
|
-
const DEFAULT_MAPPING_PATH = '.github/beads-mapping.json';
|
|
19
|
-
|
|
20
|
-
function isBot(login) {
|
|
21
|
-
if (!login) return false;
|
|
22
|
-
return login.includes('[bot]') || login === 'github-actions';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function hasSkipLabel(labels) {
|
|
26
|
-
if (!labels) return false;
|
|
27
|
-
return labels.some((l) => {
|
|
28
|
-
const name = typeof l === 'string' ? l : l.name;
|
|
29
|
-
return name === 'skip-beads-sync';
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function getGitHubLink(issue = {}) {
|
|
34
|
-
return {
|
|
35
|
-
nodeId: issue.node_id ?? issue.nodeId ?? null,
|
|
36
|
-
number: issue.number ?? null,
|
|
37
|
-
url: issue.html_url ?? null,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function createCanonicalLinkStore(mappingPath) {
|
|
42
|
-
return {
|
|
43
|
-
resolveCanonicalLink(lookup) {
|
|
44
|
-
return resolveCanonicalBeadsLink(mappingPath, lookup);
|
|
45
|
-
},
|
|
46
|
-
upsertCanonicalLink(link) {
|
|
47
|
-
return upsertCanonicalBeadsLink(mappingPath, link);
|
|
48
|
-
},
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function getCanonicalLinkStore(options = {}) {
|
|
53
|
-
const defaultStore = createCanonicalLinkStore(options.mappingPath ?? DEFAULT_MAPPING_PATH);
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
options.linkStore &&
|
|
57
|
-
typeof options.linkStore.resolveCanonicalLink === 'function' &&
|
|
58
|
-
typeof options.linkStore.upsertCanonicalLink === 'function'
|
|
59
|
-
) {
|
|
60
|
-
return options.linkStore;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return defaultStore;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Handle a GitHub issue "opened" event.
|
|
68
|
-
*
|
|
69
|
-
* @param {object} event - Parsed GitHub webhook payload
|
|
70
|
-
* @param {object} options
|
|
71
|
-
* @param {string} [options.configPath] - Path to config JSON
|
|
72
|
-
* @param {string} options.mappingPath - Path to mapping JSON
|
|
73
|
-
* @param {string} options.owner - Repository owner
|
|
74
|
-
* @param {string} options.repo - Repository name
|
|
75
|
-
* @param {object} [options.bd] - Dependency injection for bd functions
|
|
76
|
-
* @param {object} [options.github] - Dependency injection for github-api functions
|
|
77
|
-
* @param {object} [options.linkStore] - Dependency injection for canonical link store
|
|
78
|
-
* @param {object} [options.configOverride] - Merge into loaded config (for testing)
|
|
79
|
-
* @returns {object} Result object
|
|
80
|
-
*/
|
|
81
|
-
export function handleOpened(event, options = {}) {
|
|
82
|
-
const {
|
|
83
|
-
configPath,
|
|
84
|
-
owner,
|
|
85
|
-
repo,
|
|
86
|
-
bd = {},
|
|
87
|
-
github = {},
|
|
88
|
-
configOverride,
|
|
89
|
-
} = options;
|
|
90
|
-
|
|
91
|
-
const bdCreate = bd.bdCreate ?? realBdCreate;
|
|
92
|
-
const createOrEditComment = github.createOrEditComment ?? realCreateOrEditComment;
|
|
93
|
-
const findSyncComment = github.findSyncComment ?? realFindSyncComment;
|
|
94
|
-
const canonicalLinkStore = getCanonicalLinkStore(options);
|
|
95
|
-
|
|
96
|
-
let config = loadConfig(configPath);
|
|
97
|
-
if (configOverride) {
|
|
98
|
-
config = { ...config, ...configOverride };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
const issue = event.issue;
|
|
102
|
-
const issueNumber = issue.number;
|
|
103
|
-
const rawTitle = issue.title;
|
|
104
|
-
const labels = issue.labels || [];
|
|
105
|
-
const assignee = config.mapAssignee ? issue.assignee?.login : undefined;
|
|
106
|
-
const htmlUrl = issue.html_url;
|
|
107
|
-
const body = issue.body ?? '';
|
|
108
|
-
const authorAssociation = issue.author_association;
|
|
109
|
-
const rawLabelNames = labels.map((l) => (typeof l === 'string' ? l : l.name));
|
|
110
|
-
const { type, priority } = mapLabels(rawLabelNames, config);
|
|
111
|
-
const externalRef = `gh-${issueNumber}`;
|
|
112
|
-
|
|
113
|
-
if (isBot(event.sender?.login)) {
|
|
114
|
-
return { skipped: true, reason: 'bot actor' };
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (hasSkipLabel(labels)) {
|
|
118
|
-
return { skipped: true, reason: 'skip label' };
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
if (body.includes('no-beads')) {
|
|
122
|
-
return { skipped: true, reason: 'no-beads in body' };
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (config.publicRepoGate === 'author_association') {
|
|
126
|
-
const allowed = config.gateAssociations || [];
|
|
127
|
-
if (!allowed.includes(authorAssociation)) {
|
|
128
|
-
return { skipped: true, reason: 'author not authorized' };
|
|
129
|
-
}
|
|
130
|
-
} else if (config.publicRepoGate === 'label') {
|
|
131
|
-
const gateLabelName = config.gateLabelName || 'beads-track';
|
|
132
|
-
const hasGateLabel = labels.some((l) => {
|
|
133
|
-
const name = typeof l === 'string' ? l : l.name;
|
|
134
|
-
return name === gateLabelName;
|
|
135
|
-
});
|
|
136
|
-
if (!hasGateLabel) {
|
|
137
|
-
return { skipped: true, reason: `missing required label: ${gateLabelName}` };
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const canonicalLink = canonicalLinkStore.resolveCanonicalLink({
|
|
142
|
-
githubNodeId: issue.node_id ?? issue.nodeId ?? null,
|
|
143
|
-
githubNumber: issue.number,
|
|
144
|
-
});
|
|
145
|
-
if (canonicalLink) {
|
|
146
|
-
const commentBody = buildComment(canonicalLink.forgeIssueId, issueNumber, {
|
|
147
|
-
type,
|
|
148
|
-
priority,
|
|
149
|
-
externalRef,
|
|
150
|
-
});
|
|
151
|
-
createOrEditComment(owner, repo, issueNumber, commentBody);
|
|
152
|
-
return {
|
|
153
|
-
skipped: true,
|
|
154
|
-
reason: 'already synced (canonical link)',
|
|
155
|
-
beadsId: canonicalLink.forgeIssueId,
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
const existingSyncComment = parseComment(findSyncComment(owner, repo, issueNumber)?.body);
|
|
160
|
-
if (existingSyncComment?.beadsId) {
|
|
161
|
-
canonicalLinkStore.upsertCanonicalLink({
|
|
162
|
-
forgeIssueId: existingSyncComment.beadsId,
|
|
163
|
-
github: getGitHubLink(issue),
|
|
164
|
-
sources: [
|
|
165
|
-
{
|
|
166
|
-
source: 'syncComment',
|
|
167
|
-
forgeIssueId: existingSyncComment.beadsId,
|
|
168
|
-
githubNumber: issueNumber,
|
|
169
|
-
url: htmlUrl,
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
diagnostics: [],
|
|
173
|
-
});
|
|
174
|
-
const commentBody = buildComment(existingSyncComment.beadsId, issueNumber, {
|
|
175
|
-
type,
|
|
176
|
-
priority,
|
|
177
|
-
externalRef,
|
|
178
|
-
});
|
|
179
|
-
createOrEditComment(owner, repo, issueNumber, commentBody);
|
|
180
|
-
return {
|
|
181
|
-
skipped: true,
|
|
182
|
-
reason: 'already synced (sync comment)',
|
|
183
|
-
beadsId: existingSyncComment.beadsId,
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const { sanitized: sanitizedTitle, warnings: titleWarnings } = sanitizeTitle(rawTitle);
|
|
188
|
-
if (titleWarnings.length) console.warn('sanitize:', titleWarnings);
|
|
189
|
-
const beadsId = bdCreate({
|
|
190
|
-
title: sanitizedTitle,
|
|
191
|
-
type,
|
|
192
|
-
priority,
|
|
193
|
-
assignee,
|
|
194
|
-
description: htmlUrl,
|
|
195
|
-
externalRef,
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
if (!beadsId) {
|
|
199
|
-
return { success: false, reason: 'bd create failed - no beads ID returned', issueNumber };
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
canonicalLinkStore.upsertCanonicalLink({
|
|
203
|
-
forgeIssueId: beadsId,
|
|
204
|
-
github: getGitHubLink(issue),
|
|
205
|
-
sources: [
|
|
206
|
-
{
|
|
207
|
-
source: 'externalRef',
|
|
208
|
-
forgeIssueId: beadsId,
|
|
209
|
-
githubNumber: issueNumber,
|
|
210
|
-
url: htmlUrl,
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
diagnostics: [],
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
const commentBody = buildComment(beadsId, issueNumber, { type, priority, externalRef });
|
|
217
|
-
createOrEditComment(owner, repo, issueNumber, commentBody);
|
|
218
|
-
|
|
219
|
-
return { success: true, beadsId, issueNumber };
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
/**
|
|
223
|
-
* Handle a GitHub issue "closed" event.
|
|
224
|
-
*
|
|
225
|
-
* @param {object} event - Parsed GitHub webhook payload
|
|
226
|
-
* @param {object} options
|
|
227
|
-
* @param {string} options.mappingPath - Path to mapping JSON
|
|
228
|
-
* @param {string} options.owner - Repository owner
|
|
229
|
-
* @param {string} options.repo - Repository name
|
|
230
|
-
* @param {object} [options.bd] - Dependency injection for bd functions
|
|
231
|
-
* @param {object} [options.github] - Dependency injection for github-api functions
|
|
232
|
-
* @param {object} [options.linkStore] - Dependency injection for canonical link store
|
|
233
|
-
* @returns {object} Result object
|
|
234
|
-
*/
|
|
235
|
-
export function handleClosed(event, options = {}) {
|
|
236
|
-
const {
|
|
237
|
-
bd = {},
|
|
238
|
-
} = options;
|
|
239
|
-
|
|
240
|
-
const bdClose = bd.bdClose ?? realBdClose;
|
|
241
|
-
const bdShow = bd.bdShow ?? realBdShow;
|
|
242
|
-
const findSyncComment = options.github?.findSyncComment ?? realFindSyncComment;
|
|
243
|
-
const canonicalLinkStore = getCanonicalLinkStore(options);
|
|
244
|
-
|
|
245
|
-
const issue = event.issue;
|
|
246
|
-
const issueNumber = issue.number;
|
|
247
|
-
const labels = issue.labels || [];
|
|
248
|
-
const stateReason = issue.state_reason;
|
|
249
|
-
|
|
250
|
-
if (isBot(event.sender?.login)) {
|
|
251
|
-
return { skipped: true, reason: 'bot actor' };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (hasSkipLabel(labels)) {
|
|
255
|
-
return { skipped: true, reason: 'skip label' };
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
if (stateReason && stateReason !== 'completed') {
|
|
259
|
-
return { skipped: true, reason: `closed as ${stateReason}` };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const canonicalLink = canonicalLinkStore.resolveCanonicalLink({
|
|
263
|
-
githubNodeId: issue.node_id ?? issue.nodeId ?? null,
|
|
264
|
-
githubNumber: issue.number,
|
|
265
|
-
});
|
|
266
|
-
let beadsId = canonicalLink?.forgeIssueId ?? null;
|
|
267
|
-
|
|
268
|
-
if (!beadsId) {
|
|
269
|
-
const syncCommentLink = parseComment(findSyncComment(options.owner, options.repo, issueNumber)?.body);
|
|
270
|
-
if (syncCommentLink?.beadsId && syncCommentLink.issueNumber === issueNumber) {
|
|
271
|
-
canonicalLinkStore.upsertCanonicalLink({
|
|
272
|
-
forgeIssueId: syncCommentLink.beadsId,
|
|
273
|
-
github: getGitHubLink(issue),
|
|
274
|
-
sources: [
|
|
275
|
-
{
|
|
276
|
-
source: 'syncComment',
|
|
277
|
-
forgeIssueId: syncCommentLink.beadsId,
|
|
278
|
-
githubNumber: issueNumber,
|
|
279
|
-
url: issue.html_url ?? null,
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
diagnostics: [],
|
|
283
|
-
});
|
|
284
|
-
beadsId = syncCommentLink.beadsId;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
if (!beadsId) {
|
|
289
|
-
return { skipped: true, reason: 'no beads link found' };
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const status = bdShow(beadsId);
|
|
293
|
-
if (status === null) {
|
|
294
|
-
return { skipped: true, reason: 'could not determine beads status' };
|
|
295
|
-
}
|
|
296
|
-
if (status === 'closed') {
|
|
297
|
-
return { skipped: true, reason: 'already closed' };
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
bdClose(beadsId, `Closed via GitHub issue #${issueNumber}`);
|
|
301
|
-
|
|
302
|
-
return { success: true, beadsId, issueNumber };
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
306
|
-
if (process.argv[1] === __filename) {
|
|
307
|
-
const action = process.argv[2];
|
|
308
|
-
const eventPath = process.env.GITHUB_EVENT_PATH || process.argv[3];
|
|
309
|
-
|
|
310
|
-
if (!action || !eventPath) {
|
|
311
|
-
console.error('Usage: node index.mjs <opened|closed> [event-path]');
|
|
312
|
-
console.error(' GITHUB_EVENT_PATH env var is used if event-path arg is omitted.');
|
|
313
|
-
process.exit(1);
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
const event = JSON.parse(readFileSync(eventPath, 'utf-8'));
|
|
317
|
-
const [owner, repo] = (process.env.GITHUB_REPOSITORY || 'unknown/unknown').split('/');
|
|
318
|
-
|
|
319
|
-
const options = {
|
|
320
|
-
configPath: process.env.BEADS_SYNC_CONFIG || undefined,
|
|
321
|
-
mappingPath: process.env.BEADS_SYNC_MAPPING || DEFAULT_MAPPING_PATH,
|
|
322
|
-
owner,
|
|
323
|
-
repo,
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const VALID_ACTIONS = ['opened', 'closed'];
|
|
327
|
-
if (!VALID_ACTIONS.includes(action)) {
|
|
328
|
-
console.error(`Unknown action: "${action}". Expected one of: ${VALID_ACTIONS.join(', ')}`);
|
|
329
|
-
process.exit(1);
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
const handler = action === 'opened' ? handleOpened : handleClosed;
|
|
333
|
-
|
|
334
|
-
Promise.resolve(handler(event, options))
|
|
335
|
-
.then((result) => {
|
|
336
|
-
console.log(JSON.stringify(result, null, 2));
|
|
337
|
-
|
|
338
|
-
if (result.success === false) {
|
|
339
|
-
console.error(`Sync failed: ${result.reason || 'unknown'}`);
|
|
340
|
-
process.exit(1);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
const outputPath = process.env.GITHUB_OUTPUT;
|
|
344
|
-
if (outputPath) {
|
|
345
|
-
for (const [key, value] of Object.entries(result)) {
|
|
346
|
-
if (value != null) {
|
|
347
|
-
appendFileSync(outputPath, `${key}=${value}\n`);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
})
|
|
352
|
-
.catch((err) => {
|
|
353
|
-
console.error('Fatal:', err.message);
|
|
354
|
-
process.exit(1);
|
|
355
|
-
});
|
|
356
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Maps GitHub issue labels to Beads type and priority.
|
|
3
|
-
*
|
|
4
|
-
* @param {Array<string|{name: string}>} labels - GitHub labels (strings or objects)
|
|
5
|
-
* @param {object} config - Mapping configuration
|
|
6
|
-
* @param {Record<string, string>} config.labelToType - Label name to Beads type
|
|
7
|
-
* @param {Record<string, number>} config.labelToPriority - Label name to priority number
|
|
8
|
-
* @param {string} [config.defaultType="task"] - Fallback type
|
|
9
|
-
* @param {number} [config.defaultPriority=2] - Fallback priority
|
|
10
|
-
* @returns {{ type: string, priority: number }}
|
|
11
|
-
*/
|
|
12
|
-
export function mapLabels(labels, config) {
|
|
13
|
-
const {
|
|
14
|
-
defaultType = 'task',
|
|
15
|
-
defaultPriority = 2,
|
|
16
|
-
} = config;
|
|
17
|
-
const labelToType = (config.labelToType && typeof config.labelToType === 'object' && !Array.isArray(config.labelToType)) ? config.labelToType : {};
|
|
18
|
-
const labelToPriority = (config.labelToPriority && typeof config.labelToPriority === 'object' && !Array.isArray(config.labelToPriority)) ? config.labelToPriority : {};
|
|
19
|
-
|
|
20
|
-
// Normalize labels to lowercase strings
|
|
21
|
-
const names = labels.map((l) =>
|
|
22
|
-
(typeof l === 'string' ? l : l.name).toLowerCase()
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
// Build lowercase lookup maps
|
|
26
|
-
const typeLookup = Object.create(null);
|
|
27
|
-
for (const [key, value] of Object.entries(labelToType)) {
|
|
28
|
-
typeLookup[key.toLowerCase()] = value;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const priorityLookup = Object.create(null);
|
|
32
|
-
for (const [key, value] of Object.entries(labelToPriority)) {
|
|
33
|
-
priorityLookup[key.toLowerCase()] = value;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// First match wins
|
|
37
|
-
let type = defaultType;
|
|
38
|
-
for (const name of names) {
|
|
39
|
-
if (name in typeLookup) {
|
|
40
|
-
type = typeLookup[name];
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
let priority = defaultPriority;
|
|
46
|
-
for (const name of names) {
|
|
47
|
-
if (name in priorityLookup) {
|
|
48
|
-
priority = priorityLookup[name];
|
|
49
|
-
break;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
return { type, priority };
|
|
54
|
-
}
|