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
@@ -7,7 +7,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
7
7
  TEST_TMP="$(mktemp -d)"
8
8
  trap 'rm -rf "$TEST_TMP"' EXIT
9
9
 
10
- # Override TEAM_MAP_ROOT so tests don't touch real .beads/
10
+ # Override TEAM_MAP_ROOT so tests don't touch real .forge/
11
11
  export TEAM_MAP_ROOT="$TEST_TMP"
12
12
 
13
13
  # Fix "now" so stale detection is deterministic
@@ -27,75 +27,41 @@ esac
27
27
  MOCK
28
28
  chmod +x "$mock_dir/gh"
29
29
 
30
- # ── Create mock bd ───────────────────────────────────────────────────────
31
- cat > "$mock_dir/bd" << 'MOCK'
30
+ # ── Create mock forge ─────────────────────────────────────────────────────
31
+ # Emits Kernel-shaped JSON ({"data":{"issues":[...]}}) for `issue list`.
32
+ cat > "$mock_dir/forge" << 'MOCK'
32
33
  #!/usr/bin/env bash
33
- case "$1 $2" in
34
- "list --status=open,in_progress")
35
- exit 1
36
- ;;
37
- "list --status=open")
38
- echo "forge-bbb · Feature B"
39
- ;;
40
- "list --status=in_progress")
41
- echo "◐ forge-aaa · Feature A"
42
- echo "forge-ccc · Feature C"
43
- echo "◐ forge-m1n8.6 · Sub-feature X (dotted ID)"
44
- ;;
45
- "show forge-aaa")
46
- echo " forge-aaa · Feature A [● P2 · IN_PROGRESS]"
47
- echo "Owner: devone"
48
- echo "Updated: 2026-03-27T10:00:00Z"
49
- ;;
50
- "show forge-bbb")
51
- echo "○ forge-bbb · Feature B [● P2 · OPEN]"
52
- echo "Owner: devtwo"
53
- echo "Updated: 2026-03-25T10:00:00Z"
54
- ;;
55
- "show forge-ccc")
56
- echo "◐ forge-ccc · Feature C [● P1 · IN_PROGRESS]"
57
- echo "Owner: devone"
58
- echo "Updated: 2026-03-27T09:00:00Z"
59
- echo ""
60
- echo "DEPENDS ON"
61
- echo " → forge-aaa: Feature A"
62
- ;;
63
- "show forge-m1n8.6")
64
- # Dotted sub-ID — bd show must receive the full ID including .6
65
- echo "◐ forge-m1n8.6 · Sub-feature X [● P2 · IN_PROGRESS]"
66
- echo "Owner: devthree"
67
- echo "Updated: 2026-03-27T08:00:00Z"
68
- ;;
69
- "show forge-m1n8")
70
- # Parent epic — if a buggy parser truncates forge-m1n8.6 to forge-m1n8,
71
- # the workload would get this WRONG owner and trigger the guard below.
72
- echo "◐ forge-m1n8 · PARENT EPIC (WRONG — dotted ID was truncated)"
73
- echo "Owner: WRONG_OWNER_DO_NOT_USE"
74
- echo "Updated: 2026-03-20T10:00:00Z"
75
- ;;
76
- esac
34
+ args="$*"
35
+ emit() { printf '{"data":{"issues":%s}}\n' "$1"; }
36
+
37
+ # forge-aaa BLOCKS forge-ccc (kernel: the blocker carries the dependency edge),
38
+ # so forge-ccc is the blocked one — blocked_by resolves to forge-aaa by reverse scan.
39
+ aaa='{"id":"forge-aaa","title":"Feature A","status":"in_progress","assignee":"devone","updated_at":"2026-03-27T10:00:00Z","dependencies":["forge-ccc"]}'
40
+ bbb='{"id":"forge-bbb","title":"Feature B","status":"open","assignee":"devtwo","updated_at":"2026-03-25T10:00:00Z","dependencies":[]}'
41
+ ccc='{"id":"forge-ccc","title":"Feature C","status":"in_progress","assignee":"devone","updated_at":"2026-03-27T09:00:00Z","dependencies":[]}'
42
+ # Dotted sub-ID: the id flows through verbatim from JSON, never truncated.
43
+ m1n8='{"id":"forge-m1n8.6","title":"Sub-feature X (dotted ID)","status":"in_progress","assignee":"devthree","updated_at":"2026-03-27T08:00:00Z","dependencies":[]}'
44
+
45
+ if [[ "$args" == *"issue list"* ]]; then
46
+ if [[ "$args" == *"--status=open"* ]]; then emit "[$bbb]"
47
+ elif [[ "$args" == *"--status=in_progress"* ]]; then emit "[$aaa,$ccc,$m1n8]"
48
+ else emit "[]"; fi
49
+ exit 0
50
+ fi
51
+ exit 0
77
52
  MOCK
78
- chmod +x "$mock_dir/bd"
53
+ chmod +x "$mock_dir/forge"
79
54
 
80
- # ── Create mock bd with no issues ────────────────────────────────────────
81
- cat > "$mock_dir/bd-empty" << 'MOCK'
55
+ # ── Create mock forge with no issues ──────────────────────────────────────
56
+ cat > "$mock_dir/forge-empty" << 'MOCK'
82
57
  #!/usr/bin/env bash
83
- case "$1 $2" in
84
- "list --status=open,in_progress")
85
- echo ""
86
- ;;
87
- "list --status=open")
88
- echo "○ forge-should-not-appear · Open fallback sentinel"
89
- ;;
90
- "list --status=in_progress")
91
- echo "◐ forge-should-not-appear-2 · In-progress fallback sentinel"
92
- ;;
93
- esac
58
+ printf '{"data":{"issues":[]}}\n'
59
+ exit 0
94
60
  MOCK
95
- chmod +x "$mock_dir/bd-empty"
61
+ chmod +x "$mock_dir/forge-empty"
96
62
 
97
63
  export GH_CMD="$mock_dir/gh"
98
- export BD_CMD="$mock_dir/bd"
64
+ export FORGE_CMD="$mock_dir/forge"
99
65
 
100
66
  # Source the library under test
101
67
  source "$SCRIPT_DIR/lib/workload.sh"
@@ -183,13 +149,13 @@ assert_not_contains "excludes devtwo" "Developer: devtwo" "$output"
183
149
  echo ""
184
150
  echo "── Test 4: No issues → No active work ──"
185
151
  unset _GITHUB_USER_CACHE
186
- export BD_CMD="$mock_dir/bd-empty"
152
+ export FORGE_CMD="$mock_dir/forge-empty"
187
153
  rc=0
188
154
  output="$(cmd_workload 2>/dev/null)" || rc=$?
189
155
  assert_exit "exits 0" 0 "$rc"
190
156
  assert_contains "shows no active work message" "No active work" "$output"
191
- assert_not_contains "does not fall back when combined query succeeds with no issues" "forge-should-not-appear" "$output"
192
- export BD_CMD="$mock_dir/bd"
157
+ assert_not_contains "empty issue lists yield no issue entries" "forge-" "$output"
158
+ export FORGE_CMD="$mock_dir/forge"
193
159
 
194
160
  # ── Test 5: Stale assignment flagged (>48h) ──────────────────────────────
195
161
  echo ""
@@ -0,0 +1,153 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Generate the static command manifest — scripts/gen-command-manifest.js
6
+ *
7
+ * Static bundlers (`bun build --compile`) need a static require graph, but the
8
+ * command registry discovers commands at runtime via `fs.readdirSync` +
9
+ * `require(filePath)`. This generator walks `lib/commands/*.js` and emits
10
+ * `lib/commands/_manifest.js`, a checked-in module that `require`s every command
11
+ * module by a *static* relative path. The registry loads the manifest as its
12
+ * fast, bundleable path and keeps readdir auto-discovery as a dev/extension
13
+ * fallback (see lib/commands/_registry.js).
14
+ *
15
+ * The output is deterministic (sorted by filename) and timestamp-free, and the
16
+ * file is only rewritten when its content actually changes, so no-op
17
+ * regeneration never dirties the working tree.
18
+ *
19
+ * Usage:
20
+ * node scripts/gen-command-manifest.js # write if changed
21
+ * node scripts/gen-command-manifest.js --check # exit 1 if out of date
22
+ *
23
+ * @module scripts/gen-command-manifest
24
+ */
25
+
26
+ const fs = require('node:fs');
27
+ const path = require('node:path');
28
+
29
+ const COMMANDS_DIR = path.resolve(__dirname, '..', 'lib', 'commands');
30
+ const MANIFEST_PATH = path.join(COMMANDS_DIR, '_manifest.js');
31
+
32
+ /**
33
+ * List the command module filenames the manifest should enumerate.
34
+ *
35
+ * Mirrors the registry's own filter: `.js` files that do not start with `_`
36
+ * (underscore-prefixed files are registry internals, not commands), sorted for
37
+ * deterministic output.
38
+ *
39
+ * @param {string} [commandsDir] - Directory to scan (defaults to lib/commands)
40
+ * @returns {string[]} Sorted command filenames (e.g. `['add.js', 'audit.js']`)
41
+ */
42
+ function listCommandFiles(commandsDir = COMMANDS_DIR) {
43
+ return fs
44
+ .readdirSync(commandsDir)
45
+ .filter(f => f.endsWith('.js') && !f.startsWith('_'))
46
+ .sort();
47
+ }
48
+
49
+ /**
50
+ * Render the manifest source from a list of command filenames.
51
+ *
52
+ * @param {string[]} files - Sorted command filenames
53
+ * @returns {string} Full source text for lib/commands/_manifest.js
54
+ */
55
+ function renderManifest(files) {
56
+ const header = [
57
+ '/**',
58
+ ' * Static Command Manifest — GENERATED FILE, DO NOT EDIT.',
59
+ ' *',
60
+ ' * Regenerate with: node scripts/gen-command-manifest.js',
61
+ ' * Drift is enforced by test/structural/command-manifest-drift.test.js.',
62
+ ' *',
63
+ ' * This module `require`s every command by a static relative path so',
64
+ ' * `bun build --compile` can statically bundle the command graph. The registry',
65
+ ' * (lib/commands/_registry.js) consumes `commands` as its fast, bundleable path',
66
+ ' * and falls back to `fs.readdirSync` auto-discovery for dev/extension commands.',
67
+ ' *',
68
+ ' * @module commands/_manifest',
69
+ ' */',
70
+ '',
71
+ "'use strict';",
72
+ '',
73
+ '/**',
74
+ ' * @typedef {Object} ManifestEntry',
75
+ ' * @property {string} file - Command filename (e.g. `status.js`)',
76
+ ' * @property {import("./_registry").CommandModule} module - The required command module',
77
+ ' */',
78
+ '',
79
+ '/** @type {ManifestEntry[]} */',
80
+ 'const commands = [',
81
+ ];
82
+
83
+ const entries = files.map(file => {
84
+ const modPath = `./${file.replace(/\.js$/, '')}`;
85
+ return ` { file: ${JSON.stringify(file)}, module: require(${JSON.stringify(modPath)}) },`;
86
+ });
87
+
88
+ const footer = [
89
+ '];',
90
+ '',
91
+ 'module.exports = {',
92
+ ' // Absolute path of the canonical commands directory this manifest describes.',
93
+ ' // The registry applies the manifest only when asked to load this exact dir.',
94
+ ' dir: __dirname,',
95
+ ' commands,',
96
+ '};',
97
+ '',
98
+ ];
99
+
100
+ return [...header, ...entries, ...footer].join('\n');
101
+ }
102
+
103
+ /**
104
+ * Generate (or check) the manifest.
105
+ *
106
+ * @param {{ check?: boolean, commandsDir?: string, manifestPath?: string }} [opts]
107
+ * @returns {{ changed: boolean, files: string[], path: string }}
108
+ */
109
+ function generate(opts = {}) {
110
+ const commandsDir = opts.commandsDir ?? COMMANDS_DIR;
111
+ const manifestPath = opts.manifestPath ?? MANIFEST_PATH;
112
+ const files = listCommandFiles(commandsDir);
113
+ const next = renderManifest(files);
114
+
115
+ const current = fs.existsSync(manifestPath)
116
+ ? fs.readFileSync(manifestPath, 'utf8')
117
+ : null;
118
+
119
+ const changed = current !== next;
120
+
121
+ if (changed && !opts.check) {
122
+ fs.writeFileSync(manifestPath, next, 'utf8');
123
+ }
124
+
125
+ return { changed, files, path: manifestPath };
126
+ }
127
+
128
+ module.exports = { listCommandFiles, renderManifest, generate, COMMANDS_DIR, MANIFEST_PATH };
129
+
130
+ // CLI entry point
131
+ if (require.main === module) {
132
+ const check = process.argv.includes('--check');
133
+ const result = generate({ check });
134
+
135
+ if (check && result.changed) {
136
+ console.error(
137
+ '[gen-command-manifest] lib/commands/_manifest.js is out of date.\n' +
138
+ 'Run: node scripts/gen-command-manifest.js'
139
+ );
140
+ process.exit(1);
141
+ }
142
+
143
+ if (result.changed) {
144
+ console.log(
145
+ `[gen-command-manifest] wrote ${path.relative(process.cwd(), result.path)} ` +
146
+ `(${result.files.length} commands)`
147
+ );
148
+ } else {
149
+ console.log(
150
+ `[gen-command-manifest] up to date (${result.files.length} commands)`
151
+ );
152
+ }
153
+ }
@@ -0,0 +1,129 @@
1
+ // Generate lib/embedded-assets.generated.mjs — a Bun-embeddable manifest that
2
+ // statically imports every runtime asset (with { type: "file" }) and maps its
3
+ // real relative POSIX path to the embedded-file path Bun resolves at compile time.
4
+ //
5
+ // WHY a generated per-file map instead of `bun build --compile ... skills/**`:
6
+ // Bun's `embeddedFiles` exposes only a HASHED, directory-less `blob.name`
7
+ // (e.g. `SKILL-a1b2c3.md`), so every skill's `SKILL.md` would collide and the
8
+ // tree could not be reconstructed. A generated map keyed by the true relative
9
+ // path preserves the directory structure exactly; each value is that specific
10
+ // file's `/$bunfs/…` path, so `Bun.file(value)` / `fs` reads the right bytes.
11
+ //
12
+ // The output is gitignored and regenerated by the `build:binary` script right
13
+ // before `bun build --compile`, so it can never drift from the source tree. The
14
+ // completeness test (test/embedded-assets-drift.test.js) walks the same roots and
15
+ // asserts the embed set == the on-disk asset set, both ways.
16
+
17
+ import fs from 'node:fs';
18
+ import path from 'node:path';
19
+ import crypto from 'node:crypto';
20
+ import { fileURLToPath } from 'node:url';
21
+ import pkgRoot from '../lib/package-root.js';
22
+
23
+ const { ASSET_ROOTS } = pkgRoot;
24
+
25
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
26
+ const REPO_ROOT = path.resolve(__dirname, '..');
27
+ const OUT_FILE = path.join(REPO_ROOT, 'lib', 'embedded-assets.generated.mjs');
28
+
29
+ // Deterministic, locale-INDEPENDENT codepoint comparator. Locale-default sort would
30
+ // diverge by machine locale and produce different embed bytes across the step-3
31
+ // cross-compile targets, so sorting is pinned to codepoint order everywhere.
32
+ const byteCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
33
+
34
+ /** True when the asset needs the executable bit restored on extraction (files-only embedding drops mode). */
35
+ export function isExecutableAsset(relPosixPath) {
36
+ return relPosixPath.endsWith('.sh') || relPosixPath.startsWith('.forge/hooks/');
37
+ }
38
+
39
+ /** Recursively list files under an absolute path, returning repo-relative posix paths. Symlinks are skipped (materialize content, never embed links). */
40
+ function walk(absPath, relPath, out) {
41
+ const stat = fs.lstatSync(absPath);
42
+ if (stat.isSymbolicLink()) return;
43
+ if (stat.isDirectory()) {
44
+ for (const entry of fs.readdirSync(absPath).sort(byteCompare)) {
45
+ walk(path.join(absPath, entry), `${relPath}/${entry}`, out);
46
+ }
47
+ } else if (stat.isFile()) {
48
+ out.push(relPath);
49
+ }
50
+ }
51
+
52
+ /** Collect every embeddable asset as a sorted list of repo-relative posix paths. */
53
+ export function collectAssetFiles(repoRoot = REPO_ROOT, roots = ASSET_ROOTS) {
54
+ const files = [];
55
+ for (const root of roots) {
56
+ const abs = path.join(repoRoot, root);
57
+ if (!fs.existsSync(abs)) {
58
+ // Fail CLOSED: a configured asset root that is absent would silently ship an
59
+ // incomplete manifest. Error instead so the build stops loudly.
60
+ throw new Error(
61
+ `Configured ASSET_ROOT does not exist: ${root} (looked in ${abs}). ` +
62
+ 'Fix ASSET_ROOTS in lib/package-root.js or restore the missing path.'
63
+ );
64
+ }
65
+ walk(abs, root.split(path.sep).join('/'), files);
66
+ }
67
+ return [...new Set(files)].sort(byteCompare);
68
+ }
69
+
70
+ /**
71
+ * Content fingerprint over the asset SET: sha256 of each relpath + its bytes, in
72
+ * sorted order. Emitted into the manifest so the runtime cache dir identity
73
+ * changes whenever any asset content or the set changes (invalidates stale
74
+ * same-version extractions). Deterministic across machines.
75
+ */
76
+ export function assetFingerprint(repoRoot = REPO_ROOT, files = collectAssetFiles(repoRoot)) {
77
+ const h = crypto.createHash('sha256');
78
+ for (const rel of files) {
79
+ h.update(rel);
80
+ h.update('\0');
81
+ h.update(fs.readFileSync(path.join(repoRoot, ...rel.split('/'))));
82
+ }
83
+ return h.digest('hex');
84
+ }
85
+
86
+ function render(files, fingerprint) {
87
+ const imports = [];
88
+ const entries = [];
89
+ const execEntries = [];
90
+ files.forEach((rel, i) => {
91
+ const ident = `a${i}`;
92
+ // Specifier is relative to lib/embedded-assets.generated.mjs → prefix `../`.
93
+ const specifier = JSON.stringify(`../${rel}`);
94
+ imports.push(`import ${ident} from ${specifier} with { type: "file" };`);
95
+ entries.push(` ${JSON.stringify(rel)}: ${ident},`);
96
+ if (isExecutableAsset(rel)) execEntries.push(` ${JSON.stringify(rel)},`);
97
+ });
98
+ return [
99
+ '// AUTO-GENERATED by scripts/gen-embedded-assets.mjs — DO NOT EDIT.',
100
+ '// Gitignored; regenerated before every `bun build --compile`.',
101
+ ...imports,
102
+ '',
103
+ 'export const EMBEDDED_ASSETS = {',
104
+ ...entries,
105
+ '};',
106
+ '',
107
+ 'export const EXECUTABLE_ASSETS = [',
108
+ ...execEntries,
109
+ '];',
110
+ '',
111
+ `export const ASSET_FINGERPRINT = ${JSON.stringify(fingerprint)};`,
112
+ '',
113
+ ].join('\n');
114
+ }
115
+
116
+ function main() {
117
+ const files = collectAssetFiles();
118
+ const fingerprint = assetFingerprint(REPO_ROOT, files);
119
+ fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true });
120
+ fs.writeFileSync(OUT_FILE, render(files, fingerprint), 'utf8');
121
+ const execCount = files.filter(isExecutableAsset).length;
122
+ console.log(
123
+ `Embedded ${files.length} asset file(s) (${execCount} executable), fingerprint ${fingerprint.slice(0, 16)} → ${path.relative(REPO_ROOT, OUT_FILE)}`
124
+ );
125
+ }
126
+
127
+ if (import.meta.main || process.argv[1] === fileURLToPath(import.meta.url)) {
128
+ main();
129
+ }
@@ -0,0 +1,139 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Forge single-binary installer for Windows (x64).
4
+
5
+ .DESCRIPTION
6
+ Downloads the forge-windows-x64.exe binary from the GitHub Release, installs it
7
+ to %LOCALAPPDATA%\Programs\forge\forge.exe, and prints a PATH note plus the next
8
+ step. The binary bundles Forge's JavaScript but NOT its external prerequisites:
9
+ git, gh (GitHub CLI) and Git Bash must still be installed separately.
10
+
11
+ .PARAMETER Version
12
+ Release tag to install (default: latest), e.g. v1.2.3.
13
+
14
+ .PARAMETER Dir
15
+ Install directory (default: $env:LOCALAPPDATA\Programs\forge).
16
+
17
+ .PARAMETER PrintAsset
18
+ Print the resolved asset name and exit (used for verification/testing).
19
+
20
+ .EXAMPLE
21
+ irm https://raw.githubusercontent.com/harshanandak/forge/master/scripts/install.ps1 | iex
22
+
23
+ .EXAMPLE
24
+ # Pin a version (download the script, then run it with an argument):
25
+ $s = irm https://raw.githubusercontent.com/harshanandak/forge/master/scripts/install.ps1
26
+ & ([scriptblock]::Create($s)) -Version v1.2.3
27
+ #>
28
+ [CmdletBinding()]
29
+ param(
30
+ [string]$Version = 'latest',
31
+ [string]$Dir = (Join-Path $env:LOCALAPPDATA 'Programs\forge'),
32
+ [switch]$PrintAsset,
33
+ [switch]$SkipChecksum
34
+ )
35
+
36
+ $ErrorActionPreference = 'Stop'
37
+ Set-StrictMode -Version Latest
38
+
39
+ $Repo = if ($env:FORGE_REPO) { $env:FORGE_REPO } else { 'harshanandak/forge' }
40
+ if ($Version -eq 'latest' -and $env:FORGE_VERSION) { $Version = $env:FORGE_VERSION }
41
+
42
+ # Canonical asset-name mapping for Windows - the only target this script installs.
43
+ # Mirrors scripts/lib/release-asset.mjs (forge-<os>-<arch>[.exe]).
44
+ $Arch = if ($env:FORGE_ARCH) { $env:FORGE_ARCH } else { 'x64' }
45
+ if ($Arch -ne 'x64') {
46
+ throw "forge-install: unsupported architecture '$Arch'. Only windows-x64 is published."
47
+ }
48
+ $Asset = "forge-windows-$Arch.exe"
49
+
50
+ if ($PrintAsset) {
51
+ Write-Output $Asset
52
+ return
53
+ }
54
+
55
+ # Resolve version + URL. The /releases/latest/download/<asset> path redirects to
56
+ # the newest release, so no JSON API call is needed to download.
57
+ if ($Version -eq 'latest') {
58
+ $Url = "https://github.com/$Repo/releases/latest/download/$Asset"
59
+ try {
60
+ $rel = Invoke-RestMethod -Uri "https://api.github.com/repos/$Repo/releases/latest" `
61
+ -Headers @{ 'User-Agent' = 'forge-install' }
62
+ $ResolvedVersion = $rel.tag_name
63
+ } catch {
64
+ $ResolvedVersion = 'latest'
65
+ }
66
+ } else {
67
+ $Url = "https://github.com/$Repo/releases/download/$Version/$Asset"
68
+ $ResolvedVersion = $Version
69
+ }
70
+
71
+ Write-Host "forge-install: platform windows / $Arch -> asset $Asset"
72
+ Write-Host "forge-install: downloading $Asset ($ResolvedVersion)"
73
+
74
+ New-Item -ItemType Directory -Force -Path $Dir | Out-Null
75
+ $Dest = Join-Path $Dir 'forge.exe'
76
+ $Tmp = Join-Path ([System.IO.Path]::GetTempPath()) ("forge-install-" + [System.Guid]::NewGuid().ToString('N') + ".exe")
77
+
78
+ try {
79
+ Invoke-WebRequest -Uri $Url -OutFile $Tmp -UseBasicParsing -TimeoutSec 300
80
+ } catch {
81
+ throw "forge-install: download failed from $Url - is the release published for windows-x64? ($_)"
82
+ }
83
+ if (-not (Test-Path $Tmp) -or (Get-Item $Tmp).Length -eq 0) {
84
+ throw "forge-install: downloaded file is empty - asset '$Asset' may not exist in release '$ResolvedVersion'."
85
+ }
86
+
87
+ # Integrity verification (SHA-256) BEFORE installing. A mismatch is fatal
88
+ # (corrupted or substituted download). If the manifest cannot be fetched we warn
89
+ # and continue; pass -SkipChecksum or set FORGE_SKIP_CHECKSUM=1 to bypass.
90
+ if ($SkipChecksum -or $env:FORGE_SKIP_CHECKSUM -eq '1') {
91
+ Write-Host "forge-install: skipping integrity check (requested)."
92
+ } else {
93
+ $SumsUrl = $Url.Substring(0, $Url.LastIndexOf('/') + 1) + 'checksums.txt'
94
+ $SumsTmp = Join-Path ([System.IO.Path]::GetTempPath()) ("forge-install-" + [System.Guid]::NewGuid().ToString('N') + ".txt")
95
+ $expected = $null
96
+ $manifestFetched = $false
97
+ try {
98
+ Invoke-WebRequest -Uri $SumsUrl -OutFile $SumsTmp -UseBasicParsing -TimeoutSec 30
99
+ $manifestFetched = $true
100
+ foreach ($line in (Get-Content $SumsTmp)) {
101
+ # Lines are "<hash> <name>" (text) or "<hash> *<name>" (binary).
102
+ if ($line -match '^\s*([0-9a-fA-F]{64})\s+\*?(.+?)\s*$' -and $Matches[2] -eq $Asset) {
103
+ $expected = $Matches[1].ToLower()
104
+ break
105
+ }
106
+ }
107
+ } catch {
108
+ Write-Host "forge-install: could not download checksums.txt - skipping integrity check."
109
+ } finally {
110
+ if (Test-Path $SumsTmp) { Remove-Item -Force $SumsTmp }
111
+ }
112
+ if ($expected) {
113
+ $actual = (Get-FileHash -Algorithm SHA256 -Path $Tmp).Hash.ToLower()
114
+ if ($actual -ne $expected) {
115
+ Remove-Item -Force $Tmp
116
+ throw "forge-install: checksum mismatch for $Asset`n expected: $expected`n actual: $actual`nThe download may be corrupted or tampered with - aborting."
117
+ }
118
+ Write-Host "forge-install: checksum verified (sha256)."
119
+ } elseif ($manifestFetched) {
120
+ Write-Host "forge-install: $Asset not listed in checksums.txt - skipping integrity check."
121
+ }
122
+ }
123
+
124
+ Move-Item -Force -Path $Tmp -Destination $Dest
125
+
126
+ Write-Host ""
127
+ Write-Host "forge-install: installed forge $ResolvedVersion -> $Dest"
128
+
129
+ # PATH note: check the user PATH, not just the current process.
130
+ $userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
131
+ if (-not ($userPath -and ($userPath -split ';' | Where-Object { $_ -eq $Dir }))) {
132
+ Write-Host ""
133
+ Write-Host "$Dir is not on your PATH. Add it (new terminals will pick it up):"
134
+ Write-Host " [Environment]::SetEnvironmentVariable('Path', `"$Dir;`$([Environment]::GetEnvironmentVariable('Path','User'))`", 'User')"
135
+ }
136
+
137
+ Write-Host ""
138
+ Write-Host "Next: run 'forge setup' in a git repo to wire up Forge."
139
+ Write-Host "Note: git, gh (GitHub CLI) and Git Bash must be installed separately - the binary bundles Forge, not those tools."