forge-workflow 0.0.9 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (479) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -106
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +466 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/reference/TOOLCHAIN.md +658 -0
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +225 -28
  81. package/lib/beads-sync-scaffold.js +36 -107
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +744 -70
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +66 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +22 -2
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +851 -979
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +329 -11
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +15 -2
  142. package/lib/commands/test.js +58 -7
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +56 -25
  146. package/lib/commands/worktree.js +308 -128
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +184 -0
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +42 -17
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +697 -0
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/issue-sync/authority.js +100 -0
  174. package/lib/issue-sync/github-pull.js +184 -0
  175. package/lib/issue-sync/import-primitives.js +98 -0
  176. package/lib/issue-sync/legacy-link-bridge.js +436 -0
  177. package/lib/issue-sync/link-store.js +292 -0
  178. package/lib/issue-sync/project-github.js +123 -0
  179. package/lib/issue-sync/reconcile.js +195 -0
  180. package/lib/issue-sync/schema.js +126 -0
  181. package/lib/kernel/backing-issue.js +305 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/planning-buckets-schema.js +109 -0
  192. package/lib/kernel/projection-jsonl-writer.js +450 -0
  193. package/lib/kernel/readiness-model.js +329 -0
  194. package/lib/kernel/schema.js +356 -0
  195. package/lib/kernel/sqlite-driver.js +2504 -0
  196. package/lib/kernel/taxonomy-validator.js +394 -0
  197. package/lib/lefthook-check.js +8 -4
  198. package/lib/lefthook-wiring.js +413 -0
  199. package/lib/mcp-config-renderer.js +288 -0
  200. package/lib/memory/graphiti-mcp.js +106 -0
  201. package/lib/memory/router.js +387 -0
  202. package/lib/memory/typed-api.js +102 -0
  203. package/lib/memory-digest.js +195 -0
  204. package/lib/merge-rules.js +395 -0
  205. package/lib/migrate-dry-run.js +466 -0
  206. package/lib/orientation.js +863 -0
  207. package/lib/package-manager-remediation.js +103 -0
  208. package/lib/package-root.js +381 -0
  209. package/lib/patch-intent.js +890 -0
  210. package/lib/plugin-catalog.js +3 -4
  211. package/lib/plugin-manager.js +0 -5
  212. package/lib/pr-bundle.js +186 -0
  213. package/lib/pr-monitor/differ.js +195 -0
  214. package/lib/pr-monitor/events.js +0 -0
  215. package/lib/pr-monitor/gather.js +124 -0
  216. package/lib/pr-monitor/journal.js +299 -0
  217. package/lib/pr-monitor/monitor.js +146 -0
  218. package/lib/pr-monitor/render-sticky.js +157 -0
  219. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  220. package/lib/pr-monitor/watch.js +247 -0
  221. package/lib/pr-pull.js +1273 -0
  222. package/lib/pr-shepherd.js +494 -0
  223. package/lib/pr-state-validator.js +59 -0
  224. package/lib/preflight/gates.js +237 -0
  225. package/lib/preflight/runner.js +116 -0
  226. package/lib/project-discovery.js +0 -53
  227. package/lib/project-memory.js +166 -0
  228. package/lib/protected-path-manifest.js +281 -0
  229. package/lib/protected-state-surfaces.js +387 -0
  230. package/lib/release-readiness.js +2089 -0
  231. package/lib/reset.js +59 -45
  232. package/lib/review-adapter.js +68 -0
  233. package/lib/rules-sync.js +260 -0
  234. package/lib/runtime-health.js +332 -23
  235. package/lib/safety-config-renderer.js +268 -0
  236. package/lib/setup-action-log.js +1 -7
  237. package/lib/setup.js +27 -65
  238. package/lib/shell-utils.js +76 -6
  239. package/lib/skills-sync.js +330 -0
  240. package/lib/smart-status/conflicts.js +205 -0
  241. package/lib/smart-status/scoring.js +191 -0
  242. package/lib/status/beads-snapshot.js +145 -0
  243. package/lib/status/presenter.js +216 -0
  244. package/lib/status/snapshot.js +186 -0
  245. package/lib/sync-backend.js +202 -0
  246. package/lib/untrusted-content.js +52 -0
  247. package/lib/upgrade-safety.js +199 -0
  248. package/lib/workflow/enforce-stage.js +298 -47
  249. package/lib/workflow/stage-transition.js +115 -0
  250. package/lib/workflow/stages.js +30 -6
  251. package/lib/workflow/state-manager.js +159 -14
  252. package/lib/workflow/state.js +23 -1
  253. package/lib/workflow-profiles.js +17 -5
  254. package/package.json +46 -36
  255. package/rules/documentation.md +19 -0
  256. package/rules/kernel-tracking.md +26 -0
  257. package/rules/security.md +22 -0
  258. package/rules/tdd.md +20 -0
  259. package/rules/workflow.md +27 -0
  260. package/scripts/auto-backing-issue.js +47 -0
  261. package/scripts/beads-context.sh +165 -22
  262. package/scripts/beads-migrate-to-dolt.sh +7 -0
  263. package/scripts/beads-upgrade-smoke.sh +284 -0
  264. package/scripts/behavioral-judge.sh +115 -11
  265. package/scripts/benchmark.js +349 -63
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard-analyze.js +52 -17
  272. package/scripts/dep-guard-keyword-ripple.js +29 -0
  273. package/scripts/dep-guard-render-review.js +86 -0
  274. package/scripts/dep-guard.sh +64 -232
  275. package/scripts/file-index.sh +3 -0
  276. package/scripts/forge-team/lib/claim.sh +34 -18
  277. package/scripts/forge-team/lib/dashboard.sh +61 -86
  278. package/scripts/forge-team/lib/epic.sh +99 -263
  279. package/scripts/forge-team/lib/hooks.sh +26 -28
  280. package/scripts/forge-team/lib/identity.sh +4 -4
  281. package/scripts/forge-team/lib/sync-github.sh +144 -47
  282. package/scripts/forge-team/lib/verify.sh +93 -83
  283. package/scripts/forge-team/lib/workload.sh +41 -65
  284. package/scripts/forge-team/tests/claim.test.sh +25 -19
  285. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  286. package/scripts/forge-team/tests/epic.test.sh +52 -71
  287. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  288. package/scripts/forge-team/tests/identity.test.sh +3 -3
  289. package/scripts/forge-team/tests/integration.test.sh +44 -66
  290. package/scripts/forge-team/tests/sync-github.test.sh +183 -79
  291. package/scripts/forge-team/tests/verify.test.sh +37 -46
  292. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  293. package/scripts/forge-team/tests/workload.test.sh +32 -66
  294. package/scripts/gen-command-manifest.js +153 -0
  295. package/scripts/gen-embedded-assets.mjs +129 -0
  296. package/scripts/install.ps1 +139 -0
  297. package/scripts/install.sh +268 -0
  298. package/scripts/lib/beads-migrate-to-dolt.mjs +503 -0
  299. package/scripts/lib/release-asset.mjs +84 -0
  300. package/scripts/parity-check.mjs +145 -0
  301. package/scripts/parity-check.test.mjs +58 -0
  302. package/scripts/pin-agentic-workflow-images.js +112 -0
  303. package/scripts/pr-coordinator.sh +3 -0
  304. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  305. package/scripts/preflight.sh +108 -0
  306. package/scripts/protected-state-check.js +104 -0
  307. package/scripts/smart-status-score.js +31 -0
  308. package/scripts/smart-status-sessions.js +51 -0
  309. package/scripts/smart-status.sh +117 -369
  310. package/scripts/spikes/config-race-bench.js +111 -0
  311. package/scripts/spikes/harness-capability-matrix.js +13 -0
  312. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  313. package/scripts/spikes/protected-path-manifest.js +20 -0
  314. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  315. package/scripts/sync-agent-skills.js +62 -0
  316. package/scripts/sync-agentic-workflow.js +48 -0
  317. package/scripts/sync-utils.sh +3 -0
  318. package/scripts/test-ci-shard.js +251 -0
  319. package/scripts/test-dashboard.js +188 -52
  320. package/scripts/test-full-suite.js +186 -0
  321. package/scripts/test-profile.js +278 -0
  322. package/scripts/test.js +302 -28
  323. package/scripts/validate.js +143 -0
  324. package/scripts/validate.sh +18 -1
  325. package/skills/claim-safety/SKILL.md +102 -0
  326. package/skills/claim-safety/evals/evals.json +46 -0
  327. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +46 -52
  328. package/skills/dev/evals/evals.json +50 -0
  329. package/skills/hermes-forge/SKILL.md +185 -0
  330. package/skills/hermes-forge/evals/evals.json +46 -0
  331. package/skills/issue-basics/SKILL.md +111 -0
  332. package/skills/issue-basics/evals/evals.json +46 -0
  333. package/skills/kernel/SKILL.md +166 -0
  334. package/skills/kernel/evals/evals.json +50 -0
  335. package/skills/memory/SKILL.md +102 -0
  336. package/skills/parallel-deep-research/SKILL.md +14 -11
  337. package/skills/parallel-deep-research/evals/evals.json +11 -27
  338. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +134 -159
  339. package/skills/plan/evals/evals.json +42 -0
  340. package/skills/research/SKILL.md +195 -0
  341. package/skills/research/evals/evals.json +42 -0
  342. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  343. package/skills/review/evals/evals.json +42 -0
  344. package/skills/rollback/SKILL.md +110 -0
  345. package/skills/rollback/evals/evals.json +46 -0
  346. package/skills/rollback/references/methods.md +204 -0
  347. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  348. package/skills/shepherd/SKILL.md +66 -0
  349. package/skills/shepherd/evals/evals.json +42 -0
  350. package/skills/ship/SKILL.md +251 -0
  351. package/skills/ship/evals/evals.json +42 -0
  352. package/skills/smith/SKILL.md +142 -0
  353. package/skills/smith/evals/evals.json +46 -0
  354. package/skills/smith/references/autonomy-and-gates.md +94 -0
  355. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  356. package/skills/sonarcloud/evals/evals.json +46 -0
  357. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  358. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  359. package/skills/status/SKILL.md +102 -0
  360. package/skills/status/evals/evals.json +50 -0
  361. package/skills/triage-ready/SKILL.md +121 -0
  362. package/skills/triage-ready/evals/evals.json +42 -0
  363. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  364. package/skills/validate/evals/evals.json +42 -0
  365. package/skills/verify/SKILL.md +299 -0
  366. package/skills/verify/evals/evals.json +50 -0
  367. package/.claude/commands/dev.md +0 -345
  368. package/.claude/commands/plan.md +0 -566
  369. package/.claude/commands/premerge.md +0 -186
  370. package/.claude/commands/research.md +0 -42
  371. package/.claude/commands/review.md +0 -451
  372. package/.claude/commands/rollback.md +0 -721
  373. package/.claude/commands/ship.md +0 -213
  374. package/.claude/commands/sonarcloud.md +0 -152
  375. package/.claude/commands/status.md +0 -90
  376. package/.claude/commands/validate.md +0 -288
  377. package/.claude/commands/verify.md +0 -269
  378. package/.claude/rules/workflow.md +0 -121
  379. package/.cline/workflows/dev.md +0 -342
  380. package/.cline/workflows/plan.md +0 -563
  381. package/.cline/workflows/premerge.md +0 -183
  382. package/.cline/workflows/research.md +0 -39
  383. package/.cline/workflows/review.md +0 -448
  384. package/.cline/workflows/rollback.md +0 -718
  385. package/.cline/workflows/ship.md +0 -210
  386. package/.cline/workflows/sonarcloud.md +0 -146
  387. package/.cline/workflows/status.md +0 -87
  388. package/.cline/workflows/validate.md +0 -285
  389. package/.cline/workflows/verify.md +0 -266
  390. package/.codex/config.toml +0 -11
  391. package/.codex/skills/dev/SKILL.md +0 -345
  392. package/.codex/skills/plan/SKILL.md +0 -566
  393. package/.codex/skills/premerge/SKILL.md +0 -186
  394. package/.codex/skills/research/SKILL.md +0 -42
  395. package/.codex/skills/review/SKILL.md +0 -451
  396. package/.codex/skills/rollback/SKILL.md +0 -721
  397. package/.codex/skills/ship/SKILL.md +0 -213
  398. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  399. package/.codex/skills/status/SKILL.md +0 -90
  400. package/.codex/skills/validate/SKILL.md +0 -288
  401. package/.codex/skills/verify/SKILL.md +0 -269
  402. package/.cursor/commands/dev.md +0 -342
  403. package/.cursor/commands/plan.md +0 -563
  404. package/.cursor/commands/premerge.md +0 -183
  405. package/.cursor/commands/research.md +0 -39
  406. package/.cursor/commands/review.md +0 -448
  407. package/.cursor/commands/ship.md +0 -210
  408. package/.cursor/commands/sonarcloud.md +0 -146
  409. package/.cursor/commands/status.md +0 -87
  410. package/.cursor/commands/validate.md +0 -285
  411. package/.cursor/commands/verify.md +0 -266
  412. package/.cursorrules +0 -149
  413. package/.github/prompts/premerge.prompt.md +0 -188
  414. package/.github/prompts/research.prompt.md +0 -44
  415. package/.github/prompts/rollback.prompt.md +0 -723
  416. package/.github/prompts/ship.prompt.md +0 -215
  417. package/.github/prompts/status.prompt.md +0 -92
  418. package/.github/prompts/verify.prompt.md +0 -271
  419. package/.github/workflows/beads-to-github.yml +0 -56
  420. package/.github/workflows/github-to-beads.yml +0 -97
  421. package/.kilocode/workflows/dev.md +0 -346
  422. package/.kilocode/workflows/plan.md +0 -567
  423. package/.kilocode/workflows/premerge.md +0 -187
  424. package/.kilocode/workflows/research.md +0 -43
  425. package/.kilocode/workflows/review.md +0 -452
  426. package/.kilocode/workflows/rollback.md +0 -722
  427. package/.kilocode/workflows/ship.md +0 -214
  428. package/.kilocode/workflows/sonarcloud.md +0 -150
  429. package/.kilocode/workflows/status.md +0 -91
  430. package/.kilocode/workflows/validate.md +0 -289
  431. package/.kilocode/workflows/verify.md +0 -270
  432. package/.opencode/commands/dev.md +0 -345
  433. package/.opencode/commands/plan.md +0 -566
  434. package/.opencode/commands/premerge.md +0 -186
  435. package/.opencode/commands/research.md +0 -42
  436. package/.opencode/commands/review.md +0 -451
  437. package/.opencode/commands/rollback.md +0 -721
  438. package/.opencode/commands/ship.md +0 -213
  439. package/.opencode/commands/sonarcloud.md +0 -149
  440. package/.opencode/commands/status.md +0 -90
  441. package/.opencode/commands/validate.md +0 -288
  442. package/.opencode/commands/verify.md +0 -269
  443. package/.roo/commands/dev.md +0 -346
  444. package/.roo/commands/plan.md +0 -567
  445. package/.roo/commands/premerge.md +0 -187
  446. package/.roo/commands/research.md +0 -43
  447. package/.roo/commands/review.md +0 -452
  448. package/.roo/commands/rollback.md +0 -722
  449. package/.roo/commands/ship.md +0 -214
  450. package/.roo/commands/sonarcloud.md +0 -150
  451. package/.roo/commands/status.md +0 -91
  452. package/.roo/commands/validate.md +0 -289
  453. package/.roo/commands/verify.md +0 -270
  454. package/docs/BEADS_GITHUB_SYNC.md +0 -255
  455. package/docs/GREPTILE_SETUP.md +0 -400
  456. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  457. package/docs/SETUP.md +0 -663
  458. package/docs/VALIDATION.md +0 -363
  459. package/lib/agents/cline.plugin.json +0 -29
  460. package/lib/agents/copilot.plugin.json +0 -24
  461. package/lib/agents/kilocode.plugin.json +0 -22
  462. package/lib/agents/opencode.plugin.json +0 -23
  463. package/lib/agents/roo.plugin.json +0 -30
  464. package/lib/beads-health-check.js +0 -143
  465. package/lib/commands/commands-reset.js +0 -147
  466. package/opencode.json +0 -67
  467. package/scripts/beads-context.test.js +0 -567
  468. package/scripts/github-beads-sync/comment.mjs +0 -64
  469. package/scripts/github-beads-sync/config.mjs +0 -148
  470. package/scripts/github-beads-sync/github-api.mjs +0 -131
  471. package/scripts/github-beads-sync/index.mjs +0 -332
  472. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  473. package/scripts/github-beads-sync/mapping.mjs +0 -78
  474. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  475. package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
  476. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  477. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  478. package/scripts/github-beads-sync.config.json +0 -26
  479. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,177 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * doc-gate `.docgate.json` declaration loader + validator.
5
+ *
6
+ * The detector (lib/doc-gate/detect.js) abstains / escalates on repos it cannot
7
+ * confidently resolve. This module implements the research-validated
8
+ * "declaration beats inference" escape hatch (cf. corepack `packageManager`,
9
+ * knip.json): a repo may commit a `.docgate.json` that AUTHORITATIVELY declares
10
+ * its structure, so the detector resolves it and the gate enforces precisely.
11
+ *
12
+ * Design (consistent with the detector + gate):
13
+ * - Tracked-files-only: the declaration is read ONLY when `git ls-files` lists
14
+ * it, so untracked worktree clutter can never change a verdict.
15
+ * - Fail-closed: the git wrapper THROWS on failure; a git error, unreadable
16
+ * file, invalid JSON, or a schema violation is SURFACED as a non-empty
17
+ * `errors` array — an invalid declaration is never silently ignored.
18
+ * - Strict schema: unknown top-level keys are rejected and every field's type
19
+ * is validated; `version` must be 1.
20
+ *
21
+ * @module doc-gate/declaration
22
+ */
23
+
24
+ const fs = require('node:fs');
25
+ const path = require('node:path');
26
+ const cp = require('node:child_process');
27
+
28
+ const DECLARATION_FILE = '.docgate.json';
29
+ const ALLOWED_KEYS = new Set(['version', 'source', 'toolchain', 'excludeFromGate', 'rules']);
30
+ const RULE_KEYS = new Set(['when', 'requires']);
31
+
32
+ /**
33
+ * Strict git wrapper: THROWS on any failure (fail-closed). The declaration
34
+ * loader must never treat a git error as "no declaration" — that would let a
35
+ * broken repo silently skip enforcement. Callers turn the throw into an explicit
36
+ * `errors` entry.
37
+ *
38
+ * @param {string} root - Repository root.
39
+ * @param {string[]} args - git arguments.
40
+ * @returns {string} stdout.
41
+ */
42
+ function gitStrict(root, args) {
43
+ // NOSONAR S4036 - hardcoded CLI command, no user input.
44
+ const res = cp.spawnSync('git', ['-C', root, ...args], { encoding: 'utf8' }); // NOSONAR S4036
45
+ if (res.error) throw new Error(`git ${args.join(' ')}: ${res.error.message}`);
46
+ if (res.status !== 0) throw new Error(`git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`);
47
+ return res.stdout;
48
+ }
49
+
50
+ /** True when `file` is a tracked path in the repo at `root`. */
51
+ function isTracked(root, file) {
52
+ const out = gitStrict(root, ['ls-files', '--', file]);
53
+ return out.split('\n').map(s => s.trim()).filter(Boolean).length > 0;
54
+ }
55
+
56
+ /** True when `v` is an array of NON-BLANK strings (an empty array is allowed).
57
+ * Blank/whitespace entries are rejected so `source: [""]` can't promote a repo to
58
+ * DECLARED while matching no files. */
59
+ function isNonBlankStringArray(v) {
60
+ return Array.isArray(v) && v.every(x => typeof x === 'string' && x.trim() !== '');
61
+ }
62
+
63
+ /** Validate a single `rules[]` entry, pushing precise messages into `errors`. */
64
+ function validateRule(rule, index, errors) {
65
+ if (rule === null || typeof rule !== 'object' || Array.isArray(rule)) {
66
+ errors.push(`"rules[${index}]" must be an object with "when" and "requires"`);
67
+ return;
68
+ }
69
+ if (typeof rule.when !== 'string' || !rule.when) errors.push(`"rules[${index}].when" must be a non-empty string`);
70
+ if (typeof rule.requires !== 'string' || !rule.requires) errors.push(`"rules[${index}].requires" must be a non-empty string`);
71
+ for (const key of Object.keys(rule)) {
72
+ if (!RULE_KEYS.has(key)) errors.push(`"rules[${index}]" has unknown key "${key}"`);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Validate a parsed `.docgate.json` object against the strict schema.
78
+ *
79
+ * Schema: `{ version: 1, source?: string[], toolchain?: string,
80
+ * excludeFromGate?: string[], rules?: [{ when: string, requires: string }] }`.
81
+ *
82
+ * @param {*} parsed - The JSON.parse result.
83
+ * @returns {{ declaration: object|null, errors: string[] }} A valid object is
84
+ * returned as `declaration`; ANY problem yields a non-empty `errors` array and
85
+ * a null `declaration` (invalid declarations are never applied).
86
+ */
87
+ /** Validate the optional schema fields (source/toolchain/excludeFromGate/rules),
88
+ * pushing precise messages into `errors`. Extracted to keep validateDeclaration
89
+ * under the SonarCloud cognitive-complexity threshold. */
90
+ function validateOptionalFields(parsed, errors) {
91
+ if (parsed.source !== undefined && !(isNonBlankStringArray(parsed.source) && parsed.source.length > 0)) {
92
+ errors.push('"source" must be a non-empty array of non-empty strings');
93
+ }
94
+ if (parsed.toolchain !== undefined && (typeof parsed.toolchain !== 'string' || !parsed.toolchain.trim())) {
95
+ errors.push('"toolchain" must be a non-empty string');
96
+ }
97
+ if (parsed.excludeFromGate !== undefined && !isNonBlankStringArray(parsed.excludeFromGate)) {
98
+ errors.push('"excludeFromGate" must be an array of non-empty strings');
99
+ }
100
+ if (parsed.rules !== undefined) {
101
+ if (!Array.isArray(parsed.rules)) errors.push('"rules" must be an array');
102
+ else parsed.rules.forEach((rule, i) => validateRule(rule, i, errors));
103
+ }
104
+ }
105
+
106
+ function validateDeclaration(parsed) {
107
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed)) {
108
+ return { declaration: null, errors: [`${DECLARATION_FILE} must be a JSON object`] };
109
+ }
110
+ const errors = [];
111
+ for (const key of Object.keys(parsed)) {
112
+ if (!ALLOWED_KEYS.has(key)) errors.push(`unknown top-level key "${key}"`);
113
+ }
114
+ if (parsed.version !== 1) errors.push('"version" must be 1');
115
+ validateOptionalFields(parsed, errors);
116
+ if (errors.length > 0) return { declaration: null, errors };
117
+ return { declaration: parsed, errors: [] };
118
+ }
119
+
120
+ /**
121
+ * Load + validate a repo's committed `.docgate.json` declaration.
122
+ *
123
+ * Tracked-files-only: the file is read only when `git ls-files` lists it. A
124
+ * missing / untracked file is a normal "no declaration" (`{ declaration: null,
125
+ * errors: [] }`). A git error, unreadable file, invalid JSON, or a schema
126
+ * violation is surfaced as a non-empty `errors` array (fail-closed).
127
+ *
128
+ * @param {string} root - Absolute repository root (a git working tree).
129
+ * @returns {{ declaration: object|null, errors: string[] }}
130
+ */
131
+ function loadDeclaration(root) {
132
+ let tracked;
133
+ try {
134
+ tracked = isTracked(root, DECLARATION_FILE);
135
+ } catch (err) {
136
+ // FAIL-CLOSED: a git failure must surface, never be treated as "no file".
137
+ return { declaration: null, errors: [`could not determine tracked status of ${DECLARATION_FILE}: ${err.message}`] };
138
+ }
139
+ if (!tracked) return { declaration: null, errors: [] };
140
+
141
+ let raw;
142
+ try {
143
+ raw = fs.readFileSync(path.join(root, DECLARATION_FILE), 'utf8');
144
+ } catch (err) {
145
+ return { declaration: null, errors: [`could not read ${DECLARATION_FILE}: ${err.message}`] };
146
+ }
147
+
148
+ let parsed;
149
+ try {
150
+ parsed = JSON.parse(raw);
151
+ } catch (err) {
152
+ return { declaration: null, errors: [`invalid JSON in ${DECLARATION_FILE}: ${err.message}`] };
153
+ }
154
+
155
+ return validateDeclaration(parsed);
156
+ }
157
+
158
+ /**
159
+ * Build a starter `.docgate.json` object from a detector result, so a human or
160
+ * agent can commit + edit it. Uses the auto-detected source/toolchain as the
161
+ * starting point; falls back to a `src` placeholder when detection abstained.
162
+ *
163
+ * @param {object} detectResult - The result of `detect(root)`.
164
+ * @returns {object} A schema-valid declaration object.
165
+ */
166
+ function scaffoldDeclaration(detectResult) {
167
+ const declaration = { version: 1 };
168
+ const src = detectResult?.source?.value;
169
+ declaration.source = Array.isArray(src) && src.length > 0 ? [...src] : ['src'];
170
+ const toolchain = detectResult?.toolchain?.value;
171
+ if (typeof toolchain === 'string' && toolchain) declaration.toolchain = toolchain;
172
+ declaration.excludeFromGate = [];
173
+ declaration.rules = [];
174
+ return declaration;
175
+ }
176
+
177
+ module.exports = { loadDeclaration, validateDeclaration, scaffoldDeclaration, DECLARATION_FILE };
@@ -0,0 +1,289 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * doc-gate repo-structure detector.
5
+ *
6
+ * Ported (logic-for-logic) from the empirically-validated HYBRID detector v4.
7
+ * v4 was tested to ZERO "silent-wrong" (a high-confidence answer that is
8
+ * actually wrong) across 9 local repos AND 8 diverse OSS repos
9
+ * (Python/Go/Rust/JS).
10
+ *
11
+ * Design (do not regress):
12
+ * - Tracked-files-only: every EXISTENCE and CONTENT signal comes from
13
+ * `git ls-tree HEAD` / `git ls-files` (never the raw filesystem), so
14
+ * untracked clutter and gitignored worktree/node_modules lockfiles/manifests
15
+ * can't poison detection.
16
+ * - Per-field results for source/toolchain/ci/changelog/agents, each
17
+ * `{ value, source: 'code'|'ABSTAIN→agent', confidence: 'high'|'medium'|'abstain', escalate? }`.
18
+ * - Abstain-by-default on the error-prone fields; emit HIGH only where
19
+ * deterministic. `codeHighConfidence` lists the silent-wrong-eligible fields.
20
+ * - Escalation triggers: monorepo (npm/pnpm/turbo workspaces, Cargo
21
+ * `[workspace]`, `go.work`), nested gitlink wrapper (escalate ALL fields),
22
+ * multiple conflicting lockfiles, and secondary non-conventional source roots.
23
+ * - SOURCE is language/manifest-first with a packaging/private-dir BLOCKLIST so
24
+ * it never returns internal/pkg/vendor/etc as THE source (the fix that
25
+ * eliminated the two real-world silent-wrongs: Go `internal/`, Rust `pkg/`).
26
+ *
27
+ * @module doc-gate/detect
28
+ */
29
+
30
+ const fs = require('node:fs');
31
+ const path = require('node:path');
32
+ const cp = require('node:child_process');
33
+ const { loadDeclaration } = require('./declaration');
34
+
35
+ const read = p => { try { return fs.readFileSync(p, 'utf8'); } catch { return ''; } };
36
+ const readJSON = p => { try { return JSON.parse(read(p)); } catch { return null; } };
37
+ // NOSONAR S4036 - 'git' is a hardcoded CLI command with no user input; developer-tool context.
38
+ const git = (root, args) => { try { return cp.execFileSync('git', ['-C', root, ...args], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }); } catch { return ''; } }; // NOSONAR S4036
39
+ const gitLines = (root, args) => git(root, args).split('\n').map(s => s.trim()).filter(Boolean);
40
+ const trackedTop = root => new Set(gitLines(root, ['ls-tree', 'HEAD', '--name-only']));
41
+ // Top-level TRACKED directories only (git object type `tree`), so file entries
42
+ // like go.mod / README.md are never mistaken for source directories.
43
+ const trackedTopDirs = root => new Set(
44
+ git(root, ['ls-tree', 'HEAD']).split('\n').filter(Boolean)
45
+ .filter(line => line.split(/\s+/)[1] === 'tree')
46
+ .map(line => line.split('\t').pop())
47
+ .filter(Boolean),
48
+ );
49
+ const trackedMatch = (root, patterns) => gitLines(root, ['ls-files', ...patterns]);
50
+ // Content reads are tracked-files-only too: only read a manifest if it is a
51
+ // tracked top-level file, so untracked worktree clutter can't change the verdict.
52
+ const readTracked = (root, top, name) => (top.has(name) ? read(path.join(root, name)) : '');
53
+ const readTrackedJSON = (root, top, name) => (top.has(name) ? readJSON(path.join(root, name)) : null);
54
+ const ABSTAIN = trigger => ({ value: null, source: 'ABSTAIN→agent', confidence: 'abstain', escalate: true, trigger });
55
+
56
+ // Dirs that are NEVER, on their own, the gate-worthy source (Linguist-style).
57
+ const BLOCKLIST = new Set(['internal', 'pkg', 'vendor', 'testdata', 'examples', 'example', 'tests', 'test', 'docs', 'doc', 'dist', 'build', 'target', 'node_modules', 'scripts', 'tools', 'bench', 'benches', '.github', 'assets', 'public', 'static']);
58
+
59
+ function detectNesting(root) {
60
+ const lines = git(root, ['ls-tree', 'HEAD']).split('\n').filter(Boolean);
61
+ if (!lines.length) return { nested: false };
62
+ const gitlinks = lines.filter(l => l.startsWith('160000'));
63
+ if (gitlinks.length >= 1 && lines.length - gitlinks.length <= 1) return { nested: true, nestedDir: gitlinks[0].split('\t').pop() };
64
+ return { nested: false };
65
+ }
66
+
67
+ function detectMonorepo(root, top, topDirs) {
68
+ const reasons = [];
69
+ const pkg = readTrackedJSON(root, top, 'package.json');
70
+ if (pkg?.workspaces) reasons.push('npm-workspaces');
71
+ if (top.has('pnpm-workspace.yaml')) reasons.push('pnpm-workspace');
72
+ if (top.has('turbo.json')) reasons.push('turbo');
73
+ if (top.has('go.work')) reasons.push('go.work');
74
+ const cargo = readTracked(root, top, 'Cargo.toml');
75
+ if (/^\s*\[workspace\]/m.test(cargo) && /members\s*=/.test(cargo)) reasons.push('cargo-workspace');
76
+ if (['apps', 'packages', 'services'].filter(d => topDirs.has(d)).length >= 2) reasons.push('apps+packages');
77
+ return { monorepo: reasons.length > 0, reasons };
78
+ }
79
+
80
+ // --- language-specific source resolvers (kept small; detectSource dispatches) ---
81
+ // All directory candidates come from topDirs (tracked tree entries) so a tracked
82
+ // FILE named src/lib/<pkg> is never mistaken for a source directory.
83
+ function sourceRust(topDirs) {
84
+ if (topDirs.has('src')) return { value: ['src'], source: 'code', confidence: 'high', lang: 'rust' };
85
+ return ABSTAIN('rust-no-src');
86
+ }
87
+ function sourceGo(root, topDirs, has) {
88
+ const rootGo = trackedMatch(root, ['*.go']).some(f => !f.includes('/')); // tracked .go at repo root
89
+ if (rootGo) return { value: ['.'], source: 'code', confidence: 'high', lang: 'go', note: 'flat-root Go module' };
90
+ const goDirs = [...topDirs].filter(d => has(d)); // DIRECTORIES only — never a top-level file like go.mod
91
+ if (goDirs.length) return { value: goDirs, source: 'code', confidence: 'medium', lang: 'go' };
92
+ return ABSTAIN('go-no-root-source');
93
+ }
94
+ function sourcePython(root, top, topDirs) {
95
+ const py = readTracked(root, top, 'pyproject.toml');
96
+ const nameMatch = py.match(/^\s*name\s*=\s*["']([^"']+)["']/m);
97
+ const pkgName = nameMatch ? nameMatch[1].replaceAll('-', '_') : null;
98
+ if (topDirs.has('src')) return { value: ['src'], source: 'code', confidence: 'high', lang: 'python' };
99
+ if (pkgName && topDirs.has(pkgName)) return { value: [pkgName], source: 'code', confidence: 'high', lang: 'python', note: 'flat-layout package' };
100
+ return ABSTAIN('python-package-dir-unresolved');
101
+ }
102
+ function sourceJs(root, top, conv, secondaryRoots) {
103
+ if (secondaryRoots.length) return { ...ABSTAIN('secondary-roots:' + secondaryRoots.join('+')), conventionalSeen: conv };
104
+ if (conv.length >= 1 && conv.length <= 2) return { value: conv, source: 'code', confidence: 'high', lang: 'js' };
105
+ const pkg = readTrackedJSON(root, top, 'package.json') || {};
106
+ // Only count a manifest entry that is an ACTUAL ROOT-LEVEL tracked file — a
107
+ // nested entry like main:"dist/index.js" must NOT resolve source to '.'.
108
+ const rootEntry = ['index.js', 'index.ts', 'index.mjs', pkg.main, pkg.module]
109
+ .filter(Boolean)
110
+ .map(f => String(f).replace(/^\.\//, ''))
111
+ .some(f => !f.includes('/') && top.has(f));
112
+ if (rootEntry) return { value: ['.'], source: 'code', confidence: 'medium', lang: 'js', note: 'root-entry single-file lib' };
113
+ return ABSTAIN('js-no-conventional-source');
114
+ }
115
+
116
+ // Language-aware, manifest-first, blocklist-filtered source resolution.
117
+ function detectSource(root, top, topDirs, nested, mono) {
118
+ if (nested) return ABSTAIN('nested');
119
+ if (mono.monorepo) return ABSTAIN('monorepo:' + mono.reasons.join('+'));
120
+ const has = d => topDirs.has(d) && !BLOCKLIST.has(d);
121
+ const conv = ['src', 'lib', 'app'].filter(has);
122
+ const secondaryRoots = ['convex', 'supabase', 'backend', 'frontend', 'server', 'functions', 'api', 'worker', 'workers', 'edge'].filter(d => topDirs.has(d));
123
+
124
+ if (top.has('Cargo.toml')) return sourceRust(topDirs);
125
+ if (top.has('go.mod')) return sourceGo(root, topDirs, has);
126
+ if (readTracked(root, top, 'pyproject.toml') || top.has('setup.py')) return sourcePython(root, top, topDirs);
127
+ if (top.has('package.json')) return sourceJs(root, top, conv, secondaryRoots);
128
+ // Unknown stack.
129
+ if (secondaryRoots.length) return ABSTAIN('secondary-roots:' + secondaryRoots.join('+'));
130
+ if (conv.length >= 1 && conv.length <= 2) return { value: conv, source: 'code', confidence: 'high' };
131
+ return ABSTAIN(conv.length ? 'ambiguous' : 'no-conventional-source-dir');
132
+ }
133
+
134
+ const LOCK = { 'bun.lockb': 'bun', 'bun.lock': 'bun', 'pnpm-lock.yaml': 'pnpm', 'yarn.lock': 'yarn', 'package-lock.json': 'npm', 'uv.lock': 'uv', 'poetry.lock': 'poetry', 'Pipfile.lock': 'pipenv', 'Cargo.lock': 'cargo', 'go.sum': 'go', 'composer.lock': 'composer' };
135
+ function detectToolchain(root, top, nested) {
136
+ if (nested) return ABSTAIN('nested');
137
+ const hits = trackedMatch(root, Object.keys(LOCK).map(f => '*' + f));
138
+ const locks = hits.map(h => ({ file: h, manager: LOCK[h.split('/').pop()] })).filter(l => l.manager);
139
+ const managers = [...new Set(locks.map(l => l.manager))];
140
+ if (managers.length === 1) return { value: managers[0], source: 'code', confidence: 'high', lockfiles: locks.map(l => l.file) };
141
+ if (managers.length > 1) return { ...ABSTAIN('multiple-lockfiles'), conflicting: locks };
142
+ // No lockfile: fall back to manifest + non-lockfile pins.
143
+ if (top.has('Cargo.toml')) return { value: 'cargo', source: 'code', confidence: 'high', note: 'Cargo.toml (lib omits Cargo.lock)' };
144
+ if (top.has('go.mod')) return { value: 'go', source: 'code', confidence: 'high', note: 'go.mod' };
145
+ const pj = readTrackedJSON(root, top, 'package.json');
146
+ if (pj?.packageManager) return { value: String(pj.packageManager).split('@')[0], source: 'code', confidence: 'high', note: 'packageManager field' };
147
+ return { value: null, source: 'code', confidence: 'abstain', escalateOrManual: true, note: 'no lockfile/manifest toolchain signal' };
148
+ }
149
+
150
+ const AGENT_SURFACES = [['.claude', 'claude'], ['.codex', 'codex'], ['.cursor', 'cursor'], ['.cline', 'cline'], ['.roo', 'roo'], ['.kilocode', 'kilocode'], ['.opencode', 'opencode'], ['.windsurf', 'windsurf'], ['AGENTS.md', 'agents-md'], ['CLAUDE.md', 'claude'], ['GEMINI.md', 'gemini'], ['.cursorrules', 'cursor'], ['.clinerules', 'cline'], ['.github/copilot-instructions.md', 'copilot']];
151
+ function detectAgents(root, top, nested) {
152
+ const found = new Set();
153
+ for (const [surface, name] of AGENT_SURFACES) {
154
+ if (surface.includes('/') || surface.endsWith('.md')) { if (trackedMatch(root, [surface]).length) found.add(name); }
155
+ else if (top.has(surface)) found.add(name);
156
+ }
157
+ const list = [...found];
158
+ if (nested) return { value: list, source: 'ABSTAIN→agent', confidence: 'abstain', escalate: true, trigger: 'nested' };
159
+ return { value: list, source: 'code', confidence: 'medium', note: 'tracked agent-surface enumeration (non-exhaustive)' };
160
+ }
161
+
162
+ const CL_BASES = ['changelog', 'changes', 'history', 'news', 'releases'];
163
+ // Case-insensitive top-level changelog file lookup (CHANGELOG/CHANGES/HISTORY/…, .md/.rst/.txt).
164
+ function changelogByBaseName(root, top) {
165
+ const topLower = new Map([...top].map(e => [e.toLowerCase(), e]));
166
+ for (const base of CL_BASES) {
167
+ for (const ext of ['.md', '.rst', '.txt', '']) {
168
+ const actual = topLower.get(base + ext);
169
+ if (!actual) continue;
170
+ const body = read(path.join(root, actual));
171
+ const keep = /keep a changelog/i.test(body) || /##\s*\[?unreleased\]?/i.test(body);
172
+ return { value: actual, format: keep ? 'keep-a-changelog' : 'structured-or-freeform', source: 'code', confidence: keep ? 'high' : 'medium' };
173
+ }
174
+ }
175
+ return null;
176
+ }
177
+ function detectChangelog(root, top, nested) {
178
+ if (nested) return ABSTAIN('nested');
179
+ const byName = changelogByBaseName(root, top);
180
+ if (byName) return byName;
181
+ if (top.has('.changeset')) return { value: '.changeset', format: 'changesets', source: 'code', confidence: 'high' };
182
+ const docsCl = trackedMatch(root, ['docs/**/release-notes.*', 'docs/**/changelog.*', 'docs/**/changes.*', 'docs/**/CHANGELOG.*']);
183
+ if (docsCl.length) return { value: docsCl[0], format: 'docs-changelog', source: 'code', confidence: 'medium' };
184
+ if (trackedMatch(root, ['.github/release-drafter.yml']).length) return { value: 'release-drafter', source: 'code', confidence: 'high' };
185
+ if (top.has('.commitlintrc.json') || trackedMatch(root, ['.commitlintrc*', 'commitlint.config.*']).length) return { value: 'conventional-commits', source: 'code', confidence: 'medium' };
186
+ return { value: null, source: 'code', confidence: 'abstain', escalateOrManual: true, note: 'no tracked changelog mechanism' };
187
+ }
188
+
189
+ function detectCI(root, top, nested) {
190
+ if (nested) return ABSTAIN('nested');
191
+ if (top.has('.github')) {
192
+ const wf = trackedMatch(root, ['.github/workflows/*.yml', '.github/workflows/*.yaml']).map(f => f.split('/').pop());
193
+ if (wf.length) return { provider: 'github-actions', workflows: wf, source: 'code', confidence: 'high' };
194
+ }
195
+ if (top.has('.gitlab-ci.yml')) return { provider: 'gitlab-ci', source: 'code', confidence: 'medium' };
196
+ if (top.has('.circleci')) return { provider: 'circleci', source: 'code', confidence: 'medium' };
197
+ return { provider: null, source: 'code', confidence: 'abstain', escalateOrManual: true, note: 'no tracked CI provider' };
198
+ }
199
+
200
+ /**
201
+ * Apply a committed `.docgate.json` declaration on top of a detection result.
202
+ *
203
+ * "Declaration beats inference": a VALID declaration OVERRIDES the inferred
204
+ * source (and, if declared, toolchain) and promotes the verdict to DECLARED —
205
+ * but ONLY when it yields a concrete source surface (a declared `source`, or a
206
+ * source detection already resolved). This is fail-closed: a DECLARED verdict is
207
+ * enforced by the gate, so it must never rest on a null/empty surface. An
208
+ * INVALID declaration never applies; its `errors` are attached as
209
+ * `declarationErrors` so callers can surface them (the gate fails closed on them).
210
+ *
211
+ * @param {string} root - Repository root.
212
+ * @param {object} result - The plain detection result to augment.
213
+ * @returns {object} The (possibly) augmented result.
214
+ */
215
+ function applyDeclaration(root, result) {
216
+ const { declaration, errors } = loadDeclaration(root);
217
+ if (errors.length > 0) return { ...result, declarationErrors: errors };
218
+ if (!declaration) return result;
219
+
220
+ const applied = { ...result, declaration };
221
+ const overridden = new Set();
222
+ if (Array.isArray(declaration.source) && declaration.source.length > 0) {
223
+ applied.source = { value: [...declaration.source], source: 'declared', confidence: 'high' };
224
+ overridden.add('source');
225
+ }
226
+ if (typeof declaration.toolchain === 'string' && declaration.toolchain) {
227
+ applied.toolchain = { value: declaration.toolchain, source: 'declared', confidence: 'high' };
228
+ overridden.add('toolchain');
229
+ }
230
+
231
+ // An overridden field is 'declared', not code-derived, so it must not remain in
232
+ // codeHighConfidence (which lists only code-derived, silent-wrong-eligible fields).
233
+ if (overridden.size > 0) {
234
+ applied.codeHighConfidence = result.codeHighConfidence.filter(f => !overridden.has(f));
235
+ }
236
+
237
+ // Promote to DECLARED only when an enforceable source surface exists.
238
+ const surface = applied.source?.value;
239
+ if (Array.isArray(surface) && surface.length > 0) {
240
+ applied.declared = true;
241
+ applied.verdict = 'DECLARED';
242
+ applied.escalate = result.escalate.filter(e => !overridden.has(e.field));
243
+ }
244
+ return applied;
245
+ }
246
+
247
+ /**
248
+ * Run the repo-structure detector against a git working tree.
249
+ *
250
+ * When a VALID `.docgate.json` is committed at the root it OVERRIDES inference:
251
+ * the verdict becomes DECLARED (enforced by the gate exactly like CODE-RESOLVED)
252
+ * and `declaration` is attached. An INVALID declaration attaches
253
+ * `declarationErrors` and leaves normal detection untouched.
254
+ *
255
+ * @param {string} root - Absolute path to the repository root (a git working tree).
256
+ * @returns {{ repo: string,
257
+ * verdict: 'ESCALATE-TO-AGENT'|'MANUAL-CONFIG'|'CODE-RESOLVED'|'DECLARED',
258
+ * nested: boolean, monorepo: object, escalate: Array, codeHighConfidence: string[],
259
+ * source: object, toolchain: object, agents: object, changelog: object, ci: object,
260
+ * declared?: boolean, declaration?: object, declarationErrors?: string[] }}
261
+ */
262
+ function detect(root) {
263
+ const top = trackedTop(root);
264
+ const topDirs = trackedTopDirs(root);
265
+ const nesting = detectNesting(root);
266
+ const nested = nesting.nested;
267
+ const mono = detectMonorepo(root, top, topDirs);
268
+ const source = detectSource(root, top, topDirs, nested, mono);
269
+ const toolchain = detectToolchain(root, top, nested);
270
+ const agents = detectAgents(root, top, nested);
271
+ const changelog = detectChangelog(root, top, nested);
272
+ const ci = detectCI(root, top, nested);
273
+ const fields = { source, toolchain, agents, changelog, ci };
274
+ const escalate = [];
275
+ if (nested) escalate.push({ field: 'whole-repo', trigger: 'nested-gitlink', detail: nesting.nestedDir });
276
+ for (const [name, f] of Object.entries(fields)) if (f.escalate) escalate.push({ field: name, trigger: f.trigger });
277
+ const codeHigh = Object.entries(fields).filter(([, f]) => f.source === 'code' && f.confidence === 'high').map(([n]) => n);
278
+ // MANUAL-CONFIG when ANY field is a code-side abstain (changelog/ci/toolchain
279
+ // with no signal) that wasn't escalated — a repo isn't fully CODE-RESOLVED
280
+ // while, say, its toolchain is unresolved.
281
+ const manualAbstain = Object.values(fields).some(f => f.source === 'code' && f.confidence === 'abstain');
282
+ let verdict = 'CODE-RESOLVED';
283
+ if (escalate.length > 0) verdict = 'ESCALATE-TO-AGENT';
284
+ else if (manualAbstain) verdict = 'MANUAL-CONFIG';
285
+ const result = { repo: path.basename(root), verdict, nested, monorepo: mono, escalate, codeHighConfidence: codeHigh, ...fields };
286
+ return applyDeclaration(root, result);
287
+ }
288
+
289
+ module.exports = { detect, BLOCKLIST };