@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,76 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * CLI rendering for `doflow trace`/`stats`/`discover` (split out of `trace.js`, plan task H.3).
7
+ *
8
+ * Pure formatting over the view models `trace-views.js` builds: no filesystem access, no ledger
9
+ * read or write. `trace.js`'s `handle*Command` functions call into this module to turn a view
10
+ * model into terminal output and a process exit code.
11
+ */
12
+
13
+ /** ISO timestamp to a compact local-independent `MM-DD HH:MM:SS`. Keeps columns narrow without
14
+ * inventing a timezone the record does not carry. */
15
+ function shortTime(timestamp ) {
16
+ if (typeof timestamp !== 'string' || timestamp.length < 19) return '(undated)';
17
+ return `${timestamp.slice(5, 10)} ${timestamp.slice(11, 19)}`;
18
+ }
19
+
20
+ function formatMs(value ) {
21
+ if (typeof value !== 'number') return '-';
22
+ if (value < 1000) return `${value}ms`;
23
+ return `${(value / 1000).toFixed(1)}s`;
24
+ }
25
+
26
+ /**
27
+ * Records a view's exit code and hands it back to the caller.
28
+ *
29
+ * The design's uniform contract (§4.2) is `0` answered, `1` a finding to act on, `2` a usage error.
30
+ * A handler sets `process.exitCode` rather than calling `process.exit`, so buffered stdout is
31
+ * flushed before the process ends — and it also returns the code, so a test can assert it without
32
+ * spawning a process.
33
+ * @param {number} code
34
+ * @returns {number}
35
+ */
36
+ function finish(code ) {
37
+ process.exitCode = code;
38
+ return code;
39
+ }
40
+
41
+ /**
42
+ * What the view could not read, stated in the view rather than only in the JSON. A history that
43
+ * silently lost a partition is a history a reader would over-trust.
44
+ * @param {Object} ledger
45
+ * @returns {string|null}
46
+ */
47
+ function integrityNote(ledger ) {
48
+ const parts = [];
49
+ if (ledger.malformedLines) parts.push(`${ledger.malformedLines} unreadable ledger line(s) skipped`);
50
+ if (ledger.unreadableFiles.length) parts.push(`${ledger.unreadableFiles.length} unreadable partition(s): ${ledger.unreadableFiles.join(', ')}`);
51
+ return parts.length ? `Note: ${parts.join('; ')}.` : null;
52
+ }
53
+
54
+ /** One place that turns a resolved location into the sentence an empty ledger deserves. */
55
+ function printEmptyLedgerNotice(ledger , reason ) {
56
+ console.log(` ${reason}`);
57
+ console.log(` Location: ${ledger.dir}`);
58
+ console.log(' An empty ledger is a normal state for a fresh install. It is not evidence that');
59
+ console.log(' nothing ran, and no conclusion about usage can be drawn from it.');
60
+ }
61
+
62
+ const DISCOVER_MARK = Object.freeze({
63
+ FINDING: '! FINDING',
64
+ CLEAR: '✓ CLEAR',
65
+ NOT_DETERMINED: '? UNKNOWN',
66
+ NOT_APPLICABLE: '- N/A',
67
+ });
68
+
69
+ module.exports = {
70
+ shortTime,
71
+ formatMs,
72
+ finish,
73
+ integrityNote,
74
+ printEmptyLedgerNotice,
75
+ DISCOVER_MARK,
76
+ };
@@ -0,0 +1,459 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Read-side views over the run ledger (FR-012): the three inspection views — trajectory,
7
+ * aggregate stats and missed opportunities — plus the helpers they share.
8
+ *
9
+ * Split out of `trace.js` (plan task H.3): this module only *reads* the normalized records
10
+ * `RunLedger#read` produces and formats them into a view model. It never touches the filesystem
11
+ * and never writes a ledger record — that stays the sole responsibility of `trace.js`'s
12
+ * `RunLedger` class (NFR-004: one write path into `state/runs/`).
13
+ */
14
+
15
+ // ---------------------------------------------------------------------------------- view shared
16
+
17
+ /** A run is a shell process; a gap this long between two of them is a new sitting, not the same
18
+ * workflow. Used only when records carry no explicit `session`, and always reported alongside the
19
+ * view so the reader knows the boundary was inferred rather than recorded. */
20
+ const SESSION_GAP_MS = 30 * 60 * 1000;
21
+
22
+ /** Nearest-rank percentile over an unsorted numeric array. */
23
+ function percentile(values , fraction ) {
24
+ if (!values.length) return null;
25
+ const sorted = [...values].sort((a, b) => a - b);
26
+ const rank = Math.max(1, Math.ceil(fraction * sorted.length));
27
+ return sorted[rank - 1];
28
+ }
29
+
30
+ /**
31
+ * Splits records into workflow-sized groups.
32
+ * @param {Object[]} records
33
+ * @param {Object} [options]
34
+ * @param {number} [options.gapMs=SESSION_GAP_MS]
35
+ * @returns {{grouping: 'session-field'|'idle-gap', sessions: Array<{id: string, startedAt: string|null, endedAt: string|null, records: Object[]}>}}
36
+ */
37
+ function groupSessions(records , { gapMs = SESSION_GAP_MS } = {}) {
38
+ if (!records.length) return { grouping: 'idle-gap', sessions: [] };
39
+
40
+ // An explicit session id is a recorded fact; an idle gap is an inference. Prefer the fact, and
41
+ // only when every record carries one — a half-labelled history would mix the two silently.
42
+ if (records.every((record) => record.session)) {
43
+ const order = [];
44
+ const byId = new Map ();
45
+ for (const record of records) {
46
+ if (!byId.has(record.session)) { byId.set(record.session, []); order.push(record.session); }
47
+ byId.get(record.session) .push(record);
48
+ }
49
+ return {
50
+ grouping: 'session-field',
51
+ sessions: order.map((id) => ({
52
+ id,
53
+ startedAt: byId.get(id) [0].timestamp,
54
+ endedAt: byId.get(id) [byId.get(id) .length - 1].timestamp,
55
+ records: byId.get(id) ,
56
+ })),
57
+ };
58
+ }
59
+
60
+ const sessions = [];
61
+ let current = null;
62
+ for (const record of records) {
63
+ const previous = current && current.records[current.records.length - 1];
64
+ const contiguous = previous && record.at !== null && previous.at !== null && record.at - previous.at <= gapMs;
65
+ if (!current || !contiguous) {
66
+ current = { id: record.timestamp || `run-${sessions.length + 1}`, startedAt: record.timestamp, endedAt: record.timestamp, records: [] };
67
+ sessions.push(current);
68
+ }
69
+ current.records.push(record);
70
+ current.endedAt = record.timestamp;
71
+ }
72
+ return { grouping: 'idle-gap', sessions };
73
+ }
74
+
75
+ /** The ledger provenance every view carries, so a reader can always tell what was actually read. */
76
+ function ledgerSummary(read ) {
77
+ return {
78
+ dir: read.dir,
79
+ exists: read.exists,
80
+ partitions: read.files.length,
81
+ recordCount: read.records.length,
82
+ malformedLines: read.malformedLines,
83
+ unreadableFiles: read.unreadableFiles,
84
+ windowDays: read.days,
85
+ };
86
+ }
87
+
88
+ /** How many records carry each optional field. Every view reports this, because the difference
89
+ * between "no missed opportunities" and "no data to look for them in" is exactly this table. */
90
+ function fieldCoverage(records ) {
91
+ const count = (predicate ) => records.filter(predicate).length;
92
+ return {
93
+ capability: count((r) => Boolean(r.capability)),
94
+ provider: count((r) => Boolean(r.provider)),
95
+ resultCount: count((r) => r.resultCount !== undefined),
96
+ outputBytes: count((r) => r.outputBytes !== undefined),
97
+ exitCode: count((r) => r.exitCode !== undefined),
98
+ durationMs: count((r) => r.durationMs !== undefined),
99
+ };
100
+ }
101
+
102
+ // ---------------------------------------------------------------------------- view 1: trajectory
103
+
104
+ /**
105
+ * The trajectory of the current or most recent workflow (FR-012).
106
+ * @param {Object} read result of `RunLedger#read`
107
+ * @param {Object} [options]
108
+ * @param {number} [options.gapMs]
109
+ * @returns {Object} view model
110
+ */
111
+ function buildTrace(read , { gapMs = SESSION_GAP_MS } = {}) {
112
+ const ledger = ledgerSummary(read);
113
+ const { grouping, sessions } = groupSessions(read.records, { gapMs });
114
+ if (!sessions.length) {
115
+ return {
116
+ view: 'trace',
117
+ ledger,
118
+ grouping,
119
+ gapMinutes: Math.round(gapMs / 60000),
120
+ trajectory: null,
121
+ // Stated as an absence of data, never as a clean bill of health.
122
+ reason: read.exists
123
+ ? 'the ledger exists but holds no readable run records'
124
+ : 'no ledger has been written yet at this location',
125
+ };
126
+ }
127
+
128
+ const session = sessions[sessions.length - 1];
129
+ const steps = session.records.map((record , index ) => ({
130
+ n: index + 1,
131
+ timestamp: record.timestamp,
132
+ verb: record.verb,
133
+ exitCode: record.exitCode ?? null,
134
+ durationMs: record.durationMs ?? null,
135
+ capability: record.capability ?? null,
136
+ provider: record.provider ?? null,
137
+ stage: record.stage ?? null,
138
+ outcome: record.outcome ?? (record.exitCode === undefined ? null : record.exitCode === 0 ? 'ok' : 'failed'),
139
+ }));
140
+ const start = session.records[0].at;
141
+ const end = session.records[session.records.length - 1].at;
142
+
143
+ return {
144
+ view: 'trace',
145
+ ledger,
146
+ grouping,
147
+ gapMinutes: Math.round(gapMs / 60000),
148
+ priorSessions: sessions.length - 1,
149
+ trajectory: {
150
+ id: session.id,
151
+ startedAt: session.startedAt,
152
+ endedAt: session.endedAt,
153
+ wallDurationMs: start !== null && end !== null ? Math.max(0, end - start) : null,
154
+ dispatchedMs: steps.reduce((total , step ) => total + (step.durationMs ?? 0), 0),
155
+ stepCount: steps.length,
156
+ failedSteps: steps.filter((step ) => step.exitCode !== null && step.exitCode !== 0).length,
157
+ steps,
158
+ },
159
+ };
160
+ }
161
+
162
+ // ----------------------------------------------------------------------------- view 2: aggregate
163
+
164
+ /**
165
+ * Aggregate local usage (FR-012).
166
+ * @param {Object} read result of `RunLedger#read`
167
+ * @returns {Object} view model
168
+ */
169
+ function buildStats(read ) {
170
+ const records = read.records;
171
+ const ledger = ledgerSummary(read);
172
+ const durations = records.map((r) => r.durationMs).filter((value) => typeof value === 'number');
173
+
174
+ const tally = (keyOf ) => {
175
+ const map = new Map ();
176
+ for (const record of records) {
177
+ const key = keyOf(record);
178
+ if (!key) continue;
179
+ const entry = map.get(key) || { key, count: 0, failures: 0, totalMs: 0, durations: [] };
180
+ entry.count += 1;
181
+ if (record.exitCode !== undefined && record.exitCode !== 0) entry.failures += 1;
182
+ if (typeof record.durationMs === 'number') { entry.totalMs += record.durationMs; entry.durations.push(record.durationMs); }
183
+ map.set(key, entry);
184
+ }
185
+ return [...map.values()]
186
+ .map((entry) => ({
187
+ key: entry.key,
188
+ count: entry.count,
189
+ failures: entry.failures,
190
+ totalMs: entry.totalMs,
191
+ p50Ms: percentile(entry.durations, 0.5),
192
+ p95Ms: percentile(entry.durations, 0.95),
193
+ }))
194
+ .sort((a, b) => b.count - a.count || a.key.localeCompare(b.key));
195
+ };
196
+
197
+ const dated = records.filter((r) => r.timestamp);
198
+ return {
199
+ view: 'stats',
200
+ ledger,
201
+ recordCount: records.length,
202
+ span: {
203
+ first: dated.length ? dated[0].timestamp : null,
204
+ last: dated.length ? dated[dated.length - 1].timestamp : null,
205
+ activeDays: read.files.length,
206
+ },
207
+ outcomes: {
208
+ succeeded: records.filter((r) => r.exitCode === 0).length,
209
+ failed: records.filter((r) => r.exitCode !== undefined && r.exitCode !== 0).length,
210
+ // Counted separately rather than folded into "succeeded": an unrecorded exit code is not a
211
+ // success, and the three defects this feature is correcting were all of that shape.
212
+ unrecorded: records.filter((r) => r.exitCode === undefined).length,
213
+ },
214
+ durations: {
215
+ measured: durations.length,
216
+ totalMs: durations.reduce((total, value) => total + value, 0),
217
+ p50Ms: percentile(durations, 0.5),
218
+ p95Ms: percentile(durations, 0.95),
219
+ maxMs: durations.length ? Math.max(...durations) : null,
220
+ },
221
+ byVerb: tally((r) => r.verb),
222
+ byCapability: tally((r) => r.capability),
223
+ byProvider: tally((r) => r.provider),
224
+ byDay: tally((r) => (r.timestamp ? r.timestamp.slice(0, 10) : null)).sort((a , b ) => a.key.localeCompare(b.key)),
225
+ coverage: fieldCoverage(records),
226
+ };
227
+ }
228
+
229
+ // ------------------------------------------------------------------- view 3: missed opportunities
230
+
231
+ /** Capability ids the opportunity analyses reason about, kept in one place so a registry rename
232
+ * shows up as one edit rather than four scattered string literals. */
233
+ const CAPABILITY = Object.freeze({
234
+ exactSearch: 'code.exact-search',
235
+ semanticSearch: 'code.semantic-search',
236
+ relationships: 'code.relationships',
237
+ impact: 'code.impact-analysis',
238
+ compress: 'command.compress',
239
+ });
240
+
241
+ const PROVIDER = Object.freeze({
242
+ semantic: 'semble.search',
243
+ structural: 'graphify.query',
244
+ compressor: 'rtk',
245
+ });
246
+
247
+ /** A search returning this many hits was a question about the codebase, not a lookup of a known
248
+ * symbol — the result set is too large to have been the answer the caller wanted. */
249
+ const BROWSING_RESULT_SET = 40;
250
+ /** Three exact searches inside one sitting with no structural query is reference-walking by hand. */
251
+ const REPEATED_SEARCHES = 3;
252
+ /** Roughly 5k tokens of raw stdout — the point at which compression stops being a rounding error. */
253
+ const LARGE_OUTPUT_BYTES = 20000;
254
+ /** One retry is normal. Two failed verifications in one sitting is a pattern. */
255
+ const RETRY_THRESHOLD = 2;
256
+
257
+ const NOT_DETERMINED = 'NOT_DETERMINED';
258
+ const NOT_APPLICABLE = 'NOT_APPLICABLE';
259
+ const FINDING = 'FINDING';
260
+ const CLEAR = 'CLEAR';
261
+
262
+ /** Health of a provider as this analysis needs it: only `HEALTHY` counts as "was available". */
263
+ function providerAvailable(providerHealth , id ) {
264
+ const entry = providerHealth ? providerHealth[id] : undefined;
265
+ if (!entry) return null; // unknown — not the same as unavailable
266
+ return entry.status === 'HEALTHY';
267
+ }
268
+
269
+ /**
270
+ * Missed opportunities (FR-012).
271
+ *
272
+ * Every analysis returns one of four states and each is load-bearing:
273
+ * FINDING — the ledger shows the cheaper capability was available and was not used.
274
+ * CLEAR — the analysis ran against data that could have shown the pattern and did not.
275
+ * NOT_DETERMINED — the ledger lacks the field the analysis needs. Reported, never rounded to CLEAR.
276
+ * NOT_APPLICABLE — the better provider is not installed, so nothing was missed.
277
+ *
278
+ * @param {Object} read result of `RunLedger#read`
279
+ * @param {Object} [options]
280
+ * @param {Object} [options.providerHealth] map of provider id -> {status}, from `health.ts`
281
+ * @param {number} [options.gapMs]
282
+ * @returns {Object} view model
283
+ */
284
+ function buildDiscover(read , { providerHealth = null, gapMs = SESSION_GAP_MS } = {}) {
285
+ const records = read.records;
286
+ const ledger = ledgerSummary(read);
287
+ const coverage = fieldCoverage(records);
288
+ const { grouping, sessions } = groupSessions(records, { gapMs });
289
+
290
+ // Said once, because three of the four analyses depend on the same absent field and each must
291
+ // say why it could not answer rather than defaulting to "nothing found".
292
+ const noCapabilityData = {
293
+ status: NOT_DETERMINED,
294
+ detail: 'no record in this window names a capability. The dispatcher records verb-level metadata only '
295
+ + '(verb, exit code, duration, argument count), so which capability a run used is not recoverable from it. '
296
+ + 'Capability-level records are written by runtime calls that resolve a capability to a provider.',
297
+ };
298
+
299
+ const analyses = [];
300
+
301
+ // 1. A broad search that was really a semantic question.
302
+ analyses.push((() => {
303
+ const base = { id: 'search-was-a-question', title: 'Broad searches that were really semantic questions' };
304
+ if (!records.length) return { ...base, status: NOT_DETERMINED, detail: 'the ledger holds no runs to analyse.' };
305
+ if (coverage.capability === 0) return { ...base, ...noCapabilityData };
306
+ const available = providerAvailable(providerHealth, PROVIDER.semantic);
307
+ if (available === null) return { ...base, status: NOT_DETERMINED, detail: `whether \`${PROVIDER.semantic}\` could have answered was not probed, so a missed opportunity cannot be distinguished from an unavailable alternative.` };
308
+ if (!available) return { ...base, status: NOT_APPLICABLE, detail: `\`${PROVIDER.semantic}\` is not answering on this machine, so exact search was the only option available.` };
309
+
310
+ const broad = records.filter((r) => r.capability === CAPABILITY.exactSearch && (r.resultCount ?? 0) >= BROWSING_RESULT_SET);
311
+ if (coverage.resultCount === 0) {
312
+ return { ...base, status: NOT_DETERMINED, detail: 'exact searches are recorded but none carries a result count, which is the signal that separates a lookup from a browse.' };
313
+ }
314
+ if (!broad.length) return { ...base, status: CLEAR, detail: `no exact search in this window returned ${BROWSING_RESULT_SET}+ hits.` };
315
+ return {
316
+ ...base,
317
+ status: FINDING,
318
+ detail: `${broad.length} exact search${broad.length === 1 ? '' : 'es'} returned ${BROWSING_RESULT_SET}+ hits while semantic search was available. A result set that large is a question about the codebase, not a lookup.`,
319
+ observed: broad.slice(0, 10).map((r) => ({ timestamp: r.timestamp, provider: r.provider ?? null, resultCount: r.resultCount })),
320
+ largestResultSet: Math.max(...broad.map((r) => r.resultCount)),
321
+ recommendation: `Ask the question directly: \`route\` the need to \`${CAPABILITY.semanticSearch}\` (provider \`${PROVIDER.semantic}\`) before falling back to a pattern search.`,
322
+ };
323
+ })());
324
+
325
+ // 2. Manual relationship exploration where a structural provider was available.
326
+ analyses.push((() => {
327
+ const base = { id: 'manual-relationship-walk', title: 'Relationship exploration done by hand' };
328
+ if (!records.length) return { ...base, status: NOT_DETERMINED, detail: 'the ledger holds no runs to analyse.' };
329
+ if (coverage.capability === 0) return { ...base, ...noCapabilityData };
330
+ const available = providerAvailable(providerHealth, PROVIDER.structural);
331
+ if (available === null) return { ...base, status: NOT_DETERMINED, detail: `whether \`${PROVIDER.structural}\` could have answered was not probed, so a missed opportunity cannot be distinguished from an unavailable alternative.` };
332
+ if (!available) return { ...base, status: NOT_APPLICABLE, detail: `\`${PROVIDER.structural}\` is not answering on this machine, so structural queries were not an available alternative.` };
333
+
334
+ const offenders = sessions
335
+ .map((session ) => ({
336
+ id: session.id,
337
+ startedAt: session.startedAt,
338
+ searches: session.records.filter((r ) => r.capability === CAPABILITY.exactSearch).length,
339
+ structural: session.records.filter((r ) => r.capability === CAPABILITY.relationships || r.capability === CAPABILITY.impact).length,
340
+ }))
341
+ .filter((session ) => session.searches >= REPEATED_SEARCHES && session.structural === 0);
342
+ if (!offenders.length) return { ...base, status: CLEAR, detail: `no session ran ${REPEATED_SEARCHES}+ exact searches without also querying the code graph.` };
343
+ return {
344
+ ...base,
345
+ status: FINDING,
346
+ detail: `${offenders.length} session${offenders.length === 1 ? '' : 's'} ran ${REPEATED_SEARCHES}+ exact searches and never queried the code graph. Walking callers and dependents by repeated search is what the structural provider exists to replace.`,
347
+ observed: offenders.slice(0, 10),
348
+ recommendation: `Route callers/dependents/blast-radius questions to \`${CAPABILITY.relationships}\` or \`${CAPABILITY.impact}\` (provider \`${PROVIDER.structural}\`) instead of iterating exact searches.`,
349
+ };
350
+ })());
351
+
352
+ // 3. Large raw command output where compression was available.
353
+ analyses.push((() => {
354
+ const base = { id: 'uncompressed-output', title: 'Large raw command output left uncompressed' };
355
+ if (!records.length) return { ...base, status: NOT_DETERMINED, detail: 'the ledger holds no runs to analyse.' };
356
+ if (coverage.outputBytes === 0) {
357
+ return { ...base, status: NOT_DETERMINED, detail: 'no record carries an output byte volume, which is the only signal that distinguishes a large raw dump from a small one. The shell dispatcher does not measure what a verb printed.' };
358
+ }
359
+ const available = providerAvailable(providerHealth, PROVIDER.compressor);
360
+ if (available === null) return { ...base, status: NOT_DETERMINED, detail: `whether \`${PROVIDER.compressor}\` could have compressed the output was not probed.` };
361
+ if (!available) return { ...base, status: NOT_APPLICABLE, detail: `\`${PROVIDER.compressor}\` is not answering on this machine, so compression was not an available alternative.` };
362
+
363
+ const bulky = records.filter((r) => (r.outputBytes ?? 0) >= LARGE_OUTPUT_BYTES && r.provider !== PROVIDER.compressor);
364
+ if (!bulky.length) return { ...base, status: CLEAR, detail: `no uncompressed run returned ${LARGE_OUTPUT_BYTES} bytes or more.` };
365
+ const wasted = bulky.reduce((total , r ) => total + r.outputBytes, 0);
366
+ return {
367
+ ...base,
368
+ status: FINDING,
369
+ detail: `${bulky.length} run${bulky.length === 1 ? '' : 's'} returned ${LARGE_OUTPUT_BYTES}+ bytes of raw output (${wasted} bytes in total) while a compressor was available.`,
370
+ observed: bulky.slice(0, 10).map((r) => ({ timestamp: r.timestamp, verb: r.verb, provider: r.provider ?? null, outputBytes: r.outputBytes })),
371
+ recommendation: `Route the high-volume commands through \`${CAPABILITY.compress}\` (provider \`${PROVIDER.compressor}\`) rather than reading raw stdout into context.`,
372
+ };
373
+ })());
374
+
375
+ // 4. Implementation retries indicating readiness evidence was incomplete before editing.
376
+ // This is the one analysis that works on the dispatcher's verb-only records, because its
377
+ // signal is which verbs ran in what order — not what any of them was about.
378
+ analyses.push((() => {
379
+ const base = { id: 'retries-without-readiness', title: 'Implementation retries with no readiness check beforehand' };
380
+ if (!records.length) return { ...base, status: NOT_DETERMINED, detail: 'the ledger holds no runs to analyse.' };
381
+ const retrying = sessions
382
+ .map((session ) => ({
383
+ id: session.id,
384
+ startedAt: session.startedAt,
385
+ failedVerifications: session.records.filter((r ) => r.verb === 'verify' && r.exitCode !== undefined && r.exitCode !== 0).length,
386
+ recoveries: session.records.filter((r ) => r.verb === 'recover').length,
387
+ readinessCalls: session.records.filter((r ) => r.verb === 'readiness').length,
388
+ }))
389
+ .filter((session ) => session.failedVerifications >= RETRY_THRESHOLD || session.recoveries >= 1);
390
+ if (!retrying.length) {
391
+ // The retry signal is a `verify` that FAILED (non-zero exit_code) or a `recover` run. A
392
+ // `verify` record with no exit_code carries neither, and that is the shape the shell
393
+ // dispatcher writes on its own — so counting bare `verify` records as coverage let this
394
+ // report CLEAR over a window it could not read, unable to tell a clean run from nothing but
395
+ // failures. CLEAR must mean "the analysis ran against data that could have shown the pattern
396
+ // and did not"; without a readable exit_code it could not have. Found by C.7's coverage
397
+ // guard, which pinned the wrong behaviour and fails once it is corrected.
398
+ const readableVerifications = records.filter(
399
+ (r) => r.verb === 'verify' && Number.isInteger(r.exit_code),
400
+ ).length;
401
+ const recoveries = records.filter((r) => r.verb === 'recover').length;
402
+ const observedRetrySignals = readableVerifications + recoveries;
403
+ return {
404
+ ...base,
405
+ status: observedRetrySignals ? CLEAR : NOT_DETERMINED,
406
+ detail: observedRetrySignals
407
+ ? 'verification ran in this window and no session hit the retry threshold.'
408
+ : 'no `verify` with a recorded exit code and no `recover` run appears in this window, so there is no retry behaviour to judge.',
409
+ };
410
+ }
411
+ const unguarded = retrying.filter((session ) => session.readinessCalls === 0);
412
+ if (!unguarded.length) {
413
+ return {
414
+ ...base,
415
+ status: NOT_DETERMINED,
416
+ detail: `${retrying.length} session${retrying.length === 1 ? '' : 's'} retried verification, but each also called \`readiness\`. The ledger records that \`readiness\` ran, not what state it returned, so whether the evidence was actually sufficient cannot be decided from here — read the readiness report for those tasks.`,
417
+ observed: retrying.slice(0, 10),
418
+ };
419
+ }
420
+ return {
421
+ ...base,
422
+ status: FINDING,
423
+ detail: `${unguarded.length} session${unguarded.length === 1 ? '' : 's'} retried verification (or entered recovery) without ever calling \`readiness\`. Retrying an implementation is the symptom; missing evidence before the first edit is the usual cause.`,
424
+ observed: unguarded.slice(0, 10),
425
+ recommendation: 'Run `readiness` for the task class before the first edit and resolve the named missing evidence, rather than discovering the gap through a failing verification.',
426
+ };
427
+ })());
428
+
429
+ const findings = analyses.filter((analysis) => analysis.status === FINDING);
430
+ const undetermined = analyses.filter((analysis) => analysis.status === NOT_DETERMINED);
431
+
432
+ // The verdict never claims a clean bill of health over data that could not answer the question.
433
+ let verdict ;
434
+ if (findings.length) verdict = 'findings';
435
+ else if (undetermined.length === analyses.length) verdict = 'insufficient-data';
436
+ else if (undetermined.length) verdict = 'no-findings-in-the-analyses-that-could-run';
437
+ else verdict = 'no-findings';
438
+
439
+ return {
440
+ view: 'discover',
441
+ ledger,
442
+ grouping,
443
+ sessionsAnalyzed: sessions.length,
444
+ coverage,
445
+ providerHealthProbed: Boolean(providerHealth),
446
+ analyses,
447
+ findings,
448
+ verdict,
449
+ };
450
+ }
451
+
452
+ module.exports = {
453
+ SESSION_GAP_MS,
454
+ groupSessions,
455
+ buildTrace,
456
+ buildStats,
457
+ buildDiscover,
458
+ PROVIDER,
459
+ };