@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,249 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The goal service (IC-022, FR-013, FR-014): add, item, check, link, list and done, over the
7
+ * lifecycle event store. A goal is DoFlow's own record of one outcome and its checklist, independent
8
+ * of any harness command that shares the name (NFR-004). Every function takes the IC-001 root, writes
9
+ * only events and returns a result object without printing, as the follow-up service does.
10
+ *
11
+ * - A refusal is returned `{ok:false, action, finding, message}` and never thrown (exit 1).
12
+ * - A caller mistake throws `FollowupUsageError` (exit 2), before anything is written.
13
+ * - Every free-text field is masked, one line and bounded (`oneLine`), before it is stored.
14
+ * - Progress is computed here from the fold and the derived feature statuses, never written, so it
15
+ * is the same on every harness and costs no model tokens (NFR-006).
16
+ */
17
+
18
+ const nodeFs = require('node:fs');
19
+ const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
20
+ const { appendEvents, readFold } = require('./event-store.js');
21
+ const { deriveStatuses } = require('./status.js');
22
+ const { FollowupUsageError, oneLine, channelBy } = require('./followup.js');
23
+ const { goalView, goalConflicts, goalConflictNext } = require('./overview.js');
24
+
25
+ const GOAL_ID = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26
+ const GOAL_ID_MAX = 40;
27
+ const ITEM_ID = /^C[1-9][0-9]{0,5}$/;
28
+ /** Items one `goal.added` may carry: a checklist this long is a plan, not a goal. */
29
+ const ITEMS_MAX = 100;
30
+ /** Channels that name the user (DEC-004); `default` is the agent. */
31
+ const USER_CHANNELS = ['question', 'gate', 'prompt'];
32
+ /** Fold conflicts that are a refusal under their own name; any other conflict is an `illegal-transition`. */
33
+ const CONFLICT_FINDINGS = new Set(['goal-exists', 'unknown-goal', 'unknown-item', 'goal-already-linked', 'untracked-feature', 'evidence-required']);
34
+
35
+ function refusal(action , finding , message ) { return { ok: false, action, finding, message }; }
36
+
37
+ function refusalFrom(action , failed ) {
38
+ const finding = failed.conflict && CONFLICT_FINDINGS.has(failed.conflict.code) ? failed.conflict.code : failed.finding;
39
+ return refusal(action, finding, failed.message);
40
+ }
41
+
42
+ function goalIdOf(raw ) {
43
+ if (raw === undefined || raw === null || raw === '') throw new FollowupUsageError('--goal is required: a kebab-case goal id of at most 40 characters');
44
+ const id = String(raw);
45
+ if (id.length > GOAL_ID_MAX || !GOAL_ID.test(id)) {
46
+ throw new FollowupUsageError(`--goal must be kebab-case (lowercase letters and digits joined by single dashes) and at most ${GOAL_ID_MAX} characters`);
47
+ }
48
+ return id;
49
+ }
50
+
51
+ function itemIdOf(raw ) {
52
+ const list = Array.isArray(raw) ? raw : raw === undefined ? [] : [raw];
53
+ if (list.length !== 1) throw new FollowupUsageError('--item takes exactly one checklist id such as C3 for --action check');
54
+ if (!ITEM_ID.test(String(list[0]))) throw new FollowupUsageError(`--item must be a checklist id such as C3 (got '${String(list[0]).slice(0, 40)}')`);
55
+ return String(list[0]);
56
+ }
57
+
58
+ /** One masked, one-line, bounded text; a problem is a usage error naming the flag. */
59
+ function textOf(raw , label ) {
60
+ const problems = [];
61
+ const text = raw === undefined ? null : oneLine(raw, label, problems);
62
+ if (problems.length) throw new FollowupUsageError(problems.join('; '));
63
+ return text;
64
+ }
65
+
66
+ function requiredText(raw , label ) {
67
+ if (raw === undefined || raw === null) throw new FollowupUsageError(`${label} is required`);
68
+ return textOf(raw, label);
69
+ }
70
+
71
+ function unknownGoal(action , fold , id ) {
72
+ const known = fold.goals.map((g ) => g.goal);
73
+ return refusal(action, 'unknown-goal', `${id} is not a goal${known.length ? `; the goals are ${known.join(', ')}` : '; none has been added'}. Nothing was written.`);
74
+ }
75
+
76
+ /** The next free `C<n>`: one past the largest number in use, so an id is never reused. */
77
+ function nextItemId(goal ) {
78
+ let top = 0;
79
+ for (const item of goal.items) {
80
+ const m = /^C(\d+)$/.exec(item.id);
81
+ if (m) top = Math.max(top, Number(m[1]));
82
+ }
83
+ return `C${top + 1}`;
84
+ }
85
+
86
+ const progressOf = (goal ) => ({ met: goal.items.filter((i ) => i.met).length, total: goal.items.length });
87
+
88
+ function checkLine(goalId , item ) {
89
+ return `Record a met item with evidence: doflow-run goal --action check --goal ${goalId} --item ${item} --evidence "<what shows it>"`;
90
+ }
91
+
92
+ function doneLine(goalId ) {
93
+ return `Every item of ${goalId} is met; ask the user, then: doflow-run goal --action done --goal ${goalId} --channel question`;
94
+ }
95
+
96
+ // ── add, item ──────────────────────────────────────────────────────────────────────────────────
97
+
98
+ /** IC-022 `add`: one `goal.added` event with the outcome and the checklist, ids `C1`, `C2`, ... */
99
+ function addGoal({ root, goal, statement, items, channel, now = new Date(), fsImpl = nodeFs } ) {
100
+ const id = goalIdOf(goal);
101
+ const outcome = requiredText(statement, '--statement');
102
+ const list = Array.isArray(items) ? items : items === undefined ? [] : [items];
103
+ if (list.length === 0) throw new FollowupUsageError('--item is required for --action add: at least one checklist item (repeat --item for more)');
104
+ if (list.length > ITEMS_MAX) throw new FollowupUsageError(`--item was given ${list.length} times; a goal starts with at most ${ITEMS_MAX} items`);
105
+ const problems = [];
106
+ const checklist = list.map((raw, index) => ({ id: `C${index + 1}`, text: oneLine(raw, `--item ${index + 1}`, problems) }));
107
+ if (problems.length) throw new FollowupUsageError(problems.join('; '));
108
+ const by = channelBy(channel);
109
+ if (readFold(root, { fsImpl, now }).goals.some((g ) => g.goal === id)) return refusal('add', 'goal-exists', `${id} already exists. Nothing was written.`);
110
+ const out = appendEvents(root, [{ type: 'goal.added', by, data: { goal: id, outcome, items: checklist } }], { now, fsImpl });
111
+ if (!out.ok) return refusalFrom('add', out);
112
+ return {
113
+ ok: true, action: 'add', goal: id, outcome, items: checklist, events: out.written.map((w ) => w.file),
114
+ next: [`Link a feature that serves it: doflow-run goal --action link --goal ${id} --slug <slug>`, checkLine(id, 'C1')],
115
+ };
116
+ }
117
+
118
+ /** IC-022 `item`: one `goal.item-added` event; the id is the next free `C<n>`. */
119
+ function addItem({ root, goal, text, channel, now = new Date(), fsImpl = nodeFs } ) {
120
+ const id = goalIdOf(goal);
121
+ const itemText = requiredText(text, '--text');
122
+ const by = channelBy(channel);
123
+ // The id is chosen before the lock, so a writer that took it first makes this call refuse; a retry picks the next one.
124
+ for (let attempt = 0; ; attempt += 1) {
125
+ const fold = readFold(root, { fsImpl, now });
126
+ const found = fold.goals.find((g ) => g.goal === id);
127
+ if (!found) return unknownGoal('item', fold, id);
128
+ const item = { id: nextItemId(found), text: itemText };
129
+ const out = appendEvents(root, [{ type: 'goal.item-added', by, data: { goal: id, item } }], { now, fsImpl });
130
+ if (out.ok) return { ok: true, action: 'item', goal: id, item, events: out.written.map((w ) => w.file), next: [checkLine(id, item.id)] };
131
+ const raced = out.conflict && out.conflict.code === 'illegal-transition' && /already has item/.test(out.conflict.reason);
132
+ if (!raced || attempt >= 4) return refusalFrom('item', out);
133
+ }
134
+ }
135
+
136
+ // ── check, link ────────────────────────────────────────────────────────────────────────────────
137
+
138
+ /** IC-022 `check`: `goal.checked` with `met: true`, or `met: false` under `--unmet`; evidence either way (DEC-015). */
139
+ function checkItem({ root, goal, item, evidence, unmet = false, channel, now = new Date(), fsImpl = nodeFs } ) {
140
+ const id = goalIdOf(goal);
141
+ const itemId = itemIdOf(item);
142
+ const proof = requiredText(evidence, '--evidence');
143
+ const by = channelBy(channel);
144
+ const before = readFold(root, { fsImpl, now });
145
+ const found = before.goals.find((g ) => g.goal === id);
146
+ if (!found) return unknownGoal('check', before, id);
147
+ if (!found.items.some((i ) => i.id === itemId)) {
148
+ return refusal('check', 'unknown-item', `${id} has no item ${itemId}; its items are ${found.items.map((i ) => i.id).join(', ')}. Nothing was written.`);
149
+ }
150
+ const met = !unmet;
151
+ const out = appendEvents(root, [{ type: 'goal.checked', by, data: { goal: id, item: itemId, met, evidence: proof } }], { now, fsImpl });
152
+ if (!out.ok) return refusalFrom('check', out);
153
+ const after = readFold(root, { fsImpl, now }).goals.find((g ) => g.goal === id) || found;
154
+ const progress = progressOf(after);
155
+ const proposeDone = after.status === 'open' && progress.total > 0 && progress.met === progress.total;
156
+ const unchecked = after.items.find((i ) => !i.met);
157
+ return {
158
+ ok: true, action: 'check', goal: id, item: itemId, met, evidence: proof, progress, proposeDone, events: out.written.map((w ) => w.file),
159
+ next: proposeDone ? [doneLine(id)] : unchecked && after.status === 'open' ? [checkLine(id, unchecked.id)] : [],
160
+ };
161
+ }
162
+
163
+ /** IC-022 `link`: one `goal.linked` event; a feature serves one goal at most (DEC-009). */
164
+ function linkFeature({ root, goal, slug, replace = false, channel, now = new Date(), fsImpl = nodeFs } ) {
165
+ const id = goalIdOf(goal);
166
+ if (!slug) throw new FollowupUsageError('--slug is required for --action link');
167
+ if (!isSafeSlug(slug)) throw new FollowupUsageError(invalidSlugRefusal(slug).message);
168
+ const by = channelBy(channel);
169
+ const fold = readFold(root, { fsImpl, now });
170
+ if (!fold.goals.some((g ) => g.goal === id)) return unknownGoal('link', fold, id);
171
+ const feature = fold.features.find((f ) => f.slug === slug);
172
+ if (!feature) return refusal('link', 'untracked-feature', `${slug} is not a tracked feature, so no goal can link it; run doflow-run lifecycle --action init --slug ${slug} first. Nothing was written.`);
173
+ if (feature.goal === id) return { ok: true, action: 'link', goal: id, slug, linked: 'already', replaced: null, events: [], next: [] };
174
+ if (feature.goal && !replace) {
175
+ return refusal('link', 'goal-already-linked', `${slug} already serves ${feature.goal}; a feature serves one goal at most. Pass --replace to move it to ${id}. Nothing was written.`);
176
+ }
177
+ const out = appendEvents(root, [{ type: 'goal.linked', by, data: { goal: id, slug, replace: Boolean(replace) } }], { now, fsImpl });
178
+ if (!out.ok) return refusalFrom('link', out);
179
+ return { ok: true, action: 'link', goal: id, slug, linked: 'new', replaced: feature.goal || null, events: out.written.map((w ) => w.file), next: [] };
180
+ }
181
+
182
+ // ── list ───────────────────────────────────────────────────────────────────────────────────────
183
+
184
+ /** IC-022 progress for one goal: items, `progress`, `proposeDone`, linked features by status and the nudges. */
185
+ function progressView(goal , fold , statuses , conflicts = []) {
186
+ const view = goalView(goal, fold.features, statuses, conflicts);
187
+ return {
188
+ goal: goal.goal, outcome: goal.outcome, status: goal.status,
189
+ items: goal.items.map((i ) => ({ id: i.id, text: i.text, met: i.met, evidence: i.evidence })),
190
+ progress: view.items, proposeDone: view.proposeDone, features: view.features, nudges: view.nudges,
191
+ ...(view.conflicts ? { conflicts: view.conflicts } : {}),
192
+ ...(goal.status === 'done' ? { reason: goal.reason } : {}),
193
+ };
194
+ }
195
+
196
+ /** IC-022 `list`: every goal, or the one named, with its progress. Reads git only when a goal has a linked feature. */
197
+ function listGoals({ root, goal, now = new Date(), fsImpl = nodeFs } ) {
198
+ const id = goal === undefined ? null : goalIdOf(goal);
199
+ const fold = readFold(root, { fsImpl, now });
200
+ const shown = id === null ? fold.goals : fold.goals.filter((g ) => g.goal === id);
201
+ if (id !== null && shown.length === 0) return unknownGoal('list', fold, id);
202
+ const linked = new Set(fold.features.filter((f ) => shown.some((g ) => g.goal === f.goal)).map((f ) => f.slug));
203
+ const statuses = linked.size ? deriveStatuses({ root, fold }) : { statuses: {} };
204
+ const conflictsByGoal = goalConflicts(root, fold, fsImpl);
205
+ const goals = shown.map((g ) => progressView(g, fold, statuses, conflictsByGoal.get(g.goal)));
206
+ const next = [];
207
+ for (const g of goals.filter((x ) => x.status === 'open')) {
208
+ if (g.proposeDone) next.push(doneLine(g.goal));
209
+ else {
210
+ const unchecked = g.items.find((i ) => !i.met);
211
+ if (unchecked) next.push(checkLine(g.goal, unchecked.id));
212
+ }
213
+ }
214
+ for (const g of goals.filter((x ) => x.conflicts)) next.push(...goalConflictNext(g.goal, g.conflicts));
215
+ const result = { ok: true, action: 'list', goals, conflicts: fold.conflicts, unreadable: fold.unreadable, unreadableReasons: fold.unreadableReasons, next };
216
+ if (statuses.reason) result.reason = statuses.reason;
217
+ return result;
218
+ }
219
+
220
+ // ── done ───────────────────────────────────────────────────────────────────────────────────────
221
+
222
+ /**
223
+ * IC-022 `done`: one `goal.done` event. Only the user closes a goal (DEC-004), so `default` is
224
+ * refused; with unmet items it is refused unless `--reason` says why the user closes it anyway.
225
+ */
226
+ function doneGoal({ root, goal, reason, channel, now = new Date(), fsImpl = nodeFs } ) {
227
+ const id = goalIdOf(goal);
228
+ const why = textOf(reason, '--reason');
229
+ const by = channelBy(channel);
230
+ if (!USER_CHANNELS.includes(channel)) {
231
+ return refusal('done', 'not-user', `only the user marks a goal done: pass --channel question, gate or prompt once the user has said so (got ${channel ? `'${channel}'` : 'no channel'}). Nothing was written.`);
232
+ }
233
+ const fold = readFold(root, { fsImpl, now });
234
+ const found = fold.goals.find((g ) => g.goal === id);
235
+ if (!found) return unknownGoal('done', fold, id);
236
+ if (found.status === 'done') return refusal('done', 'already-done', `${id} is already done. Nothing was written.`);
237
+ const unmet = found.items.filter((i ) => !i.met).map((i ) => i.id);
238
+ if (unmet.length && !why) {
239
+ return refusal('done', 'items-unmet', `${id} has unmet items (${unmet.join(', ')}); check them with evidence, or pass --reason to say why the user closes it anyway. Nothing was written.`);
240
+ }
241
+ const out = appendEvents(root, [{ type: 'goal.done', by, data: { goal: id, reason: why } }], { now, fsImpl });
242
+ if (!out.ok) return refusalFrom('done', out);
243
+ return { ok: true, action: 'done', goal: id, reason: why, unmet, events: out.written.map((w ) => w.file), next: [] };
244
+ }
245
+
246
+ module.exports = {
247
+ addGoal, addItem, checkItem, linkFeature, listGoals, doneGoal,
248
+ GOAL_ID_MAX, ITEMS_MAX, USER_CHANNELS,
249
+ };
@@ -0,0 +1,86 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * Promotion to a new intent (IC-024, FR-005). Creates `agent-docs/intent/<kebab-title>.md` under the
7
+ * IC-001 root from headings fixed in this file, never from a template file: an installed runtime
8
+ * ships no template where it could read one. `core/shared/templates/doflow/intent-template.md`
9
+ * holds the same five headings and a guard test compares the two lists. An existing file is never
10
+ * touched; the create is exclusive, so a refusal is the file system's own answer.
11
+ */
12
+
13
+ const nodeFs = require('node:fs');
14
+ const path = require('node:path');
15
+
16
+ const INTENT_DIR_REL = path.join('agent-docs', 'intent');
17
+ const TITLE_MAX = 80;
18
+ const SLUG_MAX = 60;
19
+
20
+ /** The five intent headings after the first, each with the one-line prompt the new file carries. */
21
+ const INTENT_SECTIONS = [
22
+ { heading: '1. Problem', prompt: null },
23
+ { heading: '2. Proposed outcome', prompt: '(What would be observably different if this were addressed?)' },
24
+ { heading: '3. Affected', prompt: '(Who and what this touches.)' },
25
+ { heading: '4. Constraints', prompt: '(What already bounds this; write "none known" if nothing does.)' },
26
+ { heading: '5. Open questions', prompt: '(What you do not know yet.)' },
27
+ ];
28
+
29
+ /** `Cart robustness!` -> `cart-robustness`; empty when the title holds no letter or digit. */
30
+ function kebabTitle(title ) {
31
+ return String(title).toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '').slice(0, SLUG_MAX).replace(/-+$/, '');
32
+ }
33
+
34
+ function sourceParts(source ) {
35
+ switch (source.kind) {
36
+ case 'stage': return [source.kind, source.feature, source.stage];
37
+ case 'run': return [source.kind, source.taskClass, source.taskId, source.stage];
38
+ case 'release': return [source.kind, source.release];
39
+ case 'report': return [source.kind, source.release, source.feature];
40
+ case 'failure': return [source.kind, source.ref];
41
+ default: return [source.kind];
42
+ }
43
+ }
44
+
45
+ /**
46
+ * @param {{title:string, by:'user'|'agent', date:string, items:Array<{id:string,statement:string,source:Object}>}} intent
47
+ * @returns {string} the file content
48
+ */
49
+ function renderIntent({ title, by, date, items } ) {
50
+ const lines = [
51
+ `# Intent: ${title}`,
52
+ '',
53
+ `**Raised by:** ${by}, through doflow-run followup --action promote · **Date:** ${date}`,
54
+ ];
55
+ for (const { heading, prompt } of INTENT_SECTIONS) {
56
+ lines.push('', `## ${heading}`, '');
57
+ if (prompt) lines.push(prompt);
58
+ else for (const item of items) lines.push(`- ${item.id}: ${item.statement} (source: ${sourceParts(item.source).filter(Boolean).join(', ')})`);
59
+ }
60
+ return `${lines.join('\n')}\n`;
61
+ }
62
+
63
+ /**
64
+ * Creates the intent file with an exclusive create.
65
+ * @returns {{ok:true, file:string, path:string}|{ok:false, finding:'intent-exists'|'invalid-title', message:string}}
66
+ * `path` is root-relative with `/` separators.
67
+ */
68
+ function writeIntent(root , intent , { fsImpl = nodeFs } = {}) {
69
+ const title = String(intent.title ?? '').trim();
70
+ const name = kebabTitle(title) || intent.fallbackName || '';
71
+ if (!name || title.length > TITLE_MAX || /[\r\n\u2028\u2029\u0085]/.test(title)) {
72
+ return { ok: false, finding: 'invalid-title', message: `the title must be one line of at most ${TITLE_MAX} characters with a letter or digit in it. Nothing was written.` };
73
+ }
74
+ const rel = path.join(INTENT_DIR_REL, `${name}.md`);
75
+ const file = path.join(root, rel);
76
+ fsImpl.mkdirSync(path.dirname(file), { recursive: true });
77
+ try {
78
+ fsImpl.writeFileSync(file, renderIntent({ ...intent, title }), { encoding: 'utf8', flag: 'wx' });
79
+ } catch (error ) {
80
+ if (error.code === 'EEXIST') return { ok: false, finding: 'intent-exists', message: `${rel.split(path.sep).join('/')} already exists and was left untouched. Nothing was written.` };
81
+ throw error;
82
+ }
83
+ return { ok: true, file, path: rel.split(path.sep).join('/') };
84
+ }
85
+
86
+ module.exports = { writeIntent, renderIntent, kebabTitle, INTENT_SECTIONS, TITLE_MAX };
@@ -0,0 +1,292 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * `lifecycle --action overview | init | status` (IC-007, IC-008, IC-021): the project's open
7
+ * follow-ups, intents, goals and feature statuses, and the writes that tie a new feature to them.
8
+ * Each function returns a result object without printing. The overview is read-only: it creates no
9
+ * folder and writes no file.
10
+ */
11
+
12
+ const nodeFs = require('node:fs');
13
+ const path = require('node:path');
14
+ const { isSafeSlug, invalidSlugRefusal } = require('../task-scope.js');
15
+ const { withDerivedDone } = require('./fold.js');
16
+ const { appendEvents, readFold, readEvents } = require('./event-store.js');
17
+ const { deriveStatuses, bucketize, behindNote } = require('./status.js');
18
+ const { FollowupUsageError, parseIds } = require('./followup.js');
19
+
20
+ /** The failure store, or null when its module cannot be loaded: the overview then reports no failures. */
21
+ function failureStoreOrNull() {
22
+ try { return require('../failure/store.js'); } catch { return null; }
23
+ }
24
+
25
+ const DISCOVERY_SHOWN = 15;
26
+ const MAINTAIN_SHOWN = 50;
27
+ /** Slugs shown per finished or awaiting-release bucket: those lists only grow, and the overview runs on every brainstorm. */
28
+ const FEATURES_SHOWN = 10;
29
+
30
+ const refusal = (action , finding , message ) => ({ ok: false, action, finding, message });
31
+
32
+ function statusMap(derived ) {
33
+ return Object.fromEntries(Object.entries(derived.statuses).map(([slug, entry] ) => [slug, entry.status]));
34
+ }
35
+
36
+ /** The new and regressed failure entries inside the DoFlow repository, else null. */
37
+ function failureEntries(root ) {
38
+ const failureStore = failureStoreOrNull();
39
+ if (failureStore === null || !failureStore.isDoflowRepo(root)) return null;
40
+ return failureStore.loadEntries().entries.filter((e ) => failureStore.SHOWN_BY_DEFAULT.has(e.status)).map(failureStore.listedEntry);
41
+ }
42
+
43
+ /** Reports first, then the oldest first; the id keeps equal instants in a stable order. */
44
+ function byDiscoveryOrder(a , b ) {
45
+ const report = Number(b.source.kind === 'report') - Number(a.source.kind === 'report');
46
+ if (report !== 0) return report;
47
+ const delta = Date.parse(a.history[0].at) - Date.parse(b.history[0].at);
48
+ return delta !== 0 ? delta : (a.id < b.id ? -1 : 1);
49
+ }
50
+
51
+ function shownItem(item , pending ) {
52
+ const shown = {
53
+ id: item.id, statement: item.statement, source: item.source, state: item.state,
54
+ promoted: Boolean(item.intent), intent: item.intent, added: item.added,
55
+ };
56
+ if (pending !== undefined) shown.pending = pending;
57
+ return shown;
58
+ }
59
+
60
+ /** A bucket for display: the true count and the most recently tracked slugs, in tracking order. */
61
+ function shownBucket(slugs ) {
62
+ const shown = slugs.slice(-FEATURES_SHOWN);
63
+ return { count: slugs.length, shown, more: slugs.length - shown.length };
64
+ }
65
+
66
+ /**
67
+ * The fold's conflicts raised by goal events, by the goal they name. The fold's conflict carries only
68
+ * the event id, so the goal comes from the event; a dropped `goal.item-added` also carries the item id
69
+ * and its text, which is data from another clone, shown but never put into a `next` line. A dropped item
70
+ * whose text the goal now holds (the user re-added it) is no longer reported.
71
+ * @returns {Map<string, Array<Object>>}
72
+ */
73
+ function goalConflicts(root , fold , fsImpl = nodeFs) {
74
+ const byGoal = new Map();
75
+ const raised = fold.conflicts.filter((c ) => typeof c.type === 'string' && c.type.startsWith('goal.'));
76
+ if (raised.length === 0) return byGoal;
77
+ const events = new Map (readEvents(root, { fsImpl }).events.map((e ) => [e.id, e]));
78
+ for (const c of raised) {
79
+ const event = events.get(c.event);
80
+ const goal = event && event.data && event.data.goal;
81
+ if (typeof goal !== 'string') continue;
82
+ const entry = { ...c };
83
+ if (c.type === 'goal.item-added' && event.data.item) {
84
+ // Event files are never removed, so the dropped item is resolved once the goal holds an item with that text.
85
+ const held = fold.goals.find((g ) => g.goal === goal);
86
+ if (held && held.items.some((i ) => i.text === String(event.data.item.text ?? ''))) continue;
87
+ Object.assign(entry, { item: event.data.item.id, text: event.data.item.text });
88
+ }
89
+ if (!byGoal.has(goal)) byGoal.set(goal, []);
90
+ byGoal.get(goal).push(entry);
91
+ }
92
+ return byGoal;
93
+ }
94
+
95
+ /** One `next` line per kind of goal conflict, telling the user how to resolve it. */
96
+ function goalConflictNext(goalId , conflicts ) {
97
+ const next = [];
98
+ if (conflicts.some((c ) => c.type === 'goal.item-added')) {
99
+ next.push(`An item added to ${goalId} from another clone was dropped because its id was already taken; re-add it under a new text: doflow-run goal --action item --goal ${goalId} --text "<the item>"`);
100
+ }
101
+ if (conflicts.some((c ) => c.type === 'goal.added')) {
102
+ next.push(`Another clone added goal ${goalId} first and its record won; re-add yours under a different goal id: doflow-run goal --action add --goal <new-id> --statement "<outcome>"`);
103
+ }
104
+ if (conflicts.some((c ) => c.type !== 'goal.item-added' && c.type !== 'goal.added')) {
105
+ next.push(`An event for ${goalId} from another clone was dropped (see conflicts); redo it with doflow-run goal --action list as the current state`);
106
+ }
107
+ return next;
108
+ }
109
+
110
+ /** Progress, linked features by status and the nudges of one open goal (IC-022). */
111
+ function goalView(goal , features , statuses , conflicts = []) {
112
+ const linked = features.filter((f ) => f.goal === goal.goal).map((f ) => f.slug);
113
+ const buckets = bucketize(Object.fromEntries(linked.map((slug ) => [slug, statuses.statuses[slug] || { status: 'unknown' }])), linked);
114
+ const met = goal.items.filter((i ) => i.met).length;
115
+ const nudges = [];
116
+ if (buckets.finished.length) {
117
+ const who = buckets.finished.length === 1 ? `linked feature ${buckets.finished[0]} is finished` : `linked features ${buckets.finished.join(', ')} are finished`;
118
+ for (const item of goal.items.filter((i ) => !i.met)) nudges.push(`${item.id} is unchecked and ${who}`);
119
+ }
120
+ return {
121
+ goal: goal.goal, outcome: goal.outcome,
122
+ items: { met, total: goal.items.length },
123
+ proposeDone: goal.status === 'open' && goal.items.length > 0 && met === goal.items.length,
124
+ features: buckets,
125
+ nudges,
126
+ ...(conflicts.length ? { conflicts } : {}),
127
+ };
128
+ }
129
+
130
+ function nextLines({ shown, open, items, intents, goals, maintain, pendingItems, failures } ) {
131
+ const next = [];
132
+ if (shown < open) next.push('List the rest: doflow-run followup --action list');
133
+ if (items.length) next.push(`Take items into the new feature when its folder exists: doflow-run lifecycle --action init --slug <slug> --take ${items[0].id}`);
134
+ if (intents.length) {
135
+ next.push(`Start from a promoted intent: /do-brainstorm --intent ${intents[0].path}, then doflow-run lifecycle --action init --slug <slug> --intent ${intents[0].path}`);
136
+ }
137
+ if (goals.length) next.push(`Link the new feature to a goal: add --goal ${goals[0].goal} to the init line`);
138
+ for (const g of goals.filter((x ) => x.conflicts)) next.push(...goalConflictNext(g.goal, g.conflicts));
139
+ if (maintain && pendingItems.length) {
140
+ const id = pendingItems[0].id;
141
+ const unpromoted = pendingItems.find((i ) => !i.promoted);
142
+ next.push(
143
+ `Keep open: doflow-run followup --action settle --ids ${id} --as kept --reason "<why it stays>" --channel question`,
144
+ `Dismiss: doflow-run followup --action settle --ids ${id} --as dismissed --reason "<why>" --channel question`,
145
+ ...(unpromoted ? [`Promote to a new intent: doflow-run followup --action promote --ids ${unpromoted.id} --title "<intent title>" --channel question`] : []),
146
+ `Start a fix: doflow-run followup --action settle --ids ${id} --as fix --reason "<where it is routed>" --channel question`,
147
+ `Done outside a feature: doflow-run followup --action settle --ids ${id} --as done --evidence "<what shows it>" --channel question`,
148
+ );
149
+ }
150
+ if (failures && failures.length) {
151
+ next.push('Settle a failure entry: doflow-run failure --action settle --fp <fp> --as noise|fixed|imported --reason "<why>"');
152
+ }
153
+ if (maintain) {
154
+ for (const goal of goals.filter((g ) => g.proposeDone)) {
155
+ next.push(`Every item of ${goal.goal} is met; ask the user, then: doflow-run goal --action done --goal ${goal.goal} --channel question`);
156
+ }
157
+ }
158
+ return next;
159
+ }
160
+
161
+ /**
162
+ * IC-007.
163
+ * @param {Object} options
164
+ * @param {string} options.root the IC-001 root
165
+ * @param {boolean} [options.maintain] the `/do maintain` view: up to 50 items and `pending`
166
+ * @param {string} [options.since] ISO time; with `maintain`, an open item with no settlement at or after it is pending
167
+ */
168
+ function buildOverview({ root, maintain = false, since, now = new Date(), fsImpl = nodeFs } ) {
169
+ let sinceMs = null;
170
+ if (since !== undefined && since !== null) {
171
+ sinceMs = Date.parse(since);
172
+ if (!Number.isFinite(sinceMs)) throw new FollowupUsageError(`--since must be an ISO time such as 2026-10-05T09:00:00Z (got '${since}')`);
173
+ }
174
+ const fold = readFold(root, { fsImpl, now });
175
+ const derived = deriveStatuses({ root, fold });
176
+ const followups = withDerivedDone(fold.followups, statusMap(derived));
177
+ const open = followups.filter((item ) => item.state === 'open');
178
+ const limit = maintain ? MAINTAIN_SHOWN : DISCOVERY_SHOWN;
179
+ // A promotion settles an item for the maintain loop as much as a settlement does: it stays open with an intent.
180
+ const settledSince = (item ) => item.history.some((h ) => (h.type === 'followup.settled' || h.type === 'followup.promoted') && (sinceMs === null || Date.parse(h.at) >= sinceMs));
181
+ const pendingOf = (item ) => !settledSince(item);
182
+ // Maintain shows pending items first (IC-007), so a loop over more than MAINTAIN_SHOWN items reaches the rest.
183
+ const ordered = [...open].sort((a, b) => (maintain ? Number(pendingOf(b)) - Number(pendingOf(a)) : 0) || byDiscoveryOrder(a, b));
184
+ const items = ordered.slice(0, limit).map((item) => shownItem(item, maintain ? pendingOf(item) : undefined));
185
+
186
+ const intentGroups = new Map();
187
+ for (const item of open.filter((i ) => i.intent)) {
188
+ if (!intentGroups.has(item.intent)) intentGroups.set(item.intent, []);
189
+ intentGroups.get(item.intent).push(item.id);
190
+ }
191
+ const intents = [...intentGroups].map(([intentPath, ids]) => ({ path: intentPath, items: ids }));
192
+ const conflictsByGoal = goalConflicts(root, fold, fsImpl);
193
+ const goals = fold.goals.filter((g ) => g.status === 'open').map((g ) => goalView(g, fold.features, derived, conflictsByGoal.get(g.goal)));
194
+
195
+ const result = {
196
+ ok: true,
197
+ mode: maintain ? 'maintain' : 'discovery',
198
+ releaseMode: derived.releaseMode,
199
+ integrationRef: derived.integrationRef,
200
+ // Follow-ups, goals and intents need no git: when git fails the overview still answers, every feature
201
+ // status `unknown`, and says why here (null when git answered).
202
+ gitError: derived.failure || null,
203
+ followups: { open: open.length, shown: items.length, items },
204
+ intents,
205
+ goals,
206
+ features: derived.features,
207
+ // The bounded form of the two buckets that only grow; `features` keeps every slug (IC-007).
208
+ featuresShown: { finished: shownBucket(derived.features.finished), awaitingRelease: shownBucket(derived.features.awaitingRelease) },
209
+ conflicts: fold.conflicts,
210
+ unreadable: fold.unreadable,
211
+ unreadableReasons: fold.unreadableReasons,
212
+ // Failure entries come from the machine-wide failure store, which only the DoFlow repository's own
213
+ // maintain view reads (IC-023); nothing in the project store feeds it. Read-only: the overview
214
+ // never rotates the failure files, which is the `failure` verb's step.
215
+ failures: maintain ? failureEntries(root) : null,
216
+ };
217
+ if (derived.reason) result.reason = derived.reason;
218
+ if (derived.integrationBehind > 0) Object.assign(result, { integrationBehind: derived.integrationBehind, note: behindNote(derived.integrationRef, derived.integrationBehind) });
219
+ if (maintain) result.pending = open.filter(pendingOf).length;
220
+ result.next = nextLines({ shown: items.length, open: open.length, items, intents, goals, maintain, pendingItems: items.filter((i) => i.pending), failures: result.failures });
221
+ return result;
222
+ }
223
+
224
+ // ── init ───────────────────────────────────────────────────────────────────────────────────────
225
+
226
+ /**
227
+ * IC-008: tracks a feature, optionally taking items and linking a goal, in one all-or-nothing write.
228
+ * The feature folder is looked for under the IC-001 root itself, never in the current worktree, so a
229
+ * folder that exists only in a linked worktree is refused.
230
+ */
231
+ function initFeature({ root, slug, take, intent, goal, now = new Date(), fsImpl = nodeFs } ) {
232
+ if (!slug) throw new FollowupUsageError('--slug is required for --action init');
233
+ const bad = invalidSlugRefusal(slug);
234
+ if (bad) throw new FollowupUsageError(bad.message);
235
+ const folder = path.join(root, 'agent-docs', 'doflow', slug);
236
+ let isFolder = false;
237
+ try { isFolder = fsImpl.statSync(folder).isDirectory(); } catch { /* absent */ }
238
+ if (!isFolder) {
239
+ return refusal('init', 'no-feature-folder', `agent-docs/doflow/${slug} does not exist under the store root ${root}; a folder that exists only in a linked worktree does not count. Nothing was written.`);
240
+ }
241
+ const wanted = take === undefined || take === null ? [] : parseIds(take, '--take');
242
+ const fold = readFold(root, { fsImpl, now });
243
+ const tracked = fold.features.find((f ) => f.slug === slug);
244
+ const ids = [...wanted];
245
+ if (intent) {
246
+ for (const item of fold.followups) if (item.state === 'open' && item.intent === intent && !ids.includes(item.id)) ids.push(item.id);
247
+ }
248
+ const drafts = [];
249
+ if (!tracked) drafts.push({ type: 'feature.tracked', by: 'agent', data: { slug } });
250
+ if (ids.length) drafts.push({ type: 'followup.taken', by: 'agent', data: { ids, feature: slug } });
251
+ if (goal) {
252
+ const current = tracked && tracked.goal;
253
+ if (current && current !== goal) return refusal('init', 'goal-already-linked', `${slug} already serves ${current}; link it with --replace through the goal verb instead. Nothing was written.`);
254
+ if (current !== goal) drafts.push({ type: 'goal.linked', by: 'agent', data: { goal, slug, replace: false } });
255
+ }
256
+ const next = [];
257
+ if (intent && ids.length === 0) next.push(`No open follow-up names ${intent}; nothing was taken from it`);
258
+ let events = [];
259
+ if (drafts.length) {
260
+ const out = appendEvents(root, drafts, { now, fsImpl });
261
+ if (!out.ok) {
262
+ const code = out.conflict && ['unknown-id', 'untracked-feature', 'goal-already-linked', 'unknown-goal'].includes(out.conflict.code) ? out.conflict.code : out.finding;
263
+ return refusal('init', code, out.message);
264
+ }
265
+ events = out.written.map((w ) => w.file);
266
+ }
267
+ return { ok: true, action: 'init', slug, tracked: tracked ? 'already' : 'new', taken: ids, goal: goal || (tracked && tracked.goal) || null, events, next };
268
+ }
269
+
270
+ // ── status ─────────────────────────────────────────────────────────────────────────────────────
271
+
272
+ /** IC-021 `status`: the derived status of one tracked feature. */
273
+ function featureStatus({ root, slug, now = new Date(), fsImpl = nodeFs } ) {
274
+ if (!slug) throw new FollowupUsageError('--slug is required for --action status');
275
+ if (!isSafeSlug(slug)) throw new FollowupUsageError(invalidSlugRefusal(slug).message);
276
+ const fold = readFold(root, { fsImpl, now });
277
+ if (!fold.features.some((f ) => f.slug === slug)) {
278
+ return refusal('status', 'untracked-feature', `${slug} is not a tracked feature, so no status is derived for it; run doflow-run lifecycle --action init --slug ${slug} first.`);
279
+ }
280
+ const derived = deriveStatuses({ root, fold });
281
+ const entry = derived.statuses[slug];
282
+ const result = {
283
+ ok: true, action: 'status', slug, status: entry.status, integrationRef: derived.integrationRef,
284
+ evidence: entry.evidence, release: entry.release,
285
+ takenItems: fold.followups.filter((item ) => item.takenBy === slug && item.state === 'taken').map((item ) => item.id),
286
+ };
287
+ if (derived.reason) result.reason = derived.reason;
288
+ if (derived.integrationBehind > 0) Object.assign(result, { integrationBehind: derived.integrationBehind, note: behindNote(derived.integrationRef, derived.integrationBehind) });
289
+ return result;
290
+ }
291
+
292
+ module.exports = { buildOverview, initFeature, featureStatus, goalView, goalConflicts, goalConflictNext, DISCOVERY_SHOWN, MAINTAIN_SHOWN, FEATURES_SHOWN };