@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,373 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // Pi MCP projection: DoFlow's stdio servers merged per server into the mcpServers map of Pi's
6
+ // mcp.json. Ownership is one ledger row per server and file, whose fingerprint is the entry value
7
+ // DoFlow last verified there. A same-named entry DoFlow holds no row for is the user's and is never
8
+ // touched; an owned entry whose value no longer matches its row was edited by the user and is
9
+ // released, not overwritten or removed. The file itself is edited by splicing members into its
10
+ // original text (./json-members.ts), so every byte DoFlow does not own survives.
11
+ //
12
+ // Plan changes carry DoFlow's own entry and a hash of the text plan read, never the file's text:
13
+ // the lifecycle copies every change into the recovery record, and a user's mcp.json may hold secrets.
14
+ const fs = require('node:fs');
15
+ const path = require('node:path');
16
+ const crypto = require('node:crypto');
17
+
18
+ const { fingerprint } = require('../copy-tree.ts');
19
+ const { SKELETON, readDocument, insertMember, replaceValue, removeMember, replaceInner, renderNewDocument } = require('./json-members.ts');
20
+
21
+ const HARNESS = 'pi';
22
+ const ASSET_ID = 'guidance.codex-pointer';
23
+ const RENDERER = 'pi-mcp';
24
+ const SERVER_NAME = /^[A-Za-z0-9_-]+$/;
25
+ const ABSENT_DOCUMENT = Object.freeze({ ok: true, root: null, servers: null });
26
+
27
+ const NOTICES = Object.freeze({
28
+ surface: 'MCP: stdio servers are registered in the mcpServers map of Pi\'s mcp.json for its built-in MCP (Pi 0.99.0 or later; a project entry replaces a user entry of the same name).',
29
+ extension: 'MCP: an installed extension that registers /mcp, such as pi-mcp-adapter, replaces Pi\'s built-in MCP, and Pi then does not read mcp.json.',
30
+ trust: 'MCP: Pi reads .pi/mcp.json only after this project is trusted (/trust or --approve); DoFlow does not grant trust.',
31
+ });
32
+
33
+ function sha256(text: any): string { return crypto.createHash('sha256').update(text).digest('hex'); }
34
+
35
+ function shorten(name: string): string { return name.length > 40 ? `${name.slice(0, 37)}...` : name; }
36
+
37
+ function collisionNotice(key: string, id: string): string {
38
+ return `MCP: kept your own entry '${shorten(key)}' and did not register DoFlow's '${shorten(id)}'; rename or remove yours to let DoFlow manage it.`;
39
+ }
40
+
41
+ function releasedNotice(id: string): string {
42
+ return `MCP: entry '${shorten(id)}' was changed outside DoFlow; it is yours now and DoFlow no longer updates or removes it.`;
43
+ }
44
+
45
+ /** Pi treats two server names that differ only in `-` versus `_` as one server. */
46
+ function piServerKey(name: string): string { return name.replace(/_/g, '-'); }
47
+
48
+ function refusalFor(file: string, reason: string): string { return `Pi MCP: ${file}: ${reason}; DoFlow did not change the file`; }
49
+
50
+ function untouchedNotice(file: string): string {
51
+ const shown = file.length > 80 ? `...${file.slice(-77)}` : file;
52
+ return `MCP: left ${shown} untouched because it cannot be edited safely; DoFlow no longer manages the entries in it.`;
53
+ }
54
+
55
+ /** DoFlow's entry for one catalog server, or the reason Pi cannot take it. */
56
+ function renderPiEntry(server: any): any {
57
+ if (server.transport !== 'stdio' || typeof server.command !== 'string' || !server.command) {
58
+ return { refusal: `Pi MCP: server '${server.id}' is not a stdio server; DoFlow writes only stdio entries for Pi` };
59
+ }
60
+ if (typeof server.id !== 'string' || !SERVER_NAME.test(server.id)) {
61
+ return { refusal: `Pi MCP: server id '${server.id}' is not a valid Pi server name` };
62
+ }
63
+ const entry: any = { command: server.command };
64
+ if (Array.isArray(server.args) && server.args.length) entry.args = [...server.args];
65
+ if (server.env && typeof server.env === 'object' && Object.keys(server.env).length) entry.env = { ...server.env };
66
+ return { entry };
67
+ }
68
+
69
+ function mcpRows(ledger: any): any[] {
70
+ return (ledger?.resources || []).filter((row: any) => row.harness === HARNESS && row.kind === 'mcp-server');
71
+ }
72
+
73
+ function readSnapshotFile(file: string, fsImpl: any): any {
74
+ if (!fsImpl.existsSync(file)) return { exists: false, text: '', hash: sha256(''), doc: ABSENT_DOCUMENT };
75
+ const bytes = fsImpl.readFileSync(file);
76
+ const text = bytes.toString('utf8');
77
+ // A splice writes the decoded text back, so bytes that do not survive a UTF-8 round trip would be
78
+ // rewritten; such a file is refused like any other one DoFlow cannot edit safely.
79
+ const doc = Buffer.from(text, 'utf8').equals(bytes) ? readDocument(text) : { ok: false, reason: 'file is not valid UTF-8' };
80
+ return { exists: true, text, hash: sha256(bytes), doc };
81
+ }
82
+
83
+ /** Read the selected server file and every file a ledger row names. Nothing is read when there is
84
+ * neither a selection nor a row, so a run with no Pi MCP work touches no MCP file. */
85
+ function discoverPiMcp({ selected = [], rows = [], file, fsImpl = fs }: any): any {
86
+ const files = new Set<string>(rows.map((row: any) => row.target));
87
+ if (selected.length) files.add(file);
88
+ return Object.fromEntries([...files].map((target) => [target, readSnapshotFile(target, fsImpl)]));
89
+ }
90
+
91
+ function memberOf(doc: any, key: string): any { return doc.servers?.members.find((member: any) => member.key === key) ?? null; }
92
+
93
+ /** The servers DoFlow owns in Pi's mcp.json now: rows whose entry still holds the value they record. */
94
+ function ownedPiMcpIds({ rows = [], snapshot = {} }: any): string[] {
95
+ const owned = rows.filter((row: any) => {
96
+ const doc = snapshot[row.target]?.doc;
97
+ const member = doc?.ok ? memberOf(doc, row.identity) : null;
98
+ return member !== null && fingerprint(member.value) === row.fingerprint;
99
+ });
100
+ return [...new Set<string>(owned.map((row: any) => row.identity))];
101
+ }
102
+
103
+ function twinOf(doc: any, key: string): any {
104
+ return doc.servers?.members.find((member: any) => member.key !== key && piServerKey(member.key) === piServerKey(key)) ?? null;
105
+ }
106
+
107
+ function sameValue(a: any, b: any): boolean { return JSON.stringify(a) === JSON.stringify(b); }
108
+
109
+ function baseChange({ target, operation, id, snapshot }: any): any {
110
+ return {
111
+ assetId: ASSET_ID, target, operation,
112
+ ownershipIdentity: `doflow:${HARNESS}:mcp-server:${id}`,
113
+ kind: 'mcp-server', identity: id, baseHash: snapshot[target]?.hash ?? sha256(''),
114
+ sourceVersion: 'registry-v1', projection: { renderer: RENDERER },
115
+ };
116
+ }
117
+
118
+ function writeChange({ target, operation, id, entry, snapshot }: any): any {
119
+ return { ...baseChange({ target, operation, id, snapshot }), entry, fingerprint: fingerprint(entry), afterFingerprint: fingerprint(entry) };
120
+ }
121
+
122
+ function releaseChange({ row, snapshot }: any): any {
123
+ const id = row.identity;
124
+ return { ...baseChange({ target: row.target, operation: 'remove', id, snapshot }), release: true, fingerprint: row.fingerprint };
125
+ }
126
+
127
+ function planPiMcp({ selected = [], rows = [], file, scope, removing = false, snapshot = {}, assets = [] }: any): any {
128
+ const changes: any[] = [];
129
+ const conflicts: string[] = [];
130
+ const notices: string[] = [];
131
+ if (!removing && !selected.length && !rows.length) return { changes, conflicts, notices };
132
+ if (removing && !rows.length) return { changes, conflicts, notices };
133
+ if (!assets.some((asset: any) => asset.id === ASSET_ID)) {
134
+ return { changes, conflicts: [`Pi MCP needs the ${ASSET_ID} asset to record ownership`], notices };
135
+ }
136
+
137
+ // An install or update refuses a file it cannot edit; a removal must still uninstall everything
138
+ // else, so it releases the rows for that file and leaves the file as it is.
139
+ const refused = new Set<string>();
140
+ for (const [target, entry] of Object.entries(snapshot) as [string, any][]) {
141
+ if (entry.doc.ok) continue;
142
+ refused.add(target);
143
+ if (removing) notices.push(untouchedNotice(target));
144
+ else conflicts.push(refusalFor(target, entry.doc.reason));
145
+ }
146
+
147
+ const matched = new Set<any>();
148
+ if (!removing) {
149
+ if (selected.length) {
150
+ notices.push(NOTICES.surface, NOTICES.extension);
151
+ if (scope === 'project') notices.push(NOTICES.trust);
152
+ }
153
+ const doc = snapshot[file]?.doc ?? ABSENT_DOCUMENT;
154
+ for (const server of selected) {
155
+ const { entry: desired, refusal } = renderPiEntry(server);
156
+ if (refusal) { conflicts.push(refusal); continue; }
157
+ const row = rows.find((item: any) => item.identity === server.id && item.target === file);
158
+ if (row) matched.add(row);
159
+ const member = memberOf(doc, server.id);
160
+ const twin = twinOf(doc, server.id);
161
+ if (row && member) {
162
+ if (fingerprint(member.value) !== row.fingerprint) {
163
+ changes.push(releaseChange({ row, snapshot }));
164
+ notices.push(releasedNotice(server.id));
165
+ } else if (!sameValue(member.value, desired)) {
166
+ changes.push(writeChange({ target: file, operation: 'update', id: server.id, entry: desired, snapshot }));
167
+ }
168
+ } else if (member || twin) {
169
+ // The user's entry holds the name. An owned row whose own entry is gone is released with it,
170
+ // because re-creating DoFlow's entry beside the user's twin would give Pi two of one server.
171
+ if (row) changes.push(releaseChange({ row, snapshot }));
172
+ notices.push(collisionNotice((member ?? twin).key, server.id));
173
+ } else {
174
+ changes.push(writeChange({ target: file, operation: 'create', id: server.id, entry: desired, snapshot }));
175
+ }
176
+ }
177
+ }
178
+
179
+ for (const row of rows) {
180
+ if (matched.has(row)) continue;
181
+ const id = row.identity;
182
+ const member = memberOf(snapshot[row.target]?.doc ?? ABSENT_DOCUMENT, id);
183
+ if (member && fingerprint(member.value) === row.fingerprint) {
184
+ changes.push({ ...baseChange({ target: row.target, operation: 'remove', id, snapshot }), fingerprint: row.fingerprint,
185
+ ...(row.origin ? { origin: row.origin } : {}) });
186
+ } else {
187
+ changes.push(releaseChange({ row, snapshot }));
188
+ if (member) notices.push(releasedNotice(id));
189
+ }
190
+ }
191
+
192
+ return { changes: changes.filter((change: any) => !refused.has(change.target) || (removing && change.release)), conflicts, notices };
193
+ }
194
+
195
+ function isSymlink(file: string, fsImpl: any): boolean {
196
+ try { return fsImpl.lstatSync(file).isSymbolicLink(); } catch { return false; }
197
+ }
198
+
199
+ /** Where a write to `file` lands: a symlinked mcp.json is written through to the file it points at,
200
+ * so the link stays a link and its target receives the entries. */
201
+ function writePath(file: string, fsImpl: any): string {
202
+ if (!isSymlink(file, fsImpl)) return file;
203
+ try { return fsImpl.realpathSync(file); } catch { return path.resolve(path.dirname(file), fsImpl.readlinkSync(file)); }
204
+ }
205
+
206
+ function atomicWrite(file: string, content: string, fsImpl: any): void {
207
+ const dest = writePath(file, fsImpl);
208
+ fsImpl.mkdirSync(path.dirname(dest), { recursive: true });
209
+ // The rename gives the file the temp file's mode; an mcp.json kept private for its tokens stays so.
210
+ const mode = fsImpl.existsSync(dest) ? fsImpl.statSync(dest).mode & 0o7777 : null;
211
+ const temp = path.join(path.dirname(dest), `.${path.basename(dest)}.${process.pid}.${Date.now()}.tmp`);
212
+ try {
213
+ fsImpl.writeFileSync(temp, content, { encoding: 'utf8', flag: 'wx', ...(mode === null ? {} : { mode }) });
214
+ if (mode !== null) fsImpl.chmodSync(temp, mode);
215
+ fsImpl.renameSync(temp, dest);
216
+ } finally {
217
+ if (fsImpl.existsSync(temp)) fsImpl.rmSync(temp, { force: true });
218
+ }
219
+ }
220
+
221
+ /** Replay one file's edits against the text read now, each against a fresh parse of the last result. */
222
+ function editedText(text: string, group: any[]): string {
223
+ const parse = (current: string) => {
224
+ const doc = readDocument(current);
225
+ if (!doc.ok) throw new Error(`Pi MCP: cannot edit the planned file: ${doc.reason}`);
226
+ return doc;
227
+ };
228
+ let next = text;
229
+ for (const change of group.filter((item: any) => item.operation === 'remove')) {
230
+ const doc = parse(next);
231
+ const member = memberOf(doc, change.identity);
232
+ if (member) next = removeMember(next, doc.servers, member);
233
+ }
234
+ for (const change of group.filter((item: any) => item.operation === 'update')) {
235
+ const doc = parse(next);
236
+ const member = memberOf(doc, change.identity);
237
+ if (member) next = replaceValue(next, member, change.entry, doc);
238
+ }
239
+ const creates = group.filter((item: any) => item.operation === 'create');
240
+ for (const [index, change] of creates.entries()) {
241
+ const doc = parse(next);
242
+ if (!doc.servers) {
243
+ const entries = Object.fromEntries(creates.slice(index).map((item: any) => [item.identity, item.entry]));
244
+ return insertMember(next, doc.root, 'mcpServers', entries, doc);
245
+ }
246
+ next = insertMember(next, doc.servers, change.identity, change.entry, doc);
247
+ }
248
+ return next;
249
+ }
250
+
251
+ /** Undo what DoFlow added around its entries once the last of them is gone: the mcpServers member
252
+ * it inserted, or the inner whitespace of the container it first wrote into. `origin` is recorded
253
+ * on the ledger rows (see originFor); without it the emptied container is left as it is. */
254
+ function restoredText(text: string, origin: any): string {
255
+ const doc = readDocument(text);
256
+ if (!doc.ok || !doc.servers || doc.servers.members.length) return text;
257
+ if (origin.created === 'member') return replaceInner(text, doc.servers, origin.inner);
258
+ const next = removeMember(text, doc.root, doc.root.members.find((member: any) => member.key === 'mcpServers'));
259
+ if (origin.inner === undefined) return next;
260
+ const after = readDocument(next);
261
+ return after.root.members.length ? next : replaceInner(next, after.root, origin.inner);
262
+ }
263
+
264
+ function applyPiMcp(changes: any[], { fsImpl = fs }: { fsImpl?: any } = {}): { applied: number } {
265
+ const groups = new Map<string, any[]>();
266
+ for (const change of changes) {
267
+ if (!groups.has(change.target)) groups.set(change.target, []);
268
+ groups.get(change.target)!.push(change);
269
+ }
270
+ let applied = 0;
271
+ for (const [target, all] of groups) {
272
+ const group = all.filter((change: any) => !change.release);
273
+ if (!group.length) continue;
274
+ const exists = fsImpl.existsSync(target);
275
+ const bytes = exists ? fsImpl.readFileSync(target) : Buffer.alloc(0);
276
+ if (sha256(bytes) !== group[0].baseHash) throw new Error(`Pi MCP: ${target} changed after planning; nothing was written, re-run the command`);
277
+ const text = bytes.toString('utf8');
278
+ const creates = group.filter((change: any) => change.operation === 'create');
279
+ const origin = group.find((change: any) => change.origin)?.origin;
280
+ let next = exists ? editedText(text, group) : renderNewDocument(creates.map((change: any) => [change.identity, change.entry]));
281
+ // Only a file DoFlow created is deleted, and never through a symlink; a user's own file that
282
+ // happens to equal the skeleton stays.
283
+ if (exists && origin?.created === 'file' && next === SKELETON) {
284
+ if (!isSymlink(target, fsImpl)) { fsImpl.rmSync(target); applied += 1; continue; }
285
+ } else if (exists && origin && !creates.length) next = restoredText(next, origin);
286
+ if (next !== text) {
287
+ atomicWrite(target, next, fsImpl);
288
+ applied += 1;
289
+ }
290
+ }
291
+ return { applied };
292
+ }
293
+
294
+ function statusFor({ id, target, status }: { id: string, target: string, status: string }): any {
295
+ return { harness: HARNESS, assetId: ASSET_ID, capability: 'mcp', identity: id, target,
296
+ ownershipIdentity: `doflow:${HARNESS}:mcp-server:${id}`, status };
297
+ }
298
+
299
+ function resourceFor({ id, target, value, origin }: { id: string, target: string, value: any, origin?: any }): any {
300
+ return { assetId: ASSET_ID, target, ownershipIdentity: `doflow:${HARNESS}:mcp-server:${id}`, kind: 'mcp-server', identity: id,
301
+ fingerprint: fingerprint(value), sourceVersion: 'registry-v1', projection: { renderer: RENDERER }, ...(origin ? { origin } : {}) };
302
+ }
303
+
304
+ /** What DoFlow added to `target` besides its entries, so a later removal can take it out again:
305
+ * the whole file, the mcpServers member, or the first member of an empty mcpServers (with the
306
+ * container's original inner whitespace). A row already carrying it passes it on; otherwise it is
307
+ * read from the file as plan saw it. Only whitespace is recorded, never file content. */
308
+ function originFor(target: string, rows: any[], snapshot: any): any {
309
+ const carried = rows.find((row: any) => row.target === target && row.origin)?.origin;
310
+ if (carried) return carried;
311
+ const before = snapshot[target];
312
+ if (!before) return undefined;
313
+ if (!before.exists) return { created: 'file' };
314
+ const { doc, text } = before;
315
+ if (!doc.ok) return undefined;
316
+ const inner = (node: any) => text.slice(node.open + 1, node.close);
317
+ if (!doc.servers) return doc.root.members.length ? { created: 'key' } : { created: 'key', inner: inner(doc.root) };
318
+ return doc.servers.members.length ? undefined : { created: 'member', inner: inner(doc.servers) };
319
+ }
320
+
321
+ /** Statuses and ledger resources for the Pi MCP entries. `snapshot` is what plan read before apply:
322
+ * an entry it held that DoFlow did not own then, or held with a value its row no longer matched, is
323
+ * the user's, whatever it holds now. */
324
+ function verifyPiMcp({ selected = [], rows = [], file, snapshot = {}, removing = false, fsImpl = fs }: any): any {
325
+ const statuses: any[] = [];
326
+ const resources: any[] = [];
327
+ const conflicts: string[] = [];
328
+ const current: Record<string, any> = {};
329
+ for (const target of new Set<string>([...(selected.length ? [file] : []), ...rows.map((row: any) => row.target)])) {
330
+ current[target] = readSnapshotFile(target, fsImpl).doc;
331
+ if (!current[target].ok && !removing) conflicts.push(refusalFor(target, current[target].reason));
332
+ }
333
+ const memberNow = (target: string, id: string) => (current[target]?.ok ? memberOf(current[target], id) : null);
334
+ const foreign = (target: string, id: string) => {
335
+ const before = snapshot[target]?.doc;
336
+ if (!before?.ok) return false;
337
+ const row = rows.find((item: any) => item.identity === id && item.target === target);
338
+ const member = memberOf(before, id);
339
+ if (!member) return Boolean(twinOf(before, id));
340
+ return !row || fingerprint(member.value) !== row.fingerprint;
341
+ };
342
+
343
+ const verified = new Set<string>();
344
+ if (!removing) {
345
+ for (const server of selected) {
346
+ const { entry: desired } = renderPiEntry(server);
347
+ if (!desired) continue;
348
+ verified.add(`${file}\u0000${server.id}`);
349
+ const member = memberNow(file, server.id);
350
+ if (foreign(file, server.id)) statuses.push(statusFor({ id: server.id, target: file, status: 'not-managed' }));
351
+ else if (member && sameValue(member.value, desired)) {
352
+ statuses.push(statusFor({ id: server.id, target: file, status: 'managed' }));
353
+ resources.push(resourceFor({ id: server.id, target: file, value: member.value, origin: originFor(file, rows, snapshot) }));
354
+ } else statuses.push(statusFor({ id: server.id, target: file, status: 'missing' }));
355
+ }
356
+ }
357
+
358
+ for (const row of rows) {
359
+ const id = row.identity;
360
+ if (verified.has(`${row.target}\u0000${id}`)) continue;
361
+ const member = memberNow(row.target, id);
362
+ let status: string;
363
+ if (!current[row.target].ok) status = 'not-managed';
364
+ else if (!member) status = 'absent';
365
+ else if (foreign(row.target, id) || fingerprint(member.value) !== row.fingerprint) status = 'not-managed';
366
+ else status = 'retained';
367
+ statuses.push(statusFor({ id, target: row.target, status }));
368
+ if (status === 'managed') resources.push(resourceFor({ id, target: row.target, value: member.value, origin: originFor(row.target, rows, snapshot) }));
369
+ }
370
+ return { statuses, resources, conflicts };
371
+ }
372
+
373
+ module.exports = { NOTICES, renderPiEntry, piServerKey, mcpRows, discoverPiMcp, ownedPiMcpIds, planPiMcp, applyPiMcp, verifyPiMcp };
@@ -0,0 +1,19 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const path = require('node:path');
6
+
7
+ /** Resolve a registry-declared source path against the repo root, refusing escapes. Shared by the
8
+ * common projection module and by per-adapter native-projection normalizers, which is why it lives
9
+ * in its own module rather than in index.js — a normalizer importing index.js would be a cycle. */
10
+ function resolveRegistrySource(registry: any, source: any, label: string): string {
11
+ if (typeof registry.repoRoot !== 'string' || !registry.repoRoot) throw new Error(`Registry repoRoot is required for ${label}`);
12
+ if (typeof source !== 'string' || !source) throw new Error(`Registry ${label} source is required`);
13
+ const root = path.resolve(registry.repoRoot);
14
+ const resolved = path.resolve(root, source);
15
+ if (!resolved.startsWith(`${root}${path.sep}`)) throw new Error(`Registry ${label} source escapes repository: ${source}`);
16
+ return resolved;
17
+ }
18
+
19
+ module.exports = { resolveRegistrySource };
@@ -0,0 +1,218 @@
1
+ 'use strict';
2
+ // `doflow install` — plan the full lifecycle across the requested targets, back up, apply, and
3
+ // pin what was chosen in doflow.lock. Supports --dry-run previews and MCP selection.
4
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
5
+ const os = require('node:os');
6
+ const path = require('node:path');
7
+ const { resolveTargets, toolDirs } = require('../../install/targets.ts');
8
+ const { resolveContext, printContext } = require('../../install/context.ts');
9
+ const {
10
+ BackupError, DEFAULT_BACKUP_RETENTION, createFileBackup, applyRetention, formatBytes, retentionNote,
11
+ } = require('../../install/backup.ts');
12
+ const { backupSetFromPlan } = require('../../install/backup-set.ts');
13
+ const { ledgerPath } = require('../../state/index.ts');
14
+ const { writeManifest, readInstallManifest } = require('../../install/manifest.ts');
15
+ const { confirm } = require('../../helper/prompt.ts');
16
+ const { sourceCommit } = require('../../helper/git.ts');
17
+ const { chmodHooksExecutable } = require('../../helper/settings-scope.ts');
18
+ const { promptMcpCheckbox, resolveMcpSelections } = require('../../install/mcp.ts');
19
+ const { loadRegistry } = require('../../registry/index.ts');
20
+ const { storageAfterInstall } = require('../../runtime/storage/upkeep.ts');
21
+ const { applyLifecycle, applyMcpIndex, recordMcpOwnership } = require('../../lifecycle/index.ts');
22
+ const {
23
+ codexScope, registryLifecycleView, printRegistryLifecycle, printPlanNotices, assertSafeRegistryPlan,
24
+ lockDocument, recordLock,
25
+ } = require('../../lifecycle/view.ts');
26
+ const {
27
+ REPO_ROOT, SCRIPT_DIR, pkg, scopeOf, installPaths, reportRetiredMcp, scopeSelectionState, printMcpSelection, plannedMcpSelections,
28
+ printRecordedMcpOwnership, buildAdapterRegistry, refuseHomeRootedProject, holdRunLock, checkpointRunLock,
29
+ } = require('../shared.ts');
30
+
31
+ function cmdInstall(o: any): void {
32
+ refuseHomeRootedProject(o, 'install');
33
+ const targets = resolveTargets(o.targets);
34
+ const scope = scopeOf(o);
35
+ const hold = holdRunLock(o, scope, 'install');
36
+ try {
37
+ const dirs = toolDirs(scope);
38
+ const lifecyclePaths = installPaths(scope);
39
+ const backupRoot = lifecyclePaths.backupRoot;
40
+ // Resolved once per invocation and threaded into resolveContext/createFileBackup/writeManifest below
41
+ // — those three used to each spawn their own `git rev-parse` for the identical value.
42
+ const commit = sourceCommit(SCRIPT_DIR);
43
+
44
+ printContext(resolveContext({ repoRoot: REPO_ROOT, targets, dirs, sourceCommit: commit, ...scope }));
45
+
46
+ const registry = loadRegistry({ repoRoot: REPO_ROOT });
47
+ const existingManifest = readInstallManifest({ scopeRoot: lifecyclePaths.scopeRoot });
48
+ const { lock, ledger } = scopeSelectionState(scope);
49
+ // Each MCP-capable target gets its own selection; a real terminal is asked once for all of them.
50
+ const selection = resolveMcpSelections({
51
+ cmd: 'install', requested: o.mcp, targets, registry, lock, ledger, manifestServers: existingManifest?.mcpServers ?? null,
52
+ interactive: !o.dryRun && !o.force && Boolean(process.stdin.isTTY) && Boolean(process.stdout.isTTY),
53
+ promptFn: promptMcpCheckbox, onStale: reportRetiredMcp,
54
+ });
55
+ // One lifecycle view across every requested target — computed unconditionally (not only under
56
+ // --dry-run) so its safety gate and its plan are the exact same object the real apply below uses.
57
+ const lifecycleView = registryLifecycleView({ registry, repoRoot: REPO_ROOT, scope, dirs, targets,
58
+ mcpSelections: selection.selections, mcpAdoptable: selection.adoptable, retainedMcpIds: selection.retainedMcpIds,
59
+ force: o.force, adopt: o.adopt === true, permissions: o.permissions === true, statusline: o.statusline === true });
60
+ if (!lifecycleView.plan.safe) { assertSafeRegistryPlan(lifecycleView); return; }
61
+
62
+ printMcpSelection(lifecycleView, selection.sources, { requested: o.mcp, prefix: o.dryRun ? '[DRY]' : '[INFO]' });
63
+ // Safe-by-default surfaced, not silent: a first-ever non-interactive install selects zero MCP
64
+ // servers. Anyone scripting installs must opt in explicitly; interactive users never see this
65
+ // because the checkbox is the discovery path.
66
+ if (!o.mcp && !o.dryRun && !process.stdin.isTTY && Object.values(selection.sources).includes('default')) {
67
+ console.log('[INFO] MCP: none selected by default in non-interactive mode — pass --mcp all or --mcp <names> to include servers.');
68
+ }
69
+
70
+ // The files this run's apply can overwrite or delete; DoFlow's own records are never among them.
71
+ const buildBackupSet = () => backupSetFromPlan({
72
+ plan: lifecycleView.plan, scope: scope.global ? 'global' : 'project', scopeRoot: lifecyclePaths.scopeRoot,
73
+ exclude: [backupRoot, lifecycleView.stateRoot, lifecyclePaths.manifestPath, path.join(lifecyclePaths.doflowRoot, 'doflow.lock')],
74
+ // The ownership ledger, and nothing else from the state root (runs, evidence and recovery
75
+ // records written after the backup must survive a rollback): a rollback then restores the
76
+ // rows that describe the files it restores.
77
+ records: [ledgerPath(lifecycleView.stateRoot)],
78
+ });
79
+ const keep = o.prune ?? DEFAULT_BACKUP_RETENTION;
80
+
81
+ if (o.dryRun) {
82
+ console.log(`[INFO] Install targets: ${targets.join(' ')}`);
83
+ printRegistryLifecycle(lifecycleView, '[DRY]');
84
+ const set = o.noBackup ? null : backupStep(buildBackupSet);
85
+ if (set) printBackupDryRun(set, `${backupRoot}/install_<timestamp>`);
86
+ const retention = applyRetention({ backupRoot, keep, dryRun: true, reserve: set?.count ? 1 : 0 });
87
+ console.log(`[DRY] Backups: would keep ${retention.kept}, would remove ${retention.wouldRemove.length} (${retentionNote(o.prune, keep)})`);
88
+ console.log(`[DRY] Would write manifest: ${lifecyclePaths.manifestPath}`);
89
+ console.log('[DRY] Dry run complete — no changes written');
90
+ return;
91
+ }
92
+
93
+ if (!confirm(`Install configs to: ${targets.join(' ')}?`, o.force)) {
94
+ // Exit 1, not 0: a declined prompt is a decision, and it must not share an exit code with a
95
+ // completed run. With no stdin the prompt auto-declines, so `doflow install <path>` in a script
96
+ // or CI step printed "Aborted.", wrote zero files, and reported success. It also silently
97
+ // corrupted a set of install-timing measurements during the D.4 sweep, which is how it surfaced.
98
+ console.error('[INFO] Aborted.');
99
+ process.exit(1);
100
+ }
101
+ checkpointRunLock(hold);
102
+
103
+ let bid = '';
104
+ if (!o.noBackup) {
105
+ const backup = backupStep(() => createFileBackup({ operation: 'install', set: buildBackupSet(), backupRoot, repoRoot: SCRIPT_DIR,
106
+ sourceCommit: commit, version: pkg.version, date: new Date() }));
107
+ bid = reportBackup(backup);
108
+ } else {
109
+ console.error('[WARN] Skipping backup (--no-backup)');
110
+ }
111
+
112
+ // The ledger the lock is pinned against: the one this run leaves.
113
+ let ledgerAfter = lifecycleView.ledger;
114
+ if (lifecycleView.plan.changes.length) {
115
+ const result = applyLifecycle({ plan: lifecycleView.plan, registry: lifecycleView.registry,
116
+ adapters: buildAdapterRegistry(),
117
+ stateRoot: lifecycleView.stateRoot, ledger: lifecycleView.ledger });
118
+ for (const target of lifecycleView.plan.targets) {
119
+ if (target.skipped || !target.changes.length) continue;
120
+ const owned = result.ledger.resources.filter((resource: any) => resource.harness === target.harness).length;
121
+ console.log(`[INFO] ${target.harness}: lifecycle verified (${owned} owned resource(s))`);
122
+ }
123
+ ledgerAfter = result.ledger;
124
+ } else {
125
+ // MCP_INDEX.md is generated, not a tracked resource, so it never appears in plan.changes and
126
+ // applyLifecycle — which owns the only call that writes it — is skipped entirely when nothing
127
+ // else changed. Without this branch the index is rewritten only as a side effect of some
128
+ // unrelated asset changing, so a change to the renderer, to a server's `doc`/`shortFlag`, or
129
+ // to the resolved selection silently does nothing whenever the rest of the tree is current.
130
+ applyMcpIndex({ scopeRoot: lifecycleView.plan.scopeRoot, selectedMcp: lifecycleView.plan.mcp, mode: 'apply' });
131
+ const owned = recordMcpOwnership({ plan: lifecycleView.plan, registry: lifecycleView.registry, adapters: lifecycleView.adapters,
132
+ stateRoot: lifecycleView.stateRoot, ledger: lifecycleView.ledger });
133
+ printRecordedMcpOwnership(owned.recorded);
134
+ ledgerAfter = owned.ledger;
135
+ }
136
+ printPlanNotices(lifecycleView);
137
+
138
+ if (targets.includes('claude')) {
139
+ // A npm-packaged tarball does not reliably preserve the executable bit on arbitrary files
140
+ // (unlike git checkouts, which usually do) — copy-tree's mode-preserving copy only carries
141
+ // over whatever bit the source file actually has on this machine, so this runs after
142
+ // applyLifecycle (once the hook scripts are actually on disk) as a final, unconditional +x.
143
+ chmodHooksExecutable(dirs.claude);
144
+ }
145
+
146
+ writeManifest({ scopeRoot: lifecyclePaths.scopeRoot, scriptVersion: pkg.version, operation: 'install', repoRoot: SCRIPT_DIR, sourceCommit: commit, backupId: bid, tools: targets, date: new Date() });
147
+
148
+ // Pin what this install CHOSE, beside what earlier runs pinned. The ledger owns ownership; the
149
+ // lock owns selection — together they make the next update's delta a reviewable fact instead of a
150
+ // surprise.
151
+ const lockResult = recordLock(
152
+ scope.global ? { scope: 'global', homeDir: os.homedir() } : { scope: 'project', projectRoot: path.resolve(scope.projectRoot) },
153
+ lockDocument({
154
+ registry, scope: codexScope(scope), scopeRoot: scope.global ? os.homedir() : path.resolve(scope.projectRoot),
155
+ previous: lock, ledger: ledgerAfter, plannedTargets: targets, mcpSelections: plannedMcpSelections(lifecycleView),
156
+ }),
157
+ );
158
+ console.log(`[INFO] doflow.lock: ${lockResult.summary}`);
159
+
160
+ applyBackupRetention({ backupRoot, prune: o.prune, keep, bid });
161
+ storageAfterInstall({ scope: scope.global ? 'global' : 'project', scopeRoot: scope.global ? os.homedir() : path.resolve(scope.projectRoot) });
162
+
163
+ console.log('[OK] Installation complete!');
164
+ } finally {
165
+ hold?.release();
166
+ }
167
+ }
168
+
169
+ /** Run a backup step; a BackupError stops the command before anything is changed. */
170
+ function backupStep(step: () => any): any {
171
+ try {
172
+ return step();
173
+ } catch (error: any) {
174
+ if (!(error instanceof BackupError)) throw error;
175
+ console.error(`[ERROR] Backup failed, nothing was changed: ${error.message}`);
176
+ process.exit(1);
177
+ }
178
+ }
179
+
180
+ /** Print what a backup step wrote and return its id ('' when there was nothing to back up). */
181
+ function reportBackup(backup: any): string {
182
+ if (!backup) {
183
+ console.error('[INFO] Backup: nothing to back up (no file changes)');
184
+ return '';
185
+ }
186
+ console.error(`[INFO] Backup created: ${backup.id} (${backup.files} file(s), ${formatBytes(backup.bytes)}; ${backup.absent} not present before the run)`);
187
+ return backup.id;
188
+ }
189
+
190
+ /** The dry-run backup lines for a set the real run would back up. */
191
+ function printBackupDryRun(set: any, destination: string): void {
192
+ if (set.count === 0) {
193
+ console.log('[DRY] Backup: nothing to back up (no file changes)');
194
+ } else {
195
+ console.log(`[DRY] Would back up ${set.count} file(s), ${formatBytes(set.bytes)} (${set.bytes} bytes; ${set.absent} not present yet) to ${destination}`);
196
+ }
197
+ const mcpOnly = set.excluded.filter((item: any) => item.reason === 'mcp-entries-only').length;
198
+ if (mcpOnly > 0) console.log(`[DRY] Not backed up: ${mcpOnly} file(s) holding only MCP entries`);
199
+ }
200
+
201
+ /** Keep the newest backups after a run that changed files; never changes the exit code. */
202
+ function applyBackupRetention({ backupRoot, prune, keep, bid }: any): void {
203
+ let retention;
204
+ try {
205
+ retention = applyRetention({ backupRoot, keep, protect: bid ? [bid] : [] });
206
+ } catch (error: any) {
207
+ console.error(`[WARN] Backups: could not apply retention: ${error.message}`);
208
+ return;
209
+ }
210
+ for (const failure of retention.failed) console.error(`[WARN] Backups: could not remove ${failure.id}: ${failure.error}`);
211
+ console.error(`[INFO] Backups: kept ${retention.kept}, removed ${retention.removed.length} (${retentionNote(prune, keep)})`);
212
+ }
213
+
214
+ module.exports = cmdInstall;
215
+ module.exports.backupStep = backupStep;
216
+ module.exports.reportBackup = reportBackup;
217
+ module.exports.printBackupDryRun = printBackupDryRun;
218
+ module.exports.applyBackupRetention = applyBackupRetention;
@@ -0,0 +1,21 @@
1
+ 'use strict';
2
+ // `doflow list-backups` — the backup table for this scope. Takes no project-path positional
3
+ // (rollback's one positional slot is the backup id); scope is -g/--global vs cwd-rooted project.
4
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
5
+ const { listBackups } = require('../../install/backup.ts');
6
+ const { installPaths, printBackupTable } = require('../shared.ts');
7
+
8
+ function cmdListBackups(o: any): void {
9
+ // rollback/list-backups don't take a project-path positional (their one positional slot is the
10
+ // backup id for rollback) — scope is -g/--global vs default project rooted at cwd.
11
+ const scope = { global: o.global, projectRoot: '.' };
12
+ const lifecyclePaths = installPaths(scope);
13
+ const rows = listBackups(lifecyclePaths.backupRoot);
14
+ // listBackups now reads the canonical root AND the legacy pre-.doflow one, so the footer must name
15
+ // the roots the rows actually came from: reporting restore points as living in a directory that
16
+ // does not exist on this machine sends a user looking for recovery material to the wrong place.
17
+ const roots = [...new Set(rows.map((r: any) => r.backupRoot))];
18
+ printBackupTable(rows, roots.length ? roots.join(', ') : lifecyclePaths.backupRoot);
19
+ }
20
+
21
+ module.exports = cmdListBackups;