@khoavu882/doflow 0.0.0-stage → 2.0.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 (407) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/bin/doflow.js +44 -0
  4. package/core/.antigravity-plugin/hooks.json +39 -0
  5. package/core/.antigravity-plugin/mcp_config.json +18 -0
  6. package/core/.antigravity-plugin/plugin.json +31 -0
  7. package/core/.claude-plugin/marketplace.json +20 -0
  8. package/core/.claude-plugin/plugin.json +21 -0
  9. package/core/.codex-plugin/marketplace.json +20 -0
  10. package/core/.codex-plugin/plugin.json +35 -0
  11. package/core/.plugin/plugin.json +19 -0
  12. package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
  13. package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
  14. package/core/harnesses/claude/hooks/lib.sh +304 -0
  15. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
  16. package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
  17. package/core/harnesses/claude/hooks/post-compact.sh +74 -0
  18. package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
  19. package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
  20. package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
  21. package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
  22. package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
  23. package/core/harnesses/claude/hooks/session-end.sh +89 -0
  24. package/core/harnesses/claude/hooks/session-start.sh +5 -0
  25. package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
  26. package/core/harnesses/claude/hooks/stop-check.sh +5 -0
  27. package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
  28. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
  29. package/core/harnesses/claude/settings/keybindings.json +20 -0
  30. package/core/harnesses/claude/settings/settings.json +201 -0
  31. package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
  32. package/core/harnesses/codex/agents/core-implementer.toml +7 -0
  33. package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
  34. package/core/harnesses/codex/agents/research-writer.toml +8 -0
  35. package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
  36. package/core/harnesses/codex/agents/system-architect.toml +9 -0
  37. package/core/harnesses/codex/config/config.toml +10 -0
  38. package/core/harnesses/codex/hooks/hooks.json +122 -0
  39. package/core/harnesses/codex/hooks/lib.sh +335 -0
  40. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
  41. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
  42. package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
  43. package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
  44. package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
  45. package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
  46. package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
  47. package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
  48. package/core/harnesses/codex/hooks/session-end.sh +5 -0
  49. package/core/harnesses/codex/hooks/session-start.sh +5 -0
  50. package/core/harnesses/codex/hooks/stop-check.sh +8 -0
  51. package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
  52. package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
  53. package/core/harnesses/gemini/hooks/hooks.json +77 -0
  54. package/core/harnesses/gemini/hooks/lib.sh +302 -0
  55. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
  56. package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
  57. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
  58. package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
  59. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
  60. package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
  61. package/core/harnesses/gemini/hooks/session-end.sh +5 -0
  62. package/core/harnesses/gemini/hooks/session-start.sh +5 -0
  63. package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
  64. package/core/harnesses/kiro/agents/doflow.json +58 -0
  65. package/core/harnesses/kiro/hooks/doflow.json +53 -0
  66. package/core/harnesses/kiro/hooks/lib.sh +324 -0
  67. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
  68. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
  69. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
  70. package/core/harnesses/kiro/hooks/session-start.sh +9 -0
  71. package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
  72. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
  73. package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
  74. package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
  75. package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
  76. package/core/harnesses/shared/hooks/hooks.json +47 -0
  77. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
  78. package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
  79. package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
  80. package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
  81. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
  82. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
  83. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
  84. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
  85. package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
  86. package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
  87. package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
  88. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
  89. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
  90. package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
  91. package/core/harnesses/shared/locator/doflow-run +87 -0
  92. package/core/registry/advice.json +101 -0
  93. package/core/registry/assets.json +792 -0
  94. package/core/registry/capabilities.json +120 -0
  95. package/core/registry/contracts.json +189 -0
  96. package/core/registry/external-tools.json +191 -0
  97. package/core/registry/harnesses.json +2062 -0
  98. package/core/registry/lifecycle.json +231 -0
  99. package/core/registry/mcp.json +18 -0
  100. package/core/registry/models.json +79 -0
  101. package/core/registry/readiness-templates.json +118 -0
  102. package/core/registry/routes.json +47 -0
  103. package/core/registry/storage.json +1057 -0
  104. package/core/registry/verification.json +258 -0
  105. package/core/registry/workflows.json +790 -0
  106. package/core/shared/agent-specs/core-implementer.md +73 -0
  107. package/core/shared/agent-specs/quality-guardian.md +28 -0
  108. package/core/shared/agent-specs/research-writer.md +31 -0
  109. package/core/shared/agent-specs/spec-analyst.md +33 -0
  110. package/core/shared/agent-specs/system-architect.md +29 -0
  111. package/core/shared/guidance/DOFLOW_CORE.md +8 -0
  112. package/core/shared/guidance/FLAGS.md +25 -0
  113. package/core/shared/guidance/PRINCIPLES.md +61 -0
  114. package/core/shared/guidance/VERSION +1 -0
  115. package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
  116. package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
  117. package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
  118. package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
  119. package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
  120. package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
  121. package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
  122. package/core/shared/guidance/pointers/claude-gemini.md +2 -0
  123. package/core/shared/guidance/pointers/codex.md +6 -0
  124. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
  125. package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
  126. package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
  127. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
  128. package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
  129. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
  130. package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
  131. package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
  132. package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
  133. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
  134. package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
  135. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
  136. package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
  137. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
  138. package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
  139. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
  140. package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
  141. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
  142. package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
  143. package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
  144. package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
  145. package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
  146. package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
  147. package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
  148. package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
  149. package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
  150. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
  151. package/core/shared/scripts/doflow/bin/doflow-run +592 -0
  152. package/core/shared/skills/do/SKILL.md +128 -0
  153. package/core/shared/skills/do/references/estimation.md +13 -0
  154. package/core/shared/skills/do/references/pm_routing.md +25 -0
  155. package/core/shared/skills/do/references/tool_matrix.md +52 -0
  156. package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
  157. package/core/shared/skills/do-code-review/README.md +49 -0
  158. package/core/shared/skills/do-code-review/SKILL.md +333 -0
  159. package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
  160. package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
  161. package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
  162. package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
  163. package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
  164. package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
  165. package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
  166. package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
  167. package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
  168. package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
  169. package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
  170. package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
  171. package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
  172. package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
  173. package/core/shared/skills/do-code-review/content-types/config.md +83 -0
  174. package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
  175. package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
  176. package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
  177. package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
  178. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
  179. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
  180. package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
  181. package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
  182. package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
  183. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
  184. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
  185. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
  186. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
  187. package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
  188. package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
  189. package/core/shared/skills/do-code-review/languages/c.md +103 -0
  190. package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
  191. package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
  192. package/core/shared/skills/do-code-review/languages/dart.md +108 -0
  193. package/core/shared/skills/do-code-review/languages/go.md +92 -0
  194. package/core/shared/skills/do-code-review/languages/java.md +103 -0
  195. package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
  196. package/core/shared/skills/do-code-review/languages/php.md +109 -0
  197. package/core/shared/skills/do-code-review/languages/python.md +94 -0
  198. package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
  199. package/core/shared/skills/do-code-review/languages/rust.md +99 -0
  200. package/core/shared/skills/do-code-review/languages/shell.md +90 -0
  201. package/core/shared/skills/do-code-review/languages/swift.md +88 -0
  202. package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
  203. package/core/shared/skills/do-code-review/review-policy.json +47 -0
  204. package/core/shared/skills/do-code-review/rules/universal.md +49 -0
  205. package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
  206. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
  207. package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
  208. package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
  209. package/core/shared/skills/do-constitution/SKILL.md +72 -0
  210. package/core/shared/skills/do-design/SKILL.md +212 -0
  211. package/core/shared/skills/do-diagnose/SKILL.md +156 -0
  212. package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
  213. package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
  214. package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
  215. package/core/shared/skills/do-document/SKILL.md +81 -0
  216. package/core/shared/skills/do-document/references/api-reference.md +42 -0
  217. package/core/shared/skills/do-document/references/deep_research.md +18 -0
  218. package/core/shared/skills/do-document/references/embedded-research.md +11 -0
  219. package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
  220. package/core/shared/skills/do-document/references/index_generator.md +9 -0
  221. package/core/shared/skills/do-document/references/user-guide.md +22 -0
  222. package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
  223. package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
  224. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
  225. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
  226. package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
  227. package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
  228. package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
  229. package/core/shared/skills/do-flow/SKILL.md +186 -0
  230. package/core/shared/skills/do-flow/references/task_classes.md +134 -0
  231. package/core/shared/skills/do-git/SKILL.md +115 -0
  232. package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
  233. package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
  234. package/core/shared/skills/do-git/references/release-ritual.md +64 -0
  235. package/core/shared/skills/do-implement/SKILL.md +222 -0
  236. package/core/shared/skills/do-plan/SKILL.md +219 -0
  237. package/core/shared/skills/do-test/SKILL.md +113 -0
  238. package/core/shared/templates/doflow/constitution-template.md +28 -0
  239. package/core/shared/templates/doflow/data-model-template.md +169 -0
  240. package/core/shared/templates/doflow/design-template.md +193 -0
  241. package/core/shared/templates/doflow/external-contract-template.md +35 -0
  242. package/core/shared/templates/doflow/intent-template.md +45 -0
  243. package/core/shared/templates/doflow/plan-template.md +151 -0
  244. package/core/shared/templates/doflow/question-log-template.md +51 -0
  245. package/core/shared/templates/doflow/requirement-template.md +126 -0
  246. package/core/shared/templates/doflow/specs-template.md +55 -0
  247. package/core/shared/templates/doflow/state-template.md +59 -0
  248. package/core/shared/workflows/do-flow-chain.md +17 -0
  249. package/package.json +46 -4
  250. package/src/adapters/antigravity/index.ts +631 -0
  251. package/src/adapters/claude/index.ts +548 -0
  252. package/src/adapters/claude/mcp.ts +94 -0
  253. package/src/adapters/codex/agents.ts +208 -0
  254. package/src/adapters/codex/config.ts +272 -0
  255. package/src/adapters/codex/hooks.ts +203 -0
  256. package/src/adapters/codex/index.ts +707 -0
  257. package/src/adapters/codex/mcp.ts +271 -0
  258. package/src/adapters/codex/native-projection.ts +34 -0
  259. package/src/adapters/copilot/index.ts +413 -0
  260. package/src/adapters/copy-tree.ts +620 -0
  261. package/src/adapters/gemini/hooks.ts +210 -0
  262. package/src/adapters/gemini/index.ts +390 -0
  263. package/src/adapters/hook-commands.ts +60 -0
  264. package/src/adapters/index.ts +106 -0
  265. package/src/adapters/kiro/index.ts +310 -0
  266. package/src/adapters/mcp-entries.ts +331 -0
  267. package/src/adapters/opencode/index.ts +466 -0
  268. package/src/adapters/pi/index.ts +279 -0
  269. package/src/adapters/pi/json-members.ts +185 -0
  270. package/src/adapters/pi/mcp.ts +373 -0
  271. package/src/adapters/resolve-source.ts +19 -0
  272. package/src/cli/commands/install.ts +218 -0
  273. package/src/cli/commands/list-backups.ts +21 -0
  274. package/src/cli/commands/reconcile.ts +120 -0
  275. package/src/cli/commands/remove.ts +82 -0
  276. package/src/cli/commands/rollback.ts +172 -0
  277. package/src/cli/commands/self-update.ts +33 -0
  278. package/src/cli/commands/status.ts +138 -0
  279. package/src/cli/commands/tools.ts +92 -0
  280. package/src/cli/commands/update.ts +162 -0
  281. package/src/cli/index.ts +568 -0
  282. package/src/cli/runtime-commands.ts +281 -0
  283. package/src/cli/shared.ts +218 -0
  284. package/src/helper/bash-helper.ts +34 -0
  285. package/src/helper/git.ts +24 -0
  286. package/src/helper/guardrails.ts +49 -0
  287. package/src/helper/harness-paths.ts +140 -0
  288. package/src/helper/marker-merge.ts +116 -0
  289. package/src/helper/prompt.ts +61 -0
  290. package/src/helper/repo-root.ts +29 -0
  291. package/src/helper/settings-merge.ts +151 -0
  292. package/src/helper/settings-scope.ts +33 -0
  293. package/src/helper/toml.ts +244 -0
  294. package/src/install/backup-set.ts +149 -0
  295. package/src/install/backup.ts +724 -0
  296. package/src/install/context.ts +58 -0
  297. package/src/install/manifest.ts +145 -0
  298. package/src/install/mcp.ts +254 -0
  299. package/src/install/paths.ts +84 -0
  300. package/src/install/targets.ts +90 -0
  301. package/src/install/tool-lifecycle.ts +151 -0
  302. package/src/lifecycle/bash-availability.ts +25 -0
  303. package/src/lifecycle/index.ts +812 -0
  304. package/src/lifecycle/mcp-index.ts +28 -0
  305. package/src/lifecycle/policies.ts +73 -0
  306. package/src/lifecycle/view.ts +175 -0
  307. package/src/registry/index.ts +611 -0
  308. package/src/release/sync-plugin-versions.ts +66 -0
  309. package/src/runtime/advice/cli.ts +283 -0
  310. package/src/runtime/advice/client.ts +324 -0
  311. package/src/runtime/advice/config.ts +189 -0
  312. package/src/runtime/advice/doctor.ts +169 -0
  313. package/src/runtime/advice/endpoint-guard.ts +134 -0
  314. package/src/runtime/advice/kinds.ts +154 -0
  315. package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
  316. package/src/runtime/advice/record.ts +166 -0
  317. package/src/runtime/advice/registry.ts +170 -0
  318. package/src/runtime/advice/strength.ts +66 -0
  319. package/src/runtime/brief-tools.ts +342 -0
  320. package/src/runtime/c4-project.ts +371 -0
  321. package/src/runtime/capability-router.ts +484 -0
  322. package/src/runtime/checkouts.ts +170 -0
  323. package/src/runtime/claims.ts +468 -0
  324. package/src/runtime/cli-result.ts +55 -0
  325. package/src/runtime/cli.ts +678 -0
  326. package/src/runtime/command-detect.ts +516 -0
  327. package/src/runtime/context-pack.ts +492 -0
  328. package/src/runtime/decision-register.ts +493 -0
  329. package/src/runtime/evidence-ledger.ts +338 -0
  330. package/src/runtime/failure/capture.ts +272 -0
  331. package/src/runtime/failure/classifier.ts +68 -0
  332. package/src/runtime/failure/cli.ts +178 -0
  333. package/src/runtime/failure/home.ts +64 -0
  334. package/src/runtime/failure/store.ts +226 -0
  335. package/src/runtime/feature-resolve.ts +79 -0
  336. package/src/runtime/freshness.ts +177 -0
  337. package/src/runtime/health.ts +863 -0
  338. package/src/runtime/history-compactor.ts +237 -0
  339. package/src/runtime/implementation-gate.ts +321 -0
  340. package/src/runtime/inventory/coverage.ts +218 -0
  341. package/src/runtime/inventory/identity.ts +65 -0
  342. package/src/runtime/inventory/index.ts +705 -0
  343. package/src/runtime/inventory/precedence.ts +134 -0
  344. package/src/runtime/inventory/read-scopes.ts +131 -0
  345. package/src/runtime/inventory/siblings.ts +192 -0
  346. package/src/runtime/knowledge/chunker.ts +124 -0
  347. package/src/runtime/knowledge/hybrid.ts +149 -0
  348. package/src/runtime/knowledge/index-store.ts +145 -0
  349. package/src/runtime/knowledge/retrieval.ts +194 -0
  350. package/src/runtime/leak-scan.ts +185 -0
  351. package/src/runtime/lifecycle/cli.ts +276 -0
  352. package/src/runtime/lifecycle/event-store.ts +340 -0
  353. package/src/runtime/lifecycle/fold.ts +353 -0
  354. package/src/runtime/lifecycle/followup.ts +432 -0
  355. package/src/runtime/lifecycle/goal.ts +249 -0
  356. package/src/runtime/lifecycle/intent-writer.ts +86 -0
  357. package/src/runtime/lifecycle/overview.ts +292 -0
  358. package/src/runtime/lifecycle/release.ts +241 -0
  359. package/src/runtime/lifecycle/report-store.ts +221 -0
  360. package/src/runtime/lifecycle/retention.ts +212 -0
  361. package/src/runtime/lifecycle/root.ts +71 -0
  362. package/src/runtime/lifecycle/status.ts +353 -0
  363. package/src/runtime/lifecycle/store-upkeep.ts +393 -0
  364. package/src/runtime/locator-resolve.ts +158 -0
  365. package/src/runtime/mask.ts +210 -0
  366. package/src/runtime/mcp-consultations.ts +412 -0
  367. package/src/runtime/mcp-reach.ts +157 -0
  368. package/src/runtime/model-router.ts +153 -0
  369. package/src/runtime/outcome.ts +467 -0
  370. package/src/runtime/reach.ts +148 -0
  371. package/src/runtime/readiness-record.ts +150 -0
  372. package/src/runtime/readiness.ts +435 -0
  373. package/src/runtime/recovery.ts +282 -0
  374. package/src/runtime/research-request.ts +231 -0
  375. package/src/runtime/retrieval-plan.ts +578 -0
  376. package/src/runtime/scaffold/artifacts.ts +217 -0
  377. package/src/runtime/scaffold/fingerprint.ts +56 -0
  378. package/src/runtime/scaffold/generate.ts +911 -0
  379. package/src/runtime/scaffold/languages.ts +245 -0
  380. package/src/runtime/storage/cli.ts +64 -0
  381. package/src/runtime/storage/engine.ts +249 -0
  382. package/src/runtime/storage/registry.ts +130 -0
  383. package/src/runtime/storage/roots.ts +63 -0
  384. package/src/runtime/storage/rules.ts +282 -0
  385. package/src/runtime/storage/upkeep.ts +243 -0
  386. package/src/runtime/storage/work-status.ts +326 -0
  387. package/src/runtime/task-attempts.ts +415 -0
  388. package/src/runtime/task-classifier.ts +550 -0
  389. package/src/runtime/task-scope.ts +130 -0
  390. package/src/runtime/task-state.ts +176 -0
  391. package/src/runtime/trace/indicators.ts +235 -0
  392. package/src/runtime/trace/ledger.ts +626 -0
  393. package/src/runtime/trace/render.ts +76 -0
  394. package/src/runtime/trace/views.ts +459 -0
  395. package/src/runtime/verification/contract-runner.ts +341 -0
  396. package/src/runtime/verification/engine.ts +1461 -0
  397. package/src/runtime/verification/registry.ts +130 -0
  398. package/src/runtime/verification/scope-bound.ts +418 -0
  399. package/src/runtime/verification/test-selection.ts +172 -0
  400. package/src/runtime/workflow-engine.ts +588 -0
  401. package/src/runtime/workflow-orchestrator.ts +953 -0
  402. package/src/runtime/worktree.ts +340 -0
  403. package/src/state/index.ts +193 -0
  404. package/src/state/lockfile.ts +136 -0
  405. package/src/state/run-lock.ts +408 -0
  406. package/types/node-commonjs.d.ts +68 -0
  407. package/types/runtime.d.ts +91 -0
@@ -0,0 +1,245 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Language selection and emitters — everything `scaffold.js`'s generator needs to know about a
7
+ * target language: which extension maps to which language, which extensions are content rather
8
+ * than code, which source languages have no emitter yet, the per-language identifier-casing rules,
9
+ * the emitters themselves, and `repoLanguage()`, which asks `command-detect.ts` what toolchain the
10
+ * repository itself uses (for test stubs, which have no file extension of their own to read).
11
+ */
12
+
13
+ const { detectCommands } = require('../command-detect.ts');
14
+
15
+ /**
16
+ * Per-file language comes from the file's own extension, because `plan.md` already states it:
17
+ * `files: src/runtime/scaffold/generate.js` is not ambiguous about being JavaScript, and asking a
18
+ * repository-wide detector would be a worse answer than the one the plan wrote down.
19
+ *
20
+ * Repository-wide detection is still needed for the test stubs, which derive from acceptance
21
+ * criteria and therefore have no path to read an extension from. That question — "what toolchain
22
+ * is this repository" — is `command-detect.ts`'s, so it is asked there rather than answered a
23
+ * second time here. See `repoLanguage()` for the one place its output is interpreted.
24
+ */
25
+ const EXTENSION_LANGUAGE: Readonly<Record<string, string>> = Object.freeze({
26
+ '.js': 'javascript',
27
+ '.mjs': 'javascript',
28
+ '.cjs': 'javascript',
29
+ '.jsx': 'javascript',
30
+ '.ts': 'typescript',
31
+ '.mts': 'typescript',
32
+ '.cts': 'typescript',
33
+ '.tsx': 'typescript',
34
+ '.py': 'python',
35
+ '.go': 'go',
36
+ '.rs': 'rust',
37
+ });
38
+
39
+ /**
40
+ * Extensions that are content, not code. A plan task naming one of these is not a gap in this
41
+ * generator — there is no signature to emit for a markdown file — so these skip benignly and never
42
+ * push the run to a non-zero exit.
43
+ */
44
+ const PROSE_EXTENSIONS: ReadonlySet<string> = new Set([
45
+ '.md', '.markdown', '.txt', '.rst',
46
+ '.yaml', '.yml', '.json', '.jsonc', '.toml', '.ini', '.cfg', '.conf', '.env', '.lock',
47
+ '.html', '.css', '.scss', '.svg', '.png', '.jpg', '.gif', '.csv',
48
+ ]);
49
+
50
+ /**
51
+ * Source languages the *prose* algorithm in `references/scaffold.md` covers through its
52
+ * Default-Implementation Grammar table, but this module has no emitter for. Named explicitly so a
53
+ * plan that touches one produces "no emitter for Java", not silence — the gap is real and belongs
54
+ * in the manifest, not in a reader's assumptions.
55
+ */
56
+ const UNEMITTED_SOURCE_EXTENSIONS: Readonly<Record<string, { name: string; inProseGrammar: boolean }>> = Object.freeze({
57
+ '.java': { name: 'Java', inProseGrammar: true },
58
+ '.kt': { name: 'Kotlin', inProseGrammar: true },
59
+ '.kts': { name: 'Kotlin', inProseGrammar: true },
60
+ '.cs': { name: 'C#', inProseGrammar: true },
61
+ '.swift': { name: 'Swift', inProseGrammar: true },
62
+ '.m': { name: 'Objective-C', inProseGrammar: true },
63
+ '.h': { name: 'Objective-C or C header', inProseGrammar: false },
64
+ '.sh': { name: 'Shell', inProseGrammar: false },
65
+ '.bash': { name: 'Shell', inProseGrammar: false },
66
+ '.rb': { name: 'Ruby', inProseGrammar: false },
67
+ '.php': { name: 'PHP', inProseGrammar: false },
68
+ '.c': { name: 'C', inProseGrammar: false },
69
+ '.cc': { name: 'C++', inProseGrammar: false },
70
+ '.cpp': { name: 'C++', inProseGrammar: false },
71
+ '.hpp': { name: 'C++', inProseGrammar: false },
72
+ });
73
+
74
+ /** Splits an identifier-ish path segment into lowercase words, so every language can re-case it
75
+ * to its own convention from one parse. */
76
+ function words(name: string): string[] {
77
+ return name
78
+ .replace(/\.[^.]+$/, '')
79
+ .split(/[^A-Za-z0-9]+/)
80
+ .flatMap((part) => part.split(/(?<=[a-z0-9])(?=[A-Z])/))
81
+ .filter(Boolean)
82
+ .map((part) => part.toLowerCase());
83
+ }
84
+
85
+ const camel = (parts: string[]): string => parts.map((w, i) => (i === 0 ? w : w[0].toUpperCase() + w.slice(1))).join('') || 'scaffold';
86
+ const pascal = (parts: string[]): string => parts.map((w) => w[0].toUpperCase() + w.slice(1)).join('') || 'Scaffold';
87
+ const snake = (parts: string[]): string => parts.join('_') || 'scaffold';
88
+
89
+ /**
90
+ * One entry per language this module can emit. Each `module` and `test` body is a signature plus a
91
+ * single "not implemented" signal drawn from the Default-Implementation Grammar pinned in
92
+ * `references/scaffold.md` — the same table the external-dependency case uses, so the two halves of
93
+ * that document agree on what an unimplemented body looks like.
94
+ */
95
+ const EMITTERS: Readonly<Record<string, any>> = Object.freeze({
96
+ javascript: {
97
+ line: (t: string) => (t ? `// ${t}` : '//'),
98
+ module: ({ symbol, message }: any) => [
99
+ '/**',
100
+ ' * Placeholder signature — replace with the real surface before implementing.',
101
+ ' * @returns {never}',
102
+ ' */',
103
+ `function ${symbol}() {`,
104
+ ` throw new Error(${JSON.stringify(message)});`,
105
+ '}',
106
+ '',
107
+ `module.exports = { ${symbol} };`,
108
+ ].join('\n'),
109
+ testName: (id: string) => `${id}.test.js`,
110
+ test: ({ id, cases, message }: any) => [
111
+ "const { test } = require('node:test');",
112
+ '',
113
+ ...cases.map(({ statement }: any) => [
114
+ `test(${JSON.stringify(`${id} — ${statement}`)}, () => {`,
115
+ ` throw new Error(${JSON.stringify(message)});`,
116
+ '});',
117
+ '',
118
+ ].join('\n')),
119
+ ].join('\n').trimEnd(),
120
+ },
121
+
122
+ typescript: {
123
+ line: (t: string) => (t ? `// ${t}` : '//'),
124
+ module: ({ symbol, message }: any) => [
125
+ '/** Placeholder signature — replace with the real surface before implementing. */',
126
+ `export function ${symbol}(): never {`,
127
+ ` throw new Error(${JSON.stringify(message)});`,
128
+ '}',
129
+ ].join('\n'),
130
+ testName: (id: string) => `${id}.test.ts`,
131
+ test: ({ id, cases, message }: any) => [
132
+ "import { test } from 'node:test';",
133
+ '',
134
+ ...cases.map(({ statement }: any) => [
135
+ `test(${JSON.stringify(`${id} — ${statement}`)}, (): void => {`,
136
+ ` throw new Error(${JSON.stringify(message)});`,
137
+ '});',
138
+ '',
139
+ ].join('\n')),
140
+ ].join('\n').trimEnd(),
141
+ },
142
+
143
+ python: {
144
+ line: (t: string) => (t ? `# ${t}` : '#'),
145
+ module: ({ symbol, message }: any) => [
146
+ `def ${symbol}() -> None:`,
147
+ ' """Placeholder signature — replace with the real surface before implementing."""',
148
+ ` raise NotImplementedError(${JSON.stringify(message)})`,
149
+ ].join('\n'),
150
+ testName: (id: string) => `test_${id.toLowerCase().replace(/-/g, '_')}.py`,
151
+ test: ({ cases, message }: any) => cases.map(({ statement }: any, i: number) => [
152
+ `def test_case_${i + 1}() -> None:`,
153
+ ` """${statement.replace(/"/g, "'")}"""`,
154
+ ` raise NotImplementedError(${JSON.stringify(message)})`,
155
+ '',
156
+ ].join('\n')).join('\n').trimEnd(),
157
+ },
158
+
159
+ go: {
160
+ line: (t: string) => (t ? `// ${t}` : '//'),
161
+ module: ({ symbol, message, pkg }: any) => [
162
+ `package ${pkg}`,
163
+ '',
164
+ 'import "errors"',
165
+ '',
166
+ '// Placeholder signature — replace with the real surface before implementing.',
167
+ `func ${symbol}() error {`,
168
+ `\treturn errors.New(${JSON.stringify(message)})`,
169
+ '}',
170
+ ].join('\n'),
171
+ testName: (id: string) => `${id.toLowerCase().replace(/-/g, '_')}_test.go`,
172
+ test: ({ id, cases, message, pkg }: any) => [
173
+ `package ${pkg}`,
174
+ '',
175
+ 'import "testing"',
176
+ '',
177
+ ...cases.map(({ statement }: any, i: number) => [
178
+ `// ${statement}`,
179
+ `func Test${pascal(words(id))}Case${i + 1}(t *testing.T) {`,
180
+ `\tt.Fatal(${JSON.stringify(message)})`,
181
+ '}',
182
+ '',
183
+ ].join('\n')),
184
+ ].join('\n').trimEnd(),
185
+ },
186
+
187
+ rust: {
188
+ line: (t: string) => (t ? `// ${t}` : '//'),
189
+ module: ({ symbol, message }: any) => [
190
+ '/// Placeholder signature — replace with the real surface before implementing.',
191
+ `pub fn ${symbol}() {`,
192
+ ` unimplemented!(${JSON.stringify(message)});`,
193
+ '}',
194
+ ].join('\n'),
195
+ testName: (id: string) => `${id.toLowerCase().replace(/-/g, '_')}.rs`,
196
+ test: ({ id, cases, message }: any) => cases.map(({ statement }: any, i: number) => [
197
+ `// ${statement}`,
198
+ '#[test]',
199
+ `fn ${snake(words(id))}_case_${i + 1}() {`,
200
+ ` unimplemented!(${JSON.stringify(message)});`,
201
+ '}',
202
+ '',
203
+ ].join('\n')).join('\n').trimEnd(),
204
+ },
205
+ });
206
+
207
+ /**
208
+ * The repository's own toolchain, asked of `command-detect.ts` rather than re-derived here.
209
+ *
210
+ * Partial fit, stated rather than papered over: that module answers "which manifests are present",
211
+ * which pins the toolchain but not the dialect inside it — `package.json` alone does not say
212
+ * TypeScript. The one extra bit is read from its own output (a `typecheck` role exists only when a
213
+ * tsc script or a `tsconfig.json` was found), so no second detector is introduced.
214
+ */
215
+ function repoLanguage(repoRoot: string, fsImpl: any): any {
216
+ const detected = detectCommands({ projectRoot: repoRoot, fsImpl });
217
+ if (!detected.manifestFound) {
218
+ return { id: null, signal: 'none', why: 'no build manifest found by command-detect.ts' };
219
+ }
220
+ const has = (name: string) => detected.manifests.includes(name);
221
+ if (has('package.json')) {
222
+ const typescript = Boolean(detected.commands.typecheck);
223
+ return { id: typescript ? 'typescript' : 'javascript', signal: 'package.json', why: null };
224
+ }
225
+ if (has('Cargo.toml')) return { id: 'rust', signal: 'Cargo.toml', why: null };
226
+ if (has('go.mod')) return { id: 'go', signal: 'go.mod', why: null };
227
+ if (has('pyproject.toml')) return { id: 'python', signal: 'pyproject.toml', why: null };
228
+ return {
229
+ id: null,
230
+ signal: detected.manifests.join(', ') || 'none',
231
+ why: `command-detect.ts resolved only ${detected.manifests.join(', ') || 'nothing'}, which names no source language`,
232
+ };
233
+ }
234
+
235
+ module.exports = {
236
+ EXTENSION_LANGUAGE,
237
+ PROSE_EXTENSIONS,
238
+ UNEMITTED_SOURCE_EXTENSIONS,
239
+ words,
240
+ camel,
241
+ pascal,
242
+ snake,
243
+ EMITTERS,
244
+ repoLanguage,
245
+ };
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const path = require('node:path');
6
+ const fs = require('node:fs');
7
+ const { loadStorageRegistry } = require('./registry.ts');
8
+ const { resolveRoots, readPruneSetting } = require('./roots.ts');
9
+ const { runPass } = require('./engine.ts');
10
+ const { upkeepAfterVerb } = require('./upkeep.ts');
11
+ const { REPO_ROOT } = require('../../helper/repo-root.ts');
12
+
13
+ function handleStorageCommand({ action = 'report', json = false, global = false, projectRoot = '.', env = process.env, fsImpl = fs }: any = {}) {
14
+ if (!['report', 'prune', 'upkeep'].includes(action)) {
15
+ console.error(`doflow storage: unknown action '${action}' (expected report, prune, or upkeep)`);
16
+ process.exitCode = 2;
17
+ return;
18
+ }
19
+ if (action === 'upkeep') {
20
+ // IC-018: prints nothing and exits 0 even when the registry is unavailable; upkeep fails closed.
21
+ upkeepAfterVerb({ projectRoot: global ? null : path.resolve(projectRoot), env, fsImpl });
22
+ return;
23
+ }
24
+ let registry;
25
+ try { registry = loadStorageRegistry({ repoRoot: REPO_ROOT, fsImpl }); }
26
+ catch (error: any) {
27
+ console.error(`doflow storage: storage registry unavailable${error.message ? `: ${error.message}` : ''}`);
28
+ process.exitCode = 2;
29
+ return;
30
+ }
31
+ const roots = resolveRoots({ registry, projectRoot: global ? null : path.resolve(projectRoot), global, env });
32
+ const projectSetting = readPruneSetting({ registry, roots, rootId: 'project-state', fsImpl });
33
+ const userSetting = readPruneSetting({ registry, roots, rootId: 'user-state', fsImpl });
34
+ const setting = Object.fromEntries(Object.keys(roots).map(rootId => [rootId,
35
+ readPruneSetting({ registry, roots, rootId, fsImpl })]));
36
+ const mode = action === 'prune' ? 'prune' : 'report';
37
+ const result = runPass({ registry, roots, mode, setting, now: Date.now(), env, fsImpl });
38
+ const removed = result.stores.reduce((sum: any, row: any) => sum + row.removed, 0);
39
+ const entries = `entr${removed === 1 ? 'y' : 'ies'}`;
40
+ if (action === 'prune' && Object.values(setting).every(value => value.setting === 'off')) {
41
+ const decision = projectSetting.decidedBy || userSetting.decidedBy || path.join(roots['config-home'], registry.settings.optOutFile);
42
+ // IC-013: an explicitly set DOFLOW_RETENTION_HOURS still removes settled lifecycle events.
43
+ console.log(removed === 0 ? `storage: pruning is off (${decision}); nothing removed`
44
+ : `storage: pruning is off (${decision}); DOFLOW_RETENTION_HOURS still applied to lifecycle events: removed ${removed} ${entries}`);
45
+ return;
46
+ }
47
+ if (json) {
48
+ console.log(JSON.stringify({
49
+ setting: { project: projectSetting.setting, user: userSetting.setting,
50
+ decidedBy: { project: projectSetting.decidedBy, user: userSetting.decidedBy } },
51
+ ...result,
52
+ }, null, 2));
53
+ return;
54
+ }
55
+ console.log(`Settings: project ${projectSetting.setting}${projectSetting.decidedBy ? ` (${projectSetting.decidedBy})` : ' (default)'}; user ${userSetting.setting}${userSetting.decidedBy ? ` (${userSetting.decidedBy})` : ' (default)'}`);
56
+ for (const row of result.stores) {
57
+ const names = row.selected.slice(0, 20).map((entry: any) => entry.name);
58
+ const more = row.selected.length > 20 ? `\n ... and ${row.selected.length - 20} more` : '';
59
+ console.log(`${row.id}\n Location: ${row.location || '(not configured)'}\n Entries: ${row.entries}; size: ${row.bytes} bytes\n Bound: ${row.bound}\n Would remove: ${row.selected.length}${names.length ? `\n ${names.join('\n ')}` : ''}${more}`);
60
+ }
61
+ if (action === 'prune') console.log(`storage: removed ${removed} ${entries}`);
62
+ }
63
+
64
+ module.exports = { handleStorageCommand };
@@ -0,0 +1,249 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { randomBytes } = require('node:crypto');
8
+ const { formatBound } = require('./registry.ts');
9
+ const { resolveStorePath } = require('./roots.ts');
10
+ const { selectCandidates, recordStillCandidate, protectedLogs, removableRules, ruleOrder, projectRoot } = require('./rules.ts');
11
+ const { retainLocked } = require('../lifecycle/store-upkeep.ts');
12
+ const { parseWindow, selectExpired } = require('../lifecycle/retention.ts');
13
+ const { readEvents } = require('../lifecycle/event-store.ts');
14
+ const { createWorkCache } = require('./work-status.ts');
15
+ const { projectKey } = require('../lifecycle/report-store.ts');
16
+ const SAFE_NAMESPACE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
17
+ const TOMBSTONE = /^\.[A-Za-z0-9][A-Za-z0-9._-]*\.pruning-[0-9]+-[0-9a-f]{8}$/;
18
+
19
+ function stat(fsImpl: any,file: any) { try { return fsImpl.lstatSync(file); } catch { return null; } }
20
+ function ownedPathLinked(store: any,roots: any,fsImpl: any) {
21
+ const root=roots[store.root]; if (!root) return false;
22
+ const owned=path.basename(root)==='doflow' || path.basename(root)==='.doflow' ? root : path.dirname(root);
23
+ for (const dir of [owned,...(store.path ? [root,...store.path.split('/').reduce((a: any,part: any)=>{a.push(path.join(a.at(-1)||root,part));return a;},[])] : [root])])
24
+ if (stat(fsImpl,dir)?.isSymbolicLink()) return true;
25
+ return false;
26
+ }
27
+ function inventory(store: any,storeDir: any,fsImpl: any,shouldStop: any) {
28
+ const match=new RegExp(store.match), entries=[];
29
+ for (const name of fsImpl.readdirSync(storeDir)) {
30
+ if (shouldStop?.()) return null;
31
+ const matches=match.test(name), namespace=store.entry==='record' && SAFE_NAMESPACE.test(name) && !name.includes('..');
32
+ if (!matches && !namespace) continue;
33
+ const info=stat(fsImpl,path.join(storeDir,name));
34
+ if (shouldStop?.()) return null;
35
+ if (!info || info.isSymbolicLink()) continue;
36
+ if (store.entry==='dir' && !info.isDirectory() || store.entry==='file' && !info.isFile()
37
+ || store.entry==='record' && !(matches && info.isFile() || namespace && info.isDirectory())) continue;
38
+ entries.push({name,stat:info});
39
+ }
40
+ return entries;
41
+ }
42
+ function sweepTombstones(fsImpl: any,storeDir: any,row: any) {
43
+ for (const name of fsImpl.readdirSync(storeDir)) {
44
+ if (!TOMBSTONE.test(name)) continue;
45
+ const target=path.join(storeDir,name);
46
+ try {
47
+ const info=fsImpl.lstatSync(target);
48
+ if (!info.isDirectory() || info.isSymbolicLink()) continue;
49
+ // Node's recursive rm removes nested links themselves, never their referents.
50
+ fsImpl.rmSync(target,{recursive:true});
51
+ row.removed++;
52
+ } catch (error: any) {
53
+ if (error.code==='ENOENT') row.removed++;
54
+ else row.failed.push({name,code:error.code || 'EIO'});
55
+ }
56
+ }
57
+ }
58
+ function removalOrder(candidates: any) {
59
+ const oldest=[...candidates].sort((a,b)=>a.mtimeMs-b.mtimeMs || a.name.localeCompare(b.name));
60
+ const byName=new Map(oldest.map(candidate=>[candidate.name,candidate])), emitted=new Set(), ordered=[];
61
+ for (const candidate of oldest) {
62
+ // Preserve oldest-first order except that a task's claims must disappear before its record.
63
+ const claims=byName.get(candidate.name.replace(/\.json$/, '_claims.json'));
64
+ for (const entry of [claims,candidate]) {
65
+ if (entry && !emitted.has(entry.name)) { ordered.push(entry); emitted.add(entry.name); }
66
+ }
67
+ }
68
+ return ordered;
69
+ }
70
+ function removeOne(fsImpl: any,storeDir: any,candidate: any,store: any,context: any) {
71
+ const parts=candidate.name.split('/');
72
+ if (parts.length>2 || parts.some((part: any)=>!part || part==='.' || part==='..')) throw Object.assign(new Error('unsafe name'),{code:'EINVAL'});
73
+ const target=path.join(storeDir,...parts), lock=`${target}.lock`;
74
+ let locked=false;
75
+ try {
76
+ if (parts.length===2) {
77
+ const parent=fsImpl.lstatSync(path.join(storeDir,parts[0]));
78
+ if (parent.isSymbolicLink() || !parent.isDirectory()) return false;
79
+ }
80
+ if (store.entry==='record') {
81
+ try { fsImpl.mkdirSync(lock); locked=true; }
82
+ catch(error: any) { if (error.code==='EEXIST') return false; throw error; }
83
+ }
84
+ const info=fsImpl.lstatSync(target);
85
+ if (info.isSymbolicLink()) return false;
86
+ if (locked) {
87
+ if (!recordStillCandidate(store,candidate,info,context)) return false;
88
+ // Some record writers do not honor this lock. Catch a write during the work-status read
89
+ // too; a changed subject must wait for another pass even if it still looks finished.
90
+ const current=fsImpl.lstatSync(target);
91
+ if (current.isSymbolicLink() || !current.isFile()
92
+ || ['mtimeMs','ctimeMs','size','ino','dev'].some(key=>current[key]!==info[key])) return false;
93
+ }
94
+ if (context.shouldStop()) return false;
95
+ if (info.isDirectory()) {
96
+ // An appending hook may have refreshed a child since selection. Revalidate before hiding.
97
+ if (store.rule==='age' && !selectCandidates(store,[{name:candidate.name,stat:info}],context)?.length) return false;
98
+ if (context.shouldStop()) return false;
99
+ const tombstone=path.join(path.dirname(target),`.${path.basename(target)}.pruning-${process.pid}-${randomBytes(4).toString('hex')}`);
100
+ try { fsImpl.renameSync(target,tombstone); } catch { return false; }
101
+ const renamed=fsImpl.lstatSync(tombstone);
102
+ if (!renamed.isDirectory() || renamed.isSymbolicLink()) return false;
103
+ fsImpl.rmSync(tombstone,{recursive:true});
104
+ } else if (info.isFile()) fsImpl.unlinkSync(target);
105
+ else return false;
106
+ return true;
107
+ } catch(error: any) { if (error.code==='ENOENT') return true; throw error; }
108
+ finally {
109
+ if (locked) {
110
+ try { fsImpl.rmdirSync(lock); } catch(error: any) { if (error.code!=='ENOENT') throw error; }
111
+ }
112
+ }
113
+ }
114
+ function cleanEmptyNamespace(fsImpl: any,storeDir: any,name: any,report = false) {
115
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) return;
116
+ const dir=path.join(storeDir,name);
117
+ try {
118
+ if (report && fsImpl.readdirSync(dir).join() === '.root') fsImpl.unlinkSync(path.join(dir,'.root'));
119
+ fsImpl.rmdirSync(dir);
120
+ } catch { /* another record, or an active writer */ }
121
+ }
122
+ function projectKeyMarker(fsImpl: any,storeDir: any,roots: any) {
123
+ const project=projectRoot(roots);
124
+ if (!project) return;
125
+ let key;
126
+ try { key=projectKey(project,fsImpl); } catch { return; }
127
+ const dir=path.join(storeDir,key);
128
+ if (!stat(fsImpl,dir)?.isDirectory() || stat(fsImpl,path.join(dir,'.root'))) return;
129
+ try { fsImpl.writeFileSync(path.join(dir,'.root'),`${fsImpl.realpathSync(project)}\n`,{flag:'wx'}); } catch { /* best effort */ }
130
+ }
131
+ /** A bounded pass over registry-declared stores; report mode never writes. */
132
+ function runPass({registry,roots,stores,mode,setting={},now=Date.now(),budgetMs,maxRemovals,env=process.env,fsImpl=fs}: any) {
133
+ if (mode!=='report' && mode!=='prune') throw new Error('Storage pass mode must be report or prune');
134
+ const started=performance.now(), budget=budgetMs===undefined?(mode==='report'?Infinity:registry.settings.passBudgetMs):budgetMs;
135
+ const cap=maxRemovals===undefined?registry.settings.maxRemovalsPerPass:maxRemovals;
136
+ const requested=new Set(stores || registry.stores.filter((s: any)=>removableRules.has(s.rule)).map((s: any)=>s.id));
137
+ const ordered=[...registry.stores].filter(s=>requested.has(s.id) && removableRules.has(s.rule))
138
+ .sort((a,b)=>ruleOrder.indexOf(a.id)-ruleOrder.indexOf(b.id));
139
+ const result: any={mode,partial:false,durationMs:0,stores:[]};
140
+ let removals=0, protectedSet: any=null;
141
+ const shouldStop=()=>mode==='prune' && (removals>=cap || performance.now()-started>=budget);
142
+ const project=projectRoot(roots);
143
+ const workCache=project && ordered.some(s=>s.rule==='work' || s.protect==='open-work')
144
+ ? createWorkCache({projectRoot:project,fsImpl}) : null;
145
+ // P0/P1 run for every requested store before any budgeted inventory or selection. A stopped
146
+ // pass must still finish removing folders that an earlier interrupted pass already hid.
147
+ const ready=[];
148
+ for (const store of ordered) {
149
+ const root=roots[store.root], storeSetting=typeof setting[store.root]==='string'?setting[store.root]:setting[store.root]?.setting || 'on';
150
+ const location=root?path.join(root,...(store.path?store.path.split('/'):[])):'';
151
+ const row: any={id:store.id,root:root || '',location,bound:formatBound(store),setting:storeSetting,entries:0,bytes:0,selected:[],removed:0,protected:0,failed:[]};
152
+ result.stores.push(row);
153
+ if (!root) { row.skipped='absent'; continue; }
154
+ if (ownedPathLinked(store,roots,fsImpl) || resolveStorePath({store,roots,fsImpl})===null) { row.skipped='link'; continue; }
155
+ if (!stat(fsImpl,root) || !stat(fsImpl,location)?.isDirectory()) { row.skipped='absent'; continue; }
156
+ // IC-013: an explicit DOFLOW_RETENTION_HOURS governs lifecycle events even while pruning is off.
157
+ const explicitWindow=store.rule==='lifecycle-settled' ? parseWindow(env) : {state:'off'};
158
+ if (mode==='prune' && storeSetting==='off' && explicitWindow.state!=='on') { row.skipped='setting-off'; continue; }
159
+ if (mode==='prune') {
160
+ try { sweepTombstones(fsImpl,location,row); }
161
+ catch(error: any) { row.failed.push({name:store.id,code:error.code || 'EIO'}); continue; }
162
+ }
163
+ ready.push({store,row,location,explicitWindow,storeSetting});
164
+ }
165
+ for (const {store,row,location,explicitWindow,storeSetting} of ready) {
166
+ if (shouldStop()) { result.partial=true; break; }
167
+ let entries;
168
+ try { entries=inventory(store,location,fsImpl,shouldStop); }
169
+ catch(error: any) { row.failed.push({name:store.id,code:error.code || 'EIO'}); continue; }
170
+ if (entries===null) { result.partial=true; break; }
171
+ row.entries=entries.length;
172
+ for (const entry of entries) {
173
+ if (shouldStop()) { result.partial=true; break; }
174
+ row.bytes+=entry.stat.size;
175
+ }
176
+ if (result.partial) break;
177
+ if (store.rule==='lifecycle-settled') {
178
+ // Report and prune share one window: the env window, else the default only while the setting is on.
179
+ const window=explicitWindow.state==='on' ? {...explicitWindow,source:'env'}
180
+ : explicitWindow.state==='invalid' ? explicitWindow
181
+ : storeSetting==='off' ? {state:'off'}
182
+ : {state:'on',hours:store.bound.defaultHours,ms:store.bound.defaultHours*3600000,source:'default'};
183
+ if (window.state==='on') {
184
+ if (mode==='report') {
185
+ let snapshot;
186
+ try { snapshot=readEvents(project,{fsImpl,lockFallback:false,shouldStop}); }
187
+ catch(error: any) { row.failed.push({name:store.id,code:error.code || 'EIO'}); continue; }
188
+ if (snapshot.incomplete) { result.partial=true; break; }
189
+ if (snapshot.unreadable.length) { row.failed.push({name:store.id,code:'EIO'}); continue; }
190
+ const selection=selectExpired(snapshot.events,{now:new Date(now),windowMs:window.ms,shouldStop});
191
+ if (selection.incomplete) { result.partial=true; break; }
192
+ row.selected=selection.files.map((name: any)=>({name,reason:'settled',bytes:stat(fsImpl,path.join(location,name))?.size || 0}));
193
+ } else {
194
+ let output;
195
+ try { output=retainLocked(project,{fsImpl,now:new Date(now),window,maxFiles:Math.max(0,cap-removals),shouldStop}); }
196
+ catch(error: any) { row.failed.push({name:store.id,code:error.code || 'EIO'}); continue; }
197
+ row.selected=output.selected.map(({name,bytes}: any)=>({name,reason:'settled',bytes}));
198
+ row.removed=output.removed; removals+=row.removed;
199
+ if (output.warning.length) row.failed.push({name:store.id,code:output.code || 'EIO'});
200
+ if (output.partial) { result.partial=true; break; }
201
+ }
202
+ }
203
+ if (shouldStop()) { result.partial=true; break; }
204
+ continue;
205
+ }
206
+ let candidates;
207
+ try { candidates=selectCandidates(store,entries,{registry,roots,fsImpl,storeDir:location,now,settings:registry.settings,shouldStop,cache:workCache}); }
208
+ catch(error: any) {
209
+ if (error.partial) { result.partial=true; break; }
210
+ row.failed.push({name:store.id,code:error.code || 'EIO'}); continue;
211
+ }
212
+ if (candidates===null) { result.partial=true; break; }
213
+ if (store.protect==='open-work' && candidates.length) {
214
+ if (!protectedSet) {
215
+ const protection=protectedLogs({registry,roots,fsImpl,cache:workCache,shouldStop});
216
+ if (protection.incomplete) {
217
+ if (shouldStop()) { result.partial=true; break; }
218
+ row.failed.push({name:store.id,code:protection.reason || 'EIO'}); continue;
219
+ }
220
+ protectedSet=protection.names;
221
+ }
222
+ row.protected=candidates.filter((c: any)=>protectedSet.has(c.name)).length;
223
+ candidates=candidates.filter((c: any)=>!protectedSet.has(c.name));
224
+ }
225
+ const orderedCandidates=store.entry==='record' ? removalOrder(candidates)
226
+ : [...candidates].sort((a,b)=>a.mtimeMs-b.mtimeMs || a.name.localeCompare(b.name));
227
+ if (shouldStop()) { result.partial=true; break; }
228
+ row.selected=orderedCandidates.map(({name,reason,bytes})=>({name,reason,bytes}));
229
+ if (mode==='report') continue;
230
+ if (shouldStop()) { result.partial=true; break; }
231
+ const removalContext={registry,roots,fsImpl,storeDir:location,now,settings:registry.settings,shouldStop,cache:workCache};
232
+ for (const candidate of orderedCandidates) {
233
+ if (shouldStop()) { result.partial=true; break; }
234
+ try {
235
+ if (ownedPathLinked(store,roots,fsImpl) || resolveStorePath({store,roots,fsImpl})===null) continue;
236
+ if (removeOne(fsImpl,location,candidate,store,removalContext)) {
237
+ row.removed++; removals++;
238
+ if (store.entry==='record' && candidate.name.includes('/')) cleanEmptyNamespace(fsImpl,location,candidate.name.split('/')[0]);
239
+ if (store.rule==='report-orphan' && candidate.name.includes('/')) cleanEmptyNamespace(fsImpl,location,candidate.name.split('/')[0],true);
240
+ }
241
+ } catch(error: any) { row.failed.push({name:candidate.name,code:error.code || 'EIO'}); }
242
+ }
243
+ if (store.rule==='report-orphan' && !result.partial) projectKeyMarker(fsImpl,location,roots);
244
+ if (result.partial) break;
245
+ }
246
+ result.durationMs=performance.now()-started;
247
+ return result;
248
+ }
249
+ module.exports={runPass};