forge-workflow 0.0.10 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (454) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +3 -0
  5. package/.forge/hooks/forge-native-hook.js +245 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +150 -61
  8. package/CHANGELOG.md +681 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +171 -0
  11. package/README.md +271 -363
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +461 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +118 -0
  29. package/docs/guides/SUPPORT.md +185 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +205 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +115 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/adapter-cli.js +307 -0
  67. package/lib/adapters/beads-issue-adapter.js +127 -0
  68. package/lib/adapters/beads-kernel-compat.js +1042 -0
  69. package/lib/adapters/greptile-review-adapter.js +141 -0
  70. package/lib/adapters/kernel-issue-adapter.js +101 -0
  71. package/lib/adapters/pr-state-adapter.js +484 -0
  72. package/lib/adoption-profiles.js +126 -0
  73. package/lib/agents/README.md +2 -6
  74. package/lib/agents/claude.plugin.json +3 -8
  75. package/lib/agents/codex.plugin.json +9 -1
  76. package/lib/agents/cursor.plugin.json +2 -6
  77. package/lib/agents/hermes.plugin.json +22 -0
  78. package/lib/agents-config.js +39 -1236
  79. package/lib/audit-evidence.js +282 -0
  80. package/lib/beads-setup.js +121 -0
  81. package/lib/beads-sync-scaffold.js +25 -101
  82. package/lib/codex-skills.js +51 -1
  83. package/lib/commands/_issue.js +741 -77
  84. package/lib/commands/_manifest.js +91 -0
  85. package/lib/commands/_registry.js +85 -34
  86. package/lib/commands/_resolve-command-opts.js +261 -0
  87. package/lib/commands/_serve-security.js +270 -0
  88. package/lib/commands/adapter.js +12 -0
  89. package/lib/commands/add.js +118 -0
  90. package/lib/commands/audit.js +70 -0
  91. package/lib/commands/blocked.js +5 -0
  92. package/lib/commands/board.js +64 -0
  93. package/lib/commands/claim.js +21 -2
  94. package/lib/commands/claims.js +7 -0
  95. package/lib/commands/clean.js +485 -75
  96. package/lib/commands/close.js +2 -2
  97. package/lib/commands/comment.js +5 -0
  98. package/lib/commands/control.js +148 -0
  99. package/lib/commands/create.js +2 -2
  100. package/lib/commands/dev.js +185 -7
  101. package/lib/commands/doc-gate.js +336 -0
  102. package/lib/commands/doctor.js +156 -0
  103. package/lib/commands/explain.js +15 -0
  104. package/lib/commands/export.js +237 -0
  105. package/lib/commands/gate.js +192 -0
  106. package/lib/commands/hooks.js +242 -0
  107. package/lib/commands/inbox.js +118 -0
  108. package/lib/commands/init.js +598 -0
  109. package/lib/commands/insights.js +79 -0
  110. package/lib/commands/issue.js +12 -1
  111. package/lib/commands/issues.js +17 -0
  112. package/lib/commands/lint.js +5 -0
  113. package/lib/commands/list.js +2 -2
  114. package/lib/commands/merge.js +312 -0
  115. package/lib/commands/migrate.js +523 -0
  116. package/lib/commands/new.js +12 -0
  117. package/lib/commands/options.js +241 -0
  118. package/lib/commands/orient.js +13 -0
  119. package/lib/commands/orphans.js +5 -0
  120. package/lib/commands/patch.js +67 -0
  121. package/lib/commands/plan.js +436 -24
  122. package/lib/commands/preflight.js +211 -0
  123. package/lib/commands/prime.js +13 -0
  124. package/lib/commands/push.js +69 -2
  125. package/lib/commands/ready.js +2 -2
  126. package/lib/commands/recall.js +116 -0
  127. package/lib/commands/recap.js +61 -0
  128. package/lib/commands/recommend.js +0 -1
  129. package/lib/commands/release.js +91 -0
  130. package/lib/commands/remember.js +74 -0
  131. package/lib/commands/role.js +99 -0
  132. package/lib/commands/serve.js +581 -0
  133. package/lib/commands/setup.js +838 -972
  134. package/lib/commands/shepherd.js +436 -0
  135. package/lib/commands/ship.js +23 -1
  136. package/lib/commands/show.js +2 -2
  137. package/lib/commands/stage.js +192 -0
  138. package/lib/commands/stale.js +5 -0
  139. package/lib/commands/status.js +158 -21
  140. package/lib/commands/sync.js +34 -46
  141. package/lib/commands/team.js +4 -1
  142. package/lib/commands/test.js +43 -27
  143. package/lib/commands/update.js +2 -2
  144. package/lib/commands/upgrade.js +47 -0
  145. package/lib/commands/validate.js +43 -18
  146. package/lib/commands/worktree.js +307 -100
  147. package/lib/config-writer.js +202 -0
  148. package/lib/control-plane.js +236 -0
  149. package/lib/core/runtime-graph.js +946 -0
  150. package/lib/dep-guard/keyword-ripple.js +2 -2
  151. package/lib/deprecated-sync-cleanup.js +362 -0
  152. package/lib/detect-agent.js +2 -28
  153. package/lib/detect-worktree.js +35 -9
  154. package/lib/doc-gate/declaration.js +177 -0
  155. package/lib/doc-gate/detect.js +289 -0
  156. package/lib/doc-gate/gate.js +375 -0
  157. package/lib/doc-gate/okf-config.js +128 -0
  158. package/lib/doc-gate/okf.js +429 -0
  159. package/lib/docs-command.js +1161 -6
  160. package/lib/forge-issues.js +382 -11
  161. package/lib/forge-lock.js +262 -0
  162. package/lib/gate-events.js +193 -0
  163. package/lib/global-flags.js +74 -0
  164. package/lib/greptile-match.js +7 -63
  165. package/lib/harness-capability-matrix.js +380 -0
  166. package/lib/hook-global-installer.js +347 -0
  167. package/lib/hook-renderer.js +451 -0
  168. package/lib/inbox.js +391 -0
  169. package/lib/insights.js +397 -0
  170. package/lib/issue-adapter.js +156 -0
  171. package/lib/issue-backend.js +145 -0
  172. package/lib/issue-render.js +220 -0
  173. package/lib/kernel/backing-issue.js +305 -0
  174. package/lib/kernel/broker.js +1218 -0
  175. package/lib/kernel/cli-broker-factory.js +130 -0
  176. package/lib/kernel/conflict-signal.js +82 -0
  177. package/lib/kernel/evaluators.js +195 -0
  178. package/lib/kernel/fs-class.js +495 -0
  179. package/lib/kernel/issue-command-contract.js +559 -0
  180. package/lib/kernel/issue-id-resolver.js +186 -0
  181. package/lib/kernel/lease-enforcer.js +158 -0
  182. package/lib/kernel/migrations.js +333 -0
  183. package/lib/kernel/planning-buckets-schema.js +109 -0
  184. package/lib/kernel/projection-jsonl-writer.js +450 -0
  185. package/lib/kernel/readiness-model.js +329 -0
  186. package/lib/kernel/schema.js +356 -0
  187. package/lib/kernel/sqlite-driver.js +2504 -0
  188. package/lib/kernel/taxonomy-validator.js +394 -0
  189. package/lib/lefthook-check.js +3 -2
  190. package/lib/lefthook-wiring.js +413 -0
  191. package/lib/mcp-config-renderer.js +288 -0
  192. package/lib/memory/graphiti-mcp.js +106 -0
  193. package/lib/memory/router.js +387 -0
  194. package/lib/memory/typed-api.js +102 -0
  195. package/lib/memory-digest.js +195 -0
  196. package/lib/merge-rules.js +395 -0
  197. package/lib/migrate-dry-run.js +466 -0
  198. package/lib/orientation.js +863 -0
  199. package/lib/package-manager-remediation.js +103 -0
  200. package/lib/package-root.js +381 -0
  201. package/lib/patch-intent.js +890 -0
  202. package/lib/plugin-catalog.js +3 -4
  203. package/lib/plugin-manager.js +0 -5
  204. package/lib/pr-bundle.js +186 -0
  205. package/lib/pr-monitor/differ.js +195 -0
  206. package/lib/pr-monitor/events.js +0 -0
  207. package/lib/pr-monitor/gather.js +124 -0
  208. package/lib/pr-monitor/journal.js +299 -0
  209. package/lib/pr-monitor/monitor.js +146 -0
  210. package/lib/pr-monitor/render-sticky.js +157 -0
  211. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  212. package/lib/pr-monitor/watch.js +247 -0
  213. package/lib/pr-pull.js +1273 -0
  214. package/lib/pr-shepherd.js +494 -0
  215. package/lib/pr-state-validator.js +59 -0
  216. package/lib/preflight/gates.js +237 -0
  217. package/lib/preflight/runner.js +116 -0
  218. package/lib/project-discovery.js +0 -53
  219. package/lib/project-memory.js +99 -497
  220. package/lib/protected-path-manifest.js +281 -0
  221. package/lib/protected-state-surfaces.js +387 -0
  222. package/lib/release-readiness.js +2089 -0
  223. package/lib/reset.js +59 -45
  224. package/lib/review-adapter.js +68 -0
  225. package/lib/rules-sync.js +260 -0
  226. package/lib/runtime-health.js +241 -20
  227. package/lib/safety-config-renderer.js +268 -0
  228. package/lib/setup-action-log.js +1 -7
  229. package/lib/setup.js +27 -65
  230. package/lib/shell-utils.js +76 -6
  231. package/lib/skills-sync.js +330 -0
  232. package/lib/smart-status/scoring.js +17 -3
  233. package/lib/status/beads-snapshot.js +45 -2
  234. package/lib/status/presenter.js +169 -18
  235. package/lib/status/snapshot.js +186 -0
  236. package/lib/sync-backend.js +202 -0
  237. package/lib/untrusted-content.js +52 -0
  238. package/lib/upgrade-safety.js +199 -0
  239. package/lib/workflow/enforce-stage.js +296 -47
  240. package/lib/workflow/stage-transition.js +115 -0
  241. package/lib/workflow/stages.js +30 -6
  242. package/lib/workflow/state-manager.js +11 -22
  243. package/lib/workflow/state.js +23 -1
  244. package/lib/workflow-profiles.js +17 -5
  245. package/package.json +37 -35
  246. package/rules/documentation.md +19 -0
  247. package/rules/kernel-tracking.md +26 -0
  248. package/rules/security.md +22 -0
  249. package/rules/tdd.md +20 -0
  250. package/rules/workflow.md +27 -0
  251. package/scripts/auto-backing-issue.js +47 -0
  252. package/scripts/beads-context.sh +81 -57
  253. package/scripts/beads-upgrade-smoke.sh +24 -3
  254. package/scripts/bootstrap-windows-tools.sh +78 -0
  255. package/scripts/branch-protection.js +2 -3
  256. package/scripts/check-agents.js +34 -137
  257. package/scripts/commitlint.js +3 -1
  258. package/scripts/conflict-detect.sh +3 -0
  259. package/scripts/dep-guard.sh +22 -3
  260. package/scripts/file-index.sh +3 -0
  261. package/scripts/forge-team/lib/claim.sh +34 -18
  262. package/scripts/forge-team/lib/dashboard.sh +61 -86
  263. package/scripts/forge-team/lib/epic.sh +99 -263
  264. package/scripts/forge-team/lib/hooks.sh +26 -28
  265. package/scripts/forge-team/lib/identity.sh +4 -4
  266. package/scripts/forge-team/lib/sync-github.sh +49 -84
  267. package/scripts/forge-team/lib/verify.sh +93 -83
  268. package/scripts/forge-team/lib/workload.sh +41 -65
  269. package/scripts/forge-team/tests/claim.test.sh +25 -19
  270. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  271. package/scripts/forge-team/tests/epic.test.sh +52 -71
  272. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  273. package/scripts/forge-team/tests/identity.test.sh +3 -3
  274. package/scripts/forge-team/tests/integration.test.sh +44 -66
  275. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  276. package/scripts/forge-team/tests/verify.test.sh +37 -46
  277. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  278. package/scripts/forge-team/tests/workload.test.sh +32 -66
  279. package/scripts/gen-command-manifest.js +153 -0
  280. package/scripts/gen-embedded-assets.mjs +129 -0
  281. package/scripts/install.ps1 +139 -0
  282. package/scripts/install.sh +268 -0
  283. package/scripts/lib/release-asset.mjs +84 -0
  284. package/scripts/parity-check.mjs +145 -0
  285. package/scripts/parity-check.test.mjs +58 -0
  286. package/scripts/pin-agentic-workflow-images.js +112 -0
  287. package/scripts/pr-coordinator.sh +3 -0
  288. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  289. package/scripts/preflight.sh +21 -94
  290. package/scripts/protected-state-check.js +104 -0
  291. package/scripts/smart-status.sh +60 -57
  292. package/scripts/spikes/config-race-bench.js +111 -0
  293. package/scripts/spikes/harness-capability-matrix.js +13 -0
  294. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  295. package/scripts/spikes/protected-path-manifest.js +20 -0
  296. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  297. package/scripts/sync-agent-skills.js +62 -0
  298. package/scripts/sync-utils.sh +3 -0
  299. package/scripts/test-ci-shard.js +13 -6
  300. package/scripts/test.js +95 -12
  301. package/skills/claim-safety/SKILL.md +102 -0
  302. package/skills/claim-safety/evals/evals.json +46 -0
  303. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  304. package/skills/dev/evals/evals.json +50 -0
  305. package/skills/hermes-forge/SKILL.md +185 -0
  306. package/skills/hermes-forge/evals/evals.json +46 -0
  307. package/skills/issue-basics/SKILL.md +111 -0
  308. package/skills/issue-basics/evals/evals.json +46 -0
  309. package/skills/kernel/SKILL.md +166 -0
  310. package/skills/kernel/evals/evals.json +50 -0
  311. package/skills/memory/SKILL.md +102 -0
  312. package/skills/parallel-deep-research/SKILL.md +14 -11
  313. package/skills/parallel-deep-research/evals/evals.json +11 -27
  314. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  315. package/skills/plan/evals/evals.json +42 -0
  316. package/skills/research/SKILL.md +195 -0
  317. package/skills/research/evals/evals.json +42 -0
  318. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  319. package/skills/review/evals/evals.json +42 -0
  320. package/skills/rollback/SKILL.md +110 -0
  321. package/skills/rollback/evals/evals.json +46 -0
  322. package/skills/rollback/references/methods.md +204 -0
  323. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  324. package/skills/shepherd/SKILL.md +66 -0
  325. package/skills/shepherd/evals/evals.json +42 -0
  326. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  327. package/skills/ship/evals/evals.json +42 -0
  328. package/skills/smith/SKILL.md +142 -0
  329. package/skills/smith/evals/evals.json +46 -0
  330. package/skills/smith/references/autonomy-and-gates.md +94 -0
  331. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  332. package/skills/sonarcloud/evals/evals.json +46 -0
  333. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  334. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  335. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  336. package/skills/status/evals/evals.json +50 -0
  337. package/skills/triage-ready/SKILL.md +121 -0
  338. package/skills/triage-ready/evals/evals.json +42 -0
  339. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  340. package/skills/validate/evals/evals.json +42 -0
  341. package/skills/verify/SKILL.md +299 -0
  342. package/skills/verify/evals/evals.json +50 -0
  343. package/.claude/commands/dev.md +0 -345
  344. package/.claude/commands/plan.md +0 -566
  345. package/.claude/commands/premerge.md +0 -186
  346. package/.claude/commands/research.md +0 -42
  347. package/.claude/commands/review.md +0 -451
  348. package/.claude/commands/rollback.md +0 -721
  349. package/.claude/commands/ship.md +0 -213
  350. package/.claude/commands/sonarcloud.md +0 -152
  351. package/.claude/commands/status.md +0 -90
  352. package/.claude/commands/validate.md +0 -288
  353. package/.claude/commands/verify.md +0 -269
  354. package/.claude/rules/workflow.md +0 -121
  355. package/.cline/workflows/dev.md +0 -342
  356. package/.cline/workflows/plan.md +0 -563
  357. package/.cline/workflows/premerge.md +0 -183
  358. package/.cline/workflows/research.md +0 -39
  359. package/.cline/workflows/review.md +0 -448
  360. package/.cline/workflows/rollback.md +0 -718
  361. package/.cline/workflows/ship.md +0 -210
  362. package/.cline/workflows/sonarcloud.md +0 -146
  363. package/.cline/workflows/status.md +0 -87
  364. package/.cline/workflows/validate.md +0 -285
  365. package/.cline/workflows/verify.md +0 -266
  366. package/.codex/config.toml +0 -11
  367. package/.codex/skills/dev/SKILL.md +0 -345
  368. package/.codex/skills/plan/SKILL.md +0 -566
  369. package/.codex/skills/premerge/SKILL.md +0 -186
  370. package/.codex/skills/research/SKILL.md +0 -42
  371. package/.codex/skills/review/SKILL.md +0 -451
  372. package/.codex/skills/rollback/SKILL.md +0 -721
  373. package/.codex/skills/ship/SKILL.md +0 -213
  374. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  375. package/.codex/skills/status/SKILL.md +0 -90
  376. package/.codex/skills/validate/SKILL.md +0 -288
  377. package/.codex/skills/verify/SKILL.md +0 -269
  378. package/.cursor/commands/dev.md +0 -342
  379. package/.cursor/commands/plan.md +0 -563
  380. package/.cursor/commands/premerge.md +0 -183
  381. package/.cursor/commands/research.md +0 -39
  382. package/.cursor/commands/review.md +0 -448
  383. package/.cursor/commands/ship.md +0 -210
  384. package/.cursor/commands/sonarcloud.md +0 -146
  385. package/.cursor/commands/status.md +0 -87
  386. package/.cursor/commands/validate.md +0 -285
  387. package/.cursor/commands/verify.md +0 -266
  388. package/.cursorrules +0 -149
  389. package/.github/prompts/premerge.prompt.md +0 -188
  390. package/.github/prompts/research.prompt.md +0 -44
  391. package/.github/prompts/rollback.prompt.md +0 -723
  392. package/.github/prompts/verify.prompt.md +0 -271
  393. package/.github/workflows/beads-to-github.yml +0 -89
  394. package/.github/workflows/github-to-beads.yml +0 -100
  395. package/.kilocode/workflows/dev.md +0 -346
  396. package/.kilocode/workflows/plan.md +0 -567
  397. package/.kilocode/workflows/premerge.md +0 -187
  398. package/.kilocode/workflows/research.md +0 -43
  399. package/.kilocode/workflows/review.md +0 -452
  400. package/.kilocode/workflows/rollback.md +0 -722
  401. package/.kilocode/workflows/ship.md +0 -214
  402. package/.kilocode/workflows/sonarcloud.md +0 -150
  403. package/.kilocode/workflows/status.md +0 -91
  404. package/.kilocode/workflows/validate.md +0 -289
  405. package/.kilocode/workflows/verify.md +0 -270
  406. package/.opencode/commands/dev.md +0 -345
  407. package/.opencode/commands/plan.md +0 -566
  408. package/.opencode/commands/premerge.md +0 -186
  409. package/.opencode/commands/research.md +0 -42
  410. package/.opencode/commands/review.md +0 -451
  411. package/.opencode/commands/rollback.md +0 -721
  412. package/.opencode/commands/ship.md +0 -213
  413. package/.opencode/commands/sonarcloud.md +0 -149
  414. package/.opencode/commands/status.md +0 -90
  415. package/.opencode/commands/validate.md +0 -288
  416. package/.opencode/commands/verify.md +0 -269
  417. package/.roo/commands/dev.md +0 -346
  418. package/.roo/commands/plan.md +0 -567
  419. package/.roo/commands/premerge.md +0 -187
  420. package/.roo/commands/research.md +0 -43
  421. package/.roo/commands/review.md +0 -452
  422. package/.roo/commands/rollback.md +0 -722
  423. package/.roo/commands/ship.md +0 -214
  424. package/.roo/commands/sonarcloud.md +0 -150
  425. package/.roo/commands/status.md +0 -91
  426. package/.roo/commands/validate.md +0 -289
  427. package/.roo/commands/verify.md +0 -270
  428. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  429. package/docs/GREPTILE_SETUP.md +0 -400
  430. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  431. package/docs/SETUP.md +0 -663
  432. package/docs/VALIDATION.md +0 -363
  433. package/lib/agents/cline.plugin.json +0 -29
  434. package/lib/agents/copilot.plugin.json +0 -24
  435. package/lib/agents/kilocode.plugin.json +0 -22
  436. package/lib/agents/opencode.plugin.json +0 -23
  437. package/lib/agents/roo.plugin.json +0 -30
  438. package/lib/beads-bootstrap.js +0 -225
  439. package/lib/beads-health-check.js +0 -188
  440. package/lib/commands/commands-reset.js +0 -147
  441. package/opencode.json +0 -67
  442. package/scripts/beads-context.test.js +0 -584
  443. package/scripts/github-beads-sync/comment.mjs +0 -64
  444. package/scripts/github-beads-sync/config.mjs +0 -148
  445. package/scripts/github-beads-sync/github-api.mjs +0 -131
  446. package/scripts/github-beads-sync/index.mjs +0 -356
  447. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  448. package/scripts/github-beads-sync/mapping.mjs +0 -132
  449. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  450. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  451. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  452. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  453. package/scripts/github-beads-sync.config.json +0 -26
  454. package/scripts/sync-commands.js +0 -600
@@ -0,0 +1,495 @@
1
+ 'use strict';
2
+
3
+ // D19 — Filesystem-class detector + default-on gate helper.
4
+ //
5
+ // Two layers (the split is the testability crux):
6
+ // Layer A — classifyFromSignals(signals): PURE, zero I/O. 100% synthetic-tested.
7
+ // Layer B — gatherSignals(absPath, deps): thin I/O shell, ALL probes injectable.
8
+ // Public entry: classifyFilesystem(absPath, deps) = classifyFromSignals(gatherSignals(...)).
9
+ //
10
+ // Scope discipline: this module reports the filesystem CLASS only. Broad health
11
+ // stays in runtime-health.js. See docs/work/2026-06-06-kernel-backlog-memory-roadmap/
12
+ // D19-filesystem-doctor-design.md.
13
+
14
+ const os = require('node:os');
15
+ const { execFileSync } = require('node:child_process');
16
+
17
+ // --- Remediation message table (remediationKey -> human text) ----------------
18
+
19
+ const OVERRIDE_LINE =
20
+ 'Override (NOT recommended): set FORGE_KERNEL_ALLOW_UNSAFE_FS=1 to proceed\n' +
21
+ 'at your own risk.';
22
+
23
+ const REMEDIATION = Object.freeze({
24
+ 'local-ok': 'No action needed — the kernel database is on a local fixed disk.',
25
+ onedrive:
26
+ 'Forge kernel cannot place its SQLite database on a cloud-synced folder\n' +
27
+ '(OneDrive). SQLite WAL mode corrupts when a sync client rewrites the\n' +
28
+ 'database mid-write.\n\n' +
29
+ 'Fix: move this repository outside the OneDrive folder, e.g. a non-synced\n' +
30
+ 'local path such as C:\\dev\\<repo>, then re-run.\n\n' +
31
+ OVERRIDE_LINE,
32
+ dropbox:
33
+ 'Forge kernel cannot place its SQLite database on a Dropbox-synced folder.\n' +
34
+ 'A sync client rewriting the DB mid-write corrupts SQLite WAL mode.\n\n' +
35
+ 'Fix: move this repository outside the Dropbox folder to a non-synced\n' +
36
+ 'local path, then re-run.\n\n' +
37
+ OVERRIDE_LINE,
38
+ gdrive:
39
+ 'Forge kernel cannot place its SQLite database on a Google Drive folder.\n' +
40
+ 'A sync/streaming client rewriting the DB mid-write corrupts SQLite WAL mode.\n\n' +
41
+ 'Fix: move this repository outside the Google Drive folder to a non-synced\n' +
42
+ 'local path, then re-run.\n\n' +
43
+ OVERRIDE_LINE,
44
+ icloud:
45
+ 'Forge kernel cannot place its SQLite database under iCloud Drive\n' +
46
+ '(Library/Mobile Documents). iCloud may evict/rewrite the file and corrupt\n' +
47
+ 'SQLite WAL mode.\n\n' +
48
+ 'Fix: move this repository outside iCloud Drive to a non-synced local path,\n' +
49
+ 'then re-run.\n\n' +
50
+ OVERRIDE_LINE,
51
+ 'network-unc':
52
+ 'Forge kernel cannot place its SQLite database on a network share\n' +
53
+ '(UNC / SMB / NFS). SQLite file locking is unreliable over network\n' +
54
+ 'filesystems and can corrupt the database.\n\n' +
55
+ 'Fix: move this repository to a local fixed disk, then re-run.\n\n' +
56
+ OVERRIDE_LINE,
57
+ 'mapped-network-drive':
58
+ 'Forge kernel cannot place its SQLite database on a mapped network drive.\n' +
59
+ 'SQLite file locking is unreliable over SMB/NFS and can corrupt the database.\n\n' +
60
+ 'Fix: move this repository to a local fixed disk, then re-run.\n\n' +
61
+ OVERRIDE_LINE,
62
+ 'wsl-cross':
63
+ 'Warning: the kernel database lives on the Windows volume across the WSL\n' +
64
+ 'filesystem boundary (/mnt/<letter>). This works but is slow and has lock\n' +
65
+ 'edge cases. For best results, keep the repository on the Linux-native\n' +
66
+ 'filesystem (e.g. under your home directory).',
67
+ unknown:
68
+ 'Note: Forge could not identify this filesystem type; proceeding normally.\n' +
69
+ 'For best reliability, keep the repository on a local fixed disk rather than\n' +
70
+ 'a cloud-synced or network folder.',
71
+ });
72
+
73
+ // --- Risk-tier mapping (class -> tier) ---------------------------------------
74
+
75
+ const REFUSE_CLASSES = new Set([
76
+ 'onedrive',
77
+ 'dropbox',
78
+ 'gdrive',
79
+ 'icloud',
80
+ 'network-unc',
81
+ 'mapped-network-drive',
82
+ ]);
83
+ const WARN_CLASSES = new Set(['wsl-cross', 'unknown']);
84
+
85
+ function riskTierFor(klass) {
86
+ if (REFUSE_CLASSES.has(klass)) return 'refuse';
87
+ if (WARN_CLASSES.has(klass)) return 'warn';
88
+ return 'safe';
89
+ }
90
+
91
+ function makeClassification(klass, signal) {
92
+ return {
93
+ class: klass,
94
+ riskTier: riskTierFor(klass),
95
+ signal,
96
+ remediationKey: klass,
97
+ };
98
+ }
99
+
100
+ // --- Path helpers (pure) -----------------------------------------------------
101
+
102
+ // SQLite "paths" that never touch a real directory: in-memory and file: URIs.
103
+ function isNonFsPath(absPath) {
104
+ if (!absPath) return true;
105
+ if (absPath === ':memory:') return true;
106
+ if (/^file:/i.test(absPath)) return true;
107
+ return false;
108
+ }
109
+
110
+ // Split a path into lowercase segments on both separators (for segment matching).
111
+ function pathSegments(absPath) {
112
+ return String(absPath)
113
+ .split(/[\\/]+/)
114
+ .filter(Boolean)
115
+ .map(seg => seg.toLowerCase());
116
+ }
117
+
118
+ // Boundary-aware, case/separator-normalized prefix test. Returns true only when
119
+ // `root` covers `absPath` at a path boundary (avoids OneDrive vs OneDriveBackup).
120
+ function pathStartsWith(absPath, root, { caseInsensitive }) {
121
+ if (!root) return false;
122
+ const norm = value => {
123
+ let v = String(value).replace(/[\\/]+/g, '/').replace(/\/+$/, '');
124
+ if (caseInsensitive) v = v.toLowerCase();
125
+ return v;
126
+ };
127
+ const a = norm(absPath);
128
+ const r = norm(root);
129
+ if (a === r) return true;
130
+ return a.startsWith(`${r}/`);
131
+ }
132
+
133
+ function hasSegmentMatching(absPath, regex) {
134
+ return pathSegments(absPath).some(seg => regex.test(seg));
135
+ }
136
+
137
+ function hasExactSegment(absPath, lowerName) {
138
+ return pathSegments(absPath).includes(lowerName);
139
+ }
140
+
141
+ // --- Layer A: pure classifier ------------------------------------------------
142
+
143
+ function classifyWin32(signals) {
144
+ const { absPath, env = {} } = signals;
145
+
146
+ if (signals.isUNC) {
147
+ return makeClassification('network-unc', String.raw`UNC path (\\server\share)`);
148
+ }
149
+ if (signals.driveType === 'network') {
150
+ return makeClassification('mapped-network-drive', 'driveType=network');
151
+ }
152
+
153
+ // Cloud-sync detection is path/env-based and INDEPENDENT of the drive probe.
154
+ // It must run BEFORE the driveType==='unknown' fail-open below, so a positively
155
+ // identified OneDrive/Dropbox/gdrive path still REFUSES even when the mapped-
156
+ // drive probe failed (G1 fallback). Otherwise the highest-risk class would be
157
+ // silently downgraded to warn and the kernel would init on a synced folder.
158
+
159
+ // OneDrive: env-declared sync roots (boundary-aware) OR a path segment.
160
+ const oneDriveRoots = [env.OneDrive, env.OneDriveConsumer, env.OneDriveCommercial]
161
+ .filter(Boolean);
162
+ for (const root of oneDriveRoots) {
163
+ if (pathStartsWith(absPath, root, { caseInsensitive: true })) {
164
+ return makeClassification('onedrive', 'env OneDrive root prefix');
165
+ }
166
+ }
167
+ if (hasSegmentMatching(absPath, /^onedrive([ -].+)?$/i)) {
168
+ return makeClassification('onedrive', 'OneDrive path segment');
169
+ }
170
+
171
+ if (env.Dropbox && pathStartsWith(absPath, env.Dropbox, { caseInsensitive: true })) {
172
+ return makeClassification('dropbox', 'env Dropbox root prefix');
173
+ }
174
+ if (hasExactSegment(absPath, 'dropbox')) {
175
+ return makeClassification('dropbox', 'Dropbox path segment');
176
+ }
177
+
178
+ if (hasSegmentMatching(absPath, /^(google ?drive|googledrive|drivefs)$/i)) {
179
+ return makeClassification('gdrive', 'Google Drive path segment');
180
+ }
181
+
182
+ // Drive probe miss with no cloud signal: fail-open per G1 (warn, never block).
183
+ if (signals.driveType === 'unknown') {
184
+ return makeClassification('unknown', 'driveType probe returned unknown');
185
+ }
186
+
187
+ return makeClassification('local-ok', 'no cloud/network signal matched');
188
+ }
189
+
190
+ function classifyDarwin(signals) {
191
+ const { absPath, homedir } = signals;
192
+ const home = homedir || '';
193
+
194
+ if (pathStartsWith(absPath, `${home}/Library/Mobile Documents`, { caseInsensitive: false })) {
195
+ return makeClassification('icloud', 'iCloud Mobile Documents');
196
+ }
197
+ if (
198
+ pathStartsWith(absPath, `${home}/Dropbox`, { caseInsensitive: false }) ||
199
+ hasSegmentMatching(absPath, /^dropbox.*$/i) && absPath.includes('/Library/CloudStorage/')
200
+ ) {
201
+ return makeClassification('dropbox', 'Dropbox folder');
202
+ }
203
+ if (
204
+ pathStartsWith(absPath, `${home}/Google Drive`, { caseInsensitive: false }) ||
205
+ hasSegmentMatching(absPath, /^googledrive.*$/i) && absPath.includes('/Library/CloudStorage/')
206
+ ) {
207
+ return makeClassification('gdrive', 'Google Drive folder');
208
+ }
209
+ if (hasSegmentMatching(absPath, /^onedrive.*$/i) && absPath.includes('/Library/CloudStorage/')) {
210
+ return makeClassification('onedrive', 'OneDrive CloudStorage folder');
211
+ }
212
+
213
+ return makeClassification('local-ok', 'no cloud signal matched');
214
+ }
215
+
216
+ const NETWORK_FS_TYPES = new Set(['cifs', 'smb3', 'smb', 'nfs', 'nfs4', 'fuse.sshfs']);
217
+ const FUSE_CLOUD_MAP = new Map([
218
+ ['fuse.dropbox', 'dropbox'],
219
+ ['fuse.rclone', 'gdrive'],
220
+ ['fuse.google-drive-ocamlfuse', 'gdrive'],
221
+ ]);
222
+
223
+ function classifyLinux(signals) {
224
+ const { absPath, mountFsType, isWslInterop } = signals;
225
+
226
+ if (isWslInterop && /^\/mnt\/[a-z]\//i.test(absPath)) {
227
+ return makeClassification('wsl-cross', 'WSL interop + /mnt/<letter>');
228
+ }
229
+ if (mountFsType && NETWORK_FS_TYPES.has(mountFsType)) {
230
+ return makeClassification('network-unc', `network mount fs=${mountFsType}`);
231
+ }
232
+ if (mountFsType && FUSE_CLOUD_MAP.has(mountFsType)) {
233
+ return makeClassification(FUSE_CLOUD_MAP.get(mountFsType), `fuse mount fs=${mountFsType}`);
234
+ }
235
+ // Secondary segment signal for cloud sync folders on native mounts. These are
236
+ // PATH-based and remain valid even when the mount probe threw, so they run
237
+ // BEFORE the unknown-on-throw fallback below.
238
+ if (hasSegmentMatching(absPath, /^onedrive.*$/i)) {
239
+ return makeClassification('onedrive', 'OneDrive path segment');
240
+ }
241
+ if (hasExactSegment(absPath, 'dropbox')) {
242
+ return makeClassification('dropbox', 'Dropbox path segment');
243
+ }
244
+ if (hasSegmentMatching(absPath, /^(google ?drive)$/i)) {
245
+ return makeClassification('gdrive', 'Google Drive path segment');
246
+ }
247
+
248
+ // M3: the mount probe THREW (could not read /proc/mounts) and no path/wsl signal
249
+ // matched. Treat as unknown/warn — symmetric with win32's G1 fail-safe — instead
250
+ // of silently returning local-ok, which would suppress the warning entirely.
251
+ if (signals.mountProbeThrew) {
252
+ return makeClassification('unknown', 'mount probe failed (could not classify)');
253
+ }
254
+
255
+ return makeClassification('local-ok', 'no cloud/network/wsl signal matched');
256
+ }
257
+
258
+ function classifyFromSignals(signals = {}) {
259
+ if (isNonFsPath(signals.absPath)) {
260
+ return makeClassification('local-ok', 'non-filesystem path (memory/uri)');
261
+ }
262
+ switch (signals.platform) {
263
+ case 'win32':
264
+ return classifyWin32(signals);
265
+ case 'darwin':
266
+ return classifyDarwin(signals);
267
+ case 'linux':
268
+ return classifyLinux(signals);
269
+ default:
270
+ return makeClassification('unknown', `unrecognized platform ${signals.platform}`);
271
+ }
272
+ }
273
+
274
+ // --- Layer B: thin signal gatherer (all probes injectable) -------------------
275
+
276
+ function isUNC(absPath) {
277
+ return /^[\\/]{2}[^\\/]/.test(String(absPath || ''));
278
+ }
279
+
280
+ function driveLetterOf(absPath) {
281
+ const match = /^([a-z]):[\\/]/i.exec(String(absPath || ''));
282
+ return match ? `${match[1].toUpperCase()}:` : null;
283
+ }
284
+
285
+ // Bounded exec options for the Windows drive probes. The timeout is the BLOCKER-1
286
+ // fix: execFileSync's try/catch only catches throws, never hangs, so an
287
+ // unresponsive `net use` / PowerShell (e.g. a wedged network redirector) would
288
+ // hang the whole process indefinitely. With a timeout, a hang becomes a throw
289
+ // that the gatherSignals catch turns into driveType='unknown' → warn (never
290
+ // refuse), symmetric with the G1 fail-safe. killSignal forces the child dead.
291
+ const WIN_PROBE_EXEC_OPTIONS = Object.freeze({
292
+ encoding: 'utf8',
293
+ timeout: 1500,
294
+ killSignal: 'SIGKILL',
295
+ });
296
+
297
+ // PURE (M5): does `net use` stdout list this drive letter as a mapped (network)
298
+ // connection? Returns 'network' on a boundary-matched letter, else null. The
299
+ // regex is anchored to a line-leading "<letter>:" preceded by start/whitespace
300
+ // so a stray mention of the letter elsewhere cannot false-positive.
301
+ function parseNetUseDriveType(stdout, driveLetter) {
302
+ const letter = String(driveLetter || '').replace(':', '');
303
+ if (!letter) return null;
304
+ const letterRe = new RegExp(String.raw`(^|\s)${letter}:\s`, 'i');
305
+ for (const line of String(stdout || '').split(/\r?\n/)) {
306
+ if (letterRe.test(line)) {
307
+ return 'network';
308
+ }
309
+ }
310
+ return null;
311
+ }
312
+
313
+ // PURE (M5): a non-empty PowerShell DisplayRoot means the PSDrive is backed by a
314
+ // network/remote root. Returns 'network' when non-blank, else null.
315
+ function parseDisplayRoot(stdout) {
316
+ return String(stdout || '').trim() ? 'network' : null;
317
+ }
318
+
319
+ // Windows: detect whether a drive letter is a mapped network drive.
320
+ // FAIL-SAFE FALLBACK (locked decision G1, option a): try `net use`, then a
321
+ // PowerShell DisplayRoot fallback; on ANY probe failure (including a timeout-
322
+ // induced throw, per BLOCKER 1) the caller treats the result as 'unknown'.
323
+ // Probes use arg ARRAYS (no shell) so they remain injection-free.
324
+ function defaultProbeDriveType(driveLetter, deps = {}) {
325
+ const exec = deps.execFileSync || execFileSync;
326
+
327
+ // Primary: `net use` lists mapped drive letters (and their remote paths).
328
+ const netOut = exec('net', ['use'], WIN_PROBE_EXEC_OPTIONS);
329
+ if (parseNetUseDriveType(netOut, driveLetter) === 'network') {
330
+ return 'network';
331
+ }
332
+
333
+ // Secondary: PowerShell DisplayRoot — non-empty for network-backed PSDrives.
334
+ const psLetter = String(driveLetter).replace(':', '');
335
+ const psCmd =
336
+ `(Get-PSDrive -Name '${psLetter}' -ErrorAction SilentlyContinue).DisplayRoot`;
337
+ const psOut = exec('powershell', ['-NoProfile', '-Command', psCmd], WIN_PROBE_EXEC_OPTIONS);
338
+ if (parseDisplayRoot(psOut) === 'network') {
339
+ return 'network';
340
+ }
341
+
342
+ // Letter exists, no network backing detected → fixed.
343
+ return 'fixed';
344
+ }
345
+
346
+ // Linux: read /proc/mounts, return fs type of the longest mountpoint prefixing absPath.
347
+ function defaultProbeMounts(absPath, deps = {}) {
348
+ const readFileSync = deps.readFileSync || require('node:fs').readFileSync;
349
+ const raw = String(readFileSync('/proc/mounts', 'utf8') || '');
350
+ let best = null;
351
+ let bestLen = -1;
352
+ for (const line of raw.split('\n')) {
353
+ const parts = line.split(/\s+/);
354
+ if (parts.length < 3) continue;
355
+ const mountPoint = parts[1];
356
+ const fsType = parts[2];
357
+ if (
358
+ (absPath === mountPoint || absPath.startsWith(`${mountPoint.replace(/\/$/, '')}/`)) &&
359
+ mountPoint.length > bestLen
360
+ ) {
361
+ best = fsType;
362
+ bestLen = mountPoint.length;
363
+ }
364
+ }
365
+ return best;
366
+ }
367
+
368
+ function defaultReadWslInterop(deps = {}) {
369
+ const env = deps.env || process.env;
370
+ if (env.WSL_DISTRO_NAME) return true;
371
+ const readFileSync = deps.readFileSync || require('node:fs').readFileSync;
372
+ const version = String(readFileSync('/proc/version', 'utf8') || '');
373
+ return /microsoft/i.test(version);
374
+ }
375
+
376
+ function gatherSignals(absPath, deps = {}) {
377
+ const platform = deps.platform || process.platform;
378
+ const env = deps.env || process.env;
379
+ const homedirFn = deps.homedir || os.homedir;
380
+ const homedir = typeof homedirFn === 'function' ? homedirFn() : homedirFn;
381
+
382
+ const signals = {
383
+ platform,
384
+ absPath,
385
+ env,
386
+ homedir,
387
+ isUNC: isUNC(absPath),
388
+ driveType: null,
389
+ mountFsType: null,
390
+ mountProbeThrew: false,
391
+ isWslInterop: false,
392
+ };
393
+
394
+ if (platform === 'win32' && !signals.isUNC) {
395
+ const driveLetter = driveLetterOf(absPath);
396
+ if (driveLetter) {
397
+ const probe = deps.probeDriveType || (letter => defaultProbeDriveType(letter, deps));
398
+ try {
399
+ signals.driveType = probe(driveLetter);
400
+ } catch {
401
+ // G1 fail-safe: any probe failure → unknown → warn (never refuse).
402
+ signals.driveType = 'unknown';
403
+ }
404
+ }
405
+ }
406
+
407
+ if (platform === 'linux') {
408
+ const probeMounts = deps.probeMounts || (p => defaultProbeMounts(p, deps));
409
+ try {
410
+ signals.mountFsType = probeMounts(absPath);
411
+ } catch {
412
+ // M3: a THROWN probe is distinct from a clean no-match. Record the throw so
413
+ // classifyLinux maps it to unknown/warn (symmetric with win32) instead of
414
+ // silently fail-opening to local-ok.
415
+ signals.mountFsType = null;
416
+ signals.mountProbeThrew = true;
417
+ }
418
+ const readWsl = deps.readWslInterop || (() => defaultReadWslInterop(deps));
419
+ try {
420
+ signals.isWslInterop = Boolean(readWsl());
421
+ } catch {
422
+ signals.isWslInterop = false;
423
+ }
424
+ }
425
+
426
+ return signals;
427
+ }
428
+
429
+ function classifyFilesystem(absPath, deps = {}) {
430
+ return classifyFromSignals(gatherSignals(absPath, deps));
431
+ }
432
+
433
+ // --- Default-on gate helper --------------------------------------------------
434
+
435
+ // The unsafe-FS override is a deliberate, explicit opt-in: only the strings "1"
436
+ // and "true" (case-insensitive) enable it. A truthy-but-disabling value such as
437
+ // "0" or "false" — which Boolean() would wrongly treat as enabled — leaves the
438
+ // refuse gate ACTIVE. Anything else (unset, "", "no", "off") is also not active.
439
+ function isUnsafeFsOverrideActive(env = {}) {
440
+ const raw = env.FORGE_KERNEL_ALLOW_UNSAFE_FS;
441
+ if (typeof raw !== 'string') return false;
442
+ const value = raw.trim().toLowerCase();
443
+ return value === '1' || value === 'true';
444
+ }
445
+
446
+ function assertFilesystemSafeForKernel(dbPath, deps = {}) {
447
+ const env = deps.env || process.env;
448
+ const warn = deps.warn || (msg => console.warn(msg));
449
+ const classify = deps.classifyFilesystem || classifyFilesystem;
450
+
451
+ const classification = classify(dbPath, deps);
452
+ const remediation = REMEDIATION[classification.remediationKey] || REMEDIATION.unknown;
453
+
454
+ if (classification.riskTier === 'safe') {
455
+ return classification;
456
+ }
457
+
458
+ if (classification.riskTier === 'warn') {
459
+ warn(`forge kernel filesystem warning (${classification.class}):\n${remediation}`);
460
+ return classification;
461
+ }
462
+
463
+ // refuse
464
+ const overrideActive = isUnsafeFsOverrideActive(env);
465
+ if (overrideActive) {
466
+ warn(
467
+ `forge kernel filesystem REFUSE downgraded by FORGE_KERNEL_ALLOW_UNSAFE_FS ` +
468
+ `(${classification.class}) — override active:\n${remediation}`,
469
+ );
470
+ return classification;
471
+ }
472
+
473
+ const error = new Error(
474
+ `Forge kernel refuses to initialize on a ${classification.class} filesystem.\n\n` +
475
+ ` Detected: ${classification.signal}\n` +
476
+ ` Path: ${dbPath}\n` +
477
+ ` Class: ${classification.class}\n\n` +
478
+ `${remediation}`,
479
+ );
480
+ error.code = 'FORGE_KERNEL_UNSAFE_FS';
481
+ error.classification = classification;
482
+ throw error;
483
+ }
484
+
485
+ module.exports = {
486
+ classifyFromSignals,
487
+ gatherSignals,
488
+ classifyFilesystem,
489
+ assertFilesystemSafeForKernel,
490
+ isUnsafeFsOverrideActive,
491
+ defaultProbeDriveType,
492
+ parseNetUseDriveType,
493
+ parseDisplayRoot,
494
+ REMEDIATION,
495
+ };