@khoavu882/doflow 0.0.0-stage → 2.0.0-beta.4

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 (409) 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 +1061 -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 +48 -4
  250. package/src/adapters/antigravity/index.js +631 -0
  251. package/src/adapters/claude/index.js +548 -0
  252. package/src/adapters/claude/mcp.js +94 -0
  253. package/src/adapters/codex/agents.js +208 -0
  254. package/src/adapters/codex/config.js +272 -0
  255. package/src/adapters/codex/hooks.js +203 -0
  256. package/src/adapters/codex/index.js +707 -0
  257. package/src/adapters/codex/mcp.js +271 -0
  258. package/src/adapters/codex/native-projection.js +34 -0
  259. package/src/adapters/copilot/index.js +413 -0
  260. package/src/adapters/copy-tree.js +620 -0
  261. package/src/adapters/gemini/hooks.js +210 -0
  262. package/src/adapters/gemini/index.js +390 -0
  263. package/src/adapters/hook-commands.js +60 -0
  264. package/src/adapters/index.js +106 -0
  265. package/src/adapters/kiro/index.js +310 -0
  266. package/src/adapters/mcp-entries.js +331 -0
  267. package/src/adapters/opencode/index.js +466 -0
  268. package/src/adapters/pi/index.js +279 -0
  269. package/src/adapters/pi/json-members.js +185 -0
  270. package/src/adapters/pi/mcp.js +373 -0
  271. package/src/adapters/resolve-source.js +19 -0
  272. package/src/cli/commands/install.js +218 -0
  273. package/src/cli/commands/list-backups.js +21 -0
  274. package/src/cli/commands/reconcile.js +120 -0
  275. package/src/cli/commands/remove.js +82 -0
  276. package/src/cli/commands/rollback.js +172 -0
  277. package/src/cli/commands/self-update.js +33 -0
  278. package/src/cli/commands/status.js +138 -0
  279. package/src/cli/commands/tools.js +92 -0
  280. package/src/cli/commands/update.js +162 -0
  281. package/src/cli/index.js +568 -0
  282. package/src/cli/runtime-commands.js +281 -0
  283. package/src/cli/shared.js +218 -0
  284. package/src/helper/bash-helper.js +34 -0
  285. package/src/helper/git.js +24 -0
  286. package/src/helper/guardrails.js +49 -0
  287. package/src/helper/harness-paths.js +140 -0
  288. package/src/helper/marker-merge.js +116 -0
  289. package/src/helper/prompt.js +61 -0
  290. package/src/helper/repo-root.js +29 -0
  291. package/src/helper/settings-merge.js +151 -0
  292. package/src/helper/settings-scope.js +33 -0
  293. package/src/helper/toml.js +244 -0
  294. package/src/install/backup-set.js +149 -0
  295. package/src/install/backup.js +724 -0
  296. package/src/install/context.js +58 -0
  297. package/src/install/manifest.js +145 -0
  298. package/src/install/mcp.js +254 -0
  299. package/src/install/paths.js +84 -0
  300. package/src/install/targets.js +90 -0
  301. package/src/install/tool-lifecycle.js +151 -0
  302. package/src/lifecycle/bash-availability.js +25 -0
  303. package/src/lifecycle/index.js +812 -0
  304. package/src/lifecycle/mcp-index.js +28 -0
  305. package/src/lifecycle/policies.js +73 -0
  306. package/src/lifecycle/view.js +175 -0
  307. package/src/registry/index.js +611 -0
  308. package/src/release/build-package.js +91 -0
  309. package/src/release/changelog-section.js +70 -0
  310. package/src/release/sync-plugin-versions.js +66 -0
  311. package/src/runtime/advice/cli.js +283 -0
  312. package/src/runtime/advice/client.js +324 -0
  313. package/src/runtime/advice/config.js +189 -0
  314. package/src/runtime/advice/doctor.js +169 -0
  315. package/src/runtime/advice/endpoint-guard.js +134 -0
  316. package/src/runtime/advice/kinds.js +154 -0
  317. package/src/runtime/advice/providers/ollama-systemone.js +209 -0
  318. package/src/runtime/advice/record.js +166 -0
  319. package/src/runtime/advice/registry.js +170 -0
  320. package/src/runtime/advice/strength.js +66 -0
  321. package/src/runtime/brief-tools.js +342 -0
  322. package/src/runtime/c4-project.js +371 -0
  323. package/src/runtime/capability-router.js +484 -0
  324. package/src/runtime/checkouts.js +170 -0
  325. package/src/runtime/claims.js +468 -0
  326. package/src/runtime/cli-result.js +55 -0
  327. package/src/runtime/cli.js +678 -0
  328. package/src/runtime/command-detect.js +516 -0
  329. package/src/runtime/context-pack.js +492 -0
  330. package/src/runtime/decision-register.js +493 -0
  331. package/src/runtime/evidence-ledger.js +338 -0
  332. package/src/runtime/failure/capture.js +272 -0
  333. package/src/runtime/failure/classifier.js +68 -0
  334. package/src/runtime/failure/cli.js +178 -0
  335. package/src/runtime/failure/home.js +64 -0
  336. package/src/runtime/failure/store.js +226 -0
  337. package/src/runtime/feature-resolve.js +79 -0
  338. package/src/runtime/freshness.js +177 -0
  339. package/src/runtime/health.js +863 -0
  340. package/src/runtime/history-compactor.js +237 -0
  341. package/src/runtime/implementation-gate.js +321 -0
  342. package/src/runtime/inventory/coverage.js +218 -0
  343. package/src/runtime/inventory/identity.js +65 -0
  344. package/src/runtime/inventory/index.js +705 -0
  345. package/src/runtime/inventory/precedence.js +134 -0
  346. package/src/runtime/inventory/read-scopes.js +131 -0
  347. package/src/runtime/inventory/siblings.js +192 -0
  348. package/src/runtime/knowledge/chunker.js +124 -0
  349. package/src/runtime/knowledge/hybrid.js +149 -0
  350. package/src/runtime/knowledge/index-store.js +145 -0
  351. package/src/runtime/knowledge/retrieval.js +194 -0
  352. package/src/runtime/leak-scan.js +185 -0
  353. package/src/runtime/lifecycle/cli.js +276 -0
  354. package/src/runtime/lifecycle/event-store.js +340 -0
  355. package/src/runtime/lifecycle/fold.js +353 -0
  356. package/src/runtime/lifecycle/followup.js +432 -0
  357. package/src/runtime/lifecycle/goal.js +249 -0
  358. package/src/runtime/lifecycle/intent-writer.js +86 -0
  359. package/src/runtime/lifecycle/overview.js +292 -0
  360. package/src/runtime/lifecycle/release.js +241 -0
  361. package/src/runtime/lifecycle/report-store.js +221 -0
  362. package/src/runtime/lifecycle/retention.js +212 -0
  363. package/src/runtime/lifecycle/root.js +71 -0
  364. package/src/runtime/lifecycle/status.js +353 -0
  365. package/src/runtime/lifecycle/store-upkeep.js +393 -0
  366. package/src/runtime/locator-resolve.js +158 -0
  367. package/src/runtime/mask.js +210 -0
  368. package/src/runtime/mcp-consultations.js +412 -0
  369. package/src/runtime/mcp-reach.js +157 -0
  370. package/src/runtime/model-router.js +153 -0
  371. package/src/runtime/outcome.js +467 -0
  372. package/src/runtime/reach.js +148 -0
  373. package/src/runtime/readiness-record.js +150 -0
  374. package/src/runtime/readiness.js +435 -0
  375. package/src/runtime/recovery.js +282 -0
  376. package/src/runtime/research-request.js +231 -0
  377. package/src/runtime/retrieval-plan.js +578 -0
  378. package/src/runtime/scaffold/artifacts.js +217 -0
  379. package/src/runtime/scaffold/fingerprint.js +56 -0
  380. package/src/runtime/scaffold/generate.js +911 -0
  381. package/src/runtime/scaffold/languages.js +245 -0
  382. package/src/runtime/storage/cli.js +64 -0
  383. package/src/runtime/storage/engine.js +249 -0
  384. package/src/runtime/storage/registry.js +130 -0
  385. package/src/runtime/storage/roots.js +63 -0
  386. package/src/runtime/storage/rules.js +282 -0
  387. package/src/runtime/storage/upkeep.js +243 -0
  388. package/src/runtime/storage/work-status.js +326 -0
  389. package/src/runtime/task-attempts.js +415 -0
  390. package/src/runtime/task-classifier.js +550 -0
  391. package/src/runtime/task-scope.js +130 -0
  392. package/src/runtime/task-state.js +176 -0
  393. package/src/runtime/trace/indicators.js +235 -0
  394. package/src/runtime/trace/ledger.js +626 -0
  395. package/src/runtime/trace/render.js +76 -0
  396. package/src/runtime/trace/views.js +459 -0
  397. package/src/runtime/verification/contract-runner.js +341 -0
  398. package/src/runtime/verification/engine.js +1461 -0
  399. package/src/runtime/verification/registry.js +130 -0
  400. package/src/runtime/verification/scope-bound.js +418 -0
  401. package/src/runtime/verification/test-selection.js +172 -0
  402. package/src/runtime/workflow-engine.js +588 -0
  403. package/src/runtime/workflow-orchestrator.js +953 -0
  404. package/src/runtime/worktree.js +340 -0
  405. package/src/state/index.js +193 -0
  406. package/src/state/lockfile.js +136 -0
  407. package/src/state/run-lock.js +408 -0
  408. package/types/node-commonjs.d.ts +68 -0
  409. package/types/runtime.d.ts +91 -0
@@ -0,0 +1,178 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The `failure` verb (IC-019): `list`, `settle` and `capture` over the machine-wide failure store
7
+ * (IC-011 to IC-015). The store is per machine, so the verb ignores `-g` and works from any
8
+ * directory; only `settle --as imported` looks at the project, and only to find the DoFlow
9
+ * repository's follow-up store (IC-001 root). `--json` prints the result object unmodified; exit 0 =
10
+ * answered or written, 1 = a refusal, 2 = usage. A refusal is returned, never thrown, so it cannot
11
+ * reach failure capture (NFR-005).
12
+ */
13
+
14
+ const fs = require('node:fs');
15
+ const { finishRuntime, usageError } = require('../cli-result.js');
16
+ const { maskLine, printSafe } = require('../mask.js');
17
+ const { failureHome, captureSwitch, sentinelPath } = require('./home.js');
18
+ const { rotateIfDue } = require('./capture.js');
19
+ const store = require('./store.js');
20
+ const { projectRoot } = require('../lifecycle/root.js');
21
+ const { appendEvents, readFold, randomChars, StoreUnsafeError, StoreLockedError } = require('../lifecycle/event-store.js');
22
+ const { prepareStore } = require('../lifecycle/store-upkeep.js');
23
+
24
+ const ACTIONS = ['list', 'settle', 'capture'];
25
+ const STATEMENT_MAX = 280;
26
+ const FP = /^[0-9a-f]{16}$/;
27
+ const CONTROL = /[\u0000-\u001F\u007F-\u009F\u2028\u2029\u202A-\u202E\u2066-\u2069]/g;
28
+
29
+ /** A caller mistake (exit 2). */
30
+ class FailureUsageError extends Error {}
31
+
32
+ const refusal = (action , finding , message ) => ({ ok: false, action, finding, message });
33
+
34
+ /** A one-line reason, masked like any other free text, or null when none was given. */
35
+ function cleanReason(raw ) {
36
+ if (raw === undefined) return null;
37
+ const rawLength = String(raw).length;
38
+ if (rawLength > STATEMENT_MAX * 4) throw new FailureUsageError(`--reason is ${rawLength} characters; the limit is ${STATEMENT_MAX}`);
39
+ const text = maskLine(raw).text.trim();
40
+ if (text === '') throw new FailureUsageError('--reason is empty');
41
+ if (/[\r\n\u2028\u2029\u0085]/.test(text)) throw new FailureUsageError('--reason must be one line');
42
+ if (text.length > STATEMENT_MAX) throw new FailureUsageError(`--reason is ${text.length} characters; the limit is ${STATEMENT_MAX}`);
43
+ return text;
44
+ }
45
+
46
+ /** `DoFlow <command> <kind>: <message>`, built only from stored fields, masked and cut to 280 characters. */
47
+ function importedStatement(entry ) {
48
+ const raw = `DoFlow ${entry.command} ${entry.kind}${entry.message ? `: ${entry.message}` : ''}`;
49
+ return maskLine(raw.replace(CONTROL, ' ')).text.replace(/\s+/g, ' ').trim().slice(0, STATEMENT_MAX);
50
+ }
51
+
52
+ // ── list ───────────────────────────────────────────────────────────────────────────────────────
53
+
54
+ function listFailures({ all = false, env = process.env } = {}) {
55
+ const home = failureHome(env);
56
+ if (home) rotateIfDue(home);
57
+ const loaded = store.loadEntries({ env });
58
+ const shown = loaded.entries.filter((entry ) => all || store.SHOWN_BY_DEFAULT.has(entry.status)).map(store.listedEntry);
59
+ const result = { ok: true, action: 'list', capture: loaded.capture };
60
+ if (loaded.home === null) result.home = null;
61
+ result.entries = shown;
62
+ result.counts = loaded.counts;
63
+ result.skippedLines = loaded.skippedLines;
64
+ result.unreadable = loaded.unreadable;
65
+ result.next = shown.length
66
+ ? ['Settle each entry: doflow-run failure --action settle --fp <fp> --as noise|fixed|imported --reason "<why>"']
67
+ : [];
68
+ return result;
69
+ }
70
+
71
+ // ── settle ─────────────────────────────────────────────────────────────────────────────────────
72
+
73
+ function settleFailure({ fp, as, reason, cwd, env = process.env, now = new Date() } ) {
74
+ if (typeof fp !== 'string' || !FP.test(fp)) throw new FailureUsageError('--fp is required: the 16 hex characters of an entry from `failure --action list`');
75
+ if (!store.SETTLE_AS.includes(as)) throw new FailureUsageError(`--as must be one of ${store.SETTLE_AS.join(', ')}${as ? ` (got '${as}')` : ''}`);
76
+ const cleaned = cleanReason(reason);
77
+ if ((as === 'noise' || as === 'fixed') && cleaned === null) throw new FailureUsageError(`--reason is required for --as ${as}`);
78
+ const home = failureHome(env);
79
+ if (home === null) return { ok: true, action: 'settle', home: null, entries: [], next: [] };
80
+
81
+ let root = null;
82
+ if (as === 'imported') {
83
+ root = projectRoot(cwd || process.cwd());
84
+ if (!store.isDoflowRepo(root)) {
85
+ return refusal('settle', 'not-doflow-repo', `${root} is not the DoFlow repository (its package.json is not named @khoavu882/doflow), so a failure cannot be imported as a follow-up here. Nothing was written.`);
86
+ }
87
+ const prepared = prepareStore(root);
88
+ for (const text of prepared.lines) console.error(printSafe(`doflow failure: ${text}`));
89
+ if (!prepared.ok) return refusal('settle', prepared.finding, prepared.message);
90
+ }
91
+ const entry = store.loadEntries({ env }).entries.find((e ) => e.fp === fp);
92
+ if (!entry) return refusal('settle', 'unknown-fp', `${fp} is not a failure entry on this machine; list them with doflow-run failure --action list. Nothing was written.`);
93
+
94
+ if (as !== 'imported') {
95
+ store.appendSettlement(home, { fp, as, reason: cleaned, now });
96
+ return { ok: true, action: 'settle', fp, as, reason: cleaned, followup: null };
97
+ }
98
+ // Deduplicated on the project's own follow-ups, whatever the settlements say: a follow-up that
99
+ // already comes from this fingerprint is never created twice, even after a noise or fixed settlement.
100
+ const followups = readFold(root, { now }).followups;
101
+ const existing = followups.find((f ) => f.source && f.source.kind === 'failure' && f.source.ref === fp);
102
+ if (existing) return refusal('settle', 'already-imported', `${fp} was already imported as ${existing.id}. Nothing was written.`);
103
+ const taken = new Set(followups.map((f ) => f.id));
104
+ let id ;
105
+ do { id = `FU-${randomChars(6)}`; } while (taken.has(id));
106
+ const statement = importedStatement(entry);
107
+ const source = { kind: 'failure', ref: fp };
108
+ // The settlement goes first: if the follow-up write then fails, a retry finds no follow-up in the
109
+ // project and creates exactly one, so a failure between the two steps can never duplicate it.
110
+ store.appendSettlement(home, { fp, as, reason: cleaned, followup: id, now });
111
+ const out = appendEvents(root, [{ type: 'followup.added', by: 'agent', data: { id, statement, source } }], { now });
112
+ if (!out.ok) return refusal('settle', out.finding, `${out.message} The settlement was recorded without a follow-up; run the same command again.`);
113
+ return {
114
+ ok: true, action: 'settle', fp, as,
115
+ followup: { id, statement, source, state: 'open' },
116
+ events: out.written.map((w ) => w.file),
117
+ };
118
+ }
119
+
120
+ // ── capture ────────────────────────────────────────────────────────────────────────────────────
121
+
122
+ function captureState({ set, env = process.env, now = new Date() } = {}) {
123
+ if (set !== undefined && set !== 'on' && set !== 'off') throw new FailureUsageError(`--set must be on or off (got '${set}')`);
124
+ const home = failureHome(env);
125
+ if (home !== null && set === 'off') {
126
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
127
+ fs.writeFileSync(sentinelPath(home), `${now.toISOString()}\n`, { mode: 0o600 });
128
+ } else if (home !== null && set === 'on') {
129
+ fs.rmSync(sentinelPath(home), { force: true });
130
+ }
131
+ const state = captureSwitch(home, env);
132
+ // With no failure home nothing is captured, whatever the switch says.
133
+ const result = { ok: true, action: 'capture', effective: home === null ? 'off' : state.effective, sentinel: state.sentinel, env: state.env };
134
+ if (home === null) result.home = null;
135
+ return result;
136
+ }
137
+
138
+ // ── verb ───────────────────────────────────────────────────────────────────────────────────────
139
+
140
+ function lines(result ) {
141
+ if (result.home === null) return ['home: none (capture skipped)', 'capture: off'];
142
+ if (result.action === 'list') {
143
+ const out = [`capture ${result.capture}: ${result.entries.length} entr${result.entries.length === 1 ? 'y' : 'ies'} listed (new ${result.counts.new}, regressed ${result.counts.regressed}, noise ${result.counts.noise}, fixed ${result.counts.fixed}, imported ${result.counts.imported})`];
144
+ for (const e of result.entries) out.push(`${e.fp} ${e.status} x${e.count} ${e.command} ${e.kind}${e.message ? `: ${e.message}` : ''} (last seen ${e.lastSeen}, ${e.lastVersion})`);
145
+ if (result.unreadable.length) out.push(`unreadable failure files (not read): ${result.unreadable.join(', ')}`);
146
+ if (result.skippedLines) out.push(`${result.skippedLines} unreadable line${result.skippedLines === 1 ? '' : 's'} skipped`);
147
+ return [...out, ...result.next.map((n ) => `next: ${n}`)];
148
+ }
149
+ if (result.action === 'settle') return [`${result.fp} settled as ${result.as}${result.followup ? `, follow-up ${result.followup.id}` : ''}`];
150
+ return [`capture ${result.effective}${result.sentinel ? ' (switched off on this machine)' : ''}${result.env ? ` (DOFLOW_FAILURE_CAPTURE=${result.env})` : ''}`];
151
+ }
152
+
153
+ /**
154
+ * @param {Object} options
155
+ * @param {string} [options.action] absent when --action was not given
156
+ * @param {string} [options.cwd] where `settle --as imported` looks for the DoFlow repository
157
+ * @param {boolean} [options.all] `list`: every status, not just new and regressed
158
+ * @param {Object} [options.flags] fp, as, reason, set
159
+ */
160
+ function handleFailureCommand({ action, cwd, json = false, all = false, flags = {} } = {}) {
161
+ let result ;
162
+ try {
163
+ if (!ACTIONS.includes(action)) throw new FailureUsageError(`--action is required: one of ${ACTIONS.join(', ')}${action ? ` (got '${action}')` : ''}`);
164
+ if (action === 'list') result = listFailures({ all });
165
+ else if (action === 'settle') result = settleFailure({ fp: (flags ).fp, as: (flags ).as, reason: (flags ).reason, cwd });
166
+ else result = captureState({ set: (flags ).set });
167
+ } catch (error) {
168
+ if (error instanceof FailureUsageError || error instanceof StoreUnsafeError) return usageError('failure', (error ).message, json);
169
+ if (!(error instanceof StoreLockedError)) throw error;
170
+ result = refusal(action, 'store-locked', (error ).message);
171
+ }
172
+ if (json) console.log(JSON.stringify(result, null, 2));
173
+ else if (result.ok === false) console.log(printSafe(`${result.finding}: ${result.message}`));
174
+ else for (const line of lines(result)) console.log(printSafe(line));
175
+ return finishRuntime(result.ok === false ? 1 : 0);
176
+ }
177
+
178
+ module.exports = { handleFailureCommand, listFailures, settleFailure, captureState, importedStatement, ACTIONS };
@@ -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
+ /**
6
+ * Where DoFlow's own failures are kept on this machine, and whether capture is on (IC-011, IC-014).
7
+ * One folder per machine, shared by every project (DEC-007). Node and the bash writers resolve the
8
+ * home the same way, so a failure captured by either lands in the one list: `XDG_CONFIG_HOME` when it
9
+ * is set, non-empty and absolute, else `$HOME/.config` when `HOME` is set and absolute. Node reads the
10
+ * `HOME` variable and not `os.homedir()`, so both sides agree. A relative or missing base means
11
+ * there is no home, and capture is skipped.
12
+ */
13
+
14
+ const fs = require('node:fs');
15
+ const path = require('node:path');
16
+
17
+ const OFF_VALUES = new Set(['off', '0', 'false', 'no']);
18
+
19
+ /** @param {Object} [env] defaults to process.env
20
+ * @returns {string|null} the failure home, an absolute path, or null when none can be resolved */
21
+ function failureHome(env = process.env) {
22
+ const xdg = env.XDG_CONFIG_HOME;
23
+ if (typeof xdg === 'string' && xdg !== '') {
24
+ return path.isAbsolute(xdg) ? path.join(xdg, 'doflow', 'failures') : null;
25
+ }
26
+ const home = env.HOME;
27
+ if (typeof home === 'string' && path.isAbsolute(home)) return path.join(home, '.config', 'doflow', 'failures');
28
+ return null;
29
+ }
30
+
31
+ const sentinelPath = (home ) => path.join(home, 'off');
32
+ const eventsPath = (home ) => path.join(home, 'events.jsonl');
33
+
34
+ /** The value of `DOFLOW_FAILURE_CAPTURE` when it is set to anything, else null. */
35
+ function envSetting(env = process.env) {
36
+ const raw = env.DOFLOW_FAILURE_CAPTURE;
37
+ return typeof raw === 'string' && raw !== '' ? raw : null;
38
+ }
39
+
40
+ /**
41
+ * The capture switch (IC-014). The sentinel file wins over everything and the environment can only
42
+ * turn capture off, never on over a sentinel. One `stat`, no folder created.
43
+ * @param {string|null} home
44
+ * @param {Object} [env]
45
+ * @returns {{effective: 'on'|'off', sentinel: boolean, env: string|null}}
46
+ */
47
+ function captureSwitch(home , env = process.env) {
48
+ let sentinel = false;
49
+ if (home) {
50
+ try { sentinel = fs.statSync(sentinelPath(home)).isFile(); } catch { sentinel = false; }
51
+ }
52
+ const setting = envSetting(env);
53
+ const envOff = setting !== null && OFF_VALUES.has(setting.trim().toLowerCase());
54
+ return { effective: sentinel || envOff ? 'off' : 'on', sentinel, env: setting };
55
+ }
56
+
57
+ /** The hot-path check a writer makes: the environment first, then one `stat` of the sentinel. */
58
+ function captureIsOff(home , env = process.env) {
59
+ const setting = envSetting(env);
60
+ if (setting !== null && OFF_VALUES.has(setting.trim().toLowerCase())) return true;
61
+ try { return fs.statSync(sentinelPath(home)).isFile(); } catch { return false; }
62
+ }
63
+
64
+ module.exports = { failureHome, captureSwitch, captureIsOff, sentinelPath, eventsPath, envSetting };
@@ -0,0 +1,226 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The failure store reader and settlement writer (IC-013, IC-014, IC-015). The capture writers append
7
+ * IC-011 lines; this module folds them into entries when they are asked for, so a count is always
8
+ * derived from the retained events and only a settlement is stored separately (DEC-019).
9
+ *
10
+ * - `fp` is computed here, never by a writer: the first 16 hex characters of SHA-256 over
11
+ * `source \n command \n kind \n message \n frame` (an absent frame is the empty string). The
12
+ * version is not part of it, so the same bug in two releases is one entry.
13
+ * - The latest settlement per `fp` applies. A `fixed` entry that is seen again is `regressed`.
14
+ * - A line that does not parse, or is not an IC-011 line, is skipped and counted.
15
+ * - Reading never writes. Rotation is the `failure` verb's own step before it reads (see cli.js).
16
+ */
17
+
18
+ const fs = require('node:fs');
19
+ const path = require('node:path');
20
+ const crypto = require('node:crypto');
21
+ const { failureHome, captureSwitch, eventsPath } = require('./home.js');
22
+ const { printSafe } = require('../mask.js');
23
+
24
+ const ROTATED = /^events-\d{8}T\d{6}Z-\d+\.jsonl$/;
25
+ const SETTLEMENTS = 'settlements.jsonl';
26
+ const SOURCES = new Set(['cli', 'dispatcher', 'hook']);
27
+ const SETTLE_AS = ['noise', 'fixed', 'imported'];
28
+ const STATUSES = ['new', 'regressed', 'noise', 'fixed', 'imported'];
29
+ const SHOWN_BY_DEFAULT = new Set(['new', 'regressed']);
30
+ const PROJECTS_KEPT = 5;
31
+
32
+ const settlementsPath = (home ) => path.join(home, SETTLEMENTS);
33
+
34
+ /** @param {{source: string, command: string, kind: string, message: string, frame: string|null}} event */
35
+ function fingerprint(event ) {
36
+ const text = [event.source, event.command, event.kind, event.message, event.frame || ''].join('\n');
37
+ return crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
38
+ }
39
+
40
+ /** One IC-011 line, or null. Only the fields a fold reads are checked. */
41
+ function asEvent(value ) {
42
+ if (!value || typeof value !== 'object' || value.v !== 1) return null;
43
+ const text = (x ) => typeof x === 'string';
44
+ if (!text(value.at) || !SOURCES.has(value.source) || !text(value.command) || !text(value.kind)) return null;
45
+ return {
46
+ at: value.at,
47
+ source: value.source,
48
+ command: value.command,
49
+ harness: text(value.harness) ? value.harness : 'none',
50
+ version: text(value.version) ? value.version : 'unknown',
51
+ project: text(value.project) ? value.project : '',
52
+ kind: value.kind,
53
+ message: text(value.message) ? value.message : '',
54
+ frame: text(value.frame) ? value.frame : null,
55
+ exit: Number.isInteger(value.exit) ? value.exit : null,
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Parses a JSON-lines file into `{rows, skipped, unreadable}`. A missing file is empty. A file that is
61
+ * not a regular file (a FIFO would block the read, a device never ends, a symlink may leave the folder)
62
+ * or cannot be read is `unreadable`, and nothing is read from it.
63
+ */
64
+ function readLines(file , accept ) {
65
+ const c = fs.constants;
66
+ let text ;
67
+ let fd ;
68
+ try {
69
+ fd = fs.openSync(file, c.O_RDONLY | (c.O_NOFOLLOW || 0) | (c.O_NONBLOCK || 0));
70
+ if (!fs.fstatSync(fd).isFile()) return { rows: [], skipped: 0, unreadable: true };
71
+ text = fs.readFileSync(fd, 'utf8');
72
+ } catch (error ) {
73
+ return { rows: [], skipped: 0, unreadable: error.code !== 'ENOENT' };
74
+ } finally {
75
+ if (fd !== undefined) { try { fs.closeSync(fd); } catch { /* read already done */ } }
76
+ }
77
+ const rows = [];
78
+ let skipped = 0;
79
+ for (const line of text.split('\n')) {
80
+ if (line.trim() === '') continue;
81
+ let row = null;
82
+ try { row = accept(JSON.parse(line)); } catch { row = null; }
83
+ if (row) rows.push(row); else skipped += 1;
84
+ }
85
+ return { rows, skipped, unreadable: false };
86
+ }
87
+
88
+ /**
89
+ * Every retained event: rotated files oldest first, then the live file (IC-015).
90
+ * @param {string} home
91
+ * @returns {{events: Object[], skippedLines: number, unreadable: string[]}} `unreadable` names the files that were not read
92
+ */
93
+ function readEvents(home ) {
94
+ let rotated = [];
95
+ try { rotated = fs.readdirSync(home).filter((name ) => ROTATED.test(name)).sort(); } catch { /* no folder yet */ }
96
+ const events = [];
97
+ let skippedLines = 0;
98
+ const unreadable = [];
99
+ for (const file of [...rotated.map((name ) => path.join(home, name)), eventsPath(home)]) {
100
+ const read = readLines(file, asEvent);
101
+ events.push(...read.rows);
102
+ skippedLines += read.skipped;
103
+ if (read.unreadable) unreadable.push(path.basename(file));
104
+ }
105
+ return { events, skippedLines, unreadable };
106
+ }
107
+
108
+ function asSettlement(value ) {
109
+ if (!value || typeof value !== 'object' || value.v !== 1) return null;
110
+ if (typeof value.at !== 'string' || !/^[0-9a-f]{16}$/.test(value.fp) || !SETTLE_AS.includes(value.as)) return null;
111
+ return { at: value.at, fp: value.fp, as: value.as, reason: typeof value.reason === 'string' ? value.reason : null, followup: typeof value.followup === 'string' ? value.followup : null };
112
+ }
113
+
114
+ /** @returns {{settlements: Object[], skippedLines: number, unreadable: string[]}} in file order, which is time order */
115
+ function readSettlements(home ) {
116
+ const read = readLines(settlementsPath(home), asSettlement);
117
+ return { settlements: read.rows, skippedLines: read.skipped, unreadable: read.unreadable ? [SETTLEMENTS] : [] };
118
+ }
119
+
120
+ /** The IC-013 status of an entry from its latest settlement and the events seen after it. */
121
+ function statusOf(settlement , sinceSettlement ) {
122
+ if (!settlement) return 'new';
123
+ if (settlement.as === 'fixed') return sinceSettlement > 0 ? 'regressed' : 'fixed';
124
+ return settlement.as;
125
+ }
126
+
127
+ /**
128
+ * Folds events and settlements into IC-013 entries, newest `lastSeen` first.
129
+ * @param {Object[]} events
130
+ * @param {Object[]} settlements
131
+ * @returns {Object[]}
132
+ */
133
+ function foldEntries(events , settlements ) {
134
+ const latest = new Map();
135
+ for (const s of settlements) latest.set(s.fp, s);
136
+ const groups = new Map();
137
+ for (const event of events) {
138
+ const fp = fingerprint(event);
139
+ if (!groups.has(fp)) groups.set(fp, []);
140
+ groups.get(fp).push(event);
141
+ }
142
+ const entries = [];
143
+ for (const [fp, group] of groups) {
144
+ const ordered = (group ).map((event , index ) => ({ event, index })).sort((a , b ) => (a.event.at < b.event.at ? -1 : a.event.at > b.event.at ? 1 : a.index - b.index)).map((x ) => x.event);
145
+ const first = ordered[0];
146
+ const last = ordered[ordered.length - 1];
147
+ const settlement = latest.get(fp) || null;
148
+ const since = settlement ? ordered.filter((e ) => e.at > settlement.at).length : ordered.length;
149
+ const projects = [];
150
+ for (let i = ordered.length - 1; i >= 0 && projects.length < PROJECTS_KEPT; i--) {
151
+ if (ordered[i].project && !projects.includes(ordered[i].project)) projects.push(ordered[i].project);
152
+ }
153
+ // The file is machine-wide and may hold lines someone else wrote: the fingerprint is taken over the
154
+ // stored text, and every string an entry shows is made print-safe, so a listing and --json never carry
155
+ // a terminal escape.
156
+ entries.push(printSafe({
157
+ fp,
158
+ status: statusOf(settlement, since),
159
+ count: ordered.length,
160
+ sinceSettlement: since,
161
+ firstSeen: first.at,
162
+ lastSeen: last.at,
163
+ firstVersion: first.version,
164
+ lastVersion: last.version,
165
+ source: first.source,
166
+ command: first.command,
167
+ kind: first.kind,
168
+ message: first.message,
169
+ frame: first.frame,
170
+ projects,
171
+ harnesses: [...new Set(ordered.map((e ) => e.harness))].sort(),
172
+ exitCodes: [...new Set(ordered.map((e ) => e.exit).filter((x ) => x !== null))].sort((a , b ) => a - b),
173
+ settlement: settlement && { as: settlement.as, at: settlement.at, reason: settlement.reason, followup: settlement.followup },
174
+ }));
175
+ }
176
+ return entries.sort((a , b ) => (a.lastSeen < b.lastSeen ? 1 : a.lastSeen > b.lastSeen ? -1 : a.fp < b.fp ? -1 : 1));
177
+ }
178
+
179
+ /** The shape a listing shows (IC-019). */
180
+ function listedEntry(entry ) {
181
+ return {
182
+ fp: entry.fp, status: entry.status, count: entry.count, command: entry.command, kind: entry.kind,
183
+ message: entry.message, lastSeen: entry.lastSeen, lastVersion: entry.lastVersion,
184
+ };
185
+ }
186
+
187
+ /**
188
+ * Reads the store and folds it. With no resolvable failure home nothing is read.
189
+ * @param {Object} [options]
190
+ * @param {Object} [options.env] defaults to process.env
191
+ * @returns {{home: string|null, capture: 'on'|'off', entries: Object[], counts: Object, skippedLines: number, unreadable: string[]}}
192
+ */
193
+ function loadEntries({ env = process.env } = {}) {
194
+ const home = failureHome(env);
195
+ const counts = Object.fromEntries(STATUSES.map((status) => [status, 0]));
196
+ // With no failure home nothing is captured, whatever the switch says.
197
+ if (!home) return { home: null, capture: 'off', entries: [], counts, skippedLines: 0, unreadable: [] };
198
+ const { events, skippedLines: eventSkips, unreadable: eventFiles } = readEvents(home);
199
+ const { settlements, skippedLines: settlementSkips, unreadable: settlementFiles } = readSettlements(home);
200
+ const entries = foldEntries(events, settlements);
201
+ for (const entry of entries) counts[entry.status] += 1;
202
+ return { home, capture: captureSwitch(home, env).effective, entries, counts, skippedLines: eventSkips + settlementSkips, unreadable: [...eventFiles, ...settlementFiles] };
203
+ }
204
+
205
+ /**
206
+ * Appends one settlement line (IC-013). Only `failure --action settle` calls this.
207
+ * @param {string} home
208
+ * @param {{fp: string, as: string, reason?: string|null, followup?: string|null, now?: Date}} settlement
209
+ */
210
+ function appendSettlement(home , { fp, as, reason = null, followup = null, now = new Date() } ) {
211
+ fs.mkdirSync(home, { recursive: true, mode: 0o700 });
212
+ const line = JSON.stringify({ v: 1, at: now.toISOString(), fp, as, reason, followup });
213
+ fs.appendFileSync(settlementsPath(home), `${line}\n`, { flag: 'a', mode: 0o600 });
214
+ }
215
+
216
+ const DOFLOW_PACKAGE = '@khoavu882/doflow';
217
+
218
+ /** Whether `root` is the DoFlow repository itself: its `package.json` is named `@khoavu882/doflow`. */
219
+ function isDoflowRepo(root ) {
220
+ try { return JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')).name === DOFLOW_PACKAGE; } catch { return false; }
221
+ }
222
+
223
+ module.exports = {
224
+ isDoflowRepo, fingerprint, readEvents, readSettlements, foldEntries, listedEntry, loadEntries, appendSettlement,
225
+ settlementsPath, SETTLE_AS, STATUSES, SHOWN_BY_DEFAULT,
226
+ };
@@ -0,0 +1,79 @@
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 { execFileSync } = require('node:child_process');
7
+ const { resolveBashHelper } = require('../helper/bash-helper.js');
8
+
9
+ // `resolveActiveFeature` was moved here from `scaffold/generate.js` (which had it from
10
+ // bin/doflow.js) so the scaffold and the decision register ask the same resolver. The root comes
11
+ // from src/helper/repo-root.js rather than from counting this file's own depth: this code has
12
+ // already moved twice, and a miscounted expression would keep resolving, to the wrong directory.
13
+
14
+ /** The one resolver: beside this module in a checkout, a project `node_modules/` and a global npm install,
15
+ * else the copy projected next to an installed runtime (an installed runtime carries no `core/shared`). */
16
+ const PATHS_HELPER = resolveBashHelper('do-paths.sh');
17
+
18
+ /**
19
+ * Which feature is active, answered by `do-paths.sh` rather than by walking `agent-docs/` here.
20
+ *
21
+ * Every other consumer of "the active feature" — every chain skill, the prerequisite gate, the
22
+ * artifact validator — asks this script. A second implementation would disagree with them the
23
+ * first time branch naming, the non-git directory-scan fallback or `--slug` disambiguation came
24
+ * up, and it would disagree silently, because a scaffold generated for the wrong feature still
25
+ * looks like a scaffold.
26
+ *
27
+ * @param {Object} options
28
+ * @param {string} options.projectRoot working directory the resolution is relative to
29
+ * @param {string|null} [options.slug] explicit feature override, passed straight through
30
+ * @returns {{repoRoot:string, featureDir:string, paths:Object}|{error:string, message:string}}
31
+ * `paths` is the resolver's own parsed JSON (repo-root-relative artifact paths and has_* flags),
32
+ * so a caller needs no second resolver call.
33
+ */
34
+ function resolveActiveFeature({ projectRoot, slug = null } ) {
35
+ const helper = resolveBashHelper('do-paths.sh');
36
+ if (!helper) {
37
+ return { error: 'resolver-missing', message: 'the feature resolver (do-paths.sh) is missing from this install' };
38
+ }
39
+ const args = [helper, '--json', '--require', 'feature'];
40
+ if (slug) args.push(`--slug=${slug}`);
41
+
42
+ let stdout ;
43
+ try {
44
+ stdout = execFileSync('bash', args, { cwd: projectRoot, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
45
+ } catch (error ) {
46
+ // Exit 2 is the resolver's one non-zero path (no active feature, or an ambiguous one) and it
47
+ // still prints its error object on stdout — that object carries the hint the user needs, so
48
+ // it is read rather than replaced with a generic message. Anything else (no bash, NFR-003)
49
+ // has no stdout to read and reports the spawn failure instead.
50
+ stdout = typeof error.stdout === 'string' ? error.stdout : '';
51
+ if (!stdout.trim()) {
52
+ const detail = error.code === 'ENOENT'
53
+ ? 'bash is required to resolve the active feature and was not found on PATH'
54
+ : (error.stderr || error.message || '').toString().trim();
55
+ return { error: 'resolver-failed', message: `could not run the feature resolver: ${detail}` };
56
+ }
57
+ }
58
+
59
+ let paths ;
60
+ try {
61
+ paths = JSON.parse(stdout);
62
+ } catch {
63
+ return { error: 'resolver-unparseable', message: `the feature resolver returned output that is not JSON: ${stdout.trim().slice(0, 200)}` };
64
+ }
65
+
66
+ if (paths.error) {
67
+ const hint = paths.hint ? ` — ${paths.hint}` : '';
68
+ const candidates = Array.isArray(paths.candidate_slugs) && paths.candidate_slugs.length
69
+ ? ` (candidates: ${paths.candidate_slugs.join(', ')})`
70
+ : '';
71
+ return { error: paths.error, message: `${paths.error}${candidates}${hint}` };
72
+ }
73
+ if (!paths.repo_root || !paths.feature_dir) {
74
+ return { error: 'no-active-feature', message: 'the resolver named no active feature directory to scaffold' };
75
+ }
76
+ return { repoRoot: paths.repo_root, featureDir: path.resolve(paths.repo_root, paths.feature_dir), paths };
77
+ }
78
+
79
+ module.exports = { resolveActiveFeature, PATHS_HELPER };