@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,626 @@
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 os = require('node:os');
7
+ const path = require('node:path');
8
+ const {
9
+ SESSION_GAP_MS,
10
+ groupSessions,
11
+ buildTrace,
12
+ buildStats,
13
+ buildDiscover,
14
+ PROVIDER,
15
+ } = require('./views.ts');
16
+ const {
17
+ shortTime,
18
+ formatMs,
19
+ finish,
20
+ integrityNote,
21
+ printEmptyLedgerNotice,
22
+ DISCOVER_MARK,
23
+ } = require('./render.ts');
24
+
25
+ /**
26
+ * Task-run telemetry — the JavaScript port of
27
+ * `core/shared/scripts/doflow/evaluation/telemetry.py` (plan task B.3, component C9).
28
+ *
29
+ * Records a task's span: the events that happened inside it, how long it took, how many tokens it
30
+ * consumed. Ported from observed behaviour (plan decision D3): the event record shape, the trace
31
+ * key names, the six-decimal cost rounding and the default per-token rates are unchanged.
32
+ *
33
+ * Plan task C.3 added the second half of this file: the date-partitioned run ledger (FR-011) and
34
+ * its three inspection views (FR-012). `TaskRunTelemetry` captures one *task's* span in memory;
35
+ * `RunLedger` is the on-disk record of *dispatched runs*, written by `doflow-run` and by any
36
+ * runtime call that names the capability it used.
37
+ *
38
+ * Three defects are fixed rather than reproduced, each explained at its site:
39
+ * 1. Duration came from `time.time()`, a wall clock that an NTP step or a DST change can move
40
+ * backwards mid-run, producing negative or inflated durations.
41
+ * 2. `estimated_cost_usd` was computed from two hard-coded rates regardless of which model the
42
+ * trace named, so the number was presented as measured cost for models it did not price.
43
+ * 3. `export_trace` returned the live `events` array, so a caller mutating the exported trace
44
+ * silently rewrote the recorder's history.
45
+ */
46
+
47
+ /**
48
+ * The Python's literal rates, kept as the default so an existing caller sees the same number.
49
+ * They price one specific model; a trace naming a different one gets a cost only if the caller
50
+ * supplies its rates, because inventing a price for an unpriced model is inventing a metric.
51
+ */
52
+ const DEFAULT_TOKEN_RATES: Readonly<{ inputPerToken: number; outputPerToken: number }> = Object.freeze({
53
+ inputPerToken: 0.000003,
54
+ outputPerToken: 0.000015,
55
+ });
56
+
57
+ const COST_DECIMALS = 6;
58
+
59
+ /**
60
+ * @param {number} value
61
+ * @returns {number} rounded to six decimals, matching the Python's `round(x, 6)`
62
+ */
63
+ function roundCost(value: number): number {
64
+ const factor = 10 ** COST_DECIMALS;
65
+ return Math.round(value * factor) / factor;
66
+ }
67
+
68
+ class TaskRunTelemetry {
69
+ taskId: string;
70
+ agent: string;
71
+ model: string;
72
+ rates: { inputPerToken: number; outputPerToken: number } | null;
73
+ events: any[];
74
+ clock: () => number;
75
+ startedAtMs: number;
76
+ startedAt: string;
77
+
78
+ /**
79
+ * @param {string} taskId
80
+ * @param {Object|string} [options] a string is accepted for parity with the Python's positional
81
+ * `agent` argument
82
+ * @param {string} [options.agent='codex']
83
+ * @param {string} [options.model='default']
84
+ * @param {{inputPerToken: number, outputPerToken: number}|null} [options.rates]
85
+ * @param {Function} [options.clock] injection seam for tests; returns elapsed milliseconds
86
+ */
87
+ constructor(taskId: string, options: any = {}) {
88
+ const opts = typeof options === 'string' ? { agent: options } : options;
89
+ this.taskId = taskId;
90
+ this.agent = opts.agent || 'codex';
91
+ this.model = opts.model || 'default';
92
+ this.rates = opts.rates === undefined ? DEFAULT_TOKEN_RATES : opts.rates;
93
+ this.events = [];
94
+
95
+ // Defect fix (1): a monotonic source. `Date.now()` (like Python's `time.time()`) can step
96
+ // backwards, and a negative duration in a benchmark record is worse than no duration.
97
+ this.clock = opts.clock || (() => Number(process.hrtime.bigint() / 1000000n));
98
+ this.startedAtMs = this.clock();
99
+ this.startedAt = new Date().toISOString();
100
+ }
101
+
102
+ /**
103
+ * Appends a structured event to the span.
104
+ * @param {string} eventType
105
+ * @param {Object} [details={}]
106
+ * @returns {Object} the recorded event
107
+ */
108
+ recordEvent(eventType: string, details: any = {}): any {
109
+ const event = {
110
+ timestamp: new Date().toISOString(),
111
+ type: eventType,
112
+ details,
113
+ };
114
+ this.events.push(event);
115
+ return event;
116
+ }
117
+
118
+ /**
119
+ * Milliseconds elapsed since the recorder was constructed.
120
+ * @returns {number}
121
+ */
122
+ elapsedMs(): number {
123
+ return Math.max(0, this.clock() - this.startedAtMs);
124
+ }
125
+
126
+ /**
127
+ * Compiles the run's trace record.
128
+ * @param {boolean} success
129
+ * @param {number} [inputTokens=0]
130
+ * @param {number} [outputTokens=0]
131
+ * @returns {Object} trace record
132
+ */
133
+ exportTrace(success: boolean, inputTokens: number = 0, outputTokens: number = 0): any {
134
+ // Defect fix (2): cost is only reported when rates are known for this run. The default rates
135
+ // reproduce the Python's number exactly; a caller that clears them (`rates: null`) gets `null`
136
+ // rather than a confident figure derived from another model's price list.
137
+ const estimatedCostUsd = this.rates
138
+ ? roundCost(inputTokens * this.rates.inputPerToken + outputTokens * this.rates.outputPerToken)
139
+ : null;
140
+
141
+ return {
142
+ taskId: this.taskId,
143
+ agent: this.agent,
144
+ model: this.model,
145
+ success: Boolean(success),
146
+ startedAt: this.startedAt,
147
+ durationMs: this.elapsedMs(),
148
+ inputTokens,
149
+ outputTokens,
150
+ estimatedCostUsd,
151
+ eventCount: this.events.length,
152
+ // Defect fix (3): a copy. The Python handed out its internal list, so an exported trace and
153
+ // the recorder aliased the same array.
154
+ events: this.events.map((e) => ({ ...e })),
155
+ };
156
+ }
157
+ }
158
+
159
+ // ---------------------------------------------------------------------------------- run ledger
160
+ //
161
+ // The ledger is one JSONL file per UTC date under `<config>/state/runs/`, appended to by
162
+ // `core/shared/scripts/doflow/bin/doflow-run` for every verb it dispatches. This module owns the
163
+ // reading side plus the writing side for capability-level events the shell dispatcher cannot see
164
+ // (which capability was requested, which provider answered, how much it returned).
165
+ //
166
+ // Two rules constrain everything below.
167
+ // NFR-004 — records are metadata. The dispatcher deliberately records an argument *count* and
168
+ // never an argument value; `sanitizeRunEvent` keeps that discipline for anything written here by
169
+ // allowing a closed set of fields, validating every string against a token pattern that cannot
170
+ // express a path, a URL, a quote or a space, and dropping everything else by name.
171
+ // FR-011 note — tracing must never change what the traced command returned. Every function here
172
+ // answers rather than throws: a missing directory, an unreadable file and a corrupt line are all
173
+ // normal states of a best-effort ledger.
174
+
175
+ const RUNS_DIRNAME = 'runs';
176
+ const LEDGER_FILE_RE = /^\d{4}-\d{2}-\d{2}\.jsonl$/;
177
+
178
+ /**
179
+ * The complete writable field set. Anything not listed here never reaches the ledger.
180
+ * `wire` is the on-disk key: snake_case, matching the five fields `doflow-run` already writes, so
181
+ * one file never mixes two naming conventions.
182
+ */
183
+ const RUN_FIELDS: Readonly<Array<{ key: string; wire: string; type: string }>> = Object.freeze([
184
+ { key: 'verb', wire: 'verb', type: 'token' },
185
+ { key: 'kind', wire: 'kind', type: 'token' },
186
+ { key: 'capability', wire: 'capability', type: 'token' },
187
+ { key: 'provider', wire: 'provider', type: 'token' },
188
+ { key: 'outcome', wire: 'outcome', type: 'token' },
189
+ { key: 'stage', wire: 'stage', type: 'token' },
190
+ { key: 'workflow', wire: 'workflow', type: 'token' },
191
+ { key: 'taskClass', wire: 'task_class', type: 'token' },
192
+ { key: 'session', wire: 'session', type: 'token' },
193
+ { key: 'exitCode', wire: 'exit_code', type: 'int' },
194
+ { key: 'durationMs', wire: 'duration_ms', type: 'uint' },
195
+ { key: 'argCount', wire: 'arg_count', type: 'uint' },
196
+ { key: 'resultCount', wire: 'result_count', type: 'uint' },
197
+ { key: 'inputBytes', wire: 'input_bytes', type: 'uint' },
198
+ { key: 'outputBytes', wire: 'output_bytes', type: 'uint' },
199
+ { key: 'retryCount', wire: 'retry_count', type: 'uint' },
200
+ ]);
201
+
202
+ /**
203
+ * Identifiers only. No space, no quote, no slash, no dot-dot — a capability id (`code.exact-search`)
204
+ * and a provider id (`semble.search`) fit; a file path, a query string, a command line, a URL and
205
+ * anything with a secret's usual punctuation do not. This is the mechanical half of NFR-004: a
206
+ * caller cannot smuggle content through a field that only accepts an identifier.
207
+ */
208
+ const SAFE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
209
+
210
+ /** Timestamps are written by us or by the dispatcher's `date -u`; anything else is not a timestamp. */
211
+ const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z?$/;
212
+
213
+ /**
214
+ * The neutral-state runs directory for an install root (`<root>/.doflow`).
215
+ * @param {string} configDir an install root, e.g. `<project>/.doflow` or `~/.doflow`
216
+ * @returns {string}
217
+ */
218
+ function runsDir(configDir: string): string {
219
+ return path.join(configDir, 'state', RUNS_DIRNAME);
220
+ }
221
+
222
+ /**
223
+ * Mirrors the dispatcher's own `resolve_config_dir` (design §4.1) so a view reads the same ledger
224
+ * the dispatcher wrote. Resolving from `process.cwd()` alone would miss every run recorded by a
225
+ * dispatcher invoked from a subdirectory of the project, and report an empty ledger for a project
226
+ * that has been traced all day.
227
+ * @param {Object} [options]
228
+ * @param {string} [options.start] directory to walk up from (default: cwd)
229
+ * @param {boolean} [options.global=false] force the home-scoped install
230
+ * @param {Object} [options.env=process.env]
231
+ * @param {string} [options.homeDir]
232
+ * @param {Object} [options.fsImpl=fs]
233
+ * @param {string} [options.containing] accept a project `.doflow` only when it holds this
234
+ * subdirectory, so a project with state but no guidance tree falls through to the home install
235
+ * @returns {{dir: string, configDir: string, origin: string, searched: string[]}}
236
+ */
237
+ function resolveRunsLocation({ start, global: forceGlobal = false, env = process.env, homeDir = os.homedir(), fsImpl = fs, containing = '' }: any = {}): any {
238
+ const searched: string[] = [];
239
+ const home = path.resolve(homeDir || '.');
240
+ if (forceGlobal) {
241
+ const configDir = path.join(home, '.doflow');
242
+ return { dir: runsDir(configDir), configDir, origin: 'global', searched: [configDir] };
243
+ }
244
+ if (env.DOFLOW_CONFIG_DIR) {
245
+ const configDir = path.resolve(env.DOFLOW_CONFIG_DIR);
246
+ return { dir: runsDir(configDir), configDir, origin: 'DOFLOW_CONFIG_DIR', searched: [configDir] };
247
+ }
248
+ let dir = path.resolve(start || process.cwd());
249
+ for (;;) {
250
+ const candidate = path.join(dir, '.doflow');
251
+ searched.push(candidate);
252
+ let found = false;
253
+ try {
254
+ found = fsImpl.statSync(path.join(candidate, containing)).isDirectory();
255
+ } catch {
256
+ found = false; // Not readable is the same as not there for a lookup that must not throw.
257
+ }
258
+ if (found) return { dir: runsDir(candidate), configDir: candidate, origin: 'project', searched };
259
+ const parent = path.dirname(dir);
260
+ if (parent === dir) break;
261
+ dir = parent;
262
+ }
263
+ const configDir = path.join(home, '.doflow');
264
+ searched.push(configDir);
265
+ return { dir: runsDir(configDir), configDir, origin: 'global-fallback', searched };
266
+ }
267
+
268
+ /**
269
+ * Reduces an arbitrary object to a writable ledger record.
270
+ * @param {Object} input
271
+ * @param {Object} [options]
272
+ * @param {Date} [options.now]
273
+ * @returns {{record: Object|null, dropped: string[], reason?: string}} `dropped` names the rejected
274
+ * fields and never carries their values — a rejection report that echoed the value would leak
275
+ * exactly the content the rejection exists to keep out.
276
+ */
277
+ function sanitizeRunEvent(input: any, { now = new Date() } = {}): any {
278
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
279
+ return { record: null, dropped: [], reason: 'event must be an object' };
280
+ }
281
+ const byKey = new Map(RUN_FIELDS.map((field) => [field.key, field]));
282
+ const byWire = new Map(RUN_FIELDS.map((field) => [field.wire, field]));
283
+ const record: any = {};
284
+ const dropped: string[] = [];
285
+
286
+ const timestamp = typeof input.timestamp === 'string' && ISO_TIMESTAMP.test(input.timestamp)
287
+ ? input.timestamp
288
+ : now.toISOString();
289
+ if (input.timestamp !== undefined && timestamp !== input.timestamp) dropped.push('timestamp');
290
+ record.timestamp = timestamp;
291
+
292
+ for (const [name, value] of Object.entries(input)) {
293
+ if (name === 'timestamp') continue;
294
+ const field = byKey.get(name) || byWire.get(name);
295
+ if (!field) { dropped.push(name); continue; }
296
+ if (value === undefined || value === null) continue;
297
+ if (field.type === 'token') {
298
+ if (typeof value !== 'string' || !SAFE_TOKEN.test(value)) { dropped.push(name); continue; }
299
+ record[field.wire] = value;
300
+ } else {
301
+ const numeric = typeof value === 'number' ? value : Number(value);
302
+ if (!Number.isFinite(numeric)) { dropped.push(name); continue; }
303
+ const truncated = Math.trunc(numeric);
304
+ if (field.type === 'uint' && truncated < 0) { dropped.push(name); continue; }
305
+ record[field.wire] = truncated;
306
+ }
307
+ }
308
+
309
+ // A record with no verb cannot be attributed to anything, and every view groups by verb.
310
+ if (!record.verb) return { record: null, dropped, reason: 'event has no usable verb' };
311
+ return { record, dropped };
312
+ }
313
+
314
+ /**
315
+ * Reads one on-disk line into the canonical camelCase shape the views work in. Accepts both the
316
+ * dispatcher's snake_case keys and the camelCase a JavaScript caller would write.
317
+ * @param {*} raw parsed JSON value
318
+ * @returns {Object|null} null when the line is not a usable record
319
+ */
320
+ function normalizeRunRecord(raw: any): any | null {
321
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return null;
322
+ const out: any = { timestamp: typeof raw.timestamp === 'string' ? raw.timestamp : null };
323
+ for (const field of RUN_FIELDS) {
324
+ const value = raw[field.wire] !== undefined ? raw[field.wire] : raw[field.key];
325
+ if (value === undefined || value === null) continue;
326
+ if (field.type === 'token') {
327
+ if (typeof value === 'string' && value !== '') out[field.key] = value;
328
+ } else {
329
+ const numeric = typeof value === 'number' ? value : Number(value);
330
+ if (Number.isFinite(numeric)) out[field.key] = Math.trunc(numeric);
331
+ }
332
+ }
333
+ if (!out.verb) return null;
334
+ const parsed = out.timestamp ? Date.parse(out.timestamp) : NaN;
335
+ out.at = Number.isNaN(parsed) ? null : parsed;
336
+ return out;
337
+ }
338
+
339
+ class RunLedger {
340
+ fsImpl: any;
341
+ clock: () => Date;
342
+ dir: string;
343
+
344
+ /**
345
+ * @param {Object} [options]
346
+ * @param {string} [options.dir] the runs directory; resolved from `configDir`/cwd when omitted
347
+ * @param {string} [options.configDir] an install root (`<project>/.doflow`)
348
+ * @param {Object} [options.fsImpl=fs]
349
+ * @param {Function} [options.clock] returns the current Date; injection seam for tests
350
+ */
351
+ constructor({ dir, configDir, fsImpl = fs, clock = () => new Date() }: any = {}) {
352
+ this.fsImpl = fsImpl;
353
+ this.clock = clock;
354
+ if (dir) this.dir = path.resolve(dir);
355
+ else if (configDir) this.dir = runsDir(path.resolve(configDir));
356
+ else this.dir = resolveRunsLocation({ fsImpl }).dir;
357
+ }
358
+
359
+ /**
360
+ * Appends one metadata record. Always succeeds: an unwritable state directory degrades
361
+ * observability, never the run being observed.
362
+ * @param {Object} event
363
+ * @returns {{written: boolean, file?: string, record?: Object, dropped: string[], reason?: string}}
364
+ */
365
+ append(event: any): any {
366
+ const { record, dropped, reason } = sanitizeRunEvent(event, { now: this.clock() });
367
+ if (!record) return { written: false, dropped, reason };
368
+ try {
369
+ this.fsImpl.mkdirSync(this.dir, { recursive: true });
370
+ const file = path.join(this.dir, `${record.timestamp.slice(0, 10)}.jsonl`);
371
+ this.fsImpl.appendFileSync(file, `${JSON.stringify(record)}\n`, 'utf8');
372
+ return { written: true, file, record, dropped };
373
+ } catch (error: any) {
374
+ return { written: false, record, dropped, reason: error.message };
375
+ }
376
+ }
377
+
378
+ /**
379
+ * Reads the ledger. A missing directory, an unreadable partition and a corrupt line are reported
380
+ * as counts on the result rather than raised — but they *are* reported, so a view can say "3
381
+ * lines were unreadable" instead of quietly analysing a truncated history.
382
+ * @param {Object} [options]
383
+ * @param {number} [options.days] keep only partitions dated within the last N calendar days
384
+ * @returns {{dir: string, exists: boolean, files: string[], records: Object[], malformedLines: number, unreadableFiles: string[], days: number|null}}
385
+ */
386
+ read({ days = null }: any = {}): any {
387
+ const result: any = {
388
+ dir: this.dir,
389
+ exists: false,
390
+ files: [],
391
+ records: [],
392
+ malformedLines: 0,
393
+ unreadableFiles: [],
394
+ days: days && days > 0 ? days : null,
395
+ };
396
+
397
+ let entries: string[];
398
+ try {
399
+ entries = this.fsImpl.readdirSync(this.dir);
400
+ } catch {
401
+ return result; // No ledger yet is the normal state of a fresh install, not a failure.
402
+ }
403
+ result.exists = true;
404
+
405
+ let names: string[] = entries.filter((name) => LEDGER_FILE_RE.test(name)).sort();
406
+ if (result.days) {
407
+ // Calendar days, not "the last N files": a quiet week must not silently widen the window.
408
+ const cutoff = new Date(this.clock().getTime() - (result.days - 1) * 24 * 60 * 60 * 1000)
409
+ .toISOString().slice(0, 10);
410
+ names = names.filter((name) => name.slice(0, 10) >= cutoff);
411
+ }
412
+ result.files = names;
413
+
414
+ for (const name of names) {
415
+ let text: string;
416
+ try {
417
+ text = this.fsImpl.readFileSync(path.join(this.dir, name), 'utf8');
418
+ } catch {
419
+ result.unreadableFiles.push(name);
420
+ continue;
421
+ }
422
+ for (const line of text.split('\n')) {
423
+ if (line.trim() === '') continue;
424
+ let parsed: any;
425
+ try {
426
+ parsed = JSON.parse(line);
427
+ } catch {
428
+ // A partially written line is expected: the dispatcher appends from concurrent shells.
429
+ result.malformedLines += 1;
430
+ continue;
431
+ }
432
+ const record = normalizeRunRecord(parsed);
433
+ if (record) result.records.push(record);
434
+ else result.malformedLines += 1;
435
+ }
436
+ }
437
+
438
+ // Undated records sort last rather than being dropped: they still count toward usage.
439
+ result.records.sort((a: any, b: any) => (a.at ?? Number.MAX_SAFE_INTEGER) - (b.at ?? Number.MAX_SAFE_INTEGER));
440
+ return result;
441
+ }
442
+ }
443
+
444
+ // buildTrace, buildStats and buildDiscover (the three views, plus the helpers they share) now
445
+ // live in ./trace-views.js; shortTime/formatMs/finish/integrityNote/printEmptyLedgerNotice (CLI
446
+ // rendering) now live in ./trace-render.js. Both are required at the top of this file and used,
447
+ // unchanged, by the handle*Command functions below.
448
+
449
+ /**
450
+ * Handles `doflow trace` — the trajectory of the current or most recent workflow.
451
+ * @param {Object} options
452
+ * @param {boolean} [options.json=false]
453
+ * @param {number} [options.days]
454
+ * @param {boolean} [options.global=false]
455
+ * @param {string} [options.projectRoot]
456
+ * @returns {number} process exit code
457
+ */
458
+ function handleTraceCommand({ json = false, days = null, global: useGlobal = false, projectRoot }: any = {}): number {
459
+ const location = resolveRunsLocation({ start: projectRoot, global: useGlobal });
460
+ const view = buildTrace(new RunLedger({ dir: location.dir }).read({ days }));
461
+ view.origin = location.origin;
462
+
463
+ if (json) {
464
+ console.log(JSON.stringify(view, null, 2));
465
+ return finish(0);
466
+ }
467
+
468
+ console.log('\nDoFlow Run Trace — most recent workflow');
469
+ console.log('═'.repeat(78));
470
+ if (!view.trajectory) {
471
+ printEmptyLedgerNotice(view.ledger, view.reason);
472
+ console.log('═'.repeat(78) + '\n');
473
+ return finish(0);
474
+ }
475
+ const t = view.trajectory;
476
+ console.log(`Started: ${t.startedAt}`);
477
+ console.log(`Ended: ${t.endedAt}`);
478
+ console.log(`Steps: ${t.stepCount} (${t.failedSteps} failed) · dispatched ${formatMs(t.dispatchedMs)} of ${formatMs(t.wallDurationMs)} wall`);
479
+ console.log(`Boundary: ${view.grouping === 'session-field' ? 'recorded session id' : `inferred from a ${view.gapMinutes}-minute idle gap`}${view.priorSessions ? ` · ${view.priorSessions} earlier session(s) in this window` : ''}`);
480
+ console.log('─'.repeat(78));
481
+ console.log('#'.padEnd(5) + 'TIME'.padEnd(16) + 'VERB'.padEnd(18) + 'OUTCOME'.padEnd(10) + 'TOOK'.padEnd(9) + 'CAPABILITY');
482
+ for (const step of t.steps) {
483
+ console.log(
484
+ String(step.n).padEnd(5) +
485
+ shortTime(step.timestamp).padEnd(16) +
486
+ step.verb.padEnd(18) +
487
+ String(step.outcome ?? 'unknown').padEnd(10) +
488
+ formatMs(step.durationMs).padEnd(9) +
489
+ (step.capability ?? (step.provider ?? '-'))
490
+ );
491
+ }
492
+ const note = integrityNote(view.ledger);
493
+ if (note) {
494
+ console.log('─'.repeat(78));
495
+ console.log(note);
496
+ }
497
+ console.log('═'.repeat(78) + '\n');
498
+ return finish(0);
499
+ }
500
+
501
+ /**
502
+ * Handles `doflow stats` — aggregate local usage.
503
+ * @param {Object} options
504
+ * @returns {number} process exit code
505
+ */
506
+ function handleStatsCommand({ json = false, days = null, global: useGlobal = false, projectRoot }: any = {}): number {
507
+ const location = resolveRunsLocation({ start: projectRoot, global: useGlobal });
508
+ const view = buildStats(new RunLedger({ dir: location.dir }).read({ days }));
509
+ view.origin = location.origin;
510
+
511
+ if (json) {
512
+ console.log(JSON.stringify(view, null, 2));
513
+ return finish(0);
514
+ }
515
+
516
+ console.log('\nDoFlow Run Statistics');
517
+ console.log('═'.repeat(78));
518
+ if (!view.recordCount) {
519
+ printEmptyLedgerNotice(view.ledger, view.ledger.exists ? 'the ledger holds no readable run records' : 'no ledger has been written yet at this location');
520
+ console.log('═'.repeat(78) + '\n');
521
+ return finish(0);
522
+ }
523
+ console.log(`Records: ${view.recordCount} across ${view.span.activeDays} day partition(s)${view.ledger.windowDays ? ` (last ${view.ledger.windowDays} days)` : ''}`);
524
+ console.log(`Window: ${view.span.first} → ${view.span.last}`);
525
+ console.log(`Outcomes: ${view.outcomes.succeeded} ok · ${view.outcomes.failed} failed · ${view.outcomes.unrecorded} exit code not recorded`);
526
+ console.log(`Duration: p50 ${formatMs(view.durations.p50Ms)} · p95 ${formatMs(view.durations.p95Ms)} · max ${formatMs(view.durations.maxMs)} (${view.durations.measured} measured)`);
527
+ console.log('─'.repeat(78));
528
+ console.log('VERB'.padEnd(20) + 'RUNS'.padEnd(8) + 'FAILED'.padEnd(9) + 'P50'.padEnd(9) + 'P95');
529
+ for (const row of view.byVerb) {
530
+ console.log(row.key.padEnd(20) + String(row.count).padEnd(8) + String(row.failures).padEnd(9) + formatMs(row.p50Ms).padEnd(9) + formatMs(row.p95Ms));
531
+ }
532
+ if (view.byCapability.length || view.byProvider.length) {
533
+ console.log('─'.repeat(78));
534
+ console.log('CAPABILITY'.padEnd(30) + 'RUNS'.padEnd(8) + 'FAILED');
535
+ for (const row of view.byCapability) {
536
+ console.log(row.key.padEnd(30) + String(row.count).padEnd(8) + String(row.failures));
537
+ }
538
+ console.log('PROVIDER'.padEnd(30) + 'RUNS'.padEnd(8) + 'FAILED');
539
+ for (const row of view.byProvider) {
540
+ console.log(row.key.padEnd(30) + String(row.count).padEnd(8) + String(row.failures));
541
+ }
542
+ } else {
543
+ console.log('─'.repeat(78));
544
+ console.log('No record in this window names a capability or provider — these are written by');
545
+ console.log('runtime calls that resolve one, not by the shell dispatcher.');
546
+ }
547
+ const note = integrityNote(view.ledger);
548
+ if (note) console.log(note);
549
+ console.log('═'.repeat(78) + '\n');
550
+ return finish(0);
551
+ }
552
+
553
+ /**
554
+ * Handles `doflow discover` — missed opportunities.
555
+ * @param {Object} options
556
+ * @param {Object} [options.providerHealth] injected for tests; probed from `health.ts` otherwise
557
+ * @returns {number} process exit code — 1 when there is a finding to act on
558
+ */
559
+ function handleDiscoverCommand({ json = false, days = null, global: useGlobal = false, projectRoot, providerHealth }: any = {}): number {
560
+ const location = resolveRunsLocation({ start: projectRoot, global: useGlobal });
561
+ const read = new RunLedger({ dir: location.dir }).read({ days });
562
+
563
+ // Required lazily and defensively: a discover run must still answer if provider probing is
564
+ // unavailable for any reason — it simply reports the analyses it could not settle.
565
+ let health = providerHealth;
566
+ if (health === undefined) {
567
+ try {
568
+ health = require('../health.ts').probeProviders({ ids: Object.values(PROVIDER) });
569
+ } catch {
570
+ health = null;
571
+ }
572
+ }
573
+
574
+ const view = buildDiscover(read, { providerHealth: health });
575
+ view.origin = location.origin;
576
+
577
+ if (json) {
578
+ console.log(JSON.stringify(view, null, 2));
579
+ return finish(view.findings.length ? 1 : 0);
580
+ }
581
+
582
+ console.log('\nDoFlow Missed Opportunities');
583
+ console.log('═'.repeat(78));
584
+ console.log(`Ledger: ${view.ledger.dir}`);
585
+ console.log(`Analysed: ${view.ledger.recordCount} record(s), ${view.sessionsAnalyzed} session(s)${view.ledger.windowDays ? ` (last ${view.ledger.windowDays} days)` : ''}`);
586
+ console.log(`Verdict: ${view.verdict}`);
587
+ const note = integrityNote(view.ledger);
588
+ if (note) console.log(note.replace('Note:', 'Integrity:'));
589
+ console.log('─'.repeat(78));
590
+ for (const analysis of view.analyses) {
591
+ console.log(`${DISCOVER_MARK[analysis.status].padEnd(12)}${analysis.title}`);
592
+ console.log(` ${analysis.detail}`);
593
+ if (analysis.observed && analysis.observed.length) {
594
+ for (const item of analysis.observed.slice(0, 5)) {
595
+ console.log(` · ${JSON.stringify(item)}`);
596
+ }
597
+ }
598
+ if (analysis.recommendation) console.log(` → ${analysis.recommendation}`);
599
+ console.log('');
600
+ }
601
+ if (view.verdict === 'insufficient-data') {
602
+ console.log('No analysis could be settled from this ledger. That is a statement about the data,');
603
+ console.log('not a clean bill of health.');
604
+ }
605
+ console.log('═'.repeat(78) + '\n');
606
+ return finish(view.findings.length ? 1 : 0);
607
+ }
608
+
609
+ module.exports = {
610
+ TaskRunTelemetry,
611
+ DEFAULT_TOKEN_RATES,
612
+ RunLedger,
613
+ runsDir,
614
+ resolveRunsLocation,
615
+ sanitizeRunEvent,
616
+ normalizeRunRecord,
617
+ groupSessions,
618
+ buildTrace,
619
+ buildStats,
620
+ buildDiscover,
621
+ handleTraceCommand,
622
+ handleStatsCommand,
623
+ handleDiscoverCommand,
624
+ SESSION_GAP_MS,
625
+ RUN_FIELDS,
626
+ };