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.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. 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)