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