forge-workflow 0.0.10 → 0.1.0-beta.3

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 (468) 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 +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -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 +121 -0
  29. package/docs/guides/SUPPORT.md +190 -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 +214 -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 +155 -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/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -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/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  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.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,262 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const LOCKFILE_NAME = 'forge.lock';
8
+ const AUDIT_LOG_PATH = path.join('.forge', 'log.jsonl');
9
+ const REMOTE_SOURCE_PATTERN = /^(?:https?:|gh:|gist:|npm:)/i;
10
+
11
+ function toPosixPath(value) {
12
+ return String(value || '').replaceAll('\\', '/');
13
+ }
14
+
15
+ function normalizeRelativePath(projectRoot, fullPath) {
16
+ return toPosixPath(path.relative(projectRoot, fullPath));
17
+ }
18
+
19
+ function isInsideRoot(projectRoot, fullPath) {
20
+ const relative = path.relative(projectRoot, fullPath);
21
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
22
+ }
23
+
24
+ function lockPath(projectRoot) {
25
+ return path.join(projectRoot, LOCKFILE_NAME);
26
+ }
27
+
28
+ function defaultLock() {
29
+ return {
30
+ version: 1,
31
+ generatedBy: 'forge',
32
+ extensions: [],
33
+ };
34
+ }
35
+
36
+ function readForgeLock(projectRoot) {
37
+ const filePath = lockPath(projectRoot);
38
+ if (!fs.existsSync(filePath)) return defaultLock();
39
+ const parsed = JSON.parse(fs.readFileSync(filePath, 'utf8'));
40
+ return {
41
+ ...defaultLock(),
42
+ ...parsed,
43
+ extensions: Array.isArray(parsed.extensions) ? parsed.extensions : [],
44
+ };
45
+ }
46
+
47
+ function writeForgeLock(projectRoot, lock) {
48
+ fs.writeFileSync(lockPath(projectRoot), `${JSON.stringify(lock, null, 2)}\n`, 'utf8');
49
+ }
50
+
51
+ function appendAuditEvent(projectRoot, event) {
52
+ const logPath = path.join(projectRoot, AUDIT_LOG_PATH);
53
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
54
+ fs.appendFileSync(logPath, `${JSON.stringify({
55
+ kind: 'forge.lock',
56
+ schemaVersion: 1,
57
+ recordedAt: new Date().toISOString(),
58
+ ...event,
59
+ })}\n`, 'utf8');
60
+ }
61
+
62
+ function isRemoteSource(source) {
63
+ return REMOTE_SOURCE_PATTERN.test(String(source || ''));
64
+ }
65
+
66
+ function resolveLocalSource(projectRoot, source) {
67
+ const root = path.resolve(projectRoot);
68
+ const resolved = path.resolve(root, source);
69
+ if (!isInsideRoot(root, resolved)) {
70
+ throw new Error(`Source must stay inside project root: ${source}`);
71
+ }
72
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) {
73
+ throw new Error(`Local source does not exist: ${source}`);
74
+ }
75
+ const realRoot = fs.realpathSync(root);
76
+ const realResolved = fs.realpathSync(resolved);
77
+ if (!isInsideRoot(realRoot, realResolved)) {
78
+ throw new Error(`Source must stay inside project root: ${source}`);
79
+ }
80
+ if (!fs.statSync(realResolved).isFile()) {
81
+ throw new Error(`Local source does not exist: ${source}`);
82
+ }
83
+ return realResolved;
84
+ }
85
+
86
+ function sourceIntegrity(filePath) {
87
+ const digest = crypto
88
+ .createHash('sha512')
89
+ .update(fs.readFileSync(filePath))
90
+ .digest('base64');
91
+ return `sha512-${digest}`;
92
+ }
93
+
94
+ function verifyUnsupportedRemoteEntry(entry) {
95
+ if (!isRemoteSource(entry.source)) {
96
+ return {
97
+ name: entry.name,
98
+ status: 'fail',
99
+ reason: 'unsupported remote verification requires a remote source locator',
100
+ };
101
+ }
102
+ if (entry.resolvedPath || entry.integrity || entry.trust?.allowUntrusted !== true) {
103
+ return {
104
+ name: entry.name,
105
+ status: 'fail',
106
+ reason: 'unsupported remote entry has inconsistent lock metadata',
107
+ };
108
+ }
109
+ return {
110
+ name: entry.name,
111
+ status: 'warn',
112
+ reason: 'remote source integrity cannot be rechecked by this foundation',
113
+ };
114
+ }
115
+
116
+ function classifySource(projectRoot, source, options = {}) {
117
+ if (!source) {
118
+ throw new Error('Source is required');
119
+ }
120
+
121
+ if (isRemoteSource(source)) {
122
+ if (!options.allowUntrusted) {
123
+ throw new Error(`Untrusted source refused: ${source}. Re-run with --allow-untrusted to record it explicitly.`);
124
+ }
125
+ return {
126
+ trusted: false,
127
+ allowUntrusted: true,
128
+ resolvedPath: null,
129
+ integrity: null,
130
+ verification: 'unsupported-remote',
131
+ reason: 'remote locator requires explicit --allow-untrusted',
132
+ };
133
+ }
134
+
135
+ const resolvedPath = resolveLocalSource(projectRoot, source);
136
+ return {
137
+ trusted: true,
138
+ allowUntrusted: false,
139
+ resolvedPath,
140
+ integrity: sourceIntegrity(resolvedPath),
141
+ verification: 'sri',
142
+ reason: 'local file inside project root',
143
+ };
144
+ }
145
+
146
+ function upsertExtension(extensions, entry) {
147
+ const next = extensions.filter(existing => existing.name !== entry.name);
148
+ next.push(entry);
149
+ return next.sort((a, b) => a.name.localeCompare(b.name));
150
+ }
151
+
152
+ function addLockEntry(projectRoot, options) {
153
+ const root = path.resolve(projectRoot);
154
+ const realRoot = fs.realpathSync(root);
155
+ const sourceInfo = classifySource(root, options.source, {
156
+ allowUntrusted: Boolean(options.allowUntrusted),
157
+ });
158
+ const lock = readForgeLock(root);
159
+ const entry = {
160
+ name: options.name,
161
+ source: options.source,
162
+ resolvedPath: sourceInfo.resolvedPath ? normalizeRelativePath(realRoot, sourceInfo.resolvedPath) : null,
163
+ integrity: sourceInfo.integrity,
164
+ verification: sourceInfo.verification,
165
+ trust: {
166
+ trusted: sourceInfo.trusted,
167
+ allowUntrusted: sourceInfo.allowUntrusted,
168
+ reason: sourceInfo.reason,
169
+ },
170
+ lockedAt: new Date().toISOString(),
171
+ };
172
+
173
+ lock.extensions = upsertExtension(lock.extensions, entry);
174
+ writeForgeLock(root, lock);
175
+ appendAuditEvent(root, {
176
+ action: 'add',
177
+ name: entry.name,
178
+ source: entry.source,
179
+ trusted: entry.trust.trusted,
180
+ verification: entry.verification,
181
+ integrity: entry.integrity,
182
+ });
183
+
184
+ return { lock, entry };
185
+ }
186
+
187
+ function verifyEntry(projectRoot, entry) {
188
+ if (entry.verification === 'unsupported-remote') {
189
+ return verifyUnsupportedRemoteEntry(entry);
190
+ }
191
+ if (isRemoteSource(entry.source) || isRemoteSource(entry.resolvedPath)) {
192
+ return {
193
+ name: entry.name,
194
+ status: 'fail',
195
+ reason: 'remote source locator requires unsupported-remote verification policy',
196
+ };
197
+ }
198
+
199
+ const relativePath = entry.resolvedPath || entry.source;
200
+ const root = path.resolve(projectRoot);
201
+ const realRoot = fs.realpathSync(root);
202
+ const fullPath = path.resolve(realRoot, relativePath);
203
+ if (!isInsideRoot(realRoot, fullPath)) {
204
+ return {
205
+ name: entry.name,
206
+ status: 'fail',
207
+ reason: `source escapes project root: ${relativePath}`,
208
+ };
209
+ }
210
+ if (!fs.existsSync(fullPath) || !fs.statSync(fullPath).isFile()) {
211
+ return {
212
+ name: entry.name,
213
+ status: 'fail',
214
+ reason: `source missing: ${relativePath}`,
215
+ };
216
+ }
217
+ const realFullPath = fs.realpathSync(fullPath);
218
+ if (!isInsideRoot(realRoot, realFullPath)) {
219
+ return {
220
+ name: entry.name,
221
+ status: 'fail',
222
+ reason: `source escapes project root: ${relativePath}`,
223
+ };
224
+ }
225
+
226
+ const actual = sourceIntegrity(realFullPath);
227
+ if (actual !== entry.integrity) {
228
+ return {
229
+ name: entry.name,
230
+ status: 'fail',
231
+ reason: `integrity mismatch: expected ${entry.integrity}, got ${actual}`,
232
+ };
233
+ }
234
+
235
+ return {
236
+ name: entry.name,
237
+ status: 'pass',
238
+ reason: 'integrity verified',
239
+ };
240
+ }
241
+
242
+ function verifyForgeLock(projectRoot) {
243
+ const lock = readForgeLock(projectRoot);
244
+ const results = lock.extensions.map(entry => verifyEntry(projectRoot, entry));
245
+ return {
246
+ ok: results.every(result => result.status !== 'fail'),
247
+ path: LOCKFILE_NAME,
248
+ results,
249
+ lock,
250
+ };
251
+ }
252
+
253
+ module.exports = {
254
+ LOCKFILE_NAME,
255
+ AUDIT_LOG_PATH,
256
+ addLockEntry,
257
+ classifySource,
258
+ readForgeLock,
259
+ sourceIntegrity,
260
+ verifyForgeLock,
261
+ writeForgeLock,
262
+ };
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @module gate-events
5
+ *
6
+ * Human gates enforced by kernel EVENTS, not skill prose (Fable's key insight,
7
+ * locked in docs/work/2026-07-04-kernel-native-skills/decisions.md). A gate
8
+ * approval is a durable, queryable, resume-safe kernel event on the ISSUE:
9
+ * `gate.approved` / `gate.rejected` by an actor. `forge gate check` (and, later,
10
+ * the ship stage) refuses to proceed without the required `gate.approved` event
11
+ * UNLESS the gate is toggled off — giving evidence-checked HARD-GATEs and
12
+ * resume-from-kernel-state after a compaction/crash for free.
13
+ *
14
+ * Mechanism: a PURE APPEND to the issue's event stream via the driver primitives
15
+ * (`insertKernelEvent` + `listKernelEvents`), NOT the guarded issue-mutation
16
+ * pipeline. Gate events do not mutate the issue, so they must not participate in
17
+ * the issue-revision CAS (`lib/kernel/evaluators.js` would otherwise quarantine an
18
+ * `expected_revision:0` append against a non-zero issue revision as `stale_revision`).
19
+ * Idempotency is enforced two ways: a pre-check on the idempotency key, plus the
20
+ * unique index `idx_kernel_events_idempotency` catching a concurrent race.
21
+ *
22
+ * Config-surface note: which gates EXIST (and whether each is enabled) lives in the
23
+ * runtime graph + `.forge/config.yaml`; this module only records/reads the events.
24
+ */
25
+
26
+ const { resolveOwnedKernel, closeIfOwned } = require('./kernel/owned-kernel');
27
+ const { resolveIssueActor } = require('./forge-issues');
28
+
29
+ const GATE_APPROVED_EVENT = 'gate.approved';
30
+ const GATE_REJECTED_EVENT = 'gate.rejected';
31
+
32
+ const GATE_EVENT_TYPES = {
33
+ approved: GATE_APPROVED_EVENT,
34
+ rejected: GATE_REJECTED_EVENT,
35
+ };
36
+
37
+ const ISSUE_ENTITY_TYPE = 'issue';
38
+ const GATE_EVENT_ORIGIN = 'cli';
39
+
40
+ // Kernel lifecycle (resolve + close-what-you-built) is shared with
41
+ // grounding/context-events via lib/kernel/owned-kernel. A gate read/append that
42
+ // builds its own kernel must close it — an unclosed SQLite handle leaks and, on
43
+ // Windows, locks the DB directory (EBUSY on rmSync, kernel issue e62e4bde). Safe
44
+ // here today because gate events only fire on explicit `forge gate` commands,
45
+ // but the leak is identical so we close proactively via closeIfOwned.
46
+
47
+ /**
48
+ * Idempotency key for a gate event. Scoped to issue + gate + actor + decision so a
49
+ * SAME-actor re-approval mints no duplicate, while a later reject (different
50
+ * decision) or a different actor is recorded as its own event.
51
+ */
52
+ function gateIdempotencyKey(eventType, issueId, gateId, actor) {
53
+ return `${eventType}:${issueId}:${gateId}:${actor}`;
54
+ }
55
+
56
+ /** Shape a stored kernel_events row into the gate-event view callers consume. */
57
+ function parseGateEvent(row) {
58
+ let payload;
59
+ try {
60
+ payload = row.payload_json ? JSON.parse(row.payload_json) : {};
61
+ } catch {
62
+ payload = {};
63
+ }
64
+ const view = {
65
+ event_type: row.event_type,
66
+ gate: payload.gate,
67
+ actor: row.actor,
68
+ created_at: row.created_at,
69
+ };
70
+ if (payload.reason !== undefined) view.reason = payload.reason;
71
+ return view;
72
+ }
73
+
74
+ function isIdempotencyRace(error) {
75
+ const message = error && error.message ? String(error.message) : '';
76
+ return /UNIQUE constraint failed/i.test(message) && /idempotency_key/i.test(message);
77
+ }
78
+
79
+ /**
80
+ * Record a gate decision as a kernel event on the issue. Idempotent per
81
+ * issue+gate+actor+decision. Validates the issue exists (the caller validates the
82
+ * gate id against the known-gate set before calling).
83
+ *
84
+ * @param {string} projectRoot
85
+ * @param {Object} params
86
+ * @param {string} params.issueId
87
+ * @param {string} params.gateId
88
+ * @param {'approved'|'rejected'} params.decision
89
+ * @param {string} [params.reason]
90
+ * @param {Object} [params.env] - env source for actor resolution (FORGE_ACTOR → …).
91
+ * @param {Object} [params.deps] - injected { kernelBroker, kernelDriver }.
92
+ * @param {string} [params.now] - ISO timestamp (defaults to now).
93
+ * @returns {Promise<{ ok: boolean, duplicate?: boolean, issueMissing?: boolean, event?: Object, actor?: string }>}
94
+ */
95
+ async function recordGateEvent(projectRoot, params = {}) {
96
+ const { issueId, gateId, decision, reason, env, deps, now } = params;
97
+ const eventType = GATE_EVENT_TYPES[decision];
98
+ if (!eventType) {
99
+ throw new Error(`Unknown gate decision '${decision}' (expected 'approved' or 'rejected').`);
100
+ }
101
+
102
+ const actor = resolveIssueActor(env || process.env) || 'forge';
103
+ const kernel = await resolveOwnedKernel(projectRoot, deps);
104
+ const { driver, config } = kernel;
105
+
106
+ try {
107
+ const entity = await driver.loadKernelEntity(ISSUE_ENTITY_TYPE, issueId, {}, config);
108
+ if (!entity) {
109
+ return { ok: false, issueMissing: true, actor };
110
+ }
111
+
112
+ const idempotencyKey = gateIdempotencyKey(eventType, issueId, gateId, actor);
113
+
114
+ const existing = await driver.loadKernelEventByIdempotencyKey(idempotencyKey, {}, config);
115
+ if (existing) {
116
+ return { ok: true, duplicate: true, event: parseGateEvent(existing), actor };
117
+ }
118
+
119
+ const payload = { gate: gateId, actor };
120
+ if (typeof reason === 'string' && reason.length > 0) payload.reason = reason;
121
+
122
+ const event = {
123
+ entity_type: ISSUE_ENTITY_TYPE,
124
+ entity_id: issueId,
125
+ event_type: eventType,
126
+ idempotency_key: idempotencyKey,
127
+ expected_revision: 0,
128
+ actor,
129
+ origin: GATE_EVENT_ORIGIN,
130
+ payload,
131
+ created_at: now || new Date().toISOString(),
132
+ };
133
+
134
+ try {
135
+ const inserted = await driver.insertKernelEvent(event, {}, config);
136
+ return { ok: true, duplicate: false, event: parseGateEvent(inserted), actor };
137
+ } catch (error) {
138
+ if (isIdempotencyRace(error)) {
139
+ const winner = await driver.loadKernelEventByIdempotencyKey(idempotencyKey, {}, config);
140
+ return { ok: true, duplicate: true, event: winner ? parseGateEvent(winner) : parseGateEvent(event), actor };
141
+ }
142
+ throw error;
143
+ }
144
+ } finally {
145
+ closeIfOwned(kernel);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * List every gate event on an issue, oldest first.
151
+ *
152
+ * @param {string} projectRoot
153
+ * @param {string} issueId
154
+ * @param {{ deps?: Object }} [options]
155
+ * @returns {Promise<Array<{ event_type: string, gate: string, actor: string, created_at: string, reason?: string }>>}
156
+ */
157
+ async function listGateEvents(projectRoot, issueId, options = {}) {
158
+ const kernel = await resolveOwnedKernel(projectRoot, options.deps);
159
+ try {
160
+ const rows = await kernel.driver.listKernelEvents(ISSUE_ENTITY_TYPE, issueId, {}, kernel.config);
161
+ return (rows || [])
162
+ .filter(row => typeof row.event_type === 'string' && row.event_type.startsWith('gate.'))
163
+ .map(parseGateEvent);
164
+ } finally {
165
+ closeIfOwned(kernel);
166
+ }
167
+ }
168
+
169
+ /**
170
+ * True iff a `gate.approved` event exists for this issue + gate.
171
+ *
172
+ * @param {string} projectRoot
173
+ * @param {string} issueId
174
+ * @param {string} gateId
175
+ * @param {{ deps?: Object }} [options]
176
+ * @returns {Promise<boolean>}
177
+ */
178
+ async function isGateApproved(projectRoot, issueId, gateId, options = {}) {
179
+ const events = await listGateEvents(projectRoot, issueId, options);
180
+ return events.some(event => event.event_type === GATE_APPROVED_EVENT && event.gate === gateId);
181
+ }
182
+
183
+ module.exports = {
184
+ GATE_APPROVED_EVENT,
185
+ GATE_REJECTED_EVENT,
186
+ GATE_EVENT_TYPES,
187
+ gateIdempotencyKey,
188
+ parseGateEvent,
189
+ recordGateEvent,
190
+ listGateEvents,
191
+ isGateApproved,
192
+ };
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Global CLI flags recognized by bin/forge.js parseGlobalFlags().
5
+ *
6
+ * bin/forge.js parses these into the `flags` object but leaves them inside the
7
+ * positional `args` array it hands to command handlers. Free-text commands
8
+ * (remember, recall) that join positionals into content must strip them first,
9
+ * or `forge remember "note" -p <dir>` stores the literal `note -p <dir>`
10
+ * (kernel issue c1e090ff).
11
+ *
12
+ * Keep these sets in sync with parseGlobalFlags() in bin/forge.js.
13
+ * @module lib/global-flags
14
+ */
15
+
16
+ // Flags that consume a following value token (also accepted as --flag=value).
17
+ const GLOBAL_VALUE_FLAGS = new Set([
18
+ '--path', '-p',
19
+ '--agents',
20
+ '--merge',
21
+ '--type',
22
+ '--budget',
23
+ ]);
24
+
25
+ const GLOBAL_VALUE_FLAG_PREFIXES = ['--path=', '--agents=', '--merge=', '--type=', '--budget='];
26
+
27
+ // Boolean flags that take no value.
28
+ const GLOBAL_BOOLEAN_FLAGS = new Set([
29
+ '--quick', '-q',
30
+ '--skip-external', '--skip-services',
31
+ '--all',
32
+ '--help', '-h',
33
+ '--version', '-V',
34
+ '--yes', '-y',
35
+ '--non-interactive',
36
+ '--force',
37
+ '--verbose',
38
+ '--dry-run',
39
+ '--symlink',
40
+ '--sync',
41
+ '--interview',
42
+ ]);
43
+
44
+ /**
45
+ * Remove recognized global flags (and their values) from a positional args
46
+ * array. Plain words that merely resemble flag names (no leading dash) are
47
+ * always kept.
48
+ *
49
+ * @param {string[]} args - Raw command arguments.
50
+ * @returns {string[]} Arguments with global flags stripped.
51
+ */
52
+ function stripGlobalFlags(args) {
53
+ const kept = [];
54
+ for (let index = 0; index < args.length; index += 1) {
55
+ const arg = args[index];
56
+ if (GLOBAL_BOOLEAN_FLAGS.has(arg)) continue;
57
+ if (GLOBAL_VALUE_FLAG_PREFIXES.some((prefix) => arg.startsWith(prefix))) continue;
58
+ if (GLOBAL_VALUE_FLAGS.has(arg)) {
59
+ const next = args[index + 1];
60
+ // Mirror parsePathFlag() in bin/forge.js: the value is consumed only
61
+ // when it exists and is not itself a flag.
62
+ if (next !== undefined && !next.startsWith('-')) index += 1;
63
+ continue;
64
+ }
65
+ kept.push(arg);
66
+ }
67
+ return kept;
68
+ }
69
+
70
+ /**
71
+ * Index of the first positional token (a bare, non-flag argument), skipping any
72
+ * leading global flags and their consumed values. Returns -1 when no positional
73
+ * exists. Uses the SAME flag-consumption rules as {@link stripGlobalFlags} so the
74
+ * two agree on what counts as a positional. Unlike stripGlobalFlags this returns
75
+ * an index into the ORIGINAL array, so callers can splice out the positional
76
+ * while preserving the intervening flags.
77
+ *
78
+ * @param {string[]} args - Raw command arguments.
79
+ * @param {number} [start=0] - Index to begin scanning from.
80
+ * @returns {number} Index into `args` of the first positional token, or -1.
81
+ */
82
+ function firstPositionalIndex(args, start = 0) {
83
+ for (let index = start; index < args.length; index += 1) {
84
+ const arg = args[index];
85
+ if (GLOBAL_BOOLEAN_FLAGS.has(arg)) continue;
86
+ if (GLOBAL_VALUE_FLAG_PREFIXES.some((prefix) => arg.startsWith(prefix))) continue;
87
+ if (GLOBAL_VALUE_FLAGS.has(arg)) {
88
+ const next = args[index + 1];
89
+ if (next !== undefined && !next.startsWith('-')) index += 1;
90
+ continue;
91
+ }
92
+ // Any other dash-prefixed token is a non-global flag, not the positional.
93
+ if (arg.startsWith('-')) continue;
94
+ return index;
95
+ }
96
+ return -1;
97
+ }
98
+
99
+ module.exports = {
100
+ GLOBAL_BOOLEAN_FLAGS,
101
+ GLOBAL_VALUE_FLAGS,
102
+ stripGlobalFlags,
103
+ firstPositionalIndex,
104
+ };
@@ -1,80 +1,24 @@
1
1
  'use strict';
2
2
 
3
- const { execFileSync } = require('node:child_process');
3
+ const { GreptileReviewAdapter } = require('./adapters/greptile-review-adapter');
4
4
 
5
5
  /**
6
6
  * Match unresolved review threads to recent commits that touched the same files.
7
7
  *
8
- * For each thread, runs `git log --oneline --follow -- <file>` to find
9
- * recent commits. If a commit exists, the thread is considered resolved
10
- * (the file was modified after the review comment).
11
- *
12
- * Uses execFileSync (not exec) to prevent shell injection (OWASP A03).
8
+ * This backward-compatible export now delegates to the Greptile review adapter
9
+ * so existing Greptile scripts keep working while new review integrations use
10
+ * the ReviewAdapter SPI.
13
11
  *
14
12
  * @param {Array<{file: string, line: number}>} threads - Review threads with file paths
15
13
  * @param {string} projectRoot - Absolute path to the project root (used with git -C)
16
14
  * @param {object} [opts] - Options
17
- * @param {Function} [opts._exec] - Injected exec function for testing (defaults to execFileSync)
15
+ * @param {Function} [opts._exec] - Injected exec function for testing
18
16
  * @param {string} [opts.sinceCommit] - Merge-base SHA to restrict log to PR commits only
19
17
  * @returns {Array<{file: string, line: number, resolved: boolean, sha?: string, reason?: string}>}
20
18
  */
21
19
  function matchThreadsToCommits(threads, projectRoot, opts = {}) {
22
- if (!threads || threads.length === 0) {
23
- return [];
24
- }
25
-
26
- const exec = opts._exec || ((cmd, args) => {
27
- return execFileSync(cmd, args, { encoding: 'utf8' });
28
- });
29
-
30
- // Determine commit range — restrict to PR commits when sinceCommit provided
31
- let sinceCommit = opts.sinceCommit;
32
- if (!sinceCommit) {
33
- try {
34
- sinceCommit = exec('git', ['-C', projectRoot, 'merge-base', 'HEAD', 'main']).trim();
35
- } catch (_e) { // NOSONAR S2486
36
- /* intentional: main branch not found, try master instead */
37
- try {
38
- sinceCommit = exec('git', ['-C', projectRoot, 'merge-base', 'HEAD', 'master']).trim();
39
- } catch (_e2) { /* intentional: no merge-base available, fall back to unbounded log */ // NOSONAR S2486
40
- sinceCommit = null;
41
- }
42
- }
43
- }
44
-
45
- return threads.map((thread) => {
46
- const { file, line } = thread;
47
-
48
- try {
49
- const logArgs = [
50
- '-C', projectRoot,
51
- 'log',
52
- '--oneline',
53
- '--follow',
54
- ];
55
- if (sinceCommit) {
56
- logArgs.push(`${sinceCommit}..HEAD`);
57
- }
58
- logArgs.push('--', file);
59
-
60
- const output = exec('git', logArgs);
61
-
62
- const trimmed = (output || '').trim();
63
-
64
- if (!trimmed) {
65
- return { file, line, resolved: false, reason: 'no matching commit' };
66
- }
67
-
68
- // git log --oneline format: "<sha> <message>"
69
- // Take the first (most recent) line
70
- const firstLine = trimmed.split('\n')[0];
71
- const sha = firstLine.split(' ')[0];
72
-
73
- return { file, line, resolved: true, sha };
74
- } catch (_err) { /* intentional: git log failed (file missing or git unavailable), mark unresolved */ // NOSONAR S2486
75
- return { file, line, resolved: false, reason: 'no matching commit' };
76
- }
77
- });
20
+ const adapter = new GreptileReviewAdapter();
21
+ return adapter.score(threads, { ...opts, projectRoot });
78
22
  }
79
23
 
80
24
  module.exports = { matchThreadsToCommits };