forge-workflow 0.0.9 → 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 (479) 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 +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  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 +466 -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/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  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/reference/TOOLCHAIN.md +658 -0
  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 +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  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 +66 -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 +22 -2
  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 +851 -979
  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 +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  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 +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  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 +697 -0
  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/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,413 @@
1
+ /**
2
+ * Shared lefthook / git-hook wiring for `forge setup` and `forge init`.
3
+ *
4
+ * Single source of truth for the hook-wiring primitives. The LIVE `forge setup` /
5
+ * `forge init` path is the registry command in lib/commands/setup.js (it wins in the
6
+ * dispatcher and returns before bin/forge.js's inline setup branch is ever reached; the
7
+ * bin copy is LEGACY/DEAD — see the banner at bin/forge.js "LEGACY / DEAD SETUP PATH").
8
+ * The mid-stage repair path (enforce-stage → repairRuntimeReadiness) also consumes these
9
+ * primitives. Earlier the shadow-config fix landed only in one copy while `forge setup`
10
+ * ran a stale copy, leaving a fresh `forge setup` with TDD enforcement SILENTLY INERT
11
+ * (kernel e452422c / c713fce7 / 22e33dbf, beta blocker B3) — hence one shared module.
12
+ *
13
+ * Responsibilities:
14
+ * - Provide the REAL user-facing lefthook.yml (never lefthook's commented example).
15
+ * - Decide when it is safe to (over)write lefthook.yml (replace only a stub).
16
+ * - Install a native `.git/hooks` fallback when the lefthook binary is unavailable,
17
+ * so raw `git commit` / `git push` still enforce the TDD gate.
18
+ * - Verify honestly whether hooks are actually active, so setup can fail LOUDLY
19
+ * instead of no-op'ing.
20
+ *
21
+ * @module lib/lefthook-wiring
22
+ */
23
+
24
+ 'use strict';
25
+
26
+ const fs = require('node:fs');
27
+ const path = require('node:path');
28
+ // Shared secure spawner: resolves the command to its absolute path (which/where.exe)
29
+ // before executing, so we never search a potentially-writable PATH for `git` (S4036).
30
+ const { secureExecFileSync } = require('./shell-utils');
31
+
32
+ // Sentinel embedded in every native hook Forge writes. Lets verifyHooksActive
33
+ // distinguish a Forge-managed hook from a lefthook-managed one or a user's own.
34
+ const FORGE_NATIVE_HOOK_SENTINEL = '# forge-native-git-hook (installed by `forge setup`)';
35
+
36
+ // The lefthook.yml Forge writes into a USER project. The repo's own dev lefthook.yml
37
+ // references repo-internal scripts (scripts/branch-protection.js, lint.js, test.js, …)
38
+ // that a user's project never has, so shipping it produced hooks that fail on the first
39
+ // commit. This minimal config references only what every Forge project gets: the
40
+ // self-contained TDD gate (.forge/hooks/check-tdd.js) on pre-commit, and the project's
41
+ // own test script (a no-op when absent) on pre-push. Both hooks exist, so the
42
+ // HOOKS_NOT_ACTIVE gate is satisfied and the workflow is reachable out of the box.
43
+ const FORGE_USER_LEFTHOOK_YML = `# Forge git hooks — installed by \`forge setup\` / \`forge init\`.
44
+ # pre-commit enforces the TDD gate; pre-push runs your test script if you have one.
45
+ # Edit freely to add your own checks — Forge only replaces a fully-commented stub.
46
+
47
+ pre-commit:
48
+ commands:
49
+ forge-tdd:
50
+ run: node .forge/hooks/check-tdd.js
51
+
52
+ pre-push:
53
+ commands:
54
+ tests:
55
+ run: npm test --if-present
56
+ `;
57
+
58
+ /**
59
+ * A fresh `lefthook install` (run by lefthook's own npm postinstall) drops a stock
60
+ * EXAMPLE lefthook.yml with every hook commented out. That disposable stub used to
61
+ * block Forge from writing its config, leaving pre-commit/pre-push unwired so every
62
+ * stage command dead-ended on HOOKS_NOT_ACTIVE right after `forge init` (kernel
63
+ * c713fce7). Overwrite a missing file or such a stub, but never clobber a lefthook.yml
64
+ * that already has active (uncommented) jobs — a user's real config, or Forge's own
65
+ * once written.
66
+ *
67
+ * @param {string} lefthookTarget - Absolute path to the candidate lefthook.yml.
68
+ * @returns {boolean} true when Forge should (over)write its config at that path.
69
+ */
70
+ function forgeShouldWriteLefthookConfig(lefthookTarget) {
71
+ let stat;
72
+ try {
73
+ stat = fs.lstatSync(lefthookTarget);
74
+ } catch (error) {
75
+ if (error && error.code !== 'ENOENT') {
76
+ throw error;
77
+ }
78
+ return true; // no existing file → write Forge's config
79
+ }
80
+ // Never write THROUGH a symlink (or any non-regular file): a checked-out lefthook.yml
81
+ // pointing outside projectRoot could otherwise be created/overwritten via the
82
+ // follow-through of readFileSync/writeFileSync. Only regular files are safe.
83
+ if (!stat.isFile() || stat.isSymbolicLink()) {
84
+ return false;
85
+ }
86
+ const content = fs.readFileSync(lefthookTarget, 'utf8');
87
+ const activeLines = content
88
+ .split(/\r?\n/)
89
+ .map((line) => line.trim())
90
+ .filter((line) => line.length > 0 && !line.startsWith('#'));
91
+ return activeLines.length === 0;
92
+ }
93
+
94
+ /**
95
+ * Resolve the directory git ACTUALLY reads hooks from for a given project root.
96
+ *
97
+ * Asks git first (`git rev-parse --git-path hooks`), which is authoritative: it
98
+ * honors `core.hooksPath` (husky, a global config, etc.), so verify checks — and the
99
+ * native installer writes to — the directory git will really execute. Without this a
100
+ * project with `core.hooksPath` set would get hooks written to `.git/hooks` that git
101
+ * never runs, and verify would falsely report ACTIVE (a silent-inert false pass, B3).
102
+ *
103
+ * Falls back to a pure-filesystem resolution when git is unavailable or the path is not
104
+ * a git repo — keeps the function testable against synthetic `.git` layouts:
105
+ * - Normal repo: `.git` is a directory → `<root>/.git/hooks`.
106
+ * - Linked worktree: `.git` is a file `gitdir: <path>`; hooks live in the common git
107
+ * dir (resolved via the worktree's `commondir` file) → `<common>/hooks`.
108
+ *
109
+ * @param {string} projectRoot - Absolute path to the project root.
110
+ * @returns {string|null} Absolute hooks dir, or null when not a git repo.
111
+ */
112
+ function resolveGitHooksDir(projectRoot) {
113
+ return resolveHooksDirViaGit(projectRoot) || resolveGitHooksDirFromFs(projectRoot);
114
+ }
115
+
116
+ // Authoritative resolution via git (honors core.hooksPath). One fast spawn; returns
117
+ // null on any failure (git missing, not a repo) so the filesystem fallback can run.
118
+ function resolveHooksDirViaGit(projectRoot) {
119
+ // SAME-REPO GUARD (kernel N1 / B3): only trust git's answer when projectRoot is its OWN
120
+ // repo — a `.git` directory or a linked-worktree `.git` file present right here. Without
121
+ // this, `git -C <projectRoot> rev-parse` walks UP to an ANCESTOR repo when projectRoot is
122
+ // a not-yet-initialized folder sitting under one (e.g. a project inside a dotfiles-managed
123
+ // $HOME, or a subdir of any repo). We'd then write hooks into — and report ACTIVE from —
124
+ // a repo the user never pointed forge at (cross-repo mutation + a false-active verify).
125
+ // Returning null here defers to the filesystem fallback (which also returns null for such
126
+ // a path), so the caller warns "not a git repository" — the safe pre-F3 behavior.
127
+ if (!fs.existsSync(path.join(projectRoot, '.git'))) {
128
+ return null;
129
+ }
130
+ try {
131
+ // secureExecFileSync resolves `git` to its absolute path first (no PATH search, S4036).
132
+ // projectRoot is passed as an ARGUMENT to execFile (never a shell string), so it cannot
133
+ // be interpreted as a command — no command-injection surface.
134
+ const out = secureExecFileSync(
135
+ 'git',
136
+ ['-C', projectRoot, 'rev-parse', '--path-format=absolute', '--git-path', 'hooks'],
137
+ { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }
138
+ );
139
+ return (typeof out === 'string' && out.trim()) ? out.trim() : null;
140
+ } catch {
141
+ return null;
142
+ }
143
+ }
144
+
145
+ // Pure-filesystem fallback (no git spawn). Does NOT see core.hooksPath — only used when
146
+ // git could not answer.
147
+ function resolveGitHooksDirFromFs(projectRoot) {
148
+ const gitPath = path.join(projectRoot, '.git');
149
+ let stat;
150
+ try {
151
+ stat = fs.lstatSync(gitPath);
152
+ } catch {
153
+ return null;
154
+ }
155
+ if (stat.isDirectory()) {
156
+ return path.join(gitPath, 'hooks');
157
+ }
158
+ if (stat.isFile()) {
159
+ return resolveWorktreeHooksDir(gitPath, projectRoot);
160
+ }
161
+ return null;
162
+ }
163
+
164
+ // A linked worktree's `.git` is a file containing `gitdir: <path>`. Its hooks live in
165
+ // the common repo's hooks dir (resolved via the worktree's `commondir` file). Returns
166
+ // null when the pointer file is unreadable or malformed.
167
+ function resolveWorktreeHooksDir(gitFile, projectRoot) {
168
+ let content;
169
+ try {
170
+ content = fs.readFileSync(gitFile, 'utf8');
171
+ } catch {
172
+ return null;
173
+ }
174
+ const match = content.match(/gitdir:\s*(.+)/);
175
+ if (!match) return null;
176
+ const rawGitdir = match[1].trim();
177
+ const gitdir = path.isAbsolute(rawGitdir) ? rawGitdir : path.resolve(projectRoot, rawGitdir);
178
+ return path.join(resolveWorktreeCommonDir(gitdir), 'hooks');
179
+ }
180
+
181
+ // Follow the worktree's `commondir` pointer to the common git dir; fall back to the
182
+ // per-worktree gitdir when it is absent or unreadable.
183
+ function resolveWorktreeCommonDir(gitdir) {
184
+ const commonDirFile = path.join(gitdir, 'commondir');
185
+ try {
186
+ if (fs.existsSync(commonDirFile)) {
187
+ const rel = fs.readFileSync(commonDirFile, 'utf8').trim();
188
+ return path.isAbsolute(rel) ? rel : path.resolve(gitdir, rel);
189
+ }
190
+ } catch {
191
+ // fall through to the per-worktree gitdir
192
+ }
193
+ return gitdir;
194
+ }
195
+
196
+ const NATIVE_HOOK_BODIES = {
197
+ 'pre-commit': `#!/bin/sh
198
+ ${FORGE_NATIVE_HOOK_SENTINEL}
199
+ # Fallback TDD gate used when the lefthook binary is unavailable. Enforces the same
200
+ # check as the lefthook pre-commit job. Remove this file only if you install lefthook.
201
+ if [ -f ".forge/hooks/check-tdd.js" ]; then
202
+ node ".forge/hooks/check-tdd.js" || exit 1
203
+ fi
204
+ `,
205
+ 'pre-push': `#!/bin/sh
206
+ ${FORGE_NATIVE_HOOK_SENTINEL}
207
+ # Fallback pre-push gate used when the lefthook binary is unavailable. Runs your test
208
+ # script before pushing (a no-op when the project has none).
209
+ npm test --if-present || exit 1
210
+ `,
211
+ };
212
+
213
+ /**
214
+ * Install native `.git/hooks` pre-commit/pre-push scripts that invoke Forge's own
215
+ * TDD gate. This is the fallback that keeps enforcement live when the lefthook binary
216
+ * cannot be installed (e.g. a fresh consumer repo with no package.json), so `git
217
+ * commit` / `git push` are never silently unguarded.
218
+ *
219
+ * A pre-existing hook that is neither Forge-managed nor lefthook-managed is preserved:
220
+ * it is backed up to `<hook>.forge-backup` (once) and left in place (reported in
221
+ * `skipped`) rather than destroyed.
222
+ *
223
+ * @param {string} projectRoot - Absolute path to the project root.
224
+ * @returns {{ installed: boolean, method?: string, hooksDir?: string,
225
+ * written?: string[], skipped?: string[], reason?: string }}
226
+ */
227
+ function installNativeGitHooks(projectRoot) {
228
+ const hooksDir = resolveGitHooksDir(projectRoot);
229
+ if (!hooksDir) {
230
+ return { installed: false, reason: 'not-a-git-repo' };
231
+ }
232
+ try {
233
+ fs.mkdirSync(hooksDir, { recursive: true });
234
+ } catch (error) {
235
+ return { installed: false, reason: `hooks-dir-unwritable: ${error.message}` };
236
+ }
237
+
238
+ const written = [];
239
+ const skipped = [];
240
+ for (const [name, body] of Object.entries(NATIVE_HOOK_BODIES)) {
241
+ const outcome = writeNativeHook(path.join(hooksDir, name), body);
242
+ (outcome === 'written' ? written : skipped).push(name);
243
+ }
244
+
245
+ return {
246
+ installed: written.length > 0,
247
+ method: 'native',
248
+ hooksDir,
249
+ written,
250
+ skipped,
251
+ };
252
+ }
253
+
254
+ // Signatures that appear ONLY in a hook lefthook itself GENERATED — never in a user's own
255
+ // hook that merely runs `lefthook ...` among other steps. Lefthook's generated hooks define
256
+ // a `call_lefthook()` shim and honor the `LEFTHOOK`/`LEFTHOOK_BIN` env bypass; a custom hook
257
+ // invoking lefthook does not. Matching these (not the bare 'lefthook' substring) is what
258
+ // stops us from misclassifying a user's hook as a disposable generated artifact (F4 / kernel
259
+ // 269e5d05).
260
+ const LEFTHOOK_GENERATED_MARKERS = ['call_lefthook', 'LEFTHOOK_BIN'];
261
+
262
+ // True only for a hook lefthook GENERATED (safe to regenerate/overwrite), never for a user
263
+ // hook that merely invokes lefthook.
264
+ function isLefthookGeneratedHook(content) {
265
+ return LEFTHOOK_GENERATED_MARKERS.some((marker) => content.includes(marker));
266
+ }
267
+
268
+ // Write a single native hook. A pre-existing hook that is not Forge's own is ALWAYS backed
269
+ // up to `<hook>.forge-backup` first (once), so a user's hook is never destroyed without a
270
+ // copy — whether we preserve it (skip) or overwrite a disposable lefthook-generated one.
271
+ // Returns 'written' or 'skipped'.
272
+ function writeNativeHook(dest, body) {
273
+ if (shouldPreserveExistingHook(dest)) {
274
+ backupHookOnce(dest);
275
+ return 'skipped';
276
+ }
277
+ // Back up before overwriting an existing non-Forge hook (e.g. a lefthook-generated one, or
278
+ // anything a future classification change might reach) — never overwrite without a backup.
279
+ backupExistingNonForgeHook(dest);
280
+ fs.writeFileSync(dest, body, 'utf8');
281
+ try {
282
+ fs.chmodSync(dest, 0o755); // NOSONAR — hooks must be executable
283
+ } catch {
284
+ // Windows filesystems ignore the mode bits — non-fatal.
285
+ }
286
+ return 'written';
287
+ }
288
+
289
+ // A hook is ours to overwrite ONLY when it is missing, Forge-managed, or a genuine
290
+ // lefthook-GENERATED artifact. Anything else — including a user's custom hook that merely
291
+ // INVOKES lefthook among other steps — is preserved (F4 / kernel 269e5d05: a bare 'lefthook'
292
+ // substring test used to clobber such a user hook without a backup).
293
+ function shouldPreserveExistingHook(dest) {
294
+ if (!fs.existsSync(dest)) return false;
295
+ let existing;
296
+ try {
297
+ existing = fs.readFileSync(dest, 'utf8');
298
+ } catch {
299
+ existing = '';
300
+ }
301
+ if (existing.includes(FORGE_NATIVE_HOOK_SENTINEL)) return false; // our own hook
302
+ if (isLefthookGeneratedHook(existing)) return false; // disposable generated artifact
303
+ return true; // the user's / a third party's hook — preserve it
304
+ }
305
+
306
+ // Back up an existing hook that is NOT Forge's own before it is overwritten (once, never
307
+ // clobbering an existing backup). No-op when the file is missing/unreadable or already ours.
308
+ function backupExistingNonForgeHook(dest) {
309
+ let existing;
310
+ try {
311
+ existing = fs.readFileSync(dest, 'utf8');
312
+ } catch {
313
+ return; // missing (ENOENT) or unreadable — nothing to back up
314
+ }
315
+ if (!existing.includes(FORGE_NATIVE_HOOK_SENTINEL)) {
316
+ backupHookOnce(dest);
317
+ }
318
+ }
319
+
320
+ // Back up a hook to `<hook>.forge-backup` once (never clobber an existing backup).
321
+ function backupHookOnce(dest) {
322
+ const backup = `${dest}.forge-backup`;
323
+ try {
324
+ if (!fs.existsSync(backup)) fs.copyFileSync(dest, backup);
325
+ } catch {
326
+ // Best-effort backup; the caller still refuses to overwrite the original.
327
+ }
328
+ }
329
+
330
+ // The TDD gate script the native hook (and the lefthook pre-commit job) invokes, relative
331
+ // to the project root. The native hook guards its call with `if [ -f … ]`, so when this file
332
+ // is absent the hook runs but enforces nothing.
333
+ const FORGE_TDD_GATE_SCRIPT = path.join('.forge', 'hooks', 'check-tdd.js');
334
+
335
+ // Confirm the TDD gate script exists as a runnable (readable) regular file under projectRoot.
336
+ // Returns { ok: true } or { ok: false, reason } with a loud, specific reason.
337
+ function gateScriptStatus(projectRoot) {
338
+ const gatePath = path.join(projectRoot, FORGE_TDD_GATE_SCRIPT);
339
+ let stat;
340
+ try {
341
+ stat = fs.statSync(gatePath);
342
+ } catch {
343
+ return { ok: false, reason: `native pre-commit hook present but its gate script is missing: ${FORGE_TDD_GATE_SCRIPT}` };
344
+ }
345
+ if (!stat.isFile()) {
346
+ return { ok: false, reason: `native pre-commit hook gate script is not a regular file: ${FORGE_TDD_GATE_SCRIPT}` };
347
+ }
348
+ try {
349
+ fs.accessSync(gatePath, fs.constants.R_OK); // `node <script>` needs it readable
350
+ } catch {
351
+ return { ok: false, reason: `native pre-commit hook gate script is not readable: ${FORGE_TDD_GATE_SCRIPT}` };
352
+ }
353
+ return { ok: true };
354
+ }
355
+
356
+ /**
357
+ * Report — honestly — whether git hooks are actually active for the project, so
358
+ * `forge setup` can fail LOUDLY instead of silently no-op'ing. A hook counts as
359
+ * active only when a `pre-commit` hook exists AND is either lefthook-managed or
360
+ * carries Forge's native sentinel with its gate script present and runnable.
361
+ *
362
+ * @param {string} projectRoot - Absolute path to the project root.
363
+ * @returns {{ active: boolean, method: 'lefthook'|'native'|'none'|'unknown', reason?: string, hook?: string }}
364
+ */
365
+ function verifyHooksActive(projectRoot) {
366
+ const hooksDir = resolveGitHooksDir(projectRoot);
367
+ if (!hooksDir) {
368
+ return { active: false, method: 'none', reason: 'not a git repository' };
369
+ }
370
+ const preCommit = path.join(hooksDir, 'pre-commit');
371
+ if (!fs.existsSync(preCommit)) {
372
+ return { active: false, method: 'none', reason: 'no pre-commit hook installed' };
373
+ }
374
+ let body;
375
+ try {
376
+ body = fs.readFileSync(preCommit, 'utf8');
377
+ } catch (error) {
378
+ return { active: false, method: 'unknown', reason: `pre-commit unreadable: ${error.message}` };
379
+ }
380
+ // Check the Forge native sentinel FIRST: our native hook body mentions "lefthook"
381
+ // in a comment, so a substring test for lefthook would otherwise misclassify it.
382
+ if (body.includes(FORGE_NATIVE_HOOK_SENTINEL)) {
383
+ // F5 (kernel d96af31a): the native hook body only runs the gate when its script exists
384
+ // (`if [ -f ".forge/hooks/check-tdd.js" ]`). A present hook whose gate script is missing
385
+ // silently no-ops — so verify must confirm the script exists AND is runnable, else report
386
+ // NOT active (loud) rather than a false pass. installForgeHookScripts() copies check-tdd.js
387
+ // unconditionally, but silently continues if the packaged source was absent, so this is
388
+ // exactly the case a truthful verify has to catch.
389
+ const gate = gateScriptStatus(projectRoot);
390
+ if (!gate.ok) {
391
+ return { active: false, method: 'native', reason: gate.reason, hook: preCommit };
392
+ }
393
+ return { active: true, method: 'native', hook: preCommit };
394
+ }
395
+ if (body.includes('lefthook')) {
396
+ return { active: true, method: 'lefthook', hook: preCommit };
397
+ }
398
+ return {
399
+ active: false,
400
+ method: 'unknown',
401
+ reason: 'pre-commit hook present but not Forge- or lefthook-managed',
402
+ hook: preCommit,
403
+ };
404
+ }
405
+
406
+ module.exports = {
407
+ FORGE_NATIVE_HOOK_SENTINEL,
408
+ FORGE_USER_LEFTHOOK_YML,
409
+ forgeShouldWriteLefthookConfig,
410
+ resolveGitHooksDir,
411
+ installNativeGitHooks,
412
+ verifyHooksActive,
413
+ };