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
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
# Forge single-binary installer for macOS and Linux (POSIX sh).
|
|
3
|
+
#
|
|
4
|
+
# One-line install:
|
|
5
|
+
# curl -fsSL https://raw.githubusercontent.com/harshanandak/forge/master/scripts/install.sh | sh
|
|
6
|
+
#
|
|
7
|
+
# Install a specific version:
|
|
8
|
+
# curl -fsSL https://raw.githubusercontent.com/harshanandak/forge/master/scripts/install.sh | sh -s -- --version v1.2.3
|
|
9
|
+
#
|
|
10
|
+
# What it does: detects your OS (darwin/linux), CPU arch (x64/arm64) and, on
|
|
11
|
+
# Linux, your libc (glibc/musl), downloads the matching binary from the GitHub
|
|
12
|
+
# Release, makes it executable, and installs it to ~/.local/bin/forge.
|
|
13
|
+
#
|
|
14
|
+
# The binary bundles Forge's own JavaScript, but NOT its external prerequisites:
|
|
15
|
+
# git, gh (GitHub CLI) and — on Windows only — Git Bash still need to be on PATH.
|
|
16
|
+
#
|
|
17
|
+
# The asset-name mapping implemented by resolve_asset() below is the canonical
|
|
18
|
+
# contract shared with scripts/lib/release-asset.mjs and is exercised by
|
|
19
|
+
# scripts/release-asset.test.js via the hidden --print-asset mode.
|
|
20
|
+
|
|
21
|
+
set -eu
|
|
22
|
+
|
|
23
|
+
REPO="${FORGE_REPO:-harshanandak/forge}"
|
|
24
|
+
INSTALL_DIR="${FORGE_INSTALL_DIR:-$HOME/.local/bin}"
|
|
25
|
+
BIN_NAME="forge"
|
|
26
|
+
VERSION="latest"
|
|
27
|
+
|
|
28
|
+
# --- tiny helpers ----------------------------------------------------------
|
|
29
|
+
err() { printf '%s\n' "forge-install: $*" >&2; }
|
|
30
|
+
die() { err "$*"; exit 1; }
|
|
31
|
+
|
|
32
|
+
# --- argument parsing ------------------------------------------------------
|
|
33
|
+
PRINT_ASSET=0
|
|
34
|
+
while [ $# -gt 0 ]; do
|
|
35
|
+
case "$1" in
|
|
36
|
+
--version)
|
|
37
|
+
[ $# -ge 2 ] || die "--version requires an argument (e.g. --version v1.2.3)"
|
|
38
|
+
VERSION="$2"; shift 2 ;;
|
|
39
|
+
--version=*)
|
|
40
|
+
VERSION="${1#--version=}"; shift ;;
|
|
41
|
+
--dir)
|
|
42
|
+
[ $# -ge 2 ] || die "--dir requires a path argument"
|
|
43
|
+
INSTALL_DIR="$2"; shift 2 ;;
|
|
44
|
+
--dir=*)
|
|
45
|
+
INSTALL_DIR="${1#--dir=}"; shift ;;
|
|
46
|
+
# Hidden: print the resolved asset name and exit (used by the test suite).
|
|
47
|
+
# Honors FORGE_OS / FORGE_ARCH / FORGE_LIBC overrides so the mapping can be
|
|
48
|
+
# unit-tested for every target without touching this host's real platform.
|
|
49
|
+
--print-asset)
|
|
50
|
+
PRINT_ASSET=1; shift ;;
|
|
51
|
+
-h|--help)
|
|
52
|
+
cat <<'EOF'
|
|
53
|
+
Usage: install.sh [--version <tag>] [--dir <path>]
|
|
54
|
+
--version <tag> Install a specific release (default: latest), e.g. v1.2.3
|
|
55
|
+
--dir <path> Install directory (default: ~/.local/bin)
|
|
56
|
+
Environment overrides: FORGE_REPO, FORGE_INSTALL_DIR, FORGE_VERSION
|
|
57
|
+
EOF
|
|
58
|
+
exit 0 ;;
|
|
59
|
+
*)
|
|
60
|
+
die "unknown argument: $1 (try --help)" ;;
|
|
61
|
+
esac
|
|
62
|
+
done
|
|
63
|
+
# Env fallback for version (arg wins over env).
|
|
64
|
+
if [ "$VERSION" = "latest" ] && [ -n "${FORGE_VERSION:-}" ]; then
|
|
65
|
+
VERSION="$FORGE_VERSION"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
# --- platform detection ----------------------------------------------------
|
|
69
|
+
detect_os() {
|
|
70
|
+
if [ -n "${FORGE_OS:-}" ]; then printf '%s' "$FORGE_OS"; return; fi
|
|
71
|
+
case "$(uname -s)" in
|
|
72
|
+
Darwin) printf 'darwin' ;;
|
|
73
|
+
Linux) printf 'linux' ;;
|
|
74
|
+
*) printf 'unsupported' ;;
|
|
75
|
+
esac
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
detect_arch() {
|
|
79
|
+
if [ -n "${FORGE_ARCH:-}" ]; then printf '%s' "$FORGE_ARCH"; return; fi
|
|
80
|
+
case "$(uname -m)" in
|
|
81
|
+
x86_64|amd64) printf 'x64' ;;
|
|
82
|
+
arm64|aarch64) printf 'arm64' ;;
|
|
83
|
+
*) printf 'unsupported' ;;
|
|
84
|
+
esac
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
# Best-effort libc detection on Linux: musl vs glibc. Order of evidence:
|
|
88
|
+
# 1) explicit override, 2) a musl loader in /lib, 3) `ldd --version` text,
|
|
89
|
+
# 4) getconf glibc version. Defaults to glibc when nothing proves musl.
|
|
90
|
+
detect_libc() {
|
|
91
|
+
if [ -n "${FORGE_LIBC:-}" ]; then printf '%s' "$FORGE_LIBC"; return; fi
|
|
92
|
+
# Non-Linux platforms have no libc variants.
|
|
93
|
+
[ "$(detect_os)" = "linux" ] || { printf 'none'; return; }
|
|
94
|
+
for f in /lib/ld-musl-* /lib/libc.musl-*; do
|
|
95
|
+
[ -e "$f" ] && { printf 'musl'; return; }
|
|
96
|
+
done
|
|
97
|
+
if ldd_out="$(ldd --version 2>&1)"; then
|
|
98
|
+
case "$ldd_out" in
|
|
99
|
+
*musl*) printf 'musl'; return ;;
|
|
100
|
+
*GNU*|*GLIBC*|*glibc*) printf 'glibc'; return ;;
|
|
101
|
+
esac
|
|
102
|
+
fi
|
|
103
|
+
if getconf GNU_LIBC_VERSION >/dev/null 2>&1; then printf 'glibc'; return; fi
|
|
104
|
+
printf 'glibc'
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
# Canonical asset-name mapping — MUST stay in sync with
|
|
108
|
+
# scripts/lib/release-asset.mjs (guarded by scripts/release-asset.test.js).
|
|
109
|
+
resolve_asset() {
|
|
110
|
+
_os="$1"; _arch="$2"; _libc="$3"
|
|
111
|
+
case "$_os" in
|
|
112
|
+
darwin|linux|windows) ;;
|
|
113
|
+
*) die "unsupported OS: '$_os' (need darwin, linux or windows)" ;;
|
|
114
|
+
esac
|
|
115
|
+
case "$_arch" in
|
|
116
|
+
x64|arm64) ;;
|
|
117
|
+
*) die "unsupported arch: '$_arch' (need x64 or arm64)" ;;
|
|
118
|
+
esac
|
|
119
|
+
# Reject os/arch pairs that are not published (parity with releaseAssetName()
|
|
120
|
+
# in scripts/lib/release-asset.mjs). Windows ships x64 only, so windows/arm64
|
|
121
|
+
# must not resolve to a phantom forge-windows-arm64.exe.
|
|
122
|
+
if [ "$_os" = "windows" ] && [ "$_arch" != "x64" ]; then
|
|
123
|
+
die "unsupported platform: windows/${_arch} is not a published target (windows is x64 only)"
|
|
124
|
+
fi
|
|
125
|
+
_name="forge-${_os}-${_arch}"
|
|
126
|
+
if [ "$_os" = "linux" ] && [ "$_libc" = "musl" ]; then
|
|
127
|
+
_name="${_name}-musl"
|
|
128
|
+
fi
|
|
129
|
+
if [ "$_os" = "windows" ]; then
|
|
130
|
+
_name="${_name}.exe"
|
|
131
|
+
fi
|
|
132
|
+
printf '%s' "$_name"
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
OS="$(detect_os)"
|
|
136
|
+
ARCH="$(detect_arch)"
|
|
137
|
+
LIBC="$(detect_libc)"
|
|
138
|
+
|
|
139
|
+
[ "$OS" != "unsupported" ] || die "unsupported operating system: $(uname -s). Supported: macOS (darwin), Linux."
|
|
140
|
+
[ "$ARCH" != "unsupported" ] || die "unsupported CPU architecture: $(uname -m). Supported: x86_64/amd64 (x64), arm64/aarch64 (arm64)."
|
|
141
|
+
|
|
142
|
+
ASSET="$(resolve_asset "$OS" "$ARCH" "$LIBC")"
|
|
143
|
+
|
|
144
|
+
if [ "$PRINT_ASSET" -eq 1 ]; then
|
|
145
|
+
printf '%s\n' "$ASSET"
|
|
146
|
+
exit 0
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# --- downloader ------------------------------------------------------------
|
|
150
|
+
if command -v curl >/dev/null 2>&1; then
|
|
151
|
+
DL='curl -fSL --retry 3 --connect-timeout 10 --max-time 300 -o'
|
|
152
|
+
DL_QUIET='curl -fsSL --connect-timeout 10 --max-time 30'
|
|
153
|
+
elif command -v wget >/dev/null 2>&1; then
|
|
154
|
+
DL='wget --timeout=300 -O'
|
|
155
|
+
DL_QUIET='wget --timeout=30 -qO-'
|
|
156
|
+
else
|
|
157
|
+
die "need either curl or wget installed to download the binary."
|
|
158
|
+
fi
|
|
159
|
+
|
|
160
|
+
download_to() { # url dest
|
|
161
|
+
# shellcheck disable=SC2086
|
|
162
|
+
if command -v curl >/dev/null 2>&1; then
|
|
163
|
+
curl -fSL --retry 3 --connect-timeout 10 --max-time 300 -o "$2" "$1"
|
|
164
|
+
else
|
|
165
|
+
wget --timeout=300 -O "$2" "$1"
|
|
166
|
+
fi
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
# --- resolve version + URL -------------------------------------------------
|
|
170
|
+
if [ "$VERSION" = "latest" ]; then
|
|
171
|
+
# The /releases/latest/download/<asset> path redirects to the newest release,
|
|
172
|
+
# so we can download without hitting the JSON API. We still try to resolve the
|
|
173
|
+
# human-readable tag for the summary line (best-effort, never fatal).
|
|
174
|
+
URL="https://github.com/${REPO}/releases/latest/download/${ASSET}"
|
|
175
|
+
RESOLVED_VERSION="$(
|
|
176
|
+
$DL_QUIET "https://api.github.com/repos/${REPO}/releases/latest" 2>/dev/null \
|
|
177
|
+
| sed -n 's/.*"tag_name"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' \
|
|
178
|
+
| head -n1
|
|
179
|
+
)" || true
|
|
180
|
+
[ -n "${RESOLVED_VERSION:-}" ] || RESOLVED_VERSION="latest"
|
|
181
|
+
else
|
|
182
|
+
URL="https://github.com/${REPO}/releases/download/${VERSION}/${ASSET}"
|
|
183
|
+
RESOLVED_VERSION="$VERSION"
|
|
184
|
+
fi
|
|
185
|
+
|
|
186
|
+
printf 'forge-install: platform %s / %s / %s -> asset %s\n' "$OS" "$ARCH" "$LIBC" "$ASSET"
|
|
187
|
+
printf 'forge-install: downloading %s (%s)\n' "$ASSET" "$RESOLVED_VERSION"
|
|
188
|
+
|
|
189
|
+
# --- download + install ----------------------------------------------------
|
|
190
|
+
TMP="$(mktemp -d "${TMPDIR:-/tmp}/forge-install.XXXXXX")"
|
|
191
|
+
trap 'rm -rf "$TMP"' EXIT INT TERM
|
|
192
|
+
TMP_BIN="$TMP/$BIN_NAME"
|
|
193
|
+
|
|
194
|
+
download_to "$URL" "$TMP_BIN" || die "download failed from $URL — is the release published for your platform?"
|
|
195
|
+
[ -s "$TMP_BIN" ] || die "downloaded file is empty — the asset '$ASSET' may not exist in release '$RESOLVED_VERSION'."
|
|
196
|
+
|
|
197
|
+
# --- integrity verification (SHA-256) --------------------------------------
|
|
198
|
+
# Verify the downloaded asset against the release's checksums.txt manifest BEFORE
|
|
199
|
+
# making it executable or installing it. A mismatch is always fatal (corrupted or
|
|
200
|
+
# substituted download). If the manifest cannot be fetched or no SHA-256 tool is
|
|
201
|
+
# available we warn and continue; set FORGE_SKIP_CHECKSUM=1 to bypass entirely.
|
|
202
|
+
verify_checksum() {
|
|
203
|
+
if [ "${FORGE_SKIP_CHECKSUM:-0}" = "1" ]; then
|
|
204
|
+
printf 'forge-install: FORGE_SKIP_CHECKSUM=1 set — skipping integrity check.\n' >&2
|
|
205
|
+
return 0
|
|
206
|
+
fi
|
|
207
|
+
|
|
208
|
+
_sha=""
|
|
209
|
+
if command -v sha256sum >/dev/null 2>&1; then _sha="sha256sum";
|
|
210
|
+
elif command -v shasum >/dev/null 2>&1; then _sha="shasum";
|
|
211
|
+
elif command -v openssl >/dev/null 2>&1; then _sha="openssl";
|
|
212
|
+
fi
|
|
213
|
+
if [ -z "$_sha" ]; then
|
|
214
|
+
printf 'forge-install: no sha256 tool (sha256sum/shasum/openssl) found — skipping integrity check.\n' >&2
|
|
215
|
+
return 0
|
|
216
|
+
fi
|
|
217
|
+
|
|
218
|
+
# The manifest lives beside the asset in the same release.
|
|
219
|
+
_sums_url="${URL%/*}/checksums.txt"
|
|
220
|
+
_manifest="$TMP/checksums.txt"
|
|
221
|
+
if ! download_to "$_sums_url" "$_manifest" >/dev/null 2>&1 || [ ! -s "$_manifest" ]; then
|
|
222
|
+
printf 'forge-install: could not download checksums.txt — skipping integrity check.\n' >&2
|
|
223
|
+
return 0
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
# sha256sum manifest lines are "<hash> <name>" (text) or "<hash> *<name>"
|
|
227
|
+
# (binary); match either form for our asset.
|
|
228
|
+
_expected="$(awk -v f="$ASSET" '{ n=$2; sub(/^\*/,"",n); if (n==f) { print $1; exit } }' "$_manifest")"
|
|
229
|
+
if [ -z "$_expected" ]; then
|
|
230
|
+
printf 'forge-install: %s not listed in checksums.txt — skipping integrity check.\n' "$ASSET" >&2
|
|
231
|
+
return 0
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
case "$_sha" in
|
|
235
|
+
sha256sum) _actual="$(sha256sum "$TMP_BIN" | awk '{print $1}')" ;;
|
|
236
|
+
shasum) _actual="$(shasum -a 256 "$TMP_BIN" | awk '{print $1}')" ;;
|
|
237
|
+
openssl) _actual="$(openssl dgst -sha256 "$TMP_BIN" | awk '{print $NF}')" ;;
|
|
238
|
+
esac
|
|
239
|
+
|
|
240
|
+
if [ "$_actual" != "$_expected" ]; then
|
|
241
|
+
die "checksum mismatch for $ASSET
|
|
242
|
+
expected: $_expected
|
|
243
|
+
actual: $_actual
|
|
244
|
+
The download may be corrupted or tampered with — aborting."
|
|
245
|
+
fi
|
|
246
|
+
printf 'forge-install: checksum verified (sha256).\n'
|
|
247
|
+
}
|
|
248
|
+
verify_checksum
|
|
249
|
+
|
|
250
|
+
chmod +x "$TMP_BIN"
|
|
251
|
+
|
|
252
|
+
mkdir -p "$INSTALL_DIR"
|
|
253
|
+
DEST="$INSTALL_DIR/$BIN_NAME"
|
|
254
|
+
mv -f "$TMP_BIN" "$DEST"
|
|
255
|
+
|
|
256
|
+
printf '\nforge-install: installed forge %s -> %s\n' "$RESOLVED_VERSION" "$DEST"
|
|
257
|
+
|
|
258
|
+
# --- PATH note + next step -------------------------------------------------
|
|
259
|
+
case ":${PATH}:" in
|
|
260
|
+
*":${INSTALL_DIR}:"*) : ;; # already on PATH
|
|
261
|
+
*)
|
|
262
|
+
printf '\n%s is not on your PATH. Add it, e.g.:\n' "$INSTALL_DIR"
|
|
263
|
+
printf ' echo '\''export PATH="%s:$PATH"'\'' >> ~/.profile && . ~/.profile\n' "$INSTALL_DIR"
|
|
264
|
+
;;
|
|
265
|
+
esac
|
|
266
|
+
|
|
267
|
+
printf '\nNext: run '\''forge setup'\'' in a git repo to wire up Forge.\n'
|
|
268
|
+
printf 'Note: git and gh (GitHub CLI) must be installed separately — the binary bundles Forge, not those tools.\n'
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Canonical GitHub Release asset-name mapping for the single-binary distribution
|
|
2
|
+
// (epic 6f11d483, Step 4 / issue 50afbc47). This is the SINGLE SOURCE OF TRUTH
|
|
3
|
+
// for how a detected platform (os / arch / libc) maps to the release asset that
|
|
4
|
+
// `scripts/install.sh` and `scripts/install.ps1` download.
|
|
5
|
+
//
|
|
6
|
+
// The CI workflow `.github/workflows/build-binary.yml` compiles seven canonical
|
|
7
|
+
// bun targets and publishes each to the GitHub Release under the names produced
|
|
8
|
+
// here. The install scripts re-derive the same name from the host platform, so
|
|
9
|
+
// this table is exercised by `scripts/release-asset.test.js` (which also
|
|
10
|
+
// cross-checks the shell script's own detection against it) to guarantee the two
|
|
11
|
+
// sides never drift.
|
|
12
|
+
//
|
|
13
|
+
// bun target -> release asset
|
|
14
|
+
// bun-windows-x64 -> forge-windows-x64.exe
|
|
15
|
+
// bun-darwin-arm64 -> forge-darwin-arm64
|
|
16
|
+
// bun-darwin-x64 -> forge-darwin-x64
|
|
17
|
+
// bun-linux-x64 -> forge-linux-x64
|
|
18
|
+
// bun-linux-arm64 -> forge-linux-arm64
|
|
19
|
+
// bun-linux-x64-musl -> forge-linux-x64-musl
|
|
20
|
+
// bun-linux-arm64-musl -> forge-linux-arm64-musl
|
|
21
|
+
|
|
22
|
+
/** Operating systems we publish binaries for. */
|
|
23
|
+
export const SUPPORTED_OS = ['windows', 'darwin', 'linux'];
|
|
24
|
+
/** CPU architectures we publish binaries for. */
|
|
25
|
+
export const SUPPORTED_ARCH = ['x64', 'arm64'];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Map a detected platform to its GitHub Release asset filename.
|
|
29
|
+
*
|
|
30
|
+
* @param {object} p
|
|
31
|
+
* @param {string} p.os - 'windows' | 'darwin' | 'linux'
|
|
32
|
+
* @param {string} p.arch - 'x64' | 'arm64'
|
|
33
|
+
* @param {string} [p.libc] - 'glibc' | 'musl' (linux only; ignored elsewhere)
|
|
34
|
+
* @returns {string} asset filename, e.g. 'forge-linux-x64-musl' or 'forge-windows-x64.exe'
|
|
35
|
+
* @throws {Error} on an unsupported os/arch combination
|
|
36
|
+
*/
|
|
37
|
+
export function releaseAssetName({ os, arch, libc } = {}) {
|
|
38
|
+
if (!SUPPORTED_OS.includes(os)) {
|
|
39
|
+
throw new Error(`Unsupported OS: ${os} (supported: ${SUPPORTED_OS.join(', ')})`);
|
|
40
|
+
}
|
|
41
|
+
if (!SUPPORTED_ARCH.includes(arch)) {
|
|
42
|
+
throw new Error(`Unsupported arch: ${arch} (supported: ${SUPPORTED_ARCH.join(', ')})`);
|
|
43
|
+
}
|
|
44
|
+
// Reject os/arch pairs that are not actually published. The independent
|
|
45
|
+
// allowlists above would otherwise accept e.g. windows/arm64 and invent a
|
|
46
|
+
// `forge-windows-arm64.exe` asset that no release contains. Validate the
|
|
47
|
+
// tuple against the canonical matrix (windows is x64-only) so JS, install.sh
|
|
48
|
+
// and install.ps1 all agree on the same supported set.
|
|
49
|
+
if (!PUBLISHED_OS_ARCH.has(`${os}/${arch}`)) {
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Unsupported platform: ${os}/${arch} is not a published target ` +
|
|
52
|
+
`(supported: ${[...PUBLISHED_OS_ARCH].join(', ')})`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
let name = `forge-${os}-${arch}`;
|
|
56
|
+
if (os === 'linux' && libc === 'musl') {
|
|
57
|
+
name += '-musl';
|
|
58
|
+
}
|
|
59
|
+
if (os === 'windows') {
|
|
60
|
+
name += '.exe';
|
|
61
|
+
}
|
|
62
|
+
return name;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The full set of seven canonical assets, in the same order as the CI matrix.
|
|
67
|
+
* Exported for tests and for anyone enumerating the published release.
|
|
68
|
+
*/
|
|
69
|
+
export const CANONICAL_ASSETS = [
|
|
70
|
+
{ os: 'windows', arch: 'x64', libc: undefined, asset: 'forge-windows-x64.exe' },
|
|
71
|
+
{ os: 'darwin', arch: 'arm64', libc: undefined, asset: 'forge-darwin-arm64' },
|
|
72
|
+
{ os: 'darwin', arch: 'x64', libc: undefined, asset: 'forge-darwin-x64' },
|
|
73
|
+
{ os: 'linux', arch: 'x64', libc: 'glibc', asset: 'forge-linux-x64' },
|
|
74
|
+
{ os: 'linux', arch: 'arm64', libc: 'glibc', asset: 'forge-linux-arm64' },
|
|
75
|
+
{ os: 'linux', arch: 'x64', libc: 'musl', asset: 'forge-linux-x64-musl' },
|
|
76
|
+
{ os: 'linux', arch: 'arm64', libc: 'musl', asset: 'forge-linux-arm64-musl' },
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* The set of published `os/arch` tuples, derived from CANONICAL_ASSETS so it can
|
|
81
|
+
* never drift from the actual release matrix. Used by releaseAssetName() to
|
|
82
|
+
* reject unsupported combinations (e.g. windows/arm64).
|
|
83
|
+
*/
|
|
84
|
+
export const PUBLISHED_OS_ARCH = new Set(CANONICAL_ASSETS.map((a) => `${a.os}/${a.arch}`));
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Byte-for-byte parity check between the two Forge distribution channels.
|
|
2
|
+
//
|
|
3
|
+
// Runs `forge setup` from BOTH the npm path (`node bin/forge.js`) and the compiled
|
|
4
|
+
// single-file binary into two throwaway git projects, then compares the produced
|
|
5
|
+
// trees by relative-path set + per-file SHA-256. Catches flattening, missing files,
|
|
6
|
+
// CRLF mangling, and corruption in one assertion.
|
|
7
|
+
//
|
|
8
|
+
// This is the "one real-compile leg" the reliability guidance asks for. It is
|
|
9
|
+
// runnable locally (`bun run parity:binary`) and by the step-3 CI matrix on each
|
|
10
|
+
// host target. Requires `bun` and `git` on PATH.
|
|
11
|
+
|
|
12
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import path from 'node:path';
|
|
16
|
+
import crypto from 'node:crypto';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
20
|
+
const BIN = process.platform === 'win32' ? 'forge-bin.exe' : 'forge-bin';
|
|
21
|
+
const BIN_PATH = path.join(REPO, BIN);
|
|
22
|
+
|
|
23
|
+
function run(cmd, args, opts = {}) {
|
|
24
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
25
|
+
return r;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function sha256(file) {
|
|
29
|
+
return crypto.createHash('sha256').update(fs.readFileSync(file)).digest('hex');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Directories to skip entirely while walking a setup tree. `node_modules` is a
|
|
33
|
+
// package-manager install artifact, NOT a Forge-managed asset — `setup --quick`
|
|
34
|
+
// may run an npm/bun install whose output (dependency trees, registry metadata)
|
|
35
|
+
// is legitimately non-deterministic between runs, channels, and OSes. Comparing
|
|
36
|
+
// it produces false-positive drift, so it is excluded. `.git` is runtime VCS state.
|
|
37
|
+
const EXCLUDED_DIRS = new Set(['.git', 'node_modules']);
|
|
38
|
+
|
|
39
|
+
// Files to skip regardless of location. Lockfiles are install byproducts: their
|
|
40
|
+
// bytes vary with registry ordering/timestamps and are not Forge assets. Excluding
|
|
41
|
+
// them keeps parity strict for real assets (skills/rules/hooks/.forge/AGENTS.md)
|
|
42
|
+
// while ignoring package-manager noise. `.package-lock.json` normally lives under
|
|
43
|
+
// node_modules/ (already excluded) but is guarded here too for robustness.
|
|
44
|
+
const EXCLUDED_FILES = new Set(['package-lock.json', '.package-lock.json']);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Map of relative-posix-path → sha256 for every Forge-managed file under dir.
|
|
48
|
+
* Excludes .git, node_modules/**, and package-manager lockfiles (see the
|
|
49
|
+
* EXCLUDED_* sets above) so parity compares only Forge assets, not install output.
|
|
50
|
+
*/
|
|
51
|
+
function hashTree(dir) {
|
|
52
|
+
const map = {};
|
|
53
|
+
const walk = (abs, rel) => {
|
|
54
|
+
for (const e of fs.readdirSync(abs)) {
|
|
55
|
+
if (EXCLUDED_DIRS.has(e)) continue;
|
|
56
|
+
const a = path.join(abs, e);
|
|
57
|
+
const r = rel ? `${rel}/${e}` : e;
|
|
58
|
+
const st = fs.lstatSync(a);
|
|
59
|
+
if (st.isDirectory()) walk(a, r);
|
|
60
|
+
else if (st.isFile() && !EXCLUDED_FILES.has(e)) map[r] = sha256(a);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
walk(dir, '');
|
|
64
|
+
return map;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function makeProject(label) {
|
|
68
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `forge-parity-${label}-`));
|
|
69
|
+
execFileSync('git', ['init', '-q'], { cwd: dir });
|
|
70
|
+
execFileSync('git', ['config', 'user.email', 'parity@forge.test'], { cwd: dir });
|
|
71
|
+
execFileSync('git', ['config', 'user.name', 'parity'], { cwd: dir });
|
|
72
|
+
return dir;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function runSetup(label, cmd, args, dir) {
|
|
76
|
+
console.log(`\n[${label}] ${cmd} ${args.join(' ')} (cwd=${dir})`);
|
|
77
|
+
const r = run(cmd, args, { cwd: dir });
|
|
78
|
+
if (r.status !== 0) {
|
|
79
|
+
console.error(`[${label}] setup exited ${r.status}`);
|
|
80
|
+
if (r.stdout) console.error(r.stdout.slice(-2000));
|
|
81
|
+
if (r.stderr) console.error(r.stderr.slice(-2000));
|
|
82
|
+
throw new Error(`[${label}] setup failed`);
|
|
83
|
+
}
|
|
84
|
+
return r;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function main() {
|
|
88
|
+
// 1. Build the binary (regenerates the embed manifest first).
|
|
89
|
+
console.log('Building single-file binary (bun run build:binary)…');
|
|
90
|
+
const build = run('bun', ['run', 'build:binary'], { cwd: REPO });
|
|
91
|
+
if (build.status !== 0) {
|
|
92
|
+
console.error(build.stdout || '');
|
|
93
|
+
console.error(build.stderr || '');
|
|
94
|
+
throw new Error('build:binary failed');
|
|
95
|
+
}
|
|
96
|
+
if (!fs.existsSync(BIN_PATH)) throw new Error(`binary not found at ${BIN_PATH}`);
|
|
97
|
+
|
|
98
|
+
// 2. Run setup from both channels into fresh git projects.
|
|
99
|
+
const setupArgs = ['setup', '--quick', '--yes'];
|
|
100
|
+
const npmDir = makeProject('npm');
|
|
101
|
+
const binDir = makeProject('bin');
|
|
102
|
+
|
|
103
|
+
// try/finally so the throwaway temp git projects are removed even when setup
|
|
104
|
+
// or the comparison throws (no leaked dirs on any failure path).
|
|
105
|
+
try {
|
|
106
|
+
runSetup('npm', 'node', [path.join(REPO, 'bin', 'forge.js'), ...setupArgs], npmDir);
|
|
107
|
+
runSetup('bin', BIN_PATH, setupArgs, binDir);
|
|
108
|
+
|
|
109
|
+
// 3. Compare trees.
|
|
110
|
+
const a = hashTree(npmDir);
|
|
111
|
+
const b = hashTree(binDir);
|
|
112
|
+
const keys = new Set([...Object.keys(a), ...Object.keys(b)]);
|
|
113
|
+
const missingInBin = [];
|
|
114
|
+
const missingInNpm = [];
|
|
115
|
+
const differing = [];
|
|
116
|
+
for (const k of keys) {
|
|
117
|
+
if (!(k in b)) missingInBin.push(k);
|
|
118
|
+
else if (!(k in a)) missingInNpm.push(k);
|
|
119
|
+
else if (a[k] !== b[k]) differing.push(k);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const problems = missingInBin.length + missingInNpm.length + differing.length;
|
|
123
|
+
console.log(`\nParity: ${Object.keys(a).length} npm files vs ${Object.keys(b).length} binary files.`);
|
|
124
|
+
if (missingInBin.length) console.log(` Missing in binary: ${missingInBin.join(', ')}`);
|
|
125
|
+
if (missingInNpm.length) console.log(` Missing in npm: ${missingInNpm.join(', ')}`);
|
|
126
|
+
if (differing.length) console.log(` Differing bytes: ${differing.join(', ')}`);
|
|
127
|
+
|
|
128
|
+
if (problems > 0) {
|
|
129
|
+
console.error(`\nPARITY FAILED (${problems} discrepancies).`);
|
|
130
|
+
process.exit(1);
|
|
131
|
+
}
|
|
132
|
+
console.log('\nPARITY OK — npm and compiled binary produced byte-identical setup trees.');
|
|
133
|
+
} finally {
|
|
134
|
+
fs.rmSync(npmDir, { recursive: true, force: true });
|
|
135
|
+
fs.rmSync(binDir, { recursive: true, force: true });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Exported for unit testing the asset-vs-install-artifact filtering. `hashTree`
|
|
140
|
+
// and the exclusion sets are the load-bearing parity logic; the rest of the file
|
|
141
|
+
// drives real builds and only runs when executed directly (guarded below).
|
|
142
|
+
export { hashTree, EXCLUDED_DIRS, EXCLUDED_FILES };
|
|
143
|
+
|
|
144
|
+
// Only build + compare when run as a script, not when imported by a test.
|
|
145
|
+
if (import.meta.main) main();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// Unit test for the parity-check asset filtering. The parity check compares the
|
|
2
|
+
// npm-vs-binary `setup --quick` trees; it MUST ignore package-manager install
|
|
3
|
+
// byproducts (node_modules/**, lockfiles) that are legitimately non-deterministic
|
|
4
|
+
// between runs/channels/OSes, while staying strict for real Forge assets. This
|
|
5
|
+
// test pins that exclusion so a genuine skills/rules/hook drift still fails but
|
|
6
|
+
// install junk never produces a false-positive parity failure.
|
|
7
|
+
|
|
8
|
+
import { test, expect } from 'bun:test';
|
|
9
|
+
import fs from 'node:fs';
|
|
10
|
+
import os from 'node:os';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { hashTree, EXCLUDED_DIRS, EXCLUDED_FILES } from './parity-check.mjs';
|
|
13
|
+
|
|
14
|
+
function mkTree() {
|
|
15
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'parity-hashtree-'));
|
|
16
|
+
// Real Forge-managed assets — these MUST be compared.
|
|
17
|
+
fs.mkdirSync(path.join(dir, 'skills'), { recursive: true });
|
|
18
|
+
fs.writeFileSync(path.join(dir, 'skills', 'SKILL.md'), 'real asset\n');
|
|
19
|
+
fs.writeFileSync(path.join(dir, 'AGENTS.md'), 'real asset\n');
|
|
20
|
+
// Install byproducts — these MUST be excluded.
|
|
21
|
+
fs.mkdirSync(path.join(dir, 'node_modules', 'left-pad'), { recursive: true });
|
|
22
|
+
fs.writeFileSync(path.join(dir, 'node_modules', 'left-pad', 'index.js'), 'junk\n');
|
|
23
|
+
fs.writeFileSync(path.join(dir, 'node_modules', '.package-lock.json'), '{}\n');
|
|
24
|
+
fs.writeFileSync(path.join(dir, 'package-lock.json'), '{"v":1}\n');
|
|
25
|
+
return dir;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
test('hashTree excludes node_modules and lockfiles but keeps real assets', () => {
|
|
29
|
+
const dir = mkTree();
|
|
30
|
+
try {
|
|
31
|
+
const keys = Object.keys(hashTree(dir)).sort();
|
|
32
|
+
expect(keys).toEqual(['AGENTS.md', 'skills/SKILL.md']);
|
|
33
|
+
// No install artifact leaked into the comparison set.
|
|
34
|
+
expect(keys.some(k => k.includes('node_modules'))).toBe(false);
|
|
35
|
+
expect(keys.some(k => k.endsWith('package-lock.json'))).toBe(false);
|
|
36
|
+
} finally {
|
|
37
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('a real asset byte change is still detected (parity stays strict)', () => {
|
|
42
|
+
const dir = mkTree();
|
|
43
|
+
try {
|
|
44
|
+
const before = hashTree(dir);
|
|
45
|
+
fs.writeFileSync(path.join(dir, 'skills', 'SKILL.md'), 'DRIFTED asset\n');
|
|
46
|
+
const after = hashTree(dir);
|
|
47
|
+
expect(after['skills/SKILL.md']).not.toBe(before['skills/SKILL.md']);
|
|
48
|
+
} finally {
|
|
49
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test('exclusion sets document the intended artifacts', () => {
|
|
54
|
+
expect(EXCLUDED_DIRS.has('node_modules')).toBe(true);
|
|
55
|
+
expect(EXCLUDED_DIRS.has('.git')).toBe(true);
|
|
56
|
+
expect(EXCLUDED_FILES.has('package-lock.json')).toBe(true);
|
|
57
|
+
expect(EXCLUDED_FILES.has('.package-lock.json')).toBe(true);
|
|
58
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const DEFAULT_LOCK_FILES = [
|
|
7
|
+
'.github/agentic-workflows/behavioral-test.lock.yml',
|
|
8
|
+
'.github/workflows/behavioral-test.lock.yml',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const IMAGE_PINS = [
|
|
12
|
+
{
|
|
13
|
+
image: 'ghcr.io/github/gh-aw-mcpg:v0.3.0',
|
|
14
|
+
digest: 'sha256:9c2228324fb1f26f39dc9471612e530ae3efc3156dac05efb2e8d212878d454d',
|
|
15
|
+
pinnedImage: 'ghcr.io/github/gh-aw-mcpg@sha256:9c2228324fb1f26f39dc9471612e530ae3efc3156dac05efb2e8d212878d454d',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
image: 'ghcr.io/github/github-mcp-server:v1.0.2',
|
|
19
|
+
digest: 'sha256:26db03408086a99cf1916348dcc4f9614206658f9082a8060dc7c81ad787f4ba',
|
|
20
|
+
pinnedImage: 'ghcr.io/github/github-mcp-server@sha256:26db03408086a99cf1916348dcc4f9614206658f9082a8060dc7c81ad787f4ba',
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function updateManifestLine(line) {
|
|
25
|
+
const prefix = '# gh-aw-manifest: ';
|
|
26
|
+
if (!line.startsWith(prefix)) {
|
|
27
|
+
return line;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const manifest = JSON.parse(line.slice(prefix.length));
|
|
31
|
+
if (!Array.isArray(manifest.containers)) {
|
|
32
|
+
return line;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
manifest.containers = manifest.containers.map((container) => {
|
|
36
|
+
const pin = IMAGE_PINS.find(candidate => candidate.image === container.image);
|
|
37
|
+
if (!pin) {
|
|
38
|
+
return container;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
...container,
|
|
43
|
+
digest: pin.digest,
|
|
44
|
+
pinned_image: pin.pinnedImage,
|
|
45
|
+
};
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
return `${prefix}${JSON.stringify(manifest)}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function pinLockContent(content) {
|
|
52
|
+
const lines = content.split(/\r?\n/);
|
|
53
|
+
if (lines.length > 1) {
|
|
54
|
+
lines[1] = updateManifestLine(lines[1]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let pinned = lines.join('\n');
|
|
58
|
+
const manifestPrefix = /^# gh-aw-manifest: .*$/m;
|
|
59
|
+
const manifestMatch = pinned.match(manifestPrefix);
|
|
60
|
+
const manifestLine = manifestMatch ? manifestMatch[0] : null;
|
|
61
|
+
pinned = pinned.replace(manifestPrefix, '__GH_AW_MANIFEST_PLACEHOLDER__');
|
|
62
|
+
|
|
63
|
+
for (const pin of IMAGE_PINS) {
|
|
64
|
+
pinned = pinned.split(pin.image).join(pin.pinnedImage);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (manifestLine) {
|
|
68
|
+
pinned = pinned.replace('__GH_AW_MANIFEST_PLACEHOLDER__', updateManifestLine(manifestLine));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return pinned;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function pinLockFile(filePath) {
|
|
75
|
+
const original = fs.readFileSync(filePath, 'utf8');
|
|
76
|
+
const pinned = pinLockContent(original);
|
|
77
|
+
if (pinned !== original) {
|
|
78
|
+
fs.writeFileSync(filePath, pinned, 'utf8');
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function main() {
|
|
85
|
+
const args = process.argv.slice(2);
|
|
86
|
+
const files = args.length > 0 ? args : DEFAULT_LOCK_FILES;
|
|
87
|
+
const changed = [];
|
|
88
|
+
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
const filePath = path.resolve(process.cwd(), file);
|
|
91
|
+
if (pinLockFile(filePath)) {
|
|
92
|
+
changed.push(file);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (changed.length === 0) {
|
|
97
|
+
console.log('Agentic workflow runtime images already pinned');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
console.log(`Pinned agentic workflow runtime images: ${changed.join(', ')}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (require.main === module) {
|
|
105
|
+
main();
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
IMAGE_PINS,
|
|
110
|
+
pinLockContent,
|
|
111
|
+
pinLockFile,
|
|
112
|
+
};
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
set -euo pipefail
|
|
16
16
|
|
|
17
17
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
18
|
+
if [[ -f "$SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
|
|
19
|
+
source "$SCRIPT_DIR/bootstrap-windows-tools.sh"
|
|
20
|
+
fi
|
|
18
21
|
source "$SCRIPT_DIR/lib/sanitize.sh" || { echo "FATAL: failed to source sanitize.sh" >&2; exit 2; }
|
|
19
22
|
|
|
20
23
|
# Source these but don't fail if missing (tests may not have them)
|