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
@@ -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
+ };