forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -1,5 +1,6 @@
1
1
  const fs = require('node:fs');
2
2
  const path = require('node:path');
3
+ const { parse } = require('@babel/parser');
3
4
 
4
5
  /**
5
6
  * Allowlist mapping topic names to filenames in docs/.
@@ -13,6 +14,10 @@ const TOPICS = {
13
14
  roadmap: 'ROADMAP.md',
14
15
  };
15
16
 
17
+ const TOPIC_DIRS = {
18
+ setup: ['guides', 'reference', ''],
19
+ };
20
+
16
21
  /**
17
22
  * List all available topic names.
18
23
  * @returns {string[]}
@@ -38,14 +43,1164 @@ function getTopicContent(topic, packageDir) {
38
43
  return { error: `Unknown topic: "${topic}". Available topics: ${availableList}` };
39
44
  }
40
45
 
41
- const filePath = path.join(packageDir, 'docs', filename);
46
+ const searchDirs = TOPIC_DIRS[topic] || ['reference', 'guides', ''];
47
+
48
+ for (const dir of searchDirs) {
49
+ const filePath = path.join(packageDir, 'docs', dir, filename);
50
+ try {
51
+ const content = fs.readFileSync(filePath, 'utf-8');
52
+ return { content };
53
+ } catch (error) {
54
+ if (error?.code === 'ENOENT') {
55
+ // Try the next allowed documentation directory.
56
+ continue;
57
+ }
58
+ return { error: `Failed to read documentation file "${filePath}": ${error.message}` };
59
+ }
60
+ }
61
+
62
+ const searchedPaths = searchDirs
63
+ .map((dir) => path.join(packageDir, 'docs', dir, filename))
64
+ .join(', ');
65
+ return { error: `Documentation file "${filename}" not found at ${searchedPaths}` };
66
+ }
67
+
68
+ function walkFiles(rootDir, predicate, results = []) {
69
+ if (!fs.existsSync(rootDir)) {
70
+ return results;
71
+ }
72
+
73
+ const stack = [rootDir];
74
+ while (stack.length > 0) {
75
+ const dir = stack.pop();
76
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
77
+ const fullPath = path.join(dir, entry.name);
78
+ if (entry.isDirectory()) {
79
+ if (['node_modules', '.git', 'coverage', 'test-results', 'dist', 'build', '.next', 'out'].includes(entry.name)) {
80
+ continue;
81
+ }
82
+ stack.push(fullPath);
83
+ } else if (predicate(fullPath)) {
84
+ results.push(fullPath);
85
+ }
86
+ }
87
+ }
88
+
89
+ return results;
90
+ }
91
+
92
+ function getMarkdownFiles(packageDir) {
93
+ const rootDocs = ['README.md', 'CHANGELOG.md', 'AGENTS.md', 'CLAUDE.md']
94
+ .map((file) => path.join(packageDir, file))
95
+ .filter((file) => fs.existsSync(file));
96
+ const docsFiles = walkFiles(path.join(packageDir, 'docs'), (file) => file.endsWith('.md'));
97
+ const packageDocsFiles = walkFiles(path.join(packageDir, 'packages'), (file) => file.endsWith('.md'));
98
+ return [...rootDocs, ...docsFiles, ...packageDocsFiles].sort((a, b) => a.localeCompare(b));
99
+ }
100
+
101
+ function isExternalLink(target) {
102
+ const trimmed = target.trim();
103
+ return trimmed.startsWith('//') || (/^[a-z][a-z0-9+.-]*:/i.test(trimmed) && !/^[a-z]:[\\/]/i.test(trimmed));
104
+ }
105
+
106
+ function stripLinkDecorators(target) {
107
+ return target
108
+ .trim()
109
+ .replaceAll(/^<|>$/g, '')
110
+ .split(/[?#]/)[0];
111
+ }
112
+
113
+ function slugHeading(heading) {
114
+ return stripMarkdownReferenceLabels(stripMarkdownLinkDestinations(heading))
115
+ .trim()
116
+ .toLowerCase()
117
+ .replaceAll(/\s+/g, '-')
118
+ .replaceAll(/[`*_~[\](){}:;'"!?,./\\|+=<>@#$%^&]/g, '');
119
+ }
120
+
121
+ function stripMarkdownLinkDestinations(text) {
122
+ let result = '';
123
+ let cursor = 0;
124
+ while (cursor < text.length) {
125
+ const marker = text.indexOf('](', cursor);
126
+ if (marker === -1) {
127
+ result += text.slice(cursor);
128
+ break;
129
+ }
130
+
131
+ const labelStart = text.lastIndexOf('[', marker);
132
+ if (labelStart < cursor) {
133
+ result += text.slice(cursor, marker + 2);
134
+ cursor = marker + 2;
135
+ continue;
136
+ }
137
+
138
+ const destinationEnd = findMarkdownDestinationEnd(text, marker + 2);
139
+ if (destinationEnd === -1) {
140
+ result += text.slice(cursor);
141
+ break;
142
+ }
143
+
144
+ const replacementStart = labelStart > 0 && text[labelStart - 1] === '!' ? labelStart - 1 : labelStart;
145
+ result += text.slice(cursor, replacementStart);
146
+ result += text.slice(labelStart + 1, marker);
147
+ cursor = destinationEnd + 1;
148
+ }
149
+
150
+ return result;
151
+ }
152
+
153
+ function stripMarkdownReferenceLabels(text) {
154
+ let result = '';
155
+ let cursor = 0;
156
+ while (cursor < text.length) {
157
+ const marker = text.indexOf('][', cursor);
158
+ if (marker === -1) {
159
+ result += text.slice(cursor);
160
+ break;
161
+ }
162
+
163
+ const labelStart = text.lastIndexOf('[', marker);
164
+ const referenceEnd = text.indexOf(']', marker + 2);
165
+ if (labelStart < cursor || referenceEnd === -1) {
166
+ result += text.slice(cursor, marker + 2);
167
+ cursor = marker + 2;
168
+ continue;
169
+ }
170
+
171
+ const replacementStart = labelStart > 0 && text[labelStart - 1] === '!' ? labelStart - 1 : labelStart;
172
+ result += text.slice(cursor, replacementStart);
173
+ result += text.slice(labelStart + 1, marker);
174
+ cursor = referenceEnd + 1;
175
+ }
176
+
177
+ return result;
178
+ }
179
+
180
+ function collectAnchors(content) {
181
+ const anchors = new Set();
182
+ const seen = new Map();
183
+ let activeFence = null;
184
+ let previousTextLine = null;
185
+ for (const line of content.split(/\r?\n/)) {
186
+ const fence = getMarkdownFence(line);
187
+ if (fence) {
188
+ activeFence = updateActiveMarkdownFence(activeFence, fence);
189
+ previousTextLine = null;
190
+ continue;
191
+ }
192
+
193
+ if (activeFence) {
194
+ previousTextLine = null;
195
+ continue;
196
+ }
197
+
198
+ const heading = getMarkdownHeadingText(line);
199
+ if (heading) {
200
+ addAnchor(anchors, seen, heading);
201
+ previousTextLine = null;
202
+ continue;
203
+ }
204
+
205
+ if (previousTextLine && isSetextHeadingUnderline(line)) {
206
+ addAnchor(anchors, seen, previousTextLine);
207
+ previousTextLine = null;
208
+ continue;
209
+ }
210
+
211
+ previousTextLine = line.trim() ? line.trim() : null;
212
+ }
213
+ return anchors;
214
+ }
215
+
216
+ function addAnchor(anchors, seen, heading) {
217
+ const slug = slugHeading(heading);
218
+ const count = seen.get(slug) || 0;
219
+ anchors.add(count === 0 ? slug : `${slug}-${count}`);
220
+ seen.set(slug, count + 1);
221
+ }
222
+
223
+ function isSetextHeadingUnderline(line) {
224
+ const trimmed = line.trim();
225
+ if (!trimmed) {
226
+ return false;
227
+ }
228
+
229
+ const marker = trimmed[0];
230
+ if (marker !== '=' && marker !== '-') {
231
+ return false;
232
+ }
233
+
234
+ for (const char of trimmed) {
235
+ if (char !== marker) {
236
+ return false;
237
+ }
238
+ }
239
+ return true;
240
+ }
241
+
242
+ function getMarkdownHeadingText(line) {
243
+ const trimmed = line.trimStart();
244
+ let level = 0;
245
+ while (trimmed[level] === '#' && level < 6) {
246
+ level++;
247
+ }
248
+
249
+ if (level === 0 || (trimmed[level] !== ' ' && trimmed[level] !== '\t')) {
250
+ return null;
251
+ }
252
+
253
+ return trimClosingHeadingHashes(trimmed.slice(level + 1).trim());
254
+ }
255
+
256
+ function trimClosingHeadingHashes(text) {
257
+ let end = text.length;
258
+ while (end > 0 && text[end - 1] === '#') {
259
+ end--;
260
+ }
261
+
262
+ return text.slice(0, end).trimEnd();
263
+ }
264
+
265
+ function findMarkdownLinks(content) {
266
+ const links = [];
267
+ const referenceDefinitions = _collectReferenceDefinitions(content);
268
+ let offset = 0;
269
+ let activeFence = null;
270
+ let allowIndentedListContinuation = false;
271
+ const lines = content.split('\n');
272
+ for (let index = 0; index < lines.length; index++) {
273
+ const line = lines[index];
274
+ const fence = getMarkdownFence(line);
275
+ if (fence) {
276
+ activeFence = updateActiveMarkdownFence(activeFence, fence);
277
+ offset += line.length + 1;
278
+ allowIndentedListContinuation = false;
279
+ continue;
280
+ }
281
+
282
+ const isListContinuation = allowIndentedListContinuation && isIndentedMarkdownCodeLine(line);
283
+ if (!activeFence && !isIndentedMarkdownCodeLine(line, { isListContinuation })) {
284
+ links.push(...findMarkdownLinksInLine(line, offset, referenceDefinitions, lines[index + 1]));
285
+ }
286
+ allowIndentedListContinuation = shouldAllowIndentedListContinuation(line, isListContinuation);
287
+ offset += line.length + 1;
288
+ }
289
+ return links;
290
+ }
291
+
292
+ function _collectReferenceDefinitions(content) {
293
+ const definitions = new Map();
294
+ let activeFence = null;
295
+ let allowIndentedListContinuation = false;
296
+ const lines = content.split('\n');
297
+ for (let index = 0; index < lines.length; index++) {
298
+ const line = lines[index];
299
+ const fence = getMarkdownFence(line);
300
+ if (fence) {
301
+ activeFence = updateActiveMarkdownFence(activeFence, fence);
302
+ allowIndentedListContinuation = false;
303
+ continue;
304
+ }
305
+ const isListContinuation = allowIndentedListContinuation && isIndentedMarkdownCodeLine(line);
306
+ if (activeFence || isIndentedMarkdownCodeLine(line, { isListContinuation })) {
307
+ allowIndentedListContinuation = shouldAllowIndentedListContinuation(line, isListContinuation);
308
+ continue;
309
+ }
310
+
311
+ const definition = _getReferenceLinkDefinition(line, lines[index + 1]);
312
+ const label = definition ? _normalizeReferenceLabel(definition.label) : null;
313
+ if (definition && !definitions.has(label)) {
314
+ definitions.set(label, definition.target);
315
+ }
316
+ allowIndentedListContinuation = shouldAllowIndentedListContinuation(line, isListContinuation);
317
+ }
318
+ return definitions;
319
+ }
320
+
321
+ function findMarkdownLinksInLine(line, offset, referenceDefinitions = new Map(), nextLine = '') {
322
+ const links = [];
323
+ const referenceDefinition = _getReferenceLinkDefinition(line, nextLine);
324
+ if (referenceDefinition) {
325
+ const label = _normalizeReferenceLabel(referenceDefinition.label);
326
+ if (referenceDefinitions.get(label) === referenceDefinition.target) {
327
+ links.push({ target: referenceDefinition.target, index: offset });
328
+ }
329
+ return links;
330
+ }
331
+
332
+ let cursor = 0;
333
+ while (cursor < line.length) {
334
+ const start = line.indexOf('](', cursor);
335
+ if (start === -1) {
336
+ break;
337
+ }
338
+
339
+ const labelStart = line.lastIndexOf('[', start);
340
+ if (
341
+ isInsideInlineCode(line, start) ||
342
+ (labelStart !== -1 && isEscapedMarkdownCharacter(line, labelStart))
343
+ ) {
344
+ cursor = start + 2;
345
+ continue;
346
+ }
347
+
348
+ const end = findMarkdownDestinationEnd(line, start + 2);
349
+ if (end === -1) {
350
+ break;
351
+ }
352
+
353
+ const target = getMarkdownLinkTarget(line.slice(start + 2, end));
354
+ if (target) {
355
+ links.push({ target, index: offset + start });
356
+ }
357
+ cursor = end + 1;
358
+ }
359
+ links.push(..._findReferenceLinkUsagesInLine(line, offset, referenceDefinitions));
360
+ return links;
361
+ }
362
+
363
+ function isIndentedMarkdownCodeLine(line, { isListContinuation = false } = {}) {
364
+ if (!line.startsWith(' ') && !line.startsWith('\t')) {
365
+ return false;
366
+ }
367
+ return !(isListContinuation && line.startsWith(' ') && !line.startsWith(' '));
368
+ }
369
+
370
+ function shouldAllowIndentedListContinuation(line, isListContinuation) {
371
+ if (isMarkdownListItemLine(line)) {
372
+ return true;
373
+ }
374
+ return isListContinuation && line.trim() !== '';
375
+ }
376
+
377
+ function isMarkdownListItemLine(line) {
378
+ return /^\s{0,3}(?:[-+*]|\d{1,9}[.)])\s+/.test(line);
379
+ }
380
+
381
+ function getMarkdownFence(line) {
382
+ let leadingSpaces = 0;
383
+ while (line[leadingSpaces] === ' ') {
384
+ leadingSpaces += 1;
385
+ }
386
+ if (leadingSpaces > 3) {
387
+ return null;
388
+ }
389
+
390
+ const trimmed = line.slice(leadingSpaces);
391
+ const marker = trimmed[0];
392
+ if (marker !== '`' && marker !== '~') {
393
+ return null;
394
+ }
395
+
396
+ let length = 0;
397
+ while (trimmed[length] === marker) {
398
+ length += 1;
399
+ }
400
+
401
+ if (length < 3) {
402
+ return null;
403
+ }
404
+
405
+ return { marker, length, hasInfo: trimmed.slice(length).trim() !== '' };
406
+ }
407
+
408
+ function updateActiveMarkdownFence(activeFence, fence) {
409
+ if (!activeFence) {
410
+ return fence;
411
+ }
412
+ return fence.marker === activeFence.marker && fence.length >= activeFence.length && !fence.hasInfo
413
+ ? null
414
+ : activeFence;
415
+ }
416
+
417
+ function _findReferenceLinkUsagesInLine(line, offset, referenceDefinitions) {
418
+ const links = [];
419
+ let cursor = 0;
420
+ while (cursor < line.length) {
421
+ const usage = findNextReferenceLinkUsage(line, cursor);
422
+ if (!usage) {
423
+ break;
424
+ }
425
+
426
+ const label = _normalizeReferenceLabel(usage.rawLabel);
427
+ const target = referenceDefinitions.get(label);
428
+ if (target) {
429
+ links.push({ target, index: offset + usage.labelStart });
430
+ } else if (usage.hasFullReference) {
431
+ const missingReference = usage.rawLabel.trim();
432
+ if (missingReference) {
433
+ links.push({ missingReference, index: offset + usage.labelStart });
434
+ }
435
+ }
436
+ cursor = usage.nextCursor;
437
+ }
438
+ return links;
439
+ }
440
+
441
+ function findNextReferenceLinkUsage(line, cursor) {
442
+ while (cursor < line.length) {
443
+ const labelStart = line.indexOf('[', cursor);
444
+ if (labelStart === -1) {
445
+ return null;
446
+ }
447
+
448
+ const skippedCursor = getSkippedReferenceLabelCursor(line, labelStart);
449
+ if (skippedCursor !== null) {
450
+ cursor = skippedCursor;
451
+ continue;
452
+ }
453
+
454
+ const labelEnd = line.indexOf(']', labelStart + 1);
455
+ if (labelEnd === -1) {
456
+ return null;
457
+ }
458
+
459
+ const reference = parseReferenceSuffix(line, labelStart, labelEnd);
460
+ if (reference) {
461
+ return reference;
462
+ }
463
+ cursor = labelEnd + 1;
464
+ }
465
+ return null;
466
+ }
467
+
468
+ function getSkippedReferenceLabelCursor(line, labelStart) {
469
+ if (
470
+ (labelStart > 0 && line[labelStart - 1] === '!') ||
471
+ isEscapedMarkdownCharacter(line, labelStart) ||
472
+ isInsideInlineCode(line, labelStart)
473
+ ) {
474
+ return labelStart + 1;
475
+ }
476
+ if (line[labelStart + 1] === '^') {
477
+ return labelStart + 1;
478
+ }
479
+ return null;
480
+ }
481
+
482
+ function isEscapedMarkdownCharacter(line, index) {
483
+ let slashCount = 0;
484
+ for (let cursor = index - 1; cursor >= 0 && line[cursor] === '\\'; cursor--) {
485
+ slashCount++;
486
+ }
487
+ return slashCount % 2 === 1;
488
+ }
489
+
490
+ function parseReferenceSuffix(line, labelStart, labelEnd) {
491
+ if (line[labelEnd + 1] === '(') {
492
+ return null;
493
+ }
494
+
495
+ const hasFullReference = line[labelEnd + 1] === '[';
496
+ const referenceEnd = hasFullReference ? line.indexOf(']', labelEnd + 2) : labelEnd;
497
+ if (referenceEnd === -1) {
498
+ return null;
499
+ }
500
+
501
+ const rawLabel = hasFullReference
502
+ ? line.slice(labelEnd + 2, referenceEnd) || line.slice(labelStart + 1, labelEnd)
503
+ : line.slice(labelStart + 1, labelEnd);
504
+ return { hasFullReference, labelStart, nextCursor: referenceEnd + 1, rawLabel };
505
+ }
506
+
507
+ function findMarkdownDestinationEnd(line, start) {
508
+ let depth = 0;
509
+ for (let index = start; index < line.length; index++) {
510
+ const char = line[index];
511
+ if (char === '\\') {
512
+ index++;
513
+ continue;
514
+ }
515
+ if (char === '(') {
516
+ depth++;
517
+ } else if (char === ')') {
518
+ if (depth === 0) {
519
+ return index;
520
+ }
521
+ depth--;
522
+ }
523
+ }
524
+ return -1;
525
+ }
526
+
527
+ function isInsideInlineCode(line, index) {
528
+ let cursor = 0;
529
+ while (cursor < index) {
530
+ if (line[cursor] !== '`') {
531
+ cursor++;
532
+ continue;
533
+ }
534
+ if (cursor > 0 && line[cursor - 1] === '\\') {
535
+ cursor++;
536
+ continue;
537
+ }
538
+
539
+ const start = cursor;
540
+ while (cursor < line.length && line[cursor] === '`') {
541
+ cursor++;
542
+ }
543
+
544
+ const marker = line.slice(start, cursor);
545
+ const end = line.indexOf(marker, cursor);
546
+ if (end === -1) {
547
+ return false;
548
+ }
549
+ if (index > cursor && index < end) {
550
+ return true;
551
+ }
552
+ cursor = end + marker.length;
553
+ }
554
+ return false;
555
+ }
556
+
557
+ function getMarkdownLinkTarget(rawDestination) {
558
+ const trimmed = rawDestination.trim();
559
+ if (trimmed.startsWith('<')) {
560
+ const end = trimmed.indexOf('>');
561
+ return end > 1 ? trimmed.slice(0, end + 1) : trimmed;
562
+ }
563
+
564
+ const titleStart = _findUnescapedWhitespaceIndex(trimmed);
565
+ const target = titleStart === -1 ? trimmed : trimmed.slice(0, titleStart);
566
+ return target.replace(/\\([ \t])/g, '$1');
567
+ }
568
+
569
+ function _findUnescapedWhitespaceIndex(text) {
570
+ for (let index = 0; index < text.length; index++) {
571
+ if (text[index] === '\\') {
572
+ index++;
573
+ continue;
574
+ }
575
+ if (/\s/.test(text[index])) {
576
+ return index;
577
+ }
578
+ }
579
+ return -1;
580
+ }
581
+
582
+ function _getReferenceLinkDefinition(line, nextLine = '') {
583
+ const trimmed = line.trimStart();
584
+ if (!trimmed.startsWith('[')) {
585
+ return null;
586
+ }
587
+
588
+ const labelEnd = trimmed.indexOf(']:');
589
+ if (labelEnd <= 1) {
590
+ return null;
591
+ }
592
+ if (trimmed.slice(1, labelEnd).startsWith('^')) {
593
+ return null;
594
+ }
595
+
596
+ const rawTarget = trimmed.slice(labelEnd + 2);
597
+ const target = getMarkdownLinkTarget(rawTarget.trim() ? rawTarget : nextLine);
598
+ return target ? { label: trimmed.slice(1, labelEnd), target } : null;
599
+ }
600
+
601
+ function _normalizeReferenceLabel(label) {
602
+ return label.trim().replace(/\s+/g, ' ').toLowerCase();
603
+ }
604
+
605
+ function lineForIndex(content, index) {
606
+ return content.slice(0, index).split(/\r?\n/).length;
607
+ }
608
+
609
+ function formatLinkTargetForReport(rawTarget) {
610
+ const normalized = rawTarget.trim().replaceAll('\\', '/');
611
+ if (
612
+ normalized.startsWith('/') ||
613
+ (!path.isAbsolute(normalized) && !/^[A-Za-z]:\//.test(normalized))
614
+ ) {
615
+ return rawTarget;
616
+ }
617
+
618
+ const worktreeIndex = normalized.indexOf('/.worktrees/');
619
+ if (worktreeIndex >= 0) {
620
+ return `<repo>${normalized.slice(worktreeIndex)}`;
621
+ }
622
+
623
+ const repoIndex = normalized.toLowerCase().lastIndexOf('/forge/');
624
+ if (repoIndex >= 0) {
625
+ return `<repo>/${normalized.slice(repoIndex + '/forge/'.length)}`;
626
+ }
627
+
628
+ return `<absolute-path>/${path.posix.basename(normalized)}`;
629
+ }
630
+
631
+ function isWithinRoot(rootDir, candidatePath) {
632
+ const relative = path.relative(
633
+ fs.realpathSync.native(path.resolve(rootDir)),
634
+ resolveRealPathForContainment(candidatePath)
635
+ );
636
+ return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
637
+ }
638
+
639
+ function resolveRealPathForContainment(candidatePath) {
640
+ const resolved = path.resolve(candidatePath);
641
+ if (fs.existsSync(resolved)) {
642
+ return fs.realpathSync.native(resolved);
643
+ }
644
+
645
+ const missingSegments = [];
646
+ let current = resolved;
647
+ while (!fs.existsSync(current)) {
648
+ const parent = path.dirname(current);
649
+ if (parent === current) {
650
+ return resolved;
651
+ }
652
+ missingSegments.unshift(path.basename(current));
653
+ current = parent;
654
+ }
655
+
656
+ return path.join(fs.realpathSync.native(current), ...missingSegments);
657
+ }
658
+
659
+ function resolveMarkdownLink(packageDir, file, rawTarget) {
660
+ const [filePart, anchorPart] = rawTarget.replaceAll(/^<|>$/g, '').split('#');
661
+ if (!filePart) {
662
+ return { anchorPart, resolved: file };
663
+ }
664
+
665
+ const targetPath = decodeLocalMarkdownPath(stripLinkDecorators(filePart));
666
+ const resolved = targetPath.startsWith('/') && !targetPath.startsWith('//')
667
+ ? path.resolve(packageDir, targetPath.slice(1))
668
+ : path.resolve(path.dirname(file), targetPath);
669
+ return { anchorPart, resolved };
670
+ }
671
+
672
+ function decodeLocalMarkdownPath(targetPath) {
673
+ try {
674
+ return decodeURIComponent(targetPath);
675
+ } catch {
676
+ return targetPath;
677
+ }
678
+ }
679
+
680
+ function checkResolvedLink(packageDir, file, relFile, line, rawTarget) {
681
+ const { anchorPart, resolved } = resolveMarkdownLink(packageDir, file, rawTarget);
682
+ const target = formatLinkTargetForReport(rawTarget);
683
+ if (!isWithinRoot(packageDir, resolved)) {
684
+ return { file: relFile, line, target, reason: 'Link escapes project root' };
685
+ }
686
+
687
+ if (!fs.existsSync(resolved)) {
688
+ return { file: relFile, line, target, reason: 'Target file does not exist' };
689
+ }
690
+
691
+ if (anchorPart && resolved.endsWith('.md')) {
692
+ const targetContent = fs.readFileSync(resolved, 'utf8');
693
+ const anchors = collectAnchors(targetContent);
694
+ if (!anchors.has(slugHeading(decodeMarkdownAnchor(anchorPart)))) {
695
+ return { file: relFile, line, target, reason: 'Target anchor does not exist' };
696
+ }
697
+ }
698
+
699
+ return null;
700
+ }
701
+
702
+ function decodeMarkdownAnchor(anchor) {
703
+ try {
704
+ return decodeURIComponent(anchor);
705
+ } catch {
706
+ return anchor;
707
+ }
708
+ }
709
+
710
+ function checkMarkdownFileLinks(packageDir, file) {
711
+ const brokenLinks = [];
712
+ let linksChecked = 0;
713
+ const content = fs.readFileSync(file, 'utf8');
714
+ const relFile = path.relative(packageDir, file).replaceAll('\\', '/');
715
+ for (const link of findMarkdownLinks(content)) {
716
+ const line = lineForIndex(content, link.index);
717
+ if (link.missingReference) {
718
+ brokenLinks.push({
719
+ file: relFile,
720
+ line,
721
+ target: link.missingReference,
722
+ reason: 'Reference link definition does not exist',
723
+ });
724
+ linksChecked++;
725
+ continue;
726
+ }
727
+
728
+ const rawTarget = link.target.trim();
729
+ if (!rawTarget || isExternalLink(rawTarget)) {
730
+ continue;
731
+ }
732
+
733
+ linksChecked++;
734
+ const brokenLink = checkResolvedLink(packageDir, file, relFile, line, rawTarget);
735
+ if (brokenLink) {
736
+ brokenLinks.push(brokenLink);
737
+ }
738
+ }
739
+
740
+ return { linksChecked, brokenLinks };
741
+ }
742
+
743
+ function checkMarkdownLinks(packageDir) {
744
+ const files = getMarkdownFiles(packageDir);
745
+ const totals = { filesChecked: files.length, linksChecked: 0, brokenLinks: [] };
746
+ for (const file of files) {
747
+ const result = checkMarkdownFileLinks(packageDir, file);
748
+ totals.linksChecked += result.linksChecked;
749
+ totals.brokenLinks.push(...result.brokenLinks);
750
+ }
751
+ return totals;
752
+ }
753
+
754
+ function getSourceFiles(packageDir) {
755
+ const sourceRoots = ['lib', 'bin', 'scripts', 'src', 'apps', 'packages'];
756
+ const files = new Set();
757
+ for (const file of _getRootSourceEntryFiles(packageDir)) {
758
+ files.add(file);
759
+ }
760
+ for (const dir of sourceRoots) {
761
+ for (const file of walkFiles(path.join(packageDir, dir), isJavaScriptSourceFile)) {
762
+ files.add(file);
763
+ }
764
+ }
765
+
766
+ return Array.from(files)
767
+ .sort((a, b) => a.localeCompare(b));
768
+ }
769
+
770
+ function _getRootSourceEntryFiles(packageDir) {
771
+ const candidates = new Set([
772
+ 'index.js',
773
+ 'index.jsx',
774
+ 'index.mjs',
775
+ 'index.cjs',
776
+ 'index.ts',
777
+ 'index.tsx',
778
+ 'index.mts',
779
+ 'index.cts',
780
+ ]);
781
+ for (const entry of _getPackageJsonSourceEntries(packageDir)) {
782
+ candidates.add(entry);
783
+ }
784
+
785
+ return Array.from(candidates)
786
+ .map((file) => path.resolve(packageDir, file))
787
+ .filter((file) => isWithinRoot(packageDir, file) && fs.existsSync(file) && isJavaScriptSourceFile(file));
788
+ }
789
+
790
+ function _getPackageJsonSourceEntries(packageDir) {
791
+ const packageJsonPath = path.join(packageDir, 'package.json');
792
+ if (!fs.existsSync(packageJsonPath)) {
793
+ return [];
794
+ }
42
795
 
43
796
  try {
44
- const content = fs.readFileSync(filePath, 'utf-8');
45
- return { content };
46
- } catch (_error) {
47
- return { error: `Documentation file "${filename}" not found at ${filePath}` };
797
+ const manifest = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
798
+ return _collectPackageEntryValues([manifest.main, manifest.module, manifest.browser, manifest.exports, manifest.bin]);
799
+ } catch {
800
+ return [];
801
+ }
802
+ }
803
+
804
+ function _collectPackageEntryValues(values) {
805
+ const entries = [];
806
+ for (const value of Array.isArray(values) ? values : [values]) {
807
+ if (typeof value === 'string') {
808
+ entries.push(value);
809
+ } else if (value && typeof value === 'object') {
810
+ entries.push(..._collectPackageEntryValues(Object.values(value)));
811
+ }
812
+ }
813
+ return entries;
814
+ }
815
+
816
+ function isJavaScriptSourceFile(file) {
817
+ if (!['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.mts', '.cts'].includes(path.extname(file))) {
818
+ return false;
819
+ }
820
+ if (hasTestPathSegment(file)) {
821
+ return false;
822
+ }
823
+ return !/(?:^|[.-])(test|spec)\.[cm]?[jt]sx?$/.test(path.basename(file));
824
+ }
825
+
826
+ function hasTestPathSegment(file) {
827
+ return file
828
+ .replaceAll('\\', '/')
829
+ .split('/')
830
+ .some((segment) => ['test', 'tests', '__tests__', 'spec', 'specs', '__specs__'].includes(segment));
831
+ }
832
+
833
+ function hasLeadingJsDoc(source, comments, node) {
834
+ const previous = comments
835
+ .filter((comment) => comment.end <= node.start)
836
+ .sort((a, b) => b.end - a.end)[0];
837
+
838
+ if (previous?.type !== 'CommentBlock' || !previous.value.trim().startsWith('*')) {
839
+ return false;
840
+ }
841
+
842
+ return source.slice(previous.end, node.start).trim() === '';
843
+ }
844
+
845
+ function getPublicDocTargets(_source, ast) {
846
+ const targets = [];
847
+ const declarations = collectTopLevelDocDeclarations(ast);
848
+ for (const node of ast.program.body) {
849
+ if (node.type === 'ExportNamedDeclaration') {
850
+ addDocTargetsFromDeclaration(targets, node.declaration, node);
851
+ addDocTargetsFromExportSpecifiers(targets, node, declarations);
852
+ } else if (node.type === 'ExportDefaultDeclaration') {
853
+ addDocTargetsFromDeclaration(targets, node.declaration, node);
854
+ addDocTargetFromDefaultExportIdentifier(targets, node, declarations);
855
+ } else if (node.type === 'ExpressionStatement') {
856
+ addDocTargetFromCommonJsExport(targets, node, declarations);
857
+ }
858
+ }
859
+ return targets.filter((target) => !getDocTargetPublicName(target.name).startsWith('_'));
860
+ }
861
+
862
+ function getDocTargetPublicName(name) {
863
+ const parts = String(name || '').split('.');
864
+ return parts[parts.length - 1] || '';
865
+ }
866
+
867
+ function collectTopLevelDocDeclarations(ast) {
868
+ const declarations = new Map();
869
+ for (const node of ast.program.body) {
870
+ const targets = [];
871
+ addDocTargetsFromDeclaration(targets, node);
872
+ for (const target of targets) {
873
+ declarations.set(target.name, target);
874
+ }
875
+ }
876
+ return declarations;
877
+ }
878
+
879
+ function addDocTargetsFromExportSpecifiers(targets, node, declarations) {
880
+ for (const specifier of node.specifiers || []) {
881
+ if (specifier.type !== 'ExportSpecifier' || specifier.local?.type !== 'Identifier') {
882
+ continue;
883
+ }
884
+
885
+ const declaration = declarations.get(specifier.local.name);
886
+ if (declaration) {
887
+ targets.push({
888
+ name: getExportedSpecifierName(specifier) || declaration.name,
889
+ node: declaration.node,
890
+ });
891
+ }
48
892
  }
49
893
  }
50
894
 
51
- module.exports = { listTopics, getTopicContent, TOPICS };
895
+ function addDocTargetFromDefaultExportIdentifier(targets, node, declarations) {
896
+ if (node.declaration?.type !== 'Identifier') {
897
+ return;
898
+ }
899
+
900
+ const declaration = declarations.get(node.declaration.name);
901
+ if (declaration) {
902
+ targets.push({ name: 'default', node: declaration.node });
903
+ }
904
+ }
905
+
906
+ function getExportedSpecifierName(specifier) {
907
+ if (specifier.exported?.type === 'Identifier') {
908
+ return specifier.exported.name;
909
+ }
910
+ return specifier.exported?.type === 'StringLiteral' ? specifier.exported.value : null;
911
+ }
912
+
913
+ function addDocTargetsFromDeclaration(targets, node, reportNode = node) {
914
+ if (!node) {
915
+ return;
916
+ }
917
+
918
+ if (
919
+ reportNode?.type === 'ExportDefaultDeclaration' &&
920
+ [
921
+ 'ArrowFunctionExpression',
922
+ 'FunctionExpression',
923
+ 'ClassExpression',
924
+ 'FunctionDeclaration',
925
+ 'ClassDeclaration',
926
+ ].includes(node.type)
927
+ ) {
928
+ targets.push({ name: 'default', node: reportNode });
929
+ } else if (node.type === 'FunctionDeclaration' && node.id?.name) {
930
+ targets.push({ name: node.id.name, node: reportNode });
931
+ } else if (node.type === 'ClassDeclaration' && node.id?.name) {
932
+ targets.push({ name: node.id.name, node: reportNode });
933
+ } else if (node.type === 'VariableDeclaration') {
934
+ for (const declaration of node.declarations) {
935
+ const initType = declaration.init?.type;
936
+ if (
937
+ declaration.id?.type === 'Identifier' &&
938
+ ['ArrowFunctionExpression', 'FunctionExpression', 'ClassExpression'].includes(initType)
939
+ ) {
940
+ targets.push({ name: declaration.id.name, node: reportNode });
941
+ }
942
+ }
943
+ }
944
+ }
945
+
946
+ function addDocTargetFromCommonJsExport(targets, node, declarations = new Map()) {
947
+ const expression = node.expression;
948
+ if (expression?.type !== 'AssignmentExpression') {
949
+ return;
950
+ }
951
+
952
+ const initType = expression.right?.type;
953
+ const name = getCommonJsExportName(expression.left);
954
+ if (!name) {
955
+ return;
956
+ }
957
+
958
+ if (['ArrowFunctionExpression', 'FunctionExpression', 'ClassExpression'].includes(initType)) {
959
+ targets.push({ name, node });
960
+ } else if (name === 'module.exports' && initType === 'ObjectExpression') {
961
+ addDocTargetsFromObjectExport(targets, expression.right, declarations);
962
+ } else if (initType === 'Identifier') {
963
+ const declaration = declarations.get(expression.right.name);
964
+ if (declaration) {
965
+ targets.push({ name, node: declaration.node });
966
+ }
967
+ }
968
+ }
969
+
970
+ function addDocTargetsFromObjectExport(targets, node, declarations = new Map()) {
971
+ for (const property of node.properties || []) {
972
+ const hasExportedFunction = property.type === 'ObjectMethod' || (
973
+ property.type === 'ObjectProperty' &&
974
+ ['ArrowFunctionExpression', 'FunctionExpression', 'ClassExpression'].includes(property.value?.type)
975
+ );
976
+ if (hasExportedFunction) {
977
+ const name = getObjectPropertyName(property);
978
+ if (name) {
979
+ targets.push({ name: `module.exports.${name}`, node: property });
980
+ }
981
+ } else if (property.type === 'ObjectProperty' && property.value?.type === 'Identifier') {
982
+ const name = getObjectPropertyName(property);
983
+ const declaration = declarations.get(property.value.name);
984
+ if (name && declaration) {
985
+ targets.push({ name: `module.exports.${name}`, node: declaration.node });
986
+ }
987
+ }
988
+ }
989
+ }
990
+
991
+ function getCommonJsExportName(node) {
992
+ if (node?.type !== 'MemberExpression') {
993
+ return null;
994
+ }
995
+
996
+ if (node.object?.type === 'Identifier' && node.object.name === 'exports') {
997
+ return getMemberPropertyName(node);
998
+ }
999
+
1000
+ if (
1001
+ node.object?.type === 'MemberExpression' &&
1002
+ node.object.object?.type === 'Identifier' &&
1003
+ node.object.object.name === 'module' &&
1004
+ getMemberPropertyName(node.object) === 'exports'
1005
+ ) {
1006
+ return getMemberPropertyName(node);
1007
+ }
1008
+
1009
+ if (node.object?.type === 'Identifier' && node.object.name === 'module' && getMemberPropertyName(node) === 'exports') {
1010
+ return 'module.exports';
1011
+ }
1012
+
1013
+ return null;
1014
+ }
1015
+
1016
+ function getMemberPropertyName(node) {
1017
+ if (node.computed) {
1018
+ return node.property?.type === 'StringLiteral' ? node.property.value : null;
1019
+ }
1020
+ return node.property?.type === 'Identifier' ? node.property.name : null;
1021
+ }
1022
+
1023
+ function getObjectPropertyName(node) {
1024
+ if (node.computed) {
1025
+ return node.key?.type === 'StringLiteral' ? node.key.value : null;
1026
+ }
1027
+ if (node.key?.type === 'Identifier') {
1028
+ return node.key.name;
1029
+ }
1030
+ return node.key?.type === 'StringLiteral' ? node.key.value : null;
1031
+ }
1032
+
1033
+ function checkDocstringCoverage(packageDir) {
1034
+ const missing = [];
1035
+ let total = 0;
1036
+ let documented = 0;
1037
+ const files = getSourceFiles(packageDir);
1038
+
1039
+ for (const file of files) {
1040
+ const source = fs.readFileSync(file, 'utf8');
1041
+ let ast;
1042
+ try {
1043
+ ast = parse(source, {
1044
+ sourceType: 'unambiguous',
1045
+ plugins: getParserPlugins(file),
1046
+ attachComment: true,
1047
+ locations: true,
1048
+ ranges: false,
1049
+ });
1050
+ } catch (error) {
1051
+ missing.push({
1052
+ file: path.relative(packageDir, file).replaceAll('\\', '/'),
1053
+ name: '<parse-error>',
1054
+ line: 1,
1055
+ reason: error.message,
1056
+ });
1057
+ continue;
1058
+ }
1059
+
1060
+ for (const target of getPublicDocTargets(source, ast)) {
1061
+ total++;
1062
+ if (hasLeadingJsDoc(source, ast.comments || [], target.node)) {
1063
+ documented++;
1064
+ } else {
1065
+ missing.push({
1066
+ file: path.relative(packageDir, file).replaceAll('\\', '/'),
1067
+ name: target.name,
1068
+ line: target.node.loc?.start?.line || 1,
1069
+ reason: 'Missing leading JSDoc block',
1070
+ });
1071
+ }
1072
+ }
1073
+ }
1074
+
1075
+ const percent = total === 0 ? 100 : Math.round((documented / total) * 10000) / 100;
1076
+ return { filesChecked: files.length, total, documented, percent, missing };
1077
+ }
1078
+
1079
+ function getParserPlugins(file) {
1080
+ const ext = path.extname(file).toLowerCase();
1081
+ const plugins = ['topLevelAwait', ['decorators', { decoratorsBeforeExport: true }]];
1082
+ if (['.ts', '.tsx', '.mts', '.cts'].includes(ext)) {
1083
+ plugins.push('typescript');
1084
+ }
1085
+ if (['.js', '.mjs', '.jsx', '.tsx'].includes(ext)) {
1086
+ plugins.push('jsx');
1087
+ }
1088
+ return plugins;
1089
+ }
1090
+
1091
+ function brokenLinkKey(item) {
1092
+ return `${item.file}\0${item.line}\0${item.target}\0${item.reason}`;
1093
+ }
1094
+
1095
+ function loadDocsBaseline(packageDir, baselinePath) {
1096
+ if (!baselinePath) {
1097
+ return new Set();
1098
+ }
1099
+
1100
+ const resolved = path.resolve(packageDir, baselinePath);
1101
+ if (!isWithinRoot(packageDir, resolved)) {
1102
+ throw new Error(`Baseline path escapes project root: "${baselinePath}"`);
1103
+ }
1104
+ if (!fs.existsSync(resolved)) {
1105
+ return new Set();
1106
+ }
1107
+
1108
+ try {
1109
+ const parsed = JSON.parse(fs.readFileSync(resolved, 'utf8'));
1110
+ return new Set((parsed.brokenLinks || []).map(brokenLinkKey));
1111
+ } catch (error) {
1112
+ throw new Error(`Invalid docs baseline JSON at "${resolved}": ${error.message}`);
1113
+ }
1114
+ }
1115
+
1116
+ function writeDocsBaseline(packageDir, baselinePath, result) {
1117
+ const resolved = path.resolve(packageDir, baselinePath);
1118
+ if (!isWithinRoot(packageDir, resolved)) {
1119
+ throw new Error(`Baseline path escapes project root: "${baselinePath}"`);
1120
+ }
1121
+ fs.mkdirSync(path.dirname(resolved), { recursive: true });
1122
+ fs.writeFileSync(
1123
+ resolved,
1124
+ `${JSON.stringify({
1125
+ generatedBy: 'forge docs verify --write-baseline',
1126
+ brokenLinks: result.links.allBrokenLinks || result.links.brokenLinks,
1127
+ }, null, 2)}\n`,
1128
+ 'utf8'
1129
+ );
1130
+ }
1131
+
1132
+ function validateDocs(packageDir, options = {}) {
1133
+ const minDocstringCoverage = Number.isFinite(options.minDocstringCoverage)
1134
+ ? options.minDocstringCoverage
1135
+ : 0;
1136
+ const links = checkMarkdownLinks(packageDir);
1137
+ const baseline = loadDocsBaseline(packageDir, options.baselinePath);
1138
+ const allBrokenLinks = links.brokenLinks;
1139
+ const newBrokenLinks = allBrokenLinks.filter((item) => !baseline.has(brokenLinkKey(item)));
1140
+ links.allBrokenLinks = allBrokenLinks;
1141
+ links.knownBrokenLinks = allBrokenLinks.length - newBrokenLinks.length;
1142
+ links.brokenLinks = newBrokenLinks;
1143
+ const docstrings = checkDocstringCoverage(packageDir);
1144
+ const parseErrors = docstrings.missing
1145
+ .filter((item) => item.name === '<parse-error>')
1146
+ .map((item) => ({ type: 'docstring-parse-error', ...item }));
1147
+ const failures = [
1148
+ ...links.brokenLinks.map((item) => ({ type: 'broken-link', ...item })),
1149
+ ...parseErrors,
1150
+ ...(docstrings.percent < minDocstringCoverage
1151
+ ? [{
1152
+ type: 'docstring-coverage',
1153
+ reason: `Docstring coverage ${docstrings.percent}% is below ${minDocstringCoverage}%`,
1154
+ }]
1155
+ : []),
1156
+ ];
1157
+
1158
+ return {
1159
+ ok: failures.length === 0,
1160
+ links,
1161
+ docstrings,
1162
+ failures,
1163
+ };
1164
+ }
1165
+
1166
+ function formatDocsValidation(result) {
1167
+ const lines = [
1168
+ 'Forge docs validation',
1169
+ '',
1170
+ `Markdown files checked: ${result.links.filesChecked}`,
1171
+ `Markdown links checked: ${result.links.linksChecked}`,
1172
+ `Broken links: ${result.links.brokenLinks.length}`,
1173
+ `Known broken links in baseline: ${result.links.knownBrokenLinks || 0}`,
1174
+ `Source files checked: ${result.docstrings.filesChecked}`,
1175
+ `Docstring coverage: ${result.docstrings.documented}/${result.docstrings.total} (${result.docstrings.percent}%)`,
1176
+ ];
1177
+
1178
+ if (result.links.brokenLinks.length > 0) {
1179
+ lines.push('', 'Broken links:');
1180
+ for (const item of result.links.brokenLinks) {
1181
+ lines.push(` - ${item.file}:${item.line} ${item.target} (${item.reason})`);
1182
+ }
1183
+ }
1184
+
1185
+ if (result.docstrings.missing.length > 0) {
1186
+ lines.push('', 'Missing docstrings:');
1187
+ for (const item of result.docstrings.missing.slice(0, 50)) {
1188
+ lines.push(` - ${item.file}:${item.line} ${item.name} (${item.reason})`);
1189
+ }
1190
+ if (result.docstrings.missing.length > 50) {
1191
+ lines.push(` ... ${result.docstrings.missing.length - 50} more`);
1192
+ }
1193
+ }
1194
+
1195
+ lines.push('', result.ok ? 'Docs validation passed.' : 'Docs validation failed.');
1196
+ return lines.join('\n');
1197
+ }
1198
+
1199
+ module.exports = {
1200
+ listTopics,
1201
+ getTopicContent,
1202
+ validateDocs,
1203
+ formatDocsValidation,
1204
+ writeDocsBaseline,
1205
+ TOPICS,
1206
+ };