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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (407) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/bin/doflow.js +44 -0
  4. package/core/.antigravity-plugin/hooks.json +39 -0
  5. package/core/.antigravity-plugin/mcp_config.json +18 -0
  6. package/core/.antigravity-plugin/plugin.json +31 -0
  7. package/core/.claude-plugin/marketplace.json +20 -0
  8. package/core/.claude-plugin/plugin.json +21 -0
  9. package/core/.codex-plugin/marketplace.json +20 -0
  10. package/core/.codex-plugin/plugin.json +35 -0
  11. package/core/.plugin/plugin.json +19 -0
  12. package/core/harnesses/antigravity/hooks/pre-implementation-gate.sh +9 -0
  13. package/core/harnesses/antigravity/hooks/stop-check.sh +10 -0
  14. package/core/harnesses/claude/hooks/lib.sh +304 -0
  15. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +20 -0
  16. package/core/harnesses/claude/hooks/permission-audit.sh +47 -0
  17. package/core/harnesses/claude/hooks/post-compact.sh +74 -0
  18. package/core/harnesses/claude/hooks/post-edit-lint.sh +36 -0
  19. package/core/harnesses/claude/hooks/post-tool-failure.sh +47 -0
  20. package/core/harnesses/claude/hooks/pre-bash-guard.sh +20 -0
  21. package/core/harnesses/claude/hooks/pre-compact.sh +43 -0
  22. package/core/harnesses/claude/hooks/pre-implement-gate.sh +21 -0
  23. package/core/harnesses/claude/hooks/session-end.sh +89 -0
  24. package/core/harnesses/claude/hooks/session-start.sh +5 -0
  25. package/core/harnesses/claude/hooks/skill-config-audit.sh +39 -0
  26. package/core/harnesses/claude/hooks/stop-check.sh +5 -0
  27. package/core/harnesses/claude/hooks/subagent-audit.sh +7 -0
  28. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -0
  29. package/core/harnesses/claude/settings/keybindings.json +20 -0
  30. package/core/harnesses/claude/settings/settings.json +201 -0
  31. package/core/harnesses/claude/statusline/doflow-statusline.sh +49 -0
  32. package/core/harnesses/codex/agents/core-implementer.toml +7 -0
  33. package/core/harnesses/codex/agents/quality-guardian.toml +8 -0
  34. package/core/harnesses/codex/agents/research-writer.toml +8 -0
  35. package/core/harnesses/codex/agents/spec-analyst.toml +8 -0
  36. package/core/harnesses/codex/agents/system-architect.toml +9 -0
  37. package/core/harnesses/codex/config/config.toml +10 -0
  38. package/core/harnesses/codex/hooks/hooks.json +122 -0
  39. package/core/harnesses/codex/hooks/lib.sh +335 -0
  40. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +18 -0
  41. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +52 -0
  42. package/core/harnesses/codex/hooks/post-edit-lint.sh +6 -0
  43. package/core/harnesses/codex/hooks/pre-bash-guard.sh +18 -0
  44. package/core/harnesses/codex/hooks/pre-compact.impl.sh +46 -0
  45. package/core/harnesses/codex/hooks/pre-compact.sh +9 -0
  46. package/core/harnesses/codex/hooks/pre-implement-gate.sh +19 -0
  47. package/core/harnesses/codex/hooks/session-end.impl.sh +92 -0
  48. package/core/harnesses/codex/hooks/session-end.sh +5 -0
  49. package/core/harnesses/codex/hooks/session-start.sh +5 -0
  50. package/core/harnesses/codex/hooks/stop-check.sh +8 -0
  51. package/core/harnesses/codex/hooks/subagent-audit.sh +8 -0
  52. package/core/harnesses/codex/hooks/user-prompt-submit.sh +6 -0
  53. package/core/harnesses/gemini/hooks/hooks.json +77 -0
  54. package/core/harnesses/gemini/hooks/lib.sh +302 -0
  55. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +21 -0
  56. package/core/harnesses/gemini/hooks/post-edit-lint.sh +39 -0
  57. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +21 -0
  58. package/core/harnesses/gemini/hooks/pre-compact.sh +38 -0
  59. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +22 -0
  60. package/core/harnesses/gemini/hooks/session-end.impl.sh +89 -0
  61. package/core/harnesses/gemini/hooks/session-end.sh +5 -0
  62. package/core/harnesses/gemini/hooks/session-start.sh +5 -0
  63. package/core/harnesses/gemini/settings/adapter-defaults.json +4 -0
  64. package/core/harnesses/kiro/agents/doflow.json +58 -0
  65. package/core/harnesses/kiro/hooks/doflow.json +53 -0
  66. package/core/harnesses/kiro/hooks/lib.sh +324 -0
  67. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +9 -0
  68. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +9 -0
  69. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +9 -0
  70. package/core/harnesses/kiro/hooks/session-start.sh +9 -0
  71. package/core/harnesses/kiro/hooks/stop-check.sh +43 -0
  72. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +39 -0
  73. package/core/harnesses/shared/hooks/adapters/antigravity.js +111 -0
  74. package/core/harnesses/shared/hooks/adapters/arg-fields.js +19 -0
  75. package/core/harnesses/shared/hooks/adapters/gemini.js +114 -0
  76. package/core/harnesses/shared/hooks/hooks.json +47 -0
  77. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +91 -0
  78. package/core/harnesses/shared/hooks/policies/capture-failure.sh +97 -0
  79. package/core/harnesses/shared/hooks/policies/deny-json.sh +29 -0
  80. package/core/harnesses/shared/hooks/policies/lib.sh +501 -0
  81. package/core/harnesses/shared/hooks/policies/mcp-policy.conf +21 -0
  82. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +91 -0
  83. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +328 -0
  84. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +496 -0
  85. package/core/harnesses/shared/hooks/policies/prompt-nudge.jq +131 -0
  86. package/core/harnesses/shared/hooks/policies/session-context.sh +141 -0
  87. package/core/harnesses/shared/hooks/policies/stop-check.sh +264 -0
  88. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +46 -0
  89. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +235 -0
  90. package/core/harnesses/shared/hooks/stream-hook-runner.js +590 -0
  91. package/core/harnesses/shared/locator/doflow-run +87 -0
  92. package/core/registry/advice.json +101 -0
  93. package/core/registry/assets.json +792 -0
  94. package/core/registry/capabilities.json +120 -0
  95. package/core/registry/contracts.json +189 -0
  96. package/core/registry/external-tools.json +191 -0
  97. package/core/registry/harnesses.json +2062 -0
  98. package/core/registry/lifecycle.json +231 -0
  99. package/core/registry/mcp.json +18 -0
  100. package/core/registry/models.json +79 -0
  101. package/core/registry/readiness-templates.json +118 -0
  102. package/core/registry/routes.json +47 -0
  103. package/core/registry/storage.json +1057 -0
  104. package/core/registry/verification.json +258 -0
  105. package/core/registry/workflows.json +790 -0
  106. package/core/shared/agent-specs/core-implementer.md +73 -0
  107. package/core/shared/agent-specs/quality-guardian.md +28 -0
  108. package/core/shared/agent-specs/research-writer.md +31 -0
  109. package/core/shared/agent-specs/spec-analyst.md +33 -0
  110. package/core/shared/agent-specs/system-architect.md +29 -0
  111. package/core/shared/guidance/DOFLOW_CORE.md +8 -0
  112. package/core/shared/guidance/FLAGS.md +25 -0
  113. package/core/shared/guidance/PRINCIPLES.md +61 -0
  114. package/core/shared/guidance/VERSION +1 -0
  115. package/core/shared/guidance/mcp/MCP_Context7.md +18 -0
  116. package/core/shared/guidance/mcp/MCP_Sequential.md +17 -0
  117. package/core/shared/guidance/modes/MODE_Brainstorming.md +46 -0
  118. package/core/shared/guidance/modes/MODE_DeepResearch.md +69 -0
  119. package/core/shared/guidance/modes/MODE_Introspection.md +33 -0
  120. package/core/shared/guidance/modes/MODE_Orchestration.md +57 -0
  121. package/core/shared/guidance/modes/MODE_Task_Management.md +74 -0
  122. package/core/shared/guidance/pointers/claude-gemini.md +2 -0
  123. package/core/shared/guidance/pointers/codex.md +6 -0
  124. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +373 -0
  125. package/core/shared/guidance/references/ARTIFACT_VOICE.md +104 -0
  126. package/core/shared/guidance/references/CONSTITUTION_BASE.md +47 -0
  127. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +161 -0
  128. package/core/shared/guidance/references/DOFLOW_CHAIN.md +87 -0
  129. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +93 -0
  130. package/core/shared/guidance/references/MODEL_SELECTION.md +74 -0
  131. package/core/shared/guidance/references/ORCHESTRATION.md +36 -0
  132. package/core/shared/guidance/references/RESEARCH_CONFIG.md +21 -0
  133. package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +81 -0
  134. package/core/shared/guidance/rules/RULE_01_SAFETY.md +25 -0
  135. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +23 -0
  136. package/core/shared/guidance/rules/RULE_03_QUALITY.md +14 -0
  137. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +46 -0
  138. package/core/shared/scripts/doflow/assets/mermaid-theme.json +46 -0
  139. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +167 -0
  140. package/core/shared/scripts/doflow/bash/do-git-state.sh +546 -0
  141. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +208 -0
  142. package/core/shared/scripts/doflow/bash/do-paths.sh +347 -0
  143. package/core/shared/scripts/doflow/bash/do-prereqs.sh +66 -0
  144. package/core/shared/scripts/doflow/bash/do-review-package.sh +127 -0
  145. package/core/shared/scripts/doflow/bash/do-task-brief.sh +698 -0
  146. package/core/shared/scripts/doflow/bash/render-audit.sh +224 -0
  147. package/core/shared/scripts/doflow/bash/render-diagrams.sh +177 -0
  148. package/core/shared/scripts/doflow/bash/render-puml.sh +223 -0
  149. package/core/shared/scripts/doflow/bash/sync-context.sh +66 -0
  150. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +526 -0
  151. package/core/shared/scripts/doflow/bin/doflow-run +592 -0
  152. package/core/shared/skills/do/SKILL.md +128 -0
  153. package/core/shared/skills/do/references/estimation.md +13 -0
  154. package/core/shared/skills/do/references/pm_routing.md +25 -0
  155. package/core/shared/skills/do/references/tool_matrix.md +52 -0
  156. package/core/shared/skills/do-brainstorm/SKILL.md +252 -0
  157. package/core/shared/skills/do-code-review/README.md +49 -0
  158. package/core/shared/skills/do-code-review/SKILL.md +333 -0
  159. package/core/shared/skills/do-code-review/assets/sample_c_clean.c +71 -0
  160. package/core/shared/skills/do-code-review/assets/sample_c_smells.c +66 -0
  161. package/core/shared/skills/do-code-review/assets/sample_cpp_shift.cpp +19 -0
  162. package/core/shared/skills/do-code-review/assets/sample_csharp_clean.cs +100 -0
  163. package/core/shared/skills/do-code-review/assets/sample_csharp_smells.cs +78 -0
  164. package/core/shared/skills/do-code-review/assets/sample_java_clean.java +55 -0
  165. package/core/shared/skills/do-code-review/assets/sample_java_smells.java +56 -0
  166. package/core/shared/skills/do-code-review/assets/sample_json_clean.json +7 -0
  167. package/core/shared/skills/do-code-review/assets/sample_markdown_clean.md +30 -0
  168. package/core/shared/skills/do-code-review/assets/sample_markdown_smells.md +29 -0
  169. package/core/shared/skills/do-code-review/assets/sample_shell_heredoc.sh +38 -0
  170. package/core/shared/skills/do-code-review/assets/sample_shell_smells.sh +15 -0
  171. package/core/shared/skills/do-code-review/assets/sample_typescript_strings.ts +36 -0
  172. package/core/shared/skills/do-code-review/assets/sample_yaml_smells.yaml +6 -0
  173. package/core/shared/skills/do-code-review/content-types/config.md +83 -0
  174. package/core/shared/skills/do-code-review/content-types/markdown.md +132 -0
  175. package/core/shared/skills/do-code-review/expected_outputs/sample_c_clean_quality.json +79 -0
  176. package/core/shared/skills/do-code-review/expected_outputs/sample_c_smells_quality.json +169 -0
  177. package/core/shared/skills/do-code-review/expected_outputs/sample_cpp_shift_quality.json +49 -0
  178. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_clean_quality.json +149 -0
  179. package/core/shared/skills/do-code-review/expected_outputs/sample_csharp_smells_quality.json +157 -0
  180. package/core/shared/skills/do-code-review/expected_outputs/sample_java_clean_quality.json +67 -0
  181. package/core/shared/skills/do-code-review/expected_outputs/sample_java_smells_quality.json +97 -0
  182. package/core/shared/skills/do-code-review/expected_outputs/sample_json_clean_quality.json +33 -0
  183. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_clean_quality.json +1 -0
  184. package/core/shared/skills/do-code-review/expected_outputs/sample_markdown_smells_quality.json +16 -0
  185. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_heredoc_quality.json +54 -0
  186. package/core/shared/skills/do-code-review/expected_outputs/sample_shell_smells_quality.json +91 -0
  187. package/core/shared/skills/do-code-review/expected_outputs/sample_typescript_strings_quality.json +60 -0
  188. package/core/shared/skills/do-code-review/expected_outputs/sample_yaml_smells_quality.json +46 -0
  189. package/core/shared/skills/do-code-review/languages/c.md +103 -0
  190. package/core/shared/skills/do-code-review/languages/cpp.md +103 -0
  191. package/core/shared/skills/do-code-review/languages/csharp.md +97 -0
  192. package/core/shared/skills/do-code-review/languages/dart.md +108 -0
  193. package/core/shared/skills/do-code-review/languages/go.md +92 -0
  194. package/core/shared/skills/do-code-review/languages/java.md +103 -0
  195. package/core/shared/skills/do-code-review/languages/kotlin.md +88 -0
  196. package/core/shared/skills/do-code-review/languages/php.md +109 -0
  197. package/core/shared/skills/do-code-review/languages/python.md +94 -0
  198. package/core/shared/skills/do-code-review/languages/ruby.md +105 -0
  199. package/core/shared/skills/do-code-review/languages/rust.md +99 -0
  200. package/core/shared/skills/do-code-review/languages/shell.md +90 -0
  201. package/core/shared/skills/do-code-review/languages/swift.md +88 -0
  202. package/core/shared/skills/do-code-review/languages/typescript.md +97 -0
  203. package/core/shared/skills/do-code-review/review-policy.json +47 -0
  204. package/core/shared/skills/do-code-review/rules/universal.md +49 -0
  205. package/core/shared/skills/do-code-review/scripts/code_quality_checker.py +1696 -0
  206. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +532 -0
  207. package/core/shared/skills/do-code-review/scripts/pr_analyzer.py +620 -0
  208. package/core/shared/skills/do-code-review/scripts/review_report_generator.py +505 -0
  209. package/core/shared/skills/do-constitution/SKILL.md +72 -0
  210. package/core/shared/skills/do-design/SKILL.md +212 -0
  211. package/core/shared/skills/do-diagnose/SKILL.md +156 -0
  212. package/core/shared/skills/do-diagnose/references/code_audit.md +15 -0
  213. package/core/shared/skills/do-diagnose/references/refactoring.md +12 -0
  214. package/core/shared/skills/do-diagnose/references/root_cause.md +17 -0
  215. package/core/shared/skills/do-document/SKILL.md +81 -0
  216. package/core/shared/skills/do-document/references/api-reference.md +42 -0
  217. package/core/shared/skills/do-document/references/deep_research.md +18 -0
  218. package/core/shared/skills/do-document/references/embedded-research.md +11 -0
  219. package/core/shared/skills/do-document/references/implementation-flow.md +105 -0
  220. package/core/shared/skills/do-document/references/index_generator.md +9 -0
  221. package/core/shared/skills/do-document/references/user-guide.md +22 -0
  222. package/core/shared/skills/do-execute-plan/SKILL.md +261 -0
  223. package/core/shared/skills/do-execute-plan/references/handoff_record.md +57 -0
  224. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +92 -0
  225. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +155 -0
  226. package/core/shared/skills/do-execute-plan/references/run_position.md +63 -0
  227. package/core/shared/skills/do-execute-plan/references/scaffold.md +441 -0
  228. package/core/shared/skills/do-execute-plan/references/task_attempts.md +41 -0
  229. package/core/shared/skills/do-flow/SKILL.md +186 -0
  230. package/core/shared/skills/do-flow/references/task_classes.md +134 -0
  231. package/core/shared/skills/do-git/SKILL.md +115 -0
  232. package/core/shared/skills/do-git/references/hotfix-ritual.md +44 -0
  233. package/core/shared/skills/do-git/references/lifecycle-policy.md +54 -0
  234. package/core/shared/skills/do-git/references/release-ritual.md +64 -0
  235. package/core/shared/skills/do-implement/SKILL.md +222 -0
  236. package/core/shared/skills/do-plan/SKILL.md +219 -0
  237. package/core/shared/skills/do-test/SKILL.md +113 -0
  238. package/core/shared/templates/doflow/constitution-template.md +28 -0
  239. package/core/shared/templates/doflow/data-model-template.md +169 -0
  240. package/core/shared/templates/doflow/design-template.md +193 -0
  241. package/core/shared/templates/doflow/external-contract-template.md +35 -0
  242. package/core/shared/templates/doflow/intent-template.md +45 -0
  243. package/core/shared/templates/doflow/plan-template.md +151 -0
  244. package/core/shared/templates/doflow/question-log-template.md +51 -0
  245. package/core/shared/templates/doflow/requirement-template.md +126 -0
  246. package/core/shared/templates/doflow/specs-template.md +55 -0
  247. package/core/shared/templates/doflow/state-template.md +59 -0
  248. package/core/shared/workflows/do-flow-chain.md +17 -0
  249. package/package.json +46 -4
  250. package/src/adapters/antigravity/index.ts +631 -0
  251. package/src/adapters/claude/index.ts +548 -0
  252. package/src/adapters/claude/mcp.ts +94 -0
  253. package/src/adapters/codex/agents.ts +208 -0
  254. package/src/adapters/codex/config.ts +272 -0
  255. package/src/adapters/codex/hooks.ts +203 -0
  256. package/src/adapters/codex/index.ts +707 -0
  257. package/src/adapters/codex/mcp.ts +271 -0
  258. package/src/adapters/codex/native-projection.ts +34 -0
  259. package/src/adapters/copilot/index.ts +413 -0
  260. package/src/adapters/copy-tree.ts +620 -0
  261. package/src/adapters/gemini/hooks.ts +210 -0
  262. package/src/adapters/gemini/index.ts +390 -0
  263. package/src/adapters/hook-commands.ts +60 -0
  264. package/src/adapters/index.ts +106 -0
  265. package/src/adapters/kiro/index.ts +310 -0
  266. package/src/adapters/mcp-entries.ts +331 -0
  267. package/src/adapters/opencode/index.ts +466 -0
  268. package/src/adapters/pi/index.ts +279 -0
  269. package/src/adapters/pi/json-members.ts +185 -0
  270. package/src/adapters/pi/mcp.ts +373 -0
  271. package/src/adapters/resolve-source.ts +19 -0
  272. package/src/cli/commands/install.ts +218 -0
  273. package/src/cli/commands/list-backups.ts +21 -0
  274. package/src/cli/commands/reconcile.ts +120 -0
  275. package/src/cli/commands/remove.ts +82 -0
  276. package/src/cli/commands/rollback.ts +172 -0
  277. package/src/cli/commands/self-update.ts +33 -0
  278. package/src/cli/commands/status.ts +138 -0
  279. package/src/cli/commands/tools.ts +92 -0
  280. package/src/cli/commands/update.ts +162 -0
  281. package/src/cli/index.ts +568 -0
  282. package/src/cli/runtime-commands.ts +281 -0
  283. package/src/cli/shared.ts +218 -0
  284. package/src/helper/bash-helper.ts +34 -0
  285. package/src/helper/git.ts +24 -0
  286. package/src/helper/guardrails.ts +49 -0
  287. package/src/helper/harness-paths.ts +140 -0
  288. package/src/helper/marker-merge.ts +116 -0
  289. package/src/helper/prompt.ts +61 -0
  290. package/src/helper/repo-root.ts +29 -0
  291. package/src/helper/settings-merge.ts +151 -0
  292. package/src/helper/settings-scope.ts +33 -0
  293. package/src/helper/toml.ts +244 -0
  294. package/src/install/backup-set.ts +149 -0
  295. package/src/install/backup.ts +724 -0
  296. package/src/install/context.ts +58 -0
  297. package/src/install/manifest.ts +145 -0
  298. package/src/install/mcp.ts +254 -0
  299. package/src/install/paths.ts +84 -0
  300. package/src/install/targets.ts +90 -0
  301. package/src/install/tool-lifecycle.ts +151 -0
  302. package/src/lifecycle/bash-availability.ts +25 -0
  303. package/src/lifecycle/index.ts +812 -0
  304. package/src/lifecycle/mcp-index.ts +28 -0
  305. package/src/lifecycle/policies.ts +73 -0
  306. package/src/lifecycle/view.ts +175 -0
  307. package/src/registry/index.ts +611 -0
  308. package/src/release/sync-plugin-versions.ts +66 -0
  309. package/src/runtime/advice/cli.ts +283 -0
  310. package/src/runtime/advice/client.ts +324 -0
  311. package/src/runtime/advice/config.ts +189 -0
  312. package/src/runtime/advice/doctor.ts +169 -0
  313. package/src/runtime/advice/endpoint-guard.ts +134 -0
  314. package/src/runtime/advice/kinds.ts +154 -0
  315. package/src/runtime/advice/providers/ollama-systemone.ts +209 -0
  316. package/src/runtime/advice/record.ts +166 -0
  317. package/src/runtime/advice/registry.ts +170 -0
  318. package/src/runtime/advice/strength.ts +66 -0
  319. package/src/runtime/brief-tools.ts +342 -0
  320. package/src/runtime/c4-project.ts +371 -0
  321. package/src/runtime/capability-router.ts +484 -0
  322. package/src/runtime/checkouts.ts +170 -0
  323. package/src/runtime/claims.ts +468 -0
  324. package/src/runtime/cli-result.ts +55 -0
  325. package/src/runtime/cli.ts +678 -0
  326. package/src/runtime/command-detect.ts +516 -0
  327. package/src/runtime/context-pack.ts +492 -0
  328. package/src/runtime/decision-register.ts +493 -0
  329. package/src/runtime/evidence-ledger.ts +338 -0
  330. package/src/runtime/failure/capture.ts +272 -0
  331. package/src/runtime/failure/classifier.ts +68 -0
  332. package/src/runtime/failure/cli.ts +178 -0
  333. package/src/runtime/failure/home.ts +64 -0
  334. package/src/runtime/failure/store.ts +226 -0
  335. package/src/runtime/feature-resolve.ts +79 -0
  336. package/src/runtime/freshness.ts +177 -0
  337. package/src/runtime/health.ts +863 -0
  338. package/src/runtime/history-compactor.ts +237 -0
  339. package/src/runtime/implementation-gate.ts +321 -0
  340. package/src/runtime/inventory/coverage.ts +218 -0
  341. package/src/runtime/inventory/identity.ts +65 -0
  342. package/src/runtime/inventory/index.ts +705 -0
  343. package/src/runtime/inventory/precedence.ts +134 -0
  344. package/src/runtime/inventory/read-scopes.ts +131 -0
  345. package/src/runtime/inventory/siblings.ts +192 -0
  346. package/src/runtime/knowledge/chunker.ts +124 -0
  347. package/src/runtime/knowledge/hybrid.ts +149 -0
  348. package/src/runtime/knowledge/index-store.ts +145 -0
  349. package/src/runtime/knowledge/retrieval.ts +194 -0
  350. package/src/runtime/leak-scan.ts +185 -0
  351. package/src/runtime/lifecycle/cli.ts +276 -0
  352. package/src/runtime/lifecycle/event-store.ts +340 -0
  353. package/src/runtime/lifecycle/fold.ts +353 -0
  354. package/src/runtime/lifecycle/followup.ts +432 -0
  355. package/src/runtime/lifecycle/goal.ts +249 -0
  356. package/src/runtime/lifecycle/intent-writer.ts +86 -0
  357. package/src/runtime/lifecycle/overview.ts +292 -0
  358. package/src/runtime/lifecycle/release.ts +241 -0
  359. package/src/runtime/lifecycle/report-store.ts +221 -0
  360. package/src/runtime/lifecycle/retention.ts +212 -0
  361. package/src/runtime/lifecycle/root.ts +71 -0
  362. package/src/runtime/lifecycle/status.ts +353 -0
  363. package/src/runtime/lifecycle/store-upkeep.ts +393 -0
  364. package/src/runtime/locator-resolve.ts +158 -0
  365. package/src/runtime/mask.ts +210 -0
  366. package/src/runtime/mcp-consultations.ts +412 -0
  367. package/src/runtime/mcp-reach.ts +157 -0
  368. package/src/runtime/model-router.ts +153 -0
  369. package/src/runtime/outcome.ts +467 -0
  370. package/src/runtime/reach.ts +148 -0
  371. package/src/runtime/readiness-record.ts +150 -0
  372. package/src/runtime/readiness.ts +435 -0
  373. package/src/runtime/recovery.ts +282 -0
  374. package/src/runtime/research-request.ts +231 -0
  375. package/src/runtime/retrieval-plan.ts +578 -0
  376. package/src/runtime/scaffold/artifacts.ts +217 -0
  377. package/src/runtime/scaffold/fingerprint.ts +56 -0
  378. package/src/runtime/scaffold/generate.ts +911 -0
  379. package/src/runtime/scaffold/languages.ts +245 -0
  380. package/src/runtime/storage/cli.ts +64 -0
  381. package/src/runtime/storage/engine.ts +249 -0
  382. package/src/runtime/storage/registry.ts +130 -0
  383. package/src/runtime/storage/roots.ts +63 -0
  384. package/src/runtime/storage/rules.ts +282 -0
  385. package/src/runtime/storage/upkeep.ts +243 -0
  386. package/src/runtime/storage/work-status.ts +326 -0
  387. package/src/runtime/task-attempts.ts +415 -0
  388. package/src/runtime/task-classifier.ts +550 -0
  389. package/src/runtime/task-scope.ts +130 -0
  390. package/src/runtime/task-state.ts +176 -0
  391. package/src/runtime/trace/indicators.ts +235 -0
  392. package/src/runtime/trace/ledger.ts +626 -0
  393. package/src/runtime/trace/render.ts +76 -0
  394. package/src/runtime/trace/views.ts +459 -0
  395. package/src/runtime/verification/contract-runner.ts +341 -0
  396. package/src/runtime/verification/engine.ts +1461 -0
  397. package/src/runtime/verification/registry.ts +130 -0
  398. package/src/runtime/verification/scope-bound.ts +418 -0
  399. package/src/runtime/verification/test-selection.ts +172 -0
  400. package/src/runtime/workflow-engine.ts +588 -0
  401. package/src/runtime/workflow-orchestrator.ts +953 -0
  402. package/src/runtime/worktree.ts +340 -0
  403. package/src/state/index.ts +193 -0
  404. package/src/state/lockfile.ts +136 -0
  405. package/src/state/run-lock.ts +408 -0
  406. package/types/node-commonjs.d.ts +68 -0
  407. package/types/runtime.d.ts +91 -0
@@ -0,0 +1,224 @@
1
+ #!/usr/bin/env bash
2
+ # render-audit.sh — projects the orchestrator's own journal into a human-readable audit.md.
3
+ #
4
+ # audit.md is generated, never hand-authored: this script deterministically OVERWRITES it in full
5
+ # from `.doflow/state/orchestration/<slug>.json`'s `history[]` array (WorkflowOrchestrator's own
6
+ # journal — the single source of truth for stage/gate handoffs), one markdown table row per
7
+ # entry. It never appends across runs and never reads the previous audit.md — a stale row can
8
+ # only come from a stale journal, never from this script's own accumulation.
9
+ #
10
+ # Fail-open, matching validate-artifacts.sh's own convention: no active feature, no resolver, or
11
+ # no orchestration run recorded yet are all reported via a note and exit 0 — never a hard error,
12
+ # and no audit.md is written when there is nothing to render.
13
+ #
14
+ # Usage: render-audit.sh [--slug=<slug>] [--json]
15
+ # No --slug → resolve the active feature via do-paths.sh, exactly like validate-artifacts.sh.
16
+
17
+ set -uo pipefail
18
+
19
+ emit_json=false
20
+ slug_override=""
21
+ bad_args=()
22
+ for a in "$@"; do
23
+ case "$a" in
24
+ --json) emit_json=true ;;
25
+ --slug=*) slug_override="${a#--slug=}" ;;
26
+ *) bad_args+=("$a") ;;
27
+ esac
28
+ done
29
+ # An unrecognized argument previously fell through silently — including the space-separated
30
+ # `--slug value` form (only `--slug=value` is accepted), which meant a typo rendered the ACTIVE
31
+ # feature's audit.md instead of the one the caller named, with no signal that anything went wrong.
32
+ if [ "${#bad_args[@]}" -gt 0 ]; then
33
+ args_joined="${bad_args[*]}"
34
+ if [ "$emit_json" = true ]; then
35
+ # Built with jq, not `printf '%s'` interpolation: bad_args is raw argv with no escaping applied
36
+ # to it, and an argument containing a `"` previously produced a document with a duplicate "ok"
37
+ # key that JSON resolves to true — inverting this hard failure into an apparent success for any
38
+ # caller branching on .ok. jq is checked for below the arg-parsing block, not above it, so a
39
+ # minimally-escaped fallback covers the case this runs before that check does.
40
+ if command -v jq >/dev/null 2>&1; then
41
+ jq -nc --arg args "$args_joined" '{"ok":false,"error":"unrecognized-argument","args":$args}' >&2
42
+ else
43
+ esc_args=$(printf '%s' "$args_joined" | sed 's/\\/\\\\/g; s/"/\\"/g')
44
+ printf '{"ok":false,"error":"unrecognized-argument","args":"%s"}\n' "$esc_args" >&2
45
+ fi
46
+ else
47
+ printf 'render-audit: unrecognized argument(s): %s (use --slug=<value>, not --slug <value>)\n' "$args_joined" >&2
48
+ fi
49
+ exit 2
50
+ fi
51
+
52
+ note() {
53
+ if [ "$emit_json" = true ]; then
54
+ printf '{"ok":true,"note":"%s","written":false}\n' "$1"
55
+ else
56
+ printf 'render-audit: %s (audit.md not written)\n' "$1"
57
+ fi
58
+ exit 0
59
+ }
60
+
61
+ command -v jq >/dev/null 2>&1 || note "jq-absent"
62
+
63
+ script_dir="$(cd "$(dirname "$0")" && pwd)"
64
+ RESOLVER="$script_dir/do-paths.sh"
65
+ [ -f "$RESOLVER" ] || RESOLVER="${DOFLOW_CONFIG_DIR:+$DOFLOW_CONFIG_DIR/scripts/doflow/bash/do-paths.sh}"
66
+ [ -f "$RESOLVER" ] || note "resolver-absent"
67
+
68
+ resolver_args=(--json)
69
+ [ -n "$slug_override" ] && resolver_args+=("--slug=$slug_override")
70
+ rc=0; resolved_json=$(bash "$RESOLVER" "${resolver_args[@]}" 2>/dev/null) || rc=$?
71
+ # An unsafe slug is refused, not noted: exit 2 with the resolver's error object (IC-009).
72
+ if [ "$rc" -eq 2 ] && [ "$(printf '%s' "$resolved_json" | jq -r '.error // empty' 2>/dev/null)" = "invalid-slug" ]; then
73
+ if [ "$emit_json" = true ]; then printf '%s\n' "$resolved_json"; else printf 'render-audit: %s\n' "$(printf '%s' "$resolved_json" | jq -r '.message')" >&2; fi
74
+ exit 2
75
+ fi
76
+ [ "$rc" -eq 0 ] || note "resolver-error"
77
+
78
+ repo_root=$(printf '%s' "$resolved_json" | jq -r '.repo_root // empty')
79
+ feature_slug=$(printf '%s' "$resolved_json" | jq -r '.feature_slug // empty')
80
+ audit_rel=$(printf '%s' "$resolved_json" | jq -r '.audit // empty')
81
+ [ -n "$feature_slug" ] || note "no-active-feature"
82
+
83
+ # WorkflowOrchestrator's own stateDir/runFile convention (workflow-orchestrator.ts):
84
+ # stateDir = <root>/.doflow/state/orchestration ; runFile = <stateDir>/<taskId>.json
85
+ journal_file="$repo_root/.doflow/state/orchestration/$feature_slug.json"
86
+ [ -f "$journal_file" ] || note "no orchestration run recorded yet"
87
+
88
+ jq -e '.history | type == "array"' "$journal_file" >/dev/null 2>&1 || note "journal-unreadable"
89
+
90
+ # Consumed from the resolver's own `audit` field rather than recomputed by hand — do-paths.sh is
91
+ # the single source of truth for this path. Unlike requirement/design/plan, `audit` is set once,
92
+ # unconditionally, before the legacy/structured layout branch, so it never varies by layout — but
93
+ # reading it from the resolver rather than recomputing it here still means a future resolver change
94
+ # has one place to land, not two that can drift apart.
95
+ [ -n "$audit_rel" ] || note "resolver-missing-audit-path"
96
+ audit_file="$repo_root/$audit_rel"
97
+
98
+ # Render to a temp file first, then move it into place. `>"$audit_file"` truncates the target
99
+ # before the group runs, and with no `-e` a mid-stream jq abort (a malformed or hand-corrupted
100
+ # journal field) would otherwise leave a header-only audit.md behind while this script still
101
+ # reported written:true. Same fail-open philosophy as the notes above: report a clear failure
102
+ # rather than corrupt the target.
103
+ tmp_audit="$(mktemp "${audit_file}.XXXXXX" 2>/dev/null || mktemp)"
104
+ # Every mktemp needs a matching cleanup that runs on the error paths AND on interruption — the
105
+ # explicit `rm -f "$tmp_audit"` below each failure branch does not cover a SIGINT/SIGTERM landing
106
+ # between here and the `mv`, which would otherwise leave a stray `audit.md.XXXXXX` behind inside
107
+ # the feature directory. The trap is a backstop: each failure branch below still removes it
108
+ # immediately on its own path (so a checked exit doesn't wait for the shell to unwind), and this
109
+ # catches everything else. Once the `mv` succeeds, $tmp_audit no longer exists, so a post-mv `rm -f`
110
+ # here is a correctly-silent no-op.
111
+ trap 'rm -f "$tmp_audit"' EXIT
112
+
113
+ # ── Workflow state diagram (034 IC-006) ───────────────────────────────────────────────────────
114
+ # Drawn from the SAME journal the table below is built from -- `program[]` carries the resolved
115
+ # stage/gate sequence with per-node status, and `cursor` marks where the run actually is. Because
116
+ # both come from one source, the picture cannot disagree with the rows beneath it.
117
+ #
118
+ # Stages and gates only, never individual history entries: that bounds the node count by the
119
+ # workflow definition rather than by how long the run has been going, which is what keeps this
120
+ # inside DIAGRAM_GUIDE.md's own stateDiagram-v2 ceiling.
121
+ #
122
+ # Ids are sanitized (`gate-0` -> `gate_0`) because stateDiagram-v2 reads a hyphen as syntax; the
123
+ # human-readable id is restored via the `state : label` form. A journal with no program[] emits
124
+ # nothing at all rather than an empty fence -- an empty capture here is success, not failure.
125
+ #
126
+ # RENDERED HERE, OUTSIDE the brace group below, and its status checked on the spot. Inside that
127
+ # group it could not be checked at all: `rc=$?` after the group observes only the group's LAST
128
+ # command (the history-table jq), so a failure in this one was invisible -- it emitted three lines,
129
+ # aborted, and left an unterminated ```mermaid fence with the entire history table swallowed inside
130
+ # it, while the script reported written:true and exited 0. The `[ ! -s "$tmp_audit" ]` guard did not
131
+ # catch it either, because the table still wrote bytes. Capturing first means a malformed program[]
132
+ # takes the same clear-failure path every other malformed-journal case already takes, which is what
133
+ # this script's own header promises: report a clear failure rather than corrupt the target.
134
+ if ! state_diagram="$(jq -r '
135
+ def sid: gsub("-"; "_");
136
+ (.cursor // -1) as $cur |
137
+ (.program // []) as $p |
138
+ if ($p | length) == 0 then empty else
139
+ "```mermaid",
140
+ "stateDiagram-v2",
141
+ " direction LR",
142
+ " [*] --> " + ($p[0].id | sid),
143
+ ( [range(0; ($p|length)-1)] | .[] | " " + ($p[.].id|sid) + " --> " + ($p[.+1].id|sid)
144
+ + (if $p[.].type == "gate" and ($p[.].decision // "") != "" then " : " + $p[.].decision else "" end) ),
145
+ " " + ($p[-1].id|sid) + " --> [*]",
146
+ "",
147
+ ( [range(0; $p|length)] | .[] as $i | $p[$i] |
148
+ " " + (.id|sid) + " : " + .id
149
+ + (if $i == $cur then " (current)"
150
+ elif .status == "completed" then " (done)"
151
+ elif .status == "approved" then " (approved)"
152
+ elif .status == "rejected" then " (rejected)"
153
+ else " (pending)" end) ),
154
+ "```",
155
+ ""
156
+ end
157
+ ' "$journal_file" 2>/dev/null)"; then
158
+ rm -f "$tmp_audit"
159
+ if [ "$emit_json" = true ]; then
160
+ printf '{"ok":false,"error":"state-diagram-render-failed","path":"%s"}\n' "$audit_rel" >&2
161
+ else
162
+ printf 'render-audit: failed to render the state diagram for %s (journal program[] may be malformed)\n' "$audit_rel" >&2
163
+ fi
164
+ exit 1
165
+ fi
166
+
167
+ {
168
+ printf '# Audit Trail: %s\n\n' "$feature_slug"
169
+ printf '> Generated by `render-audit.sh` from `.doflow/state/orchestration/%s.json`. Do not\n' "$feature_slug"
170
+ printf '> hand-edit — this file is fully regenerated from the orchestration journal on every run.\n\n'
171
+ [ -n "$state_diagram" ] && printf '%s\n\n' "$state_diagram"
172
+ printf '| Date | Stage/Gate | Action | Note / Reason | Flags |\n'
173
+ printf '|---|---|---|---|---|\n'
174
+ jq -r '
175
+ def esc: if . == null then "" else (tostring | gsub("\\\\"; "\\\\") | gsub("\\|"; "\\|") | gsub("\r\n|\n|\r"; " ")) end;
176
+ .history[] |
177
+ (.node // "—") as $stage |
178
+ (if .action == "decide-gate" and (.detail != null and .detail != "")
179
+ then .action + " (" + .detail + ")"
180
+ else .action end) as $action |
181
+ ((.note // "") | esc) as $note_raw |
182
+ ((.detail // "") | esc) as $detail_raw |
183
+ (if $note_raw != "" then $note_raw
184
+ elif .action == "start" and $detail_raw != "" then $detail_raw
185
+ else "—" end) as $note |
186
+ (if .forced == true then "⚠ FORCED"
187
+ elif .backfilled == true then "⚡ BACKFILLED"
188
+ else "" end) as $flags |
189
+ "| " + (.at | esc) + " | " + ($stage | esc) + " | " + ($action | esc) + " | " + $note + " | " + $flags + " |"
190
+ ' "$journal_file"
191
+ } >"$tmp_audit"
192
+ rc=$?
193
+ if [ "$rc" -ne 0 ] || [ ! -s "$tmp_audit" ]; then
194
+ rm -f "$tmp_audit"
195
+ if [ "$emit_json" = true ]; then
196
+ printf '{"ok":false,"error":"render-failed","path":"%s"}\n' "$audit_rel" >&2
197
+ else
198
+ printf 'render-audit: failed to render %s (journal may be malformed)\n' "$audit_rel" >&2
199
+ fi
200
+ exit 1
201
+ fi
202
+ chmod 644 "$tmp_audit" 2>/dev/null || true
203
+ # Unchecked, an mv failure (target dir missing — do-brainstorm's own state before its step 4
204
+ # mkdir, say — or a permissions problem) left the rendered content stranded in $tmp_audit while
205
+ # this script still reported written:true and exit 0: a false success from the one tool whose job
206
+ # is a trustworthy provenance record.
207
+ mv_err="$(mv "$tmp_audit" "$audit_file" 2>&1)"
208
+ mv_rc=$?
209
+ if [ "$mv_rc" -ne 0 ]; then
210
+ rm -f "$tmp_audit"
211
+ if [ "$emit_json" = true ]; then
212
+ printf '{"ok":false,"error":"move-failed","path":"%s"}\n' "$audit_rel" >&2
213
+ else
214
+ printf 'render-audit: failed to write %s (%s)\n' "$audit_rel" "$mv_err" >&2
215
+ fi
216
+ exit 1
217
+ fi
218
+
219
+ if [ "$emit_json" = true ]; then
220
+ printf '{"ok":true,"written":true,"path":"%s"}\n' "$audit_rel"
221
+ else
222
+ printf 'render-audit: wrote %s\n' "$audit_rel"
223
+ fi
224
+ exit 0
@@ -0,0 +1,177 @@
1
+ #!/usr/bin/env bash
2
+ # render-diagrams.sh — renders a chain artifact's fenced mermaid blocks to image files.
3
+ #
4
+ # A FAITHFUL render: the same nodes, the same edges, the same labels. The committed markdown is the
5
+ # only source of truth and is never modified, and no fenced block anywhere carries a theme
6
+ # directive — styling is supplied at render time from assets/mermaid-theme.json, so the source an
7
+ # author wrote is the source a reviewer diffs.
8
+ #
9
+ # Fail-open, matching render-audit.sh's own convention: a missing renderer, no active feature, and
10
+ # an artifact carrying no diagram are each reported via a note and exit 0 — never a hard error. This
11
+ # verb sits OUTSIDE the chain (034 FR-011: user-invoked only, never a stage), so an absent optional
12
+ # capability must never read as a broken run. Exit 1 is reserved for a renderer that ran and failed.
13
+ #
14
+ # Usage: render-diagrams.sh [--slug=<slug>] [--artifact=<path>] [--format=png|svg] [--json]
15
+ # No --slug → resolve the active feature via do-paths.sh, exactly like render-audit.sh.
16
+ # No --artifact → every chain artifact in the feature dir that contains a fenced mermaid block.
17
+
18
+ set -uo pipefail
19
+
20
+ emit_json=false
21
+ slug_override=""
22
+ artifact_override=""
23
+ format="png"
24
+ bad_args=()
25
+ for a in "$@"; do
26
+ case "$a" in
27
+ --json) emit_json=true ;;
28
+ --slug=*) slug_override="${a#--slug=}" ;;
29
+ --artifact=*) artifact_override="${a#--artifact=}" ;;
30
+ --format=*) format="${a#--format=}" ;;
31
+ *) bad_args+=("$a") ;;
32
+ esac
33
+ done
34
+
35
+ # An unrecognized argument is a hard failure naming it, never a silent fall-through: the
36
+ # space-separated `--slug value` form would otherwise render the ACTIVE feature instead of the one
37
+ # the caller named, with no signal. Same reasoning, same handling, as render-audit.sh.
38
+ if [ "${#bad_args[@]}" -gt 0 ]; then
39
+ printf 'render-diagrams: unrecognized argument(s): %s\n' "${bad_args[*]}" >&2
40
+ printf 'usage: render-diagrams.sh [--slug=<slug>] [--artifact=<path>] [--format=png|svg] [--json]\n' >&2
41
+ exit 2
42
+ fi
43
+ case "$format" in
44
+ png|svg) ;;
45
+ *) printf 'render-diagrams: --format must be png or svg, got: %s\n' "$format" >&2; exit 2 ;;
46
+ esac
47
+
48
+ # Fail-open note: report and exit 0. Nothing downstream may treat an absent capability as failure.
49
+ note() {
50
+ if [ "$emit_json" = true ]; then
51
+ if command -v jq >/dev/null 2>&1; then
52
+ jq -n --arg n "$1" '{ok:true, written:false, note:$n, rendered:[]}'
53
+ else
54
+ printf '{"ok":true,"written":false,"note":"%s","rendered":[]}\n' "$1"
55
+ fi
56
+ else
57
+ printf 'render-diagrams: %s (nothing rendered)\n' "$1"
58
+ fi
59
+ exit 0
60
+ }
61
+
62
+ command -v jq >/dev/null 2>&1 || note "jq-absent"
63
+
64
+ script_dir="$(cd "$(dirname "$0")" && pwd)"
65
+ RESOLVER="$script_dir/do-paths.sh"
66
+ [ -f "$RESOLVER" ] || RESOLVER="${DOFLOW_CONFIG_DIR:+$DOFLOW_CONFIG_DIR/scripts/doflow/bash/do-paths.sh}"
67
+ [ -f "$RESOLVER" ] || note "resolver-absent"
68
+
69
+ # The theme sits beside this script's own tree, not in the repo being rendered.
70
+ THEME="$script_dir/../assets/mermaid-theme.json"
71
+
72
+ resolver_args=(--json)
73
+ [ -n "$slug_override" ] && resolver_args+=("--slug=$slug_override")
74
+ rc=0; resolved_json=$(bash "$RESOLVER" "${resolver_args[@]}" 2>/dev/null) || rc=$?
75
+ # An unsafe slug is refused, not noted: exit 2 with the resolver's error object (IC-009).
76
+ if [ "$rc" -eq 2 ] && [ "$(printf '%s' "$resolved_json" | jq -r '.error // empty' 2>/dev/null)" = "invalid-slug" ]; then
77
+ if [ "$emit_json" = true ]; then printf '%s\n' "$resolved_json"; else printf 'render-diagrams: %s\n' "$(printf '%s' "$resolved_json" | jq -r '.message')" >&2; fi
78
+ exit 2
79
+ fi
80
+ [ "$rc" -eq 0 ] || note "resolver-error"
81
+
82
+ repo_root=$(printf '%s' "$resolved_json" | jq -r '.repo_root // empty')
83
+ feature_slug=$(printf '%s' "$resolved_json" | jq -r '.feature_slug // empty')
84
+ feature_dir=$(printf '%s' "$resolved_json" | jq -r '.feature_dir // empty')
85
+ [ -n "$feature_slug" ] || note "no-active-feature"
86
+ [ -n "$feature_dir" ] || note "resolver-missing-feature-dir"
87
+
88
+ # The renderer is an OPTIONAL dependency, deliberately absent from package.json (034 FR-012): a
89
+ # user who never renders a diagram never pays for a headless browser download. Its absence is
90
+ # reported with the way to get it, and is not a failure.
91
+ command -v mmdc >/dev/null 2>&1 || note "mermaid renderer not found. Install with: npm install -g @mermaid-js/mermaid-cli (or run: doflow doctor)"
92
+
93
+ # The theme is REQUIRED, not best-effort (034 FR-014): the render must apply the diagram-design
94
+ # roles, so an absent asset is reported rather than silently producing default-styled output. It
95
+ # fails open exactly as the absent renderer above does -- a note and exit 0 -- because this verb
96
+ # sits outside the chain and a missing shipped asset is a broken install, not a broken run. The
97
+ # earlier form appended `-c` only when the file happened to exist, which meant a reinstall gone
98
+ # wrong rendered unthemed images and reported ok:true with failures:0 and nothing on stderr.
99
+ [ -f "$THEME" ] || note "theme asset missing at $THEME. The render would not apply the DoFlow diagram theme, so it was not attempted. Reinstall the scripts tree: npx @khoavu882/doflow install"
100
+
101
+ out_dir="$repo_root/$feature_dir/design/diagrams"
102
+
103
+ # Collect the artifacts to scan. A named --artifact wins; otherwise every markdown file in the
104
+ # feature dir, in a stable order so block indices are reproducible across runs.
105
+ artifacts=()
106
+ if [ -n "$artifact_override" ]; then
107
+ candidate="$artifact_override"
108
+ [ -f "$candidate" ] || candidate="$repo_root/$artifact_override"
109
+ [ -f "$candidate" ] || { printf 'render-diagrams: no such artifact: %s\n' "$artifact_override" >&2; exit 2; }
110
+ artifacts+=("$candidate")
111
+ else
112
+ while IFS= read -r f; do artifacts+=("$f"); done < <(find "$repo_root/$feature_dir" -type f -name '*.md' -not -path '*/diagrams/*' | sort)
113
+ fi
114
+ [ "${#artifacts[@]}" -gt 0 ] || note "no markdown artifacts found under $feature_dir"
115
+
116
+ tmpdir="$(mktemp -d 2>/dev/null)" || note "mktemp-failed"
117
+ trap 'rm -rf "$tmpdir"' EXIT
118
+
119
+ rendered=()
120
+ failures=0
121
+ total_blocks=0
122
+
123
+ for artifact in "${artifacts[@]}"; do
124
+ stem="$(basename "$artifact")"; stem="${stem%.md}"
125
+
126
+ # Extract each fenced mermaid block to its own file, numbered from 01 in DOCUMENT ORDER. Index,
127
+ # not heading text, is the identity: a heading is free prose that can change without the diagram
128
+ # changing, which would silently rename an output file.
129
+ count=$(awk -v outdir="$tmpdir" -v stem="$stem" '
130
+ /^[[:space:]]*```mermaid[[:space:]]*$/ && !inblock { inblock=1; n++; f=sprintf("%s/%s-%02d.mmd", outdir, stem, n); next }
131
+ inblock && /^[[:space:]]*```[[:space:]]*$/ { inblock=0; close(f); next }
132
+ inblock { print >> f }
133
+ END { print n+0 }
134
+ ' "$artifact")
135
+
136
+ [ "$count" -gt 0 ] 2>/dev/null || continue
137
+ total_blocks=$((total_blocks + count))
138
+ mkdir -p "$out_dir" || note "cannot create $out_dir"
139
+
140
+ i=1
141
+ while [ "$i" -le "$count" ]; do
142
+ nn=$(printf '%02d' "$i")
143
+ src="$tmpdir/$stem-$nn.mmd"
144
+ dest="$out_dir/$stem-$nn.$format"
145
+ # -s 2 renders PNG at twice device scale so it stays sharp projected; ignored for svg.
146
+ mmdc_args=(-i "$src" -o "$dest" -b "#f5f5f5" -c "$THEME")
147
+ [ "$format" = "png" ] && mmdc_args+=(-s 2)
148
+ if mmdc "${mmdc_args[@]}" >/dev/null 2>&1; then
149
+ rendered+=("$feature_dir/design/diagrams/$stem-$nn.$format")
150
+ else
151
+ failures=$((failures + 1))
152
+ printf 'render-diagrams: renderer failed on %s block %s\n' "$(basename "$artifact")" "$nn" >&2
153
+ fi
154
+ i=$((i + 1))
155
+ done
156
+ done
157
+
158
+ [ "$total_blocks" -gt 0 ] || note "no fenced mermaid block found in the artifacts scanned"
159
+
160
+ # The output directory excludes its own contents, in any host project, whether or not that project
161
+ # ignores agent-docs/. `!.gitignore` keeps the rule itself trackable so it becomes durable once
162
+ # committed. The installer manages no ignore rules, so the verb owns this.
163
+ if [ -d "$out_dir" ] && [ ! -f "$out_dir/.gitignore" ]; then
164
+ printf '*\n!.gitignore\n' > "$out_dir/.gitignore"
165
+ fi
166
+
167
+ if [ "$emit_json" = true ]; then
168
+ printf '%s\n' "${rendered[@]:-}" | jq -R . | jq -s \
169
+ --argjson failures "$failures" --argjson blocks "$total_blocks" --arg dir "$feature_dir/design/diagrams" \
170
+ 'map(select(length > 0)) | {ok: ($failures == 0), written: (length > 0), dir: $dir, blocks: $blocks, failures: $failures, rendered: .}'
171
+ else
172
+ printf 'render-diagrams: %s of %s block(s) rendered into %s\n' "${#rendered[@]}" "$total_blocks" "$feature_dir/design/diagrams"
173
+ for r in "${rendered[@]:-}"; do [ -n "$r" ] && printf ' %s\n' "$r"; done
174
+ fi
175
+
176
+ [ "$failures" -eq 0 ] || exit 1
177
+ exit 0
@@ -0,0 +1,223 @@
1
+ #!/usr/bin/env bash
2
+ # render-puml.sh — renders a feature's authored PlantUML sources and projects them into its artifact.
3
+ #
4
+ # ONE verb doing BOTH derived views, deliberately (035 design R6): reading the source once means the
5
+ # committed image and the generated inline block cannot come from different revisions of it. Two
6
+ # verbs could, and that drift is what this whole feature exists to remove.
7
+ #
8
+ # Fail-open, matching render-audit.sh and render-diagrams.sh: no active feature, no design/c4/
9
+ # directory, and no renderer are each reported via a note and exit 0. Authoring C4 in PlantUML is
10
+ # OPT-IN per feature, so a feature without design/c4/ is not a broken feature -- it is the ordinary
11
+ # case, and this verb must say so rather than fail.
12
+ #
13
+ # Writes ONLY into <feature dir>/design/c4/ and one delimited region of one artifact. It never
14
+ # touches design/diagrams/, which is feature 034's ignored render output (035 IC-005).
15
+ #
16
+ # Usage: render-puml.sh [--slug=<slug>] [--check] [--json]
17
+
18
+ set -uo pipefail
19
+
20
+ emit_json=false
21
+ check_only=false
22
+ slug_override=""
23
+ bad_args=()
24
+ for a in "$@"; do
25
+ case "$a" in
26
+ --json) emit_json=true ;;
27
+ --check) check_only=true ;;
28
+ --slug=*) slug_override="${a#--slug=}" ;;
29
+ *) bad_args+=("$a") ;;
30
+ esac
31
+ done
32
+
33
+ if [ "${#bad_args[@]}" -gt 0 ]; then
34
+ printf 'render-puml: unrecognized argument(s): %s\n' "${bad_args[*]}" >&2
35
+ printf 'usage: render-puml.sh [--slug=<slug>] [--check] [--json]\n' >&2
36
+ exit 2
37
+ fi
38
+
39
+ note() {
40
+ if [ "$emit_json" = true ]; then
41
+ if command -v jq >/dev/null 2>&1; then jq -n --arg n "$1" '{ok:true, written:false, note:$n, rendered:[]}'
42
+ else printf '{"ok":true,"written":false,"note":"%s","rendered":[]}\n' "$1"; fi
43
+ else
44
+ printf 'render-puml: %s (nothing rendered)\n' "$1"
45
+ fi
46
+ exit 0
47
+ }
48
+
49
+ command -v jq >/dev/null 2>&1 || note "jq-absent"
50
+
51
+ script_dir="$(cd "$(dirname "$0")" && pwd)"
52
+ RESOLVER="$script_dir/do-paths.sh"
53
+ [ -f "$RESOLVER" ] || RESOLVER="${DOFLOW_CONFIG_DIR:-$HOME/.doflow}/scripts/doflow/bash/do-paths.sh"
54
+ [ -f "$RESOLVER" ] || note "resolver-absent"
55
+ # Resolved after repo_root below, since the runtime sits beside the repo root in a checkout and
56
+ # under <config>/runtime/ in an install -- the resolver is the only thing that knows which.
57
+
58
+ resolver_args=(--json)
59
+ [ -n "$slug_override" ] && resolver_args+=("--slug=$slug_override")
60
+ rc=0; resolved_json=$(bash "$RESOLVER" "${resolver_args[@]}" 2>/dev/null) || rc=$?
61
+ # An unsafe slug is refused, not noted: exit 2 with the resolver's error object (IC-009).
62
+ if [ "$rc" -eq 2 ] && [ "$(printf '%s' "$resolved_json" | jq -r '.error // empty' 2>/dev/null)" = "invalid-slug" ]; then
63
+ if [ "$emit_json" = true ]; then printf '%s\n' "$resolved_json"; else printf 'render-puml: %s\n' "$(printf '%s' "$resolved_json" | jq -r '.message')" >&2; fi
64
+ exit 2
65
+ fi
66
+ [ "$rc" -eq 0 ] || note "resolver-error"
67
+
68
+ repo_root=$(printf '%s' "$resolved_json" | jq -r '.repo_root // empty')
69
+ feature_slug=$(printf '%s' "$resolved_json" | jq -r '.feature_slug // empty')
70
+ feature_dir=$(printf '%s' "$resolved_json" | jq -r '.feature_dir // empty')
71
+
72
+ # The projector lives in the JS runtime, not beside the helpers: G12 keeps this repo to one runtime
73
+ # implementation, and Python in core/ is how a shadow tree grew the first time, one individually
74
+ # defensible module at a time. render-audit.sh already shells out to node for the same reason.
75
+ PROJECTOR="$repo_root/src/runtime/c4-project.ts"
76
+ [ -f "$PROJECTOR" ] || PROJECTOR="${DOFLOW_CONFIG_DIR:-$HOME/.doflow}/runtime/src/runtime/c4-project.ts"
77
+ design_rel=$(printf '%s' "$resolved_json" | jq -r '.design // empty')
78
+ [ -n "$feature_slug" ] || note "no-active-feature"
79
+ [ -n "$feature_dir" ] || note "resolver-missing-feature-dir"
80
+
81
+ src_dir="$repo_root/$feature_dir/design/c4"
82
+ # Authoring C4 in PlantUML is opt-in. No directory is the ordinary case, not a failure.
83
+ [ -d "$src_dir" ] || note "no design/c4/ directory in $feature_dir — authoring C4 in PlantUML is opt-in, so there is nothing to render"
84
+
85
+ sources=()
86
+ while IFS= read -r f; do sources+=("$f"); done < <(find "$src_dir" -maxdepth 1 -type f -name '*.puml' | sort)
87
+ [ "${#sources[@]}" -gt 0 ] || note "no .puml source found in $feature_dir/design/c4"
88
+
89
+ # Which source feeds design.md's inline block: container when present, else context. Named once so
90
+ # --check and the render loop cannot disagree about which source the block is supposed to match.
91
+ block_source_for() {
92
+ local pick="" stem
93
+ for f in "$@"; do
94
+ stem="$(basename "${f%.puml}")"
95
+ if [ "$stem" = "container" ]; then pick="$f"; fi
96
+ if [ -z "$pick" ] && [ "$stem" = "context" ]; then pick="$f"; fi
97
+ done
98
+ printf '%s' "$pick"
99
+ }
100
+
101
+ short_hash() { node -e 'const c=require("node:crypto"),f=require("node:fs");process.stdout.write(c.createHash("sha256").update(f.readFileSync(process.argv[1])).digest("hex").slice(0,12))' "$1"; }
102
+
103
+ # ── --check: re-hash and report, rendering and writing nothing (035 IC-004) ──────────────────────
104
+ if [ "$check_only" = true ]; then
105
+ stale=()
106
+ for src in "${sources[@]}"; do
107
+ want="$(short_hash "$src")"
108
+ have=""
109
+ [ -f "${src%.puml}.sha" ] && have="$(cat "${src%.puml}.sha")"
110
+ [ "$want" = "$have" ] || stale+=("$(basename "$src")")
111
+ done
112
+ # IC-004 records the revision in BOTH derived outputs, so checking only the .sha sibling checked
113
+ # only the image. The sibling is written inside the renderer-success branch below; the inline
114
+ # block is written by a separate branch that an absent marker pair can skip. They drift apart, and
115
+ # a check that reads one of them reports the other as current without ever having looked at it.
116
+ block_src="$(block_source_for "${sources[@]}")"
117
+ if [ -n "$block_src" ] && [ -n "$design_rel" ] && [ -f "$repo_root/$design_rel" ]; then
118
+ want="$(short_hash "$block_src")"
119
+ have="$(sed -n 's/.*source-hash: \([0-9a-f]*\).*/\1/p' "$repo_root/$design_rel" | head -1)"
120
+ [ "$want" = "$have" ] || stale+=("$(basename "$block_src") -> inline block in $design_rel")
121
+ fi
122
+ if [ "$emit_json" = true ]; then
123
+ printf '%s\n' "${stale[@]:-}" | jq -R . | jq -s \
124
+ --argjson ok "$([ "${#stale[@]}" -eq 0 ] && echo true || echo false)" \
125
+ 'map(select(length>0)) | {ok: $ok, checked:true, stale:.}'
126
+ else
127
+ if [ "${#stale[@]}" -eq 0 ]; then printf 'render-puml: every derived output matches its source\n'
128
+ else printf 'render-puml: stale against their source: %s\n' "${stale[*]}"; fi
129
+ fi
130
+ [ "${#stale[@]}" -eq 0 ] || exit 1
131
+ exit 0
132
+ fi
133
+
134
+ have_renderer=true
135
+ command -v plantuml >/dev/null 2>&1 || have_renderer=false
136
+
137
+ block_src="$(block_source_for "${sources[@]}")"
138
+ rendered=(); failures=0; projected=""
139
+ for src in "${sources[@]}"; do
140
+ stem="$(basename "${src%.puml}")"
141
+ digest="$(short_hash "$src")"
142
+
143
+ if [ "$have_renderer" = true ]; then
144
+ if plantuml -tpng -o "$src_dir" "$src" >/dev/null 2>&1; then
145
+ printf '%s\n' "$digest" > "${src%.puml}.sha"
146
+ rendered+=("$feature_dir/design/c4/$stem.png")
147
+ else
148
+ failures=$((failures + 1))
149
+ printf 'render-puml: renderer failed on %s\n' "$stem.puml" >&2
150
+ fi
151
+ fi
152
+
153
+ # One source feeds design.md §2's inline block; the others render only to images.
154
+ if [ "$src" = "$block_src" ]; then
155
+ # stdout only is the block: the splice writes it into authored prose, so anything the projector
156
+ # (or Node) prints on stderr, an ExperimentalWarning for one, must never become part of it.
157
+ proj_err="$(mktemp)" || note "mktemp-failed"
158
+ if ! block="$(node "$PROJECTOR" "$src" 2>"$proj_err")"; then
159
+ printf 'render-puml: %s%s\n' "$(cat "$proj_err")" "$block" >&2
160
+ rm -f "$proj_err"
161
+ exit 1
162
+ fi
163
+ [ -s "$proj_err" ] && cat "$proj_err" >&2
164
+ rm -f "$proj_err"
165
+ projected="$block"
166
+ fi
167
+ done
168
+
169
+ [ "$have_renderer" = true ] || note "PlantUML not found. Run 'doflow tools' for the install command registered for this machine (on Homebrew: brew install plantuml), or 'doflow doctor'. The sources were left unrendered."
170
+
171
+ # ── Replace the delimited region of the artifact (035 IC-004, RK3) ───────────────────────────────
172
+ # The one operation here that can destroy authored prose. Two guards: an artifact whose markers are
173
+ # absent is REPORTED and left untouched rather than repaired by guess, and the rewrite goes through
174
+ # a temp file moved into place -- the same pattern render-audit.sh uses, for the same reason.
175
+ block_written=false
176
+ if [ -n "$projected" ] && [ -n "$design_rel" ] && [ -f "$repo_root/$design_rel" ]; then
177
+ target="$repo_root/$design_rel"
178
+ if grep -q '<!-- generated from' "$target" && grep -q '<!-- end generated -->' "$target"; then
179
+ tmp="$(mktemp "${target}.XXXXXX")" || note "mktemp-failed"
180
+ trap 'rm -f "$tmp"' EXIT
181
+ # Two corrections a review found here. The end offset added the CLOSE marker's length PLUS a
182
+ # newline unconditionally, so whenever the marker was not followed by one it ate the next
183
+ # character instead -- and because the character it ate first was the newline, every later run
184
+ # ate another. It had already demoted this feature's own "### C4 Level 3" heading to "##" and
185
+ # glued it to the marker. And CLOSE is now searched FROM start, so a close marker sitting before
186
+ # the open one reads as absent rather than duplicating everything between them.
187
+ if node -e '
188
+ const fs = require("node:fs");
189
+ const [target, tmp, block] = process.argv.slice(1);
190
+ const OPEN = "<!-- generated from";
191
+ const CLOSE = "<!-- end generated -->";
192
+ const text = fs.readFileSync(target, "utf8");
193
+ const start = text.indexOf(OPEN);
194
+ const closeAt = start === -1 ? -1 : text.indexOf(CLOSE, start);
195
+ if (closeAt === -1) {
196
+ process.stderr.write("generated-block markers are absent or out of order\n");
197
+ process.exit(1);
198
+ }
199
+ const afterClose = closeAt + CLOSE.length;
200
+ const end = afterClose + (text[afterClose] === "\n" ? 1 : 0);
201
+ fs.writeFileSync(tmp, text.slice(0, start) + block + text.slice(end));
202
+ ' "$target" "$tmp" "$projected" && [ -s "$tmp" ]; then
203
+ mv "$tmp" "$target"; block_written=true
204
+ else
205
+ rm -f "$tmp"
206
+ failures=$((failures + 1))
207
+ printf 'render-puml: rewriting %s failed; it was left untouched\n' "$design_rel" >&2
208
+ fi
209
+ else
210
+ printf 'render-puml: %s carries no generated-block markers; it was left untouched\n' "$design_rel" >&2
211
+ fi
212
+ fi
213
+
214
+ if [ "$emit_json" = true ]; then
215
+ printf '%s\n' "${rendered[@]:-}" | jq -R . | jq -s \
216
+ --argjson failures "$failures" --arg dir "$feature_dir/design/c4" --argjson block "$([ "$block_written" = true ] && echo true || echo false)" \
217
+ 'map(select(length>0)) | {ok: ($failures==0), written: (length>0), dir: $dir, blockWritten: $block, failures: $failures, rendered: .}'
218
+ else
219
+ printf 'render-puml: %s source(s) rendered into %s; inline block %s\n' "${#rendered[@]}" "$feature_dir/design/c4" "$([ "$block_written" = true ] && echo written || echo 'not written')"
220
+ fi
221
+
222
+ [ "$failures" -eq 0 ] || exit 1
223
+ exit 0