forge-workflow 0.0.10 → 0.1.0-beta.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (468) hide show
  1. package/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
  2. package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
  3. package/.cursor/rules/permissions-guidance.mdc +2 -2
  4. package/.forge/hooks/check-tdd.js +82 -5
  5. package/.forge/hooks/forge-native-hook.js +431 -0
  6. package/.forge/protected-paths.yaml +157 -0
  7. package/AGENTS.md +151 -61
  8. package/CHANGELOG.md +709 -0
  9. package/CLAUDE.md +9 -118
  10. package/QUICKSTART.md +175 -0
  11. package/README.md +275 -365
  12. package/bin/forge-cmd.js +120 -9
  13. package/bin/forge-preflight.js +26 -5
  14. package/bin/forge.js +532 -489
  15. package/docs/INDEX.md +93 -0
  16. package/docs/PROJECT_DESIGN.md +685 -0
  17. package/docs/architecture/index.md +66 -0
  18. package/docs/architecture/notes/README.md +35 -0
  19. package/docs/architecture/subsystems/README.md +46 -0
  20. package/docs/forge/TOOLCHAIN.md +670 -0
  21. package/docs/forge/VALIDATION.md +82 -0
  22. package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
  23. package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
  24. package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
  25. package/docs/guides/GREPTILE_SETUP.md +46 -0
  26. package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
  27. package/docs/guides/MIGRATION.md +56 -0
  28. package/docs/guides/SETUP.md +121 -0
  29. package/docs/guides/SUPPORT.md +190 -0
  30. package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
  31. package/docs/guides/memory-backends.md +183 -0
  32. package/docs/reference/ADAPTERS.md +128 -0
  33. package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
  34. package/docs/reference/COMMANDS.md +214 -0
  35. package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
  36. package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
  37. package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
  38. package/docs/reference/HERMES_INTEGRATION.md +118 -0
  39. package/docs/reference/INSIGHTS_RECAP.md +63 -0
  40. package/docs/reference/INSTALL.md +164 -0
  41. package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
  42. package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
  43. package/docs/reference/RELEASE.md +68 -0
  44. package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
  45. package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
  46. package/docs/reference/SKILLS.md +35 -0
  47. package/docs/reference/STATUS_BOARD.md +80 -0
  48. package/docs/reference/TEMPLATES.md +106 -0
  49. package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
  50. package/docs/reference/VALIDATION.md +82 -0
  51. package/docs/reference/agent-permissions.md +169 -0
  52. package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
  53. package/docs/reference/control-plane-guarantees.md +125 -0
  54. package/docs/reference/dependency-chain.md +331 -0
  55. package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
  56. package/docs/reference/forge-kernel-schema.md +72 -0
  57. package/docs/reference/kernel-conflict-evaluators.md +27 -0
  58. package/docs/reference/patch-md-format.md +77 -0
  59. package/docs/reference/protected-state-surfaces.md +59 -0
  60. package/docs/reference/shepherd.md +155 -0
  61. package/docs/reference/superpowers-analysis.md +320 -0
  62. package/docs/reference/superpowers-integration-options.md +404 -0
  63. package/docs/reference/test-environment.md +519 -0
  64. package/docs/reference/upgrade-safety.md +59 -0
  65. package/lefthook.yml +18 -0
  66. package/lib/activation/ensure-forge-home.js +135 -0
  67. package/lib/adapter-cli.js +307 -0
  68. package/lib/adapters/beads-issue-adapter.js +127 -0
  69. package/lib/adapters/beads-kernel-compat.js +1109 -0
  70. package/lib/adapters/greptile-review-adapter.js +141 -0
  71. package/lib/adapters/kernel-issue-adapter.js +101 -0
  72. package/lib/adapters/pr-state-adapter.js +484 -0
  73. package/lib/adoption-profiles.js +139 -0
  74. package/lib/agents/README.md +2 -6
  75. package/lib/agents/claude.plugin.json +3 -8
  76. package/lib/agents/codex.plugin.json +9 -1
  77. package/lib/agents/cursor.plugin.json +2 -6
  78. package/lib/agents/hermes.plugin.json +22 -0
  79. package/lib/agents-config.js +39 -1236
  80. package/lib/audit-evidence.js +282 -0
  81. package/lib/beads-detect.js +60 -0
  82. package/lib/beads-nudge.js +91 -0
  83. package/lib/beads-setup.js +121 -0
  84. package/lib/beads-sync-scaffold.js +25 -101
  85. package/lib/codex-skills.js +51 -1
  86. package/lib/commands/_aliases.js +248 -0
  87. package/lib/commands/_issue.js +780 -77
  88. package/lib/commands/_manifest.js +93 -0
  89. package/lib/commands/_registry.js +99 -34
  90. package/lib/commands/_resolve-command-opts.js +230 -0
  91. package/lib/commands/_serve-security.js +270 -0
  92. package/lib/commands/adapter.js +12 -0
  93. package/lib/commands/add.js +118 -0
  94. package/lib/commands/audit.js +70 -0
  95. package/lib/commands/blocked.js +5 -0
  96. package/lib/commands/board.js +64 -0
  97. package/lib/commands/claim.js +21 -2
  98. package/lib/commands/claims.js +7 -0
  99. package/lib/commands/clean.js +485 -75
  100. package/lib/commands/close.js +2 -2
  101. package/lib/commands/comment.js +5 -0
  102. package/lib/commands/control.js +148 -0
  103. package/lib/commands/create.js +2 -2
  104. package/lib/commands/dev.js +185 -7
  105. package/lib/commands/doc-gate.js +336 -0
  106. package/lib/commands/doctor.js +156 -0
  107. package/lib/commands/explain.js +15 -0
  108. package/lib/commands/export.js +237 -0
  109. package/lib/commands/gate.js +209 -0
  110. package/lib/commands/hooks.js +377 -0
  111. package/lib/commands/inbox.js +118 -0
  112. package/lib/commands/init.js +604 -0
  113. package/lib/commands/insights.js +79 -0
  114. package/lib/commands/issue.js +12 -1
  115. package/lib/commands/issues.js +17 -0
  116. package/lib/commands/lint.js +5 -0
  117. package/lib/commands/list.js +2 -2
  118. package/lib/commands/memory.js +81 -0
  119. package/lib/commands/merge.js +312 -0
  120. package/lib/commands/migrate.js +362 -0
  121. package/lib/commands/new.js +12 -0
  122. package/lib/commands/options.js +241 -0
  123. package/lib/commands/orient.js +13 -0
  124. package/lib/commands/orphans.js +5 -0
  125. package/lib/commands/patch.js +67 -0
  126. package/lib/commands/plan.js +481 -29
  127. package/lib/commands/pr.js +88 -0
  128. package/lib/commands/preflight.js +211 -0
  129. package/lib/commands/prime.js +13 -0
  130. package/lib/commands/push.js +135 -2
  131. package/lib/commands/ready.js +2 -2
  132. package/lib/commands/recall.js +171 -0
  133. package/lib/commands/recap.js +75 -0
  134. package/lib/commands/recommend.js +0 -1
  135. package/lib/commands/release.js +104 -0
  136. package/lib/commands/remember.js +140 -0
  137. package/lib/commands/role.js +99 -0
  138. package/lib/commands/serve.js +581 -0
  139. package/lib/commands/setup.js +900 -971
  140. package/lib/commands/shepherd.js +501 -0
  141. package/lib/commands/ship.js +59 -1
  142. package/lib/commands/show.js +2 -2
  143. package/lib/commands/stage.js +192 -0
  144. package/lib/commands/stale.js +5 -0
  145. package/lib/commands/status.js +158 -21
  146. package/lib/commands/sync.js +34 -46
  147. package/lib/commands/team.js +4 -1
  148. package/lib/commands/test.js +43 -27
  149. package/lib/commands/update.js +2 -2
  150. package/lib/commands/upgrade.js +47 -0
  151. package/lib/commands/validate.js +43 -18
  152. package/lib/commands/worktree.js +362 -99
  153. package/lib/config-writer.js +202 -0
  154. package/lib/control-plane.js +236 -0
  155. package/lib/core/runtime-graph.js +977 -0
  156. package/lib/dep-guard/keyword-ripple.js +2 -2
  157. package/lib/deprecated-sync-cleanup.js +362 -0
  158. package/lib/detect-agent.js +2 -28
  159. package/lib/detect-worktree.js +35 -9
  160. package/lib/doc-gate/declaration.js +177 -0
  161. package/lib/doc-gate/detect.js +289 -0
  162. package/lib/doc-gate/gate.js +375 -0
  163. package/lib/doc-gate/okf-config.js +128 -0
  164. package/lib/doc-gate/okf.js +429 -0
  165. package/lib/docs-command.js +1161 -6
  166. package/lib/forge-issues.js +382 -11
  167. package/lib/forge-lock.js +262 -0
  168. package/lib/gate-events.js +192 -0
  169. package/lib/global-flags.js +104 -0
  170. package/lib/greptile-match.js +7 -63
  171. package/lib/grounding/context-events.js +230 -0
  172. package/lib/grounding/read-first.js +112 -0
  173. package/lib/harness-capability-matrix.js +380 -0
  174. package/lib/hook-global-installer.js +347 -0
  175. package/lib/hook-renderer.js +541 -0
  176. package/lib/inbox.js +391 -0
  177. package/lib/insights.js +397 -0
  178. package/lib/issue-adapter.js +156 -0
  179. package/lib/issue-backend.js +145 -0
  180. package/lib/issue-render.js +220 -0
  181. package/lib/kernel/backing-issue.js +311 -0
  182. package/lib/kernel/broker.js +1218 -0
  183. package/lib/kernel/cli-broker-factory.js +130 -0
  184. package/lib/kernel/conflict-signal.js +82 -0
  185. package/lib/kernel/evaluators.js +195 -0
  186. package/lib/kernel/fs-class.js +495 -0
  187. package/lib/kernel/issue-command-contract.js +559 -0
  188. package/lib/kernel/issue-id-resolver.js +186 -0
  189. package/lib/kernel/lease-enforcer.js +158 -0
  190. package/lib/kernel/migrations.js +333 -0
  191. package/lib/kernel/owned-kernel.js +43 -0
  192. package/lib/kernel/planning-buckets-schema.js +109 -0
  193. package/lib/kernel/projection-jsonl-writer.js +450 -0
  194. package/lib/kernel/readiness-model.js +329 -0
  195. package/lib/kernel/schema.js +356 -0
  196. package/lib/kernel/sqlite-driver.js +2540 -0
  197. package/lib/kernel/taxonomy-validator.js +394 -0
  198. package/lib/lefthook-check.js +3 -2
  199. package/lib/lefthook-wiring.js +413 -0
  200. package/lib/mcp-config-renderer.js +288 -0
  201. package/lib/memory/graphiti-mcp.js +106 -0
  202. package/lib/memory/router.js +387 -0
  203. package/lib/memory/typed-api.js +102 -0
  204. package/lib/memory-digest.js +195 -0
  205. package/lib/merge-rules.js +395 -0
  206. package/lib/migrate-dry-run.js +466 -0
  207. package/lib/orientation.js +863 -0
  208. package/lib/package-manager-remediation.js +103 -0
  209. package/lib/package-root.js +381 -0
  210. package/lib/patch-intent.js +890 -0
  211. package/lib/plugin-catalog.js +3 -4
  212. package/lib/plugin-manager.js +0 -5
  213. package/lib/pr-bundle.js +186 -0
  214. package/lib/pr-monitor/auto-actions.js +175 -0
  215. package/lib/pr-monitor/differ.js +195 -0
  216. package/lib/pr-monitor/digest.js +206 -0
  217. package/lib/pr-monitor/events.js +0 -0
  218. package/lib/pr-monitor/gather.js +124 -0
  219. package/lib/pr-monitor/journal.js +299 -0
  220. package/lib/pr-monitor/monitor.js +146 -0
  221. package/lib/pr-monitor/render-sticky.js +192 -0
  222. package/lib/pr-monitor/upsert-sticky.js +169 -0
  223. package/lib/pr-monitor/watch-lifecycle.js +95 -0
  224. package/lib/pr-monitor/watch.js +247 -0
  225. package/lib/pr-pull.js +1314 -0
  226. package/lib/pr-shepherd.js +494 -0
  227. package/lib/pr-state-validator.js +59 -0
  228. package/lib/preflight/gates.js +237 -0
  229. package/lib/preflight/runner.js +116 -0
  230. package/lib/project-discovery.js +0 -53
  231. package/lib/project-memory.js +99 -497
  232. package/lib/protected-path-manifest.js +281 -0
  233. package/lib/protected-state-surfaces.js +387 -0
  234. package/lib/release-readiness.js +2105 -0
  235. package/lib/reset.js +59 -45
  236. package/lib/review-adapter.js +68 -0
  237. package/lib/rules-sync.js +260 -0
  238. package/lib/runtime-health.js +241 -20
  239. package/lib/safety-config-renderer.js +268 -0
  240. package/lib/setup-action-log.js +1 -7
  241. package/lib/setup.js +27 -65
  242. package/lib/shell-utils.js +76 -6
  243. package/lib/skills-sync.js +330 -0
  244. package/lib/smart-status/scoring.js +17 -3
  245. package/lib/status/beads-snapshot.js +45 -2
  246. package/lib/status/presenter.js +169 -18
  247. package/lib/status/snapshot.js +186 -0
  248. package/lib/sync-backend.js +202 -0
  249. package/lib/untrusted-content.js +52 -0
  250. package/lib/upgrade-safety.js +251 -0
  251. package/lib/workflow/enforce-stage.js +351 -45
  252. package/lib/workflow/stage-transition.js +115 -0
  253. package/lib/workflow/stages.js +30 -6
  254. package/lib/workflow/state-manager.js +11 -22
  255. package/lib/workflow/state.js +23 -1
  256. package/lib/workflow-profiles.js +17 -5
  257. package/package.json +37 -35
  258. package/rules/documentation.md +19 -0
  259. package/rules/kernel-tracking.md +26 -0
  260. package/rules/security.md +22 -0
  261. package/rules/tdd.md +20 -0
  262. package/rules/workflow.md +27 -0
  263. package/scripts/auto-backing-issue.js +47 -0
  264. package/scripts/beads-context.sh +81 -57
  265. package/scripts/beads-upgrade-smoke.sh +24 -3
  266. package/scripts/bootstrap-windows-tools.sh +78 -0
  267. package/scripts/branch-protection.js +2 -3
  268. package/scripts/check-agents.js +34 -137
  269. package/scripts/commitlint.js +3 -1
  270. package/scripts/conflict-detect.sh +3 -0
  271. package/scripts/dep-guard.sh +22 -3
  272. package/scripts/file-index.sh +3 -0
  273. package/scripts/forge-team/lib/claim.sh +34 -18
  274. package/scripts/forge-team/lib/dashboard.sh +61 -86
  275. package/scripts/forge-team/lib/epic.sh +99 -263
  276. package/scripts/forge-team/lib/hooks.sh +26 -28
  277. package/scripts/forge-team/lib/identity.sh +4 -4
  278. package/scripts/forge-team/lib/sync-github.sh +49 -84
  279. package/scripts/forge-team/lib/verify.sh +93 -83
  280. package/scripts/forge-team/lib/workload.sh +41 -65
  281. package/scripts/forge-team/tests/claim.test.sh +25 -19
  282. package/scripts/forge-team/tests/dashboard.test.sh +31 -46
  283. package/scripts/forge-team/tests/epic.test.sh +52 -71
  284. package/scripts/forge-team/tests/hooks.test.sh +38 -50
  285. package/scripts/forge-team/tests/identity.test.sh +3 -3
  286. package/scripts/forge-team/tests/integration.test.sh +44 -66
  287. package/scripts/forge-team/tests/sync-github.test.sh +50 -83
  288. package/scripts/forge-team/tests/verify.test.sh +37 -46
  289. package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
  290. package/scripts/forge-team/tests/workload.test.sh +32 -66
  291. package/scripts/gen-command-manifest.js +153 -0
  292. package/scripts/gen-embedded-assets.mjs +129 -0
  293. package/scripts/install.ps1 +139 -0
  294. package/scripts/install.sh +268 -0
  295. package/scripts/lib/release-asset.mjs +84 -0
  296. package/scripts/parity-check.mjs +145 -0
  297. package/scripts/parity-check.test.mjs +58 -0
  298. package/scripts/pin-agentic-workflow-images.js +112 -0
  299. package/scripts/pr-auto-actions.js +93 -0
  300. package/scripts/pr-coordinator.sh +3 -0
  301. package/scripts/pr-verdict-label.js +50 -0
  302. package/scripts/preflight-sonar.eslint.config.mjs +44 -0
  303. package/scripts/preflight.sh +21 -94
  304. package/scripts/protected-state-check.js +104 -0
  305. package/scripts/smart-status.sh +60 -57
  306. package/scripts/spikes/config-race-bench.js +111 -0
  307. package/scripts/spikes/harness-capability-matrix.js +13 -0
  308. package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
  309. package/scripts/spikes/protected-path-manifest.js +20 -0
  310. package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
  311. package/scripts/sync-agent-skills.js +62 -0
  312. package/scripts/sync-utils.sh +3 -0
  313. package/scripts/test-ci-shard.js +13 -6
  314. package/scripts/test.js +95 -12
  315. package/skills/claim-safety/SKILL.md +102 -0
  316. package/skills/claim-safety/evals/evals.json +46 -0
  317. package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
  318. package/skills/dev/evals/evals.json +50 -0
  319. package/skills/hermes-forge/SKILL.md +185 -0
  320. package/skills/hermes-forge/evals/evals.json +46 -0
  321. package/skills/issue-basics/SKILL.md +111 -0
  322. package/skills/issue-basics/evals/evals.json +46 -0
  323. package/skills/kernel/SKILL.md +166 -0
  324. package/skills/kernel/evals/evals.json +50 -0
  325. package/skills/memory/SKILL.md +102 -0
  326. package/skills/parallel-deep-research/SKILL.md +14 -11
  327. package/skills/parallel-deep-research/evals/evals.json +11 -27
  328. package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
  329. package/skills/plan/evals/evals.json +42 -0
  330. package/skills/research/SKILL.md +195 -0
  331. package/skills/research/evals/evals.json +42 -0
  332. package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
  333. package/skills/review/evals/evals.json +42 -0
  334. package/skills/rollback/SKILL.md +110 -0
  335. package/skills/rollback/evals/evals.json +46 -0
  336. package/skills/rollback/references/methods.md +204 -0
  337. package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
  338. package/skills/shepherd/SKILL.md +66 -0
  339. package/skills/shepherd/evals/evals.json +42 -0
  340. package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
  341. package/skills/ship/evals/evals.json +42 -0
  342. package/skills/smith/SKILL.md +142 -0
  343. package/skills/smith/evals/evals.json +46 -0
  344. package/skills/smith/references/autonomy-and-gates.md +94 -0
  345. package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
  346. package/skills/sonarcloud/evals/evals.json +46 -0
  347. package/skills/sonarcloud-analysis/SKILL.md +18 -13
  348. package/skills/sonarcloud-analysis/evals/evals.json +11 -15
  349. package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
  350. package/skills/status/evals/evals.json +50 -0
  351. package/skills/triage-ready/SKILL.md +121 -0
  352. package/skills/triage-ready/evals/evals.json +42 -0
  353. package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
  354. package/skills/validate/evals/evals.json +42 -0
  355. package/skills/verify/SKILL.md +299 -0
  356. package/skills/verify/evals/evals.json +50 -0
  357. package/.claude/commands/dev.md +0 -345
  358. package/.claude/commands/plan.md +0 -566
  359. package/.claude/commands/premerge.md +0 -186
  360. package/.claude/commands/research.md +0 -42
  361. package/.claude/commands/review.md +0 -451
  362. package/.claude/commands/rollback.md +0 -721
  363. package/.claude/commands/ship.md +0 -213
  364. package/.claude/commands/sonarcloud.md +0 -152
  365. package/.claude/commands/status.md +0 -90
  366. package/.claude/commands/validate.md +0 -288
  367. package/.claude/commands/verify.md +0 -269
  368. package/.claude/rules/workflow.md +0 -121
  369. package/.cline/workflows/dev.md +0 -342
  370. package/.cline/workflows/plan.md +0 -563
  371. package/.cline/workflows/premerge.md +0 -183
  372. package/.cline/workflows/research.md +0 -39
  373. package/.cline/workflows/review.md +0 -448
  374. package/.cline/workflows/rollback.md +0 -718
  375. package/.cline/workflows/ship.md +0 -210
  376. package/.cline/workflows/sonarcloud.md +0 -146
  377. package/.cline/workflows/status.md +0 -87
  378. package/.cline/workflows/validate.md +0 -285
  379. package/.cline/workflows/verify.md +0 -266
  380. package/.codex/config.toml +0 -11
  381. package/.codex/skills/dev/SKILL.md +0 -345
  382. package/.codex/skills/plan/SKILL.md +0 -566
  383. package/.codex/skills/premerge/SKILL.md +0 -186
  384. package/.codex/skills/research/SKILL.md +0 -42
  385. package/.codex/skills/review/SKILL.md +0 -451
  386. package/.codex/skills/rollback/SKILL.md +0 -721
  387. package/.codex/skills/ship/SKILL.md +0 -213
  388. package/.codex/skills/sonarcloud/SKILL.md +0 -149
  389. package/.codex/skills/status/SKILL.md +0 -90
  390. package/.codex/skills/validate/SKILL.md +0 -288
  391. package/.codex/skills/verify/SKILL.md +0 -269
  392. package/.cursor/commands/dev.md +0 -342
  393. package/.cursor/commands/plan.md +0 -563
  394. package/.cursor/commands/premerge.md +0 -183
  395. package/.cursor/commands/research.md +0 -39
  396. package/.cursor/commands/review.md +0 -448
  397. package/.cursor/commands/ship.md +0 -210
  398. package/.cursor/commands/sonarcloud.md +0 -146
  399. package/.cursor/commands/status.md +0 -87
  400. package/.cursor/commands/validate.md +0 -285
  401. package/.cursor/commands/verify.md +0 -266
  402. package/.cursorrules +0 -149
  403. package/.github/prompts/premerge.prompt.md +0 -188
  404. package/.github/prompts/research.prompt.md +0 -44
  405. package/.github/prompts/rollback.prompt.md +0 -723
  406. package/.github/prompts/verify.prompt.md +0 -271
  407. package/.github/workflows/beads-to-github.yml +0 -89
  408. package/.github/workflows/github-to-beads.yml +0 -100
  409. package/.kilocode/workflows/dev.md +0 -346
  410. package/.kilocode/workflows/plan.md +0 -567
  411. package/.kilocode/workflows/premerge.md +0 -187
  412. package/.kilocode/workflows/research.md +0 -43
  413. package/.kilocode/workflows/review.md +0 -452
  414. package/.kilocode/workflows/rollback.md +0 -722
  415. package/.kilocode/workflows/ship.md +0 -214
  416. package/.kilocode/workflows/sonarcloud.md +0 -150
  417. package/.kilocode/workflows/status.md +0 -91
  418. package/.kilocode/workflows/validate.md +0 -289
  419. package/.kilocode/workflows/verify.md +0 -270
  420. package/.opencode/commands/dev.md +0 -345
  421. package/.opencode/commands/plan.md +0 -566
  422. package/.opencode/commands/premerge.md +0 -186
  423. package/.opencode/commands/research.md +0 -42
  424. package/.opencode/commands/review.md +0 -451
  425. package/.opencode/commands/rollback.md +0 -721
  426. package/.opencode/commands/ship.md +0 -213
  427. package/.opencode/commands/sonarcloud.md +0 -149
  428. package/.opencode/commands/status.md +0 -90
  429. package/.opencode/commands/validate.md +0 -288
  430. package/.opencode/commands/verify.md +0 -269
  431. package/.roo/commands/dev.md +0 -346
  432. package/.roo/commands/plan.md +0 -567
  433. package/.roo/commands/premerge.md +0 -187
  434. package/.roo/commands/research.md +0 -43
  435. package/.roo/commands/review.md +0 -452
  436. package/.roo/commands/rollback.md +0 -722
  437. package/.roo/commands/ship.md +0 -214
  438. package/.roo/commands/sonarcloud.md +0 -150
  439. package/.roo/commands/status.md +0 -91
  440. package/.roo/commands/validate.md +0 -289
  441. package/.roo/commands/verify.md +0 -270
  442. package/docs/BEADS_GITHUB_SYNC.md +0 -281
  443. package/docs/GREPTILE_SETUP.md +0 -400
  444. package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
  445. package/docs/SETUP.md +0 -663
  446. package/docs/VALIDATION.md +0 -363
  447. package/lib/agents/cline.plugin.json +0 -29
  448. package/lib/agents/copilot.plugin.json +0 -24
  449. package/lib/agents/kilocode.plugin.json +0 -22
  450. package/lib/agents/opencode.plugin.json +0 -23
  451. package/lib/agents/roo.plugin.json +0 -30
  452. package/lib/beads-bootstrap.js +0 -225
  453. package/lib/beads-health-check.js +0 -188
  454. package/lib/commands/commands-reset.js +0 -147
  455. package/opencode.json +0 -67
  456. package/scripts/beads-context.test.js +0 -584
  457. package/scripts/github-beads-sync/comment.mjs +0 -64
  458. package/scripts/github-beads-sync/config.mjs +0 -148
  459. package/scripts/github-beads-sync/github-api.mjs +0 -131
  460. package/scripts/github-beads-sync/index.mjs +0 -356
  461. package/scripts/github-beads-sync/label-mapper.mjs +0 -54
  462. package/scripts/github-beads-sync/mapping.mjs +0 -132
  463. package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
  464. package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
  465. package/scripts/github-beads-sync/run-bd.mjs +0 -161
  466. package/scripts/github-beads-sync/sanitize.mjs +0 -121
  467. package/scripts/github-beads-sync.config.json +0 -26
  468. package/scripts/sync-commands.js +0 -600
@@ -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
+ };