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,88 @@
1
+ 'use strict';
2
+
3
+ const ship = require('./ship');
4
+ const preflight = require('./preflight');
5
+ const shepherd = require('./shepherd');
6
+ const merge = require('./merge');
7
+ const { stripGlobalFlags } = require('../global-flags');
8
+
9
+ // One memorable surface over the EXISTING pull-request commands (kernel issue
10
+ // 6ab3f30c): every subcommand delegates to the standalone ship/preflight/shepherd/
11
+ // merge handlers — the same code, not a reimplementation. The standalone
12
+ // `forge ship`/`preflight`/`shepherd`/`merge` commands remain registered as
13
+ // back-compat aliases (see lib/commands/_aliases.js), so nothing that already
14
+ // calls them breaks. `pr ship` is the canonical PR-creation form; bare `ship`
15
+ // stays a visible shortcut.
16
+ //
17
+ // Delegates are referenced by MODULE (not a pre-bound `.handler`) so the routed
18
+ // handler is resolved at dispatch time — dispatch always reaches whatever the
19
+ // command module currently exports, keeping the standalone command the single
20
+ // source of truth for its own behaviour.
21
+ const SUBCOMMANDS = {
22
+ ship: {
23
+ module: ship,
24
+ summary: 'Create a pull request from validated feature work (= forge ship)',
25
+ },
26
+ preflight: {
27
+ module: preflight,
28
+ summary: 'Fast deterministic-gate parity with CI (= forge preflight; supports --all)',
29
+ },
30
+ shepherd: {
31
+ module: shepherd,
32
+ summary: 'Run one bounded monitor pass over a PR (= forge shepherd; --bundle/--pull/--json, events, watch)',
33
+ },
34
+ merge: {
35
+ module: merge,
36
+ summary: 'Opt-in conditional auto-merge, OFF by default (= forge merge --auto <pr>)',
37
+ },
38
+ };
39
+
40
+ const usage = 'Usage: forge pr <ship|preflight|shepherd|merge> [args]';
41
+
42
+ function renderHelp() {
43
+ const width = Math.max(...Object.keys(SUBCOMMANDS).map(name => name.length));
44
+ const lines = [
45
+ usage,
46
+ '',
47
+ 'Subcommands:',
48
+ ...Object.entries(SUBCOMMANDS).map(
49
+ ([name, { summary }]) => ` ${name.padEnd(width)} ${summary}`
50
+ ),
51
+ '',
52
+ 'Back-compat: forge ship / forge preflight / forge shepherd / forge merge remain available as aliases.',
53
+ ];
54
+ return lines.join('\n');
55
+ }
56
+
57
+ async function handler(args, flags, projectRoot, opts) {
58
+ // The subcommand is the first positional token; global flags (e.g. `-p <dir>`) are stripped
59
+ // first so they never masquerade as the subcommand.
60
+ const positional = stripGlobalFlags(args).find(arg => !arg.startsWith('-'));
61
+
62
+ if (!positional || positional === 'help' || args.includes('--help') || args.includes('-h')) {
63
+ return { success: true, output: renderHelp() };
64
+ }
65
+
66
+ const sub = SUBCOMMANDS[positional];
67
+ if (!sub) {
68
+ return {
69
+ success: false,
70
+ error: `Unknown pr subcommand: ${positional}\n\n${renderHelp()}`,
71
+ };
72
+ }
73
+
74
+ // Forward everything EXCEPT the consumed subcommand token to the delegate, preserving
75
+ // every remaining token (including flags like `--pull`/`--json`/`--bundle`, and the
76
+ // `events`/`watch` shepherd sub-shapes) so passthrough stays byte-identical.
77
+ const idx = args.indexOf(positional);
78
+ const childArgs = idx >= 0 ? [...args.slice(0, idx), ...args.slice(idx + 1)] : args;
79
+ return sub.module.handler(childArgs, flags, projectRoot, opts);
80
+ }
81
+
82
+ module.exports = {
83
+ name: 'pr',
84
+ description:
85
+ 'Unified pull-request surface: forge pr ship|preflight|shepherd|merge (wraps ship/preflight/shepherd/merge)',
86
+ usage,
87
+ handler,
88
+ };
@@ -0,0 +1,211 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `forge preflight` — fast (~2-4 min) blast-radius gate.
5
+ *
6
+ * Gives DETERMINISTIC-GATE PARITY with CI so an agent catches what CI catches
7
+ * WITHOUT waiting on the hanging full suite. Runs four gates in order, fast-fail:
8
+ *
9
+ * 1. lint — eslint --max-warnings 0 on changed files (--all → whole tree)
10
+ * 2. drift — skills mirror + registry sync + agentic-workflow structural asserts
11
+ * 3. sonar — eslint-plugin-sonarjs cognitive-complexity=15 (+ parity rules)
12
+ * 4. affected — only the tests mapped from the changed files
13
+ *
14
+ * Exit non-zero on any gate failure. Run this before `forge push`.
15
+ *
16
+ * @module commands/preflight
17
+ */
18
+
19
+ const { execFileSync } = require('node:child_process');
20
+ const { buildGates } = require('../preflight/gates');
21
+ const { runGates } = require('../preflight/runner');
22
+
23
+ /**
24
+ * Strictly resolve a base ref to diff HEAD against. Returns the ref name, or
25
+ * null when no base can be established (fresh repo: no origin/HEAD, no upstream,
26
+ * no main/master ref). Unlike test.js's resolver, this never falls back to a
27
+ * default name that doesn't exist — a missing base must be reported, not faked.
28
+ *
29
+ * @param {Function} exec - execFileSync-compatible
30
+ * @returns {string|null}
31
+ */
32
+ /** Run a git command and return trimmed stdout, or null on any failure. */
33
+ function gitTryOut(exec, args) {
34
+ try {
35
+ return String(exec('git', args, { encoding: 'utf8', timeout: 3000, stdio: ['ignore', 'pipe', 'ignore'] }) || '').trim();
36
+ } catch {
37
+ return null;
38
+ }
39
+ }
40
+
41
+ /** True when `ref` resolves to a real object in this repo. */
42
+ function gitVerifyRef(exec, ref) {
43
+ try {
44
+ exec('git', ['rev-parse', '--verify', '--quiet', ref], { stdio: 'ignore', timeout: 3000 });
45
+ return true;
46
+ } catch {
47
+ return false;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * True when `ref` resolves to the SAME commit as HEAD. Diffing HEAD against such
53
+ * a ref yields zero files, so it is never a usable integration base.
54
+ */
55
+ function refEqualsHead(exec, ref) {
56
+ const refSha = gitTryOut(exec, ['rev-parse', '--verify', '--quiet', `${ref}^{commit}`]);
57
+ if (!refSha) return false;
58
+ const headSha = gitTryOut(exec, ['rev-parse', '--verify', '--quiet', 'HEAD^{commit}']);
59
+ return !!headSha && refSha === headSha;
60
+ }
61
+
62
+ function resolveBaseRef(exec) {
63
+ const originHead = gitTryOut(exec, ['rev-parse', '--abbrev-ref', 'origin/HEAD']);
64
+ if (originHead && originHead !== 'origin/HEAD') return originHead;
65
+
66
+ // `@{upstream}` is the CURRENT branch's OWN remote-tracking ref. On a fully
67
+ // pushed feature branch it points AT HEAD, so diffing against it produces 0
68
+ // changed files and every gate passes vacuously (fail-OPEN). Accept it as a
69
+ // base only when it is a real integration point ahead of us — never when it
70
+ // resolves to our own HEAD. Fall through to a real base (origin/main, …) or
71
+ // FAIL if none exists, rather than manufacture a wrong-but-'resolved' base.
72
+ const upstream = gitTryOut(exec, ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{upstream}']);
73
+ if (upstream && !refEqualsHead(exec, upstream)) return upstream;
74
+
75
+ for (const ref of ['origin/main', 'origin/master', 'main', 'master']) {
76
+ if (gitVerifyRef(exec, ref)) return ref;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ /**
82
+ * Resolve the change set for preflight, distinguishing "base resolved, genuinely
83
+ * zero changes" from "no base branch could be resolved". Only the latter is a
84
+ * gate failure — a fresh repo with no base cannot have its blast radius computed,
85
+ * so preflight must NOT pass vacuously.
86
+ *
87
+ * @param {Function} [exec] - execFileSync-compatible (injectable for tests)
88
+ * @param {{ runAll?: boolean }} [opts]
89
+ * @returns {{ resolved: boolean, changedFiles: string[], reason?: string, baseRef?: string }}
90
+ */
91
+ function resolveChangeSet(exec = execFileSync, { runAll = false } = {}) {
92
+ if (runAll) {
93
+ return { resolved: true, changedFiles: [], reason: 'whole-tree scope (--all)' };
94
+ }
95
+ const baseRef = resolveBaseRef(exec);
96
+ if (!baseRef) {
97
+ return {
98
+ resolved: false,
99
+ changedFiles: [],
100
+ reason: 'could not resolve a base branch (no origin/HEAD, no upstream, no main/master ref)',
101
+ };
102
+ }
103
+ // Diff against the SAME base we just resolved. getChangedFiles() re-resolves
104
+ // its own diff ref (and can fall back to a different default branch), so use
105
+ // baseRef directly to keep the file list consistent with the resolved base.
106
+ return { resolved: true, baseRef, changedFiles: changedFilesForBase(exec, baseRef) };
107
+ }
108
+
109
+ /** Files changed between the merge-base of HEAD and `baseRef` and HEAD. */
110
+ function changedFilesForBase(exec, baseRef) {
111
+ const mergeBase = gitTryOut(exec, ['merge-base', 'HEAD', baseRef]);
112
+ const range = mergeBase ? `${mergeBase}...HEAD` : `${baseRef}...HEAD`;
113
+ const out = gitTryOut(exec, ['diff', '--name-only', range]);
114
+ if (out == null) return [];
115
+ return out.split(/\r?\n/).filter(Boolean);
116
+ }
117
+
118
+ /**
119
+ * Pick the change set: an injected resolveChangeSet wins; a legacy injected
120
+ * resolveChangedFiles vouches for resolution; otherwise resolve strictly.
121
+ */
122
+ function selectChangeSet(deps, runAll) {
123
+ if (deps.resolveChangeSet) {
124
+ return deps.resolveChangeSet({ runAll });
125
+ }
126
+ if (deps.resolveChangedFiles) {
127
+ return { resolved: true, changedFiles: deps.resolveChangedFiles() || [] };
128
+ }
129
+ return resolveChangeSet(execFileSync, { runAll });
130
+ }
131
+
132
+ /** Print the fail-closed remedy when no base branch could be resolved. */
133
+ function reportBaseUnresolved(log, reason) {
134
+ log('forge preflight — fast blast-radius gate (deterministic parity with CI)');
135
+ log('');
136
+ log(`preflight FAILED — ${reason}.`);
137
+ log(' Remedy: give preflight a base branch to diff against, e.g.');
138
+ log(' git fetch origin && git branch --set-upstream-to=origin/main # your default branch');
139
+ log(' (or ensure origin/HEAD is set: git remote set-head origin -a)');
140
+ log(' Or scan the whole tree explicitly: forge preflight --all');
141
+ }
142
+
143
+ /** Summary mark for one gate result. */
144
+ function gateMark(result) {
145
+ if (result.skipped) return 'SKIP';
146
+ return result.ok ? 'PASS' : 'FAIL';
147
+ }
148
+
149
+ module.exports = {
150
+ name: 'preflight',
151
+ description: 'Fast deterministic-gate parity with CI (lint, drift/registry, sonar, affected tests)',
152
+ usage: 'forge preflight [--all]',
153
+ flags: {
154
+ '--all': 'Lint/scan the whole tree instead of only changed files',
155
+ },
156
+
157
+ /**
158
+ * @param {string[]} _args
159
+ * @param {Object} flags
160
+ * @param {string} projectRoot
161
+ * @param {Object} [deps] - injectable { log, resolveChangedFiles, buildGates, runGates }
162
+ * @returns {Promise<{ success: boolean, results?: Array }>}
163
+ */
164
+ handler: async (_args, flags, projectRoot, deps = {}) => {
165
+ const log = deps.log || console.log;
166
+ const build = deps.buildGates || buildGates;
167
+ const run = deps.runGates || runGates;
168
+
169
+ const runAll = !!(flags && (flags['--all'] || flags.all));
170
+
171
+ // Resolve the change set AND whether a base branch could be established.
172
+ const changeSet = selectChangeSet(deps, runAll) || {};
173
+
174
+ // B2 (N1): an unresolvable base is fail-CLOSED. Computing "0 changed files"
175
+ // from a missing base and passing every gate manufactures false confidence.
176
+ // Anything not EXPLICITLY resolved (including a missing `resolved` field from
177
+ // a future override) fails closed — never fail open on an indeterminate base.
178
+ if (!runAll && !changeSet.resolved) {
179
+ const reason = changeSet.reason || 'base branch could not be resolved';
180
+ reportBaseUnresolved(log, reason);
181
+ return { success: false, results: [], reason, baseUnresolved: true };
182
+ }
183
+
184
+ const changedFiles = changeSet.changedFiles || [];
185
+
186
+ log('forge preflight — fast blast-radius gate (deterministic parity with CI)');
187
+ log(runAll
188
+ ? ' scope: whole tree (--all)'
189
+ : ` scope: ${changedFiles.length} changed file(s) vs base branch`);
190
+ log('');
191
+
192
+ const gates = build({ projectRoot, changedFiles, runAll });
193
+ const { ok, results } = await run(gates, { log: (line) => log(` ${line}`) });
194
+
195
+ log('');
196
+ log('Preflight summary:');
197
+ for (const r of results) {
198
+ const suffix = r.summary ? ` — ${r.summary}` : '';
199
+ log(` [${gateMark(r)}] ${r.name}${suffix}`);
200
+ }
201
+ log('');
202
+ log(ok
203
+ ? 'preflight passed — safe to push'
204
+ : 'preflight FAILED — fix the failing gate above before `forge push`');
205
+
206
+ return { success: ok, results };
207
+ },
208
+ };
209
+
210
+ module.exports.resolveChangeSet = resolveChangeSet;
211
+ module.exports.resolveBaseRef = resolveBaseRef;
@@ -0,0 +1,13 @@
1
+ 'use strict';
2
+
3
+ const {
4
+ buildPrime,
5
+ runOrientationCommand,
6
+ } = require('../orientation');
7
+
8
+ module.exports = {
9
+ name: 'prime',
10
+ description: 'Emit session-entry bounded orientation for agents',
11
+ usage: 'Usage: forge prime [--budget N] [--json]',
12
+ handler: (args, _flags, projectRoot) => runOrientationCommand(buildPrime, args, projectRoot),
13
+ };
@@ -4,6 +4,8 @@ const { execFileSync, spawnSync } = require('node:child_process');
4
4
  const fs = require('node:fs');
5
5
  const path = require('node:path');
6
6
  const forgeToken = require('../../scripts/check-forge-token');
7
+ const { startPrWatcherDetached } = require('../pr-monitor/watch-lifecycle');
8
+ const { autoShepherdRailEnabled } = require('./ship');
7
9
 
8
10
  const isWindows = process.platform === 'win32';
9
11
 
@@ -54,6 +56,113 @@ function isFirstPush(branch, execFn) {
54
56
  }
55
57
  }
56
58
 
59
+ /**
60
+ * Resolve the git worktree top-level directory.
61
+ *
62
+ * The pre-push hook checks for the nonce token relative to its own cwd, which
63
+ * git sets to the worktree root during `git push`. The handler's `projectRoot`
64
+ * is derived from INIT_CWD/cwd and can point at the MAIN repo root when forge
65
+ * runs from a linked worktree, so the token must be written to the real git
66
+ * top-level instead — otherwise lefthook never finds it and re-runs the suite.
67
+ *
68
+ * @param {function} execFn - execFileSync or mock
69
+ * @param {string} fallback - projectRoot to use when git resolution fails
70
+ * @returns {string} Absolute worktree top-level path, or the fallback
71
+ */
72
+ function resolveGitToplevel(execFn, fallback) {
73
+ try {
74
+ const top = execFn('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8' }).trim();
75
+ return top || fallback;
76
+ } catch (_err) { /* intentional: fall back to projectRoot outside a git worktree */ // NOSONAR S2486
77
+ return fallback;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Auto-file rail: best-effort, NON-BLOCKING ensure that the branch being pushed has a
83
+ * backing Kernel issue (kernel issue a4b8f56f) — so started work that is being pushed
84
+ * never goes unfiled. Idempotent (deduped by branch via ensureBackingIssue); a
85
+ * main/master/detached/ignore branch is skipped, and ANY failure degrades to null and
86
+ * NEVER aborts the push. Injectable via `deps._ensureBackingIssue` for tests.
87
+ *
88
+ * @param {string} projectRoot
89
+ * @param {function} execFn - execFileSync (for branch resolution)
90
+ * @param {object} deps - DI (may inject `_ensureBackingIssue`, `_kernelDriver`, `_kernelBroker`)
91
+ * @returns {Promise<object|null>}
92
+ */
93
+ async function autoFileBackingIssueForPush(projectRoot, execFn, deps = {}) {
94
+ try {
95
+ // Lazy require INSIDE the try: a module-load failure must degrade to null and
96
+ // NEVER abort the push (mirrors the worktree helper; CodeRabbit #370).
97
+ const ensure = deps._ensureBackingIssue || require('../kernel/backing-issue').ensureBackingIssue;
98
+ const branch = getCurrentBranch(execFn);
99
+ if (!branch || branch === 'unknown') return null;
100
+ let driver = deps._kernelDriver;
101
+ let broker = deps._kernelBroker;
102
+ if (!driver || !broker) {
103
+ if (!fs.existsSync(path.join(projectRoot, '.git'))) return null;
104
+ const { buildMigratedKernelIssueDeps } = require('../kernel/cli-broker-factory');
105
+ const built = await buildMigratedKernelIssueDeps({ projectRoot });
106
+ driver = driver || built.kernelDriver;
107
+ broker = broker || built.kernelBroker;
108
+ }
109
+ return await ensure({ branch, projectRoot, driver, broker });
110
+ } catch (_err) { /* non-blocking: tracking must never break a push */ // NOSONAR S2486
111
+ return null;
112
+ }
113
+ }
114
+
115
+ /**
116
+ * Resolve the OPEN PR number for the current branch via `gh pr view`. Returns
117
+ * null when there is no PR, gh is unavailable, or anything errors (fail-open).
118
+ * NEVER throws.
119
+ *
120
+ * @param {function} execFn - execFileSync or mock
121
+ * @returns {number|null}
122
+ */
123
+ function resolveOpenPrNumber(execFn) {
124
+ try {
125
+ const out = execFn('gh', ['pr', 'view', '--json', 'number', '-q', '.number'], {
126
+ encoding: 'utf8', timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'],
127
+ });
128
+ const n = Number.parseInt(String(out).trim(), 10);
129
+ return Number.isInteger(n) && n > 0 ? n : null;
130
+ } catch (_err) { /* intentional: no open PR / gh missing → arm nothing */ // NOSONAR S2486
131
+ return null;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Best-effort, NON-BLOCKING arm of the constant PR watcher after a successful
137
+ * push, when an OPEN PR exists for the current branch. This closes the gap where
138
+ * PRs not born from `forge ship` (gh pr create, the GitHub UI, an earlier push)
139
+ * never got a watcher. Gated by the default-ON `rail.auto_shepherd`, idempotent
140
+ * via the watch loop's own PID/journal lock, and reusing the same
141
+ * `startPrWatcherDetached` as ship. MUST NEVER throw into or fail the push: a
142
+ * disabled rail, no PR, a gh error, or a spawn error all degrade to
143
+ * `{ armed: false }`.
144
+ *
145
+ * @param {object} params
146
+ * @returns {{ armed: boolean, reason?: string, prNumber?: number }}
147
+ */
148
+ function maybeArmWatcherAfterPush({
149
+ projectRoot,
150
+ execFn,
151
+ startWatcher = startPrWatcherDetached,
152
+ railEnabled = autoShepherdRailEnabled,
153
+ prLookup = resolveOpenPrNumber,
154
+ }) {
155
+ try {
156
+ if (!railEnabled(projectRoot)) return { armed: false, reason: 'rail.auto_shepherd disabled' };
157
+ const prNumber = prLookup(execFn);
158
+ if (!prNumber) return { armed: false, reason: 'no-open-pr' };
159
+ const res = startWatcher({ prNumber, cwd: projectRoot });
160
+ return { armed: !!(res && res.started), reason: res && res.reason, prNumber };
161
+ } catch (err) {
162
+ return { armed: false, reason: err.message };
163
+ }
164
+ }
165
+
57
166
  /**
58
167
  * Run branch protection check as a subprocess.
59
168
  * @param {function} execFn - execFileSync or mock
@@ -150,6 +259,10 @@ module.exports = {
150
259
  return { success: false, quickMode, lintPassed: false, pushed: false };
151
260
  }
152
261
 
262
+ // Auto-file rail (non-blocking): ensure the branch being pushed has a backing
263
+ // Kernel issue so started work never goes unfiled. Never aborts the push.
264
+ await autoFileBackingIssueForPush(projectRoot, execFn, deps || {});
265
+
153
266
  // Step 2: Lint
154
267
  const lintPassed = runLint(spawnFn, pkgManager, projectRoot);
155
268
  if (!lintPassed) {
@@ -163,9 +276,11 @@ module.exports = {
163
276
  return { success: false, quickMode, lintPassed: true, testsPassed: false, pushed: false };
164
277
  }
165
278
 
166
- // Step 4: Write nonce token so lefthook skips pre-push hooks
279
+ // Step 4: Write nonce token so lefthook skips pre-push hooks.
280
+ // Write to the git worktree top-level (where the hook checks), not the
281
+ // inherited projectRoot, so the skip works from linked worktrees too.
167
282
  try {
168
- tokenWrite(projectRoot);
283
+ tokenWrite(resolveGitToplevel(execFn, projectRoot));
169
284
  } catch (_err) { /* intentional: non-fatal, lefthook will re-run hooks */ // NOSONAR S2486
170
285
  log('Warning: Could not write forge push token — lefthook hooks will run again');
171
286
  }
@@ -185,6 +300,17 @@ module.exports = {
185
300
  };
186
301
  }
187
302
 
303
+ // Arm the constant PR watcher for this branch's open PR (best-effort,
304
+ // gated by rail.auto_shepherd, never fails the push). Covers PRs not born
305
+ // from `forge ship`.
306
+ maybeArmWatcherAfterPush({
307
+ projectRoot,
308
+ execFn,
309
+ startWatcher: deps?.startWatcher,
310
+ railEnabled: deps?.railEnabled,
311
+ prLookup: deps?.prLookup,
312
+ });
313
+
188
314
  return {
189
315
  success: true,
190
316
  quickMode,
@@ -193,4 +319,11 @@ module.exports = {
193
319
  pushed: true,
194
320
  };
195
321
  },
322
+
323
+ // Exposed for unit tests; not part of the CLI surface.
324
+ _internal: {
325
+ autoFileBackingIssueForPush,
326
+ maybeArmWatcherAfterPush,
327
+ resolveOpenPrNumber,
328
+ },
196
329
  };
@@ -1,5 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const { makeAliasCommand } = require('./_issue');
3
+ const { createIssueSubcommand } = require('./_issue');
4
4
 
5
- module.exports = makeAliasCommand('ready');
5
+ module.exports = createIssueSubcommand('ready');
@@ -0,0 +1,171 @@
1
+ 'use strict';
2
+
3
+ const memoryRouter = require('../memory/router');
4
+ const { stripGlobalFlags } = require('../global-flags');
5
+ const { fenceUntrusted } = require('../untrusted-content');
6
+
7
+ const usage = 'Usage: forge recall [query] [--kind <type>] [--limit N] [--all] [--json]';
8
+
9
+ // Reserved tag prefix that `remember --kind` writes (kernel issue 8cc1db4d). A `--kind`
10
+ // filter keeps only notes carrying this tag; the prefix is stripped when surfacing the
11
+ // derived `type` field so a note's user tags stay clean. The filter FLAG is `--kind` (NOT
12
+ // `--type`): `--type` is a reserved GLOBAL flag hard-validated to workflow classifications.
13
+ const TYPE_TAG_PREFIX = 'type:';
14
+
15
+ // When a `--kind` filter is active the tag filter runs in the command layer (the store is
16
+ // not reimplemented), so scan a generous window of recent notes before filtering rather than
17
+ // the small default page — otherwise the type match could fall outside the default limit.
18
+ const TYPE_FILTER_SCAN = 1000;
19
+
20
+ /**
21
+ * Separate the optional positional query from `--kind <type>`, `--limit N`, and `--json`.
22
+ * Global flags (e.g. `-p <dir>`, `--all`) are stripped first so they never corrupt the
23
+ * search query (kernel issue c1e090ff). `--all` is a GLOBAL boolean flag, so the handler
24
+ * reads it from its `flags` argument (or the raw args on a direct call) — not from here.
25
+ * `--kind` is NOT a global flag, so it survives the strip and is parsed here.
26
+ *
27
+ * @param {string[]} rawArgs - Raw command arguments.
28
+ * @returns {{ query: string, limit: (number|undefined), type: (string|undefined), json: boolean }}
29
+ */
30
+ function parseArgs(rawArgs) {
31
+ const args = stripGlobalFlags(rawArgs);
32
+ const words = [];
33
+ let limit;
34
+ let type;
35
+ let json = false;
36
+
37
+ for (let index = 0; index < args.length; index += 1) {
38
+ const arg = args[index];
39
+ if (arg === '--json') {
40
+ json = true;
41
+ } else if (arg === '--limit') {
42
+ const value = Number(args[index + 1]);
43
+ if (Number.isInteger(value) && value > 0) {
44
+ limit = value;
45
+ index += 1;
46
+ }
47
+ } else if (arg.startsWith('--limit=')) {
48
+ const value = Number(arg.slice('--limit='.length));
49
+ if (Number.isInteger(value) && value > 0) {
50
+ limit = value;
51
+ }
52
+ } else if (arg === '--kind') {
53
+ const value = args[index + 1];
54
+ if (value && !value.startsWith('--')) {
55
+ type = value.trim();
56
+ index += 1;
57
+ }
58
+ } else if (arg.startsWith('--kind=')) {
59
+ type = arg.slice('--kind='.length).trim();
60
+ } else {
61
+ words.push(arg);
62
+ }
63
+ }
64
+
65
+ return { query: words.join(' ').trim(), limit, type, json };
66
+ }
67
+
68
+ // Derive a note's `type` from its reserved `type:` tag (undefined when untyped), so any
69
+ // surface — JSON and text — can show and filter by kind without exposing the tag encoding.
70
+ function typeOf(entry) {
71
+ const tag = (entry.tags || []).find(t => t.startsWith(TYPE_TAG_PREFIX));
72
+ return tag ? tag.slice(TYPE_TAG_PREFIX.length) : undefined;
73
+ }
74
+
75
+ function withType(entry) {
76
+ const type = typeOf(entry);
77
+ return type ? { ...entry, type } : entry;
78
+ }
79
+
80
+ function formatEntry(entry) {
81
+ const date = entry.timestamp ? entry.timestamp.slice(0, 10) : '';
82
+ const prefix = date ? `${date} ` : '';
83
+ // The reserved `type:` tag renders as a leading `(kind)` marker, not as a raw tag, so the
84
+ // displayed tags stay the user's own labels.
85
+ const type = typeOf(entry);
86
+ const userTags = (entry.tags || []).filter(t => !t.startsWith(TYPE_TAG_PREFIX));
87
+ const tagSuffix = userTags.length > 0 ? ` [${userTags.join(', ')}]` : '';
88
+ const typeMarker = type ? `(${type}) ` : '';
89
+ // Machine/insights records are LABELED with their source so they are never mistaken for a
90
+ // plain human note; human `remember` notes render clean.
91
+ const marker = entry.machine && entry.sourceAgent ? `(${entry.sourceAgent}) ` : '';
92
+ // Stored note text is UNTRUSTED (a planted memory could carry injected directives),
93
+ // so the human/agent-facing render is provenance-fenced. The `--json` path above
94
+ // keeps the raw note so programmatic consumers/parsers are unaffected.
95
+ const note = fenceUntrusted(entry.note, { source: 'memory' });
96
+ return `- ${prefix}${marker}${typeMarker}${note}${tagSuffix}`;
97
+ }
98
+
99
+ async function handler(args, flags, projectRoot) {
100
+ const { query, limit, type, json } = parseArgs(args);
101
+ // `--all` is a GLOBAL boolean flag: in production bin/forge.js strips it from
102
+ // args and sets flags.all; on a direct handler call it may still be in args.
103
+ const all = Boolean(flags && flags.all) || args.includes('--all');
104
+
105
+ // A `--type` filter scans a generous recent window, then keeps only matching notes — the
106
+ // read stays entirely in the existing store (no schema change). `--limit` is re-applied
107
+ // AFTER filtering so it caps the typed result set, not the pre-filter scan.
108
+ const recallLimit = type ? Math.max(limit ?? 0, TYPE_FILTER_SCAN) : limit;
109
+ const result = memoryRouter.recall(projectRoot, { query, limit: recallLimit, all });
110
+ let notes = result.notes.map(withType);
111
+ let { total, capped } = result;
112
+ const { scope } = result;
113
+ if (type) {
114
+ notes = notes.filter(entry => entry.type === type);
115
+ total = notes.length;
116
+ if (limit && notes.length > limit) {
117
+ notes = notes.slice(0, limit);
118
+ capped = true;
119
+ } else {
120
+ capped = false;
121
+ }
122
+ }
123
+
124
+ if (json) {
125
+ // Object (not a bare array) so programmatic consumers see the total and whether the
126
+ // result was truncated (raise --limit to page further).
127
+ return {
128
+ success: true,
129
+ output: `${JSON.stringify({ notes, total, capped, scope }, null, 2)}\n`,
130
+ };
131
+ }
132
+
133
+ if (notes.length === 0) {
134
+ const reason = query
135
+ ? `No notes match "${query}".`
136
+ : 'No notes remembered yet. Use "forge remember <note>" to add one.';
137
+ return { success: true, output: reason };
138
+ }
139
+
140
+ const noun = scope === 'all' && !query ? 'stored memory record(s)' : 'remembered note(s)';
141
+ let header;
142
+ if (query) {
143
+ // BM25 returns at most `limit`; when full, signal it is the TOP-N, not the whole set.
144
+ header = capped
145
+ ? `Top ${notes.length} note(s) matching "${query}" (raise --limit for more):`
146
+ : `${notes.length} note(s) matching "${query}":`;
147
+ } else if (capped) {
148
+ // Never a bare full dump: show the newest N and the true total.
149
+ header = `Showing ${notes.length} of ${total} ${noun} (newest first):`;
150
+ } else {
151
+ header = `${total} ${noun}:`;
152
+ }
153
+ const lines = notes.map(formatEntry);
154
+ return {
155
+ success: true,
156
+ output: [header, ...lines].join('\n'),
157
+ };
158
+ }
159
+
160
+ module.exports = {
161
+ name: 'recall',
162
+ description: 'Retrieve project-memory notes from the kernel-backed memory store',
163
+ usage,
164
+ flags: {
165
+ '--kind': 'Filter to notes of a type (decision|bugfix|gotcha|...); --type is reserved',
166
+ '--limit': 'Cap the number of notes returned',
167
+ '--all': 'Include machine/insights records in the no-query listing (query already searches all)',
168
+ '--json': 'Emit machine-readable JSON output',
169
+ },
170
+ handler,
171
+ };