@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,134 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * The precedence resolver (design C4, contract IC-003 consumer, FR-004).
7
+ *
8
+ * Given a logical asset present at one or more scopes and the harness's recorded
9
+ * `scopePrecedence` block (`core/registry/harnesses.json`), names the copy the harness will load —
10
+ * or withholds that verdict and states why. This module is pure and synchronous: it looks at
11
+ * nothing on disk and reads no registry itself: the caller passes the one harness's precedence
12
+ * block, already loaded.
13
+ *
14
+ * **The winner is derived from `order` and `mode` together, never from either alone** (IC-003).
15
+ * An order alone is ambiguous between naming the winner first and naming it last, and the
16
+ * recorded corpus contains both conventions for the same field: `claude` records
17
+ * `["user","project"]` with `mode: "first-wins"` (the first entry, `user`, wins); `copilot`
18
+ * records `["project","user"]` with `mode: "last-wins"` (the *last* entry, `user`, wins — the same
19
+ * winner as `claude`, reached from the opposite order convention); `codex` records
20
+ * `["user","project"]` with `mode: "last-wins"` (the last entry, `project`, wins — a naive
21
+ * first-match reading of that same order would get it backwards).
22
+ *
23
+ * A `merged` mode means the copies are combined rather than chosen between, so no single copy
24
+ * "wins". An `unestablished` mode means the order may be documented (`gemini` documents one) or
25
+ * may not (`pi`, `antigravity` document neither), but either way the rule for choosing between
26
+ * copies is not — so no winner is named. Those two cases are separate states under IC-003 and the
27
+ * withheld reason says which: a documented order with an undocumented winner names the order it
28
+ * knows, an undocumented order says the order itself is unestablished. Neither may report as the
29
+ * other. All are withheld verdicts, never a guessed default
30
+ * (design A3: "if a default is later wanted, it becomes a recorded order with its basis stated,
31
+ * not an implicit fallback").
32
+ *
33
+ * An asset present at only one scope has no verdict to withhold and is not "shadowed" at all: it
34
+ * is reported plainly, with that one scope named as trivially where the harness's copy comes from,
35
+ * regardless of whether the harness has any precedence block, or what it says.
36
+ */
37
+
38
+ /**
39
+ * @typedef {Object} ShadowVerdict The `SHADOW_VERDICT` entity of the feature's data model.
40
+ * @property {string[]} presentAt The scopes at which this logical asset has a recorded copy, in
41
+ * the order given by the caller. Always at least one entry.
42
+ * @property {boolean} shadowed Whether more than one scope holds a copy of this asset. `false`
43
+ * means there is exactly one copy and nothing to resolve between.
44
+ * @property {string|null} winner The scope whose copy the harness will load. Set to the sole
45
+ * present scope when `shadowed` is `false`; set to the resolved scope when `shadowed` is `true`
46
+ * and the precedence block names an unambiguous winner (`first-wins`/`last-wins`); `null` exactly
47
+ * when `shadowed` is `true` and the verdict is withheld.
48
+ * @property {string|null} withheldReason Populated exactly when `winner` is `null`; states why no
49
+ * winner is named. `null` in every other case, including the un-shadowed case, which never
50
+ * withholds anything.
51
+ */
52
+
53
+ /**
54
+ * Resolves the shadow verdict for one logical asset at one harness.
55
+ *
56
+ * @param {string[]} presentScopes The scopes at which a copy of this logical asset is recorded.
57
+ * Order is not significant here — it is not read as a precedence order, only as "where does a
58
+ * copy exist". Deduplicated internally; malformed entries (non-string, empty) are dropped.
59
+ * @param {Object|null|undefined} precedence The harness's recorded `scopePrecedence` block
60
+ * (`core/registry/harnesses.json`), exactly as stored — at minimum `{ mode, order? }` — or
61
+ * `null`/`undefined` when the harness has none recorded yet (IC-003's fifth state: the question
62
+ * has not been asked). Never defaulted by this function.
63
+ * @returns {ShadowVerdict|null} `null` when `presentScopes` carries no usable scope name — there is
64
+ * no asset to resolve a verdict for. Otherwise a `ShadowVerdict`.
65
+ */
66
+ function resolveShadowVerdict(presentScopes: any, precedence: any): any {
67
+ const scopes = Array.isArray(presentScopes)
68
+ ? [...new Set(presentScopes.filter((scope: any) => typeof scope === 'string' && scope !== ''))]
69
+ : [];
70
+ if (scopes.length === 0) return null;
71
+
72
+ if (scopes.length === 1) {
73
+ return { presentAt: scopes, shadowed: false, winner: scopes[0], withheldReason: null };
74
+ }
75
+
76
+ // More than one scope holds a copy: a verdict is either named or withheld, never defaulted.
77
+ if (!precedence || typeof precedence !== 'object') {
78
+ return {
79
+ presentAt: scopes,
80
+ shadowed: true,
81
+ winner: null,
82
+ withheldReason: 'no scope-precedence is recorded for this harness',
83
+ };
84
+ }
85
+
86
+ const { mode, order } = precedence;
87
+
88
+ if (mode === 'merged') {
89
+ return {
90
+ presentAt: scopes,
91
+ shadowed: true,
92
+ winner: null,
93
+ withheldReason: 'the harness merges copies from every scope rather than choosing between them',
94
+ };
95
+ }
96
+
97
+ if (mode === 'unestablished') {
98
+ // IC-003 keeps these two states distinct: an order established with the mode explicitly
99
+ // unestablished (`gemini`) is not the same finding as an order sought and found undocumented
100
+ // (`pi`, `antigravity`). Both withhold the verdict; only one of them knows the reading order.
101
+ const orderIsEstablished = Array.isArray(order) && order.length > 0;
102
+ return {
103
+ presentAt: scopes,
104
+ shadowed: true,
105
+ winner: null,
106
+ withheldReason: orderIsEstablished
107
+ ? `the harness's consultation order is established (${order.join(' then ')}) but its rule for choosing between copies is not`
108
+ : "the harness's scope-resolution order is unestablished",
109
+ };
110
+ }
111
+
112
+ if (mode === 'first-wins' || mode === 'last-wins') {
113
+ if (!Array.isArray(order) || order.length === 0) {
114
+ return {
115
+ presentAt: scopes,
116
+ shadowed: true,
117
+ winner: null,
118
+ withheldReason: `the harness's precedence block records mode "${mode}" but no consultation order`,
119
+ };
120
+ }
121
+ const winner = mode === 'first-wins' ? order[0] : order[order.length - 1];
122
+ return { presentAt: scopes, shadowed: true, winner, withheldReason: null };
123
+ }
124
+
125
+ // An absent or unrecognized mode is never guessed at (IC-003: order alone is ambiguous).
126
+ return {
127
+ presentAt: scopes,
128
+ shadowed: true,
129
+ winner: null,
130
+ withheldReason: `the harness's precedence block records no usable resolution mode (got ${JSON.stringify(mode ?? null)})`,
131
+ };
132
+ }
133
+
134
+ module.exports = { resolveShadowVerdict };
@@ -0,0 +1,131 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /**
6
+ * C2 — dual-scope reader (FR-002, NFR-002).
7
+ *
8
+ * The rest of the CLI resolves scope once per invocation, as a boolean, and derives every root
9
+ * from it; a cross-scope report cannot be assembled that way. This module calls the existing
10
+ * scope-resolved lifecycle view twice — once per scope descriptor it constructs — and returns one
11
+ * snapshot per scope. It changes no other caller's scope handling and it reads only: nothing here
12
+ * writes, moves, or creates recorded state (NFR-001).
13
+ *
14
+ * Two constraints shape the interface:
15
+ *
16
+ * - **The global scope root is not an argument (design R7).** `registryLifecycleView` derives it
17
+ * from `os.homedir()` whenever the scope descriptor carries `global: true`, and silently ignores
18
+ * a `projectRoot` on that same descriptor. So this reader supplies only the project root; the
19
+ * global root is inherited from the environment, which is correct at runtime. A test that needs
20
+ * a synthetic global root must relocate `os.homedir` around the (synchronous) call.
21
+ * - **A scope with nothing recorded is a value, not a failure (NFR-002).** A repository never
22
+ * installed into is the ordinary state, so its snapshot is returned empty and `recorded: false`
23
+ * rather than raising, and reporting that absence creates no ledger on disk.
24
+ *
25
+ * Two further properties are load-bearing for the currency judgement downstream, because that
26
+ * judgement is read from the plan this module returns beside the resources:
27
+ *
28
+ * - **The restriction restricts the resources, not only the plan.** `targets` bounds the plan the
29
+ * view computes, so a resource of an unrequested harness has no plan to be judged against. It is
30
+ * therefore left out of the snapshot rather than returned beside a plan that never looked at it —
31
+ * IC-001 makes `targets` a restriction to named harnesses, so a harness outside it is not part of
32
+ * what was asked. Returning the whole ledger here made every unrequested harness's copy read as
33
+ * current, which is the one thing this reader must not let a consumer conclude.
34
+ * - **The plan is computed with the selections the scope recorded.** `planLifecycle` gives each
35
+ * harness the MCP servers handed to it for that harness, so an empty list is not a neutral
36
+ * default: it is a positive statement that no server was chosen, and the plan then proposes to
37
+ * remove every one the harness owns. `doflow reconcile` and `doflow status` derive each
38
+ * harness's selection from the scope's own `doflow.lock` and ledger through
39
+ * `recordedMcpSelections` (`src/install/mcp.js`); this reader calls the same function per scope,
40
+ * so the plan a currency judgement is derived from is the plan the repair command would produce.
41
+ */
42
+
43
+ const os = require('node:os');
44
+ const { registryLifecycleView, LIFECYCLE_HARNESSES } = require('../../lifecycle/view.ts');
45
+ const { readLock } = require('../../state/lockfile.ts');
46
+ const { stateRoot, readLedger } = require('../../state/index.ts');
47
+ const { recordedMcpSelections } = require('../../install/mcp.ts');
48
+ const { readInstallManifest } = require('../../install/manifest.ts');
49
+
50
+ /**
51
+ * @typedef {Object} ScopeSnapshot The `SCOPE_SNAPSHOT` entity of the feature's data model.
52
+ * @property {'global'|'project'} scope Which scope was read.
53
+ * @property {string} scopeRoot The root that scope resolved to — home directory, or project root.
54
+ * @property {string} stateRoot The neutral state root beneath `scopeRoot`; may not exist on disk.
55
+ * @property {boolean} recorded Whether anything is recorded at this scope. Derived from the
56
+ * ledger's contents rather than from the ledger file's existence, so a scope holding an empty
57
+ * ledger and a scope holding no ledger at all report alike — both have nothing recorded. It
58
+ * describes the **scope**, not the restriction: a scope holding a full install reports `true`
59
+ * under a restriction that matches none of it, because "nothing is recorded here" would be false.
60
+ * @property {string[]} targets The harnesses this snapshot was read for — what `resources` is
61
+ * restricted to, stated rather than left for a consumer to infer from the rows present.
62
+ * @property {Array<Object>} resources The `MANAGED_RESOURCE` records of the requested harnesses,
63
+ * exactly as stored. Read only.
64
+ * @property {Object} ledger The whole neutral ledger, unrestricted, for consumers needing more than
65
+ * the requested harnesses' resources.
66
+ * @property {Object} plan The lifecycle plan for this scope and these harnesses, already computed by
67
+ * the view; it is what a currency judgement is derived from downstream.
68
+ */
69
+
70
+ /** @returns {ScopeSnapshot} */
71
+ function snapshot(scope: any, view: any, targets: any): any {
72
+ const { ledger } = view;
73
+ const wanted = new Set(targets);
74
+ return {
75
+ scope,
76
+ scopeRoot: ledger.scopeRoot,
77
+ stateRoot: view.stateRoot,
78
+ recorded: ledger.resources.length > 0 || Object.keys(ledger.targets).length > 0,
79
+ targets,
80
+ resources: ledger.resources.filter((resource: any) => wanted.has(resource?.harness)),
81
+ ledger,
82
+ plan: view.plan,
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Read the recorded state of both scopes within one invocation.
88
+ *
89
+ * @param {Object} options
90
+ * @param {Object} options.registry Registry loaded once by the caller and threaded through, as
91
+ * every other lifecycle-view call site does.
92
+ * @param {string} options.repoRoot Root of the DoFlow source the view reads native sources from.
93
+ * @param {string} options.projectRoot Root of the project scope. The global scope's root is not a
94
+ * parameter — see R7 above.
95
+ * @param {string[]} [options.targets] Harnesses to read. Defaults to every lifecycle harness,
96
+ * which is what an inventory of the whole install wants. Both the plan and the returned resources
97
+ * are restricted to these.
98
+ * @param {{global: Object|null, project: Object|null}} [options.locks] Lock documents per scope, for
99
+ * a caller that already read them (the report reads them anyway, for repair coverage). Omitted,
100
+ * each scope's lock is read from disk — `readLock` returns `null` for a scope with none, and a
101
+ * scope with no lock is a normal value here, not a failure (NFR-002).
102
+ * @returns {{global: ScopeSnapshot, project: ScopeSnapshot, scopes: ScopeSnapshot[]}}
103
+ * @throws when a lock file exists but cannot be parsed. Propagated deliberately: the plan would be
104
+ * computed from a selection nobody can read, so a currency judgement derived from it would be
105
+ * fiction. `reconcile` fails on the same document.
106
+ */
107
+ function readScopes({ registry, repoRoot, projectRoot, targets = LIFECYCLE_HARNESSES, locks }: any): any {
108
+ const wanted = [...new Set(targets)];
109
+ const resolved = locks ?? {
110
+ // The global lock root is the home directory, the same root design R7 keeps out of this
111
+ // signature; `readLock` defaults to `os.homedir()`, so both halves agree without a parameter.
112
+ global: readLock({ scope: 'global' }),
113
+ project: readLock({ scope: 'project', projectRoot }),
114
+ };
115
+ const read = (scope: any) => {
116
+ // Each scope's own ledger, at the root the view itself reads it from.
117
+ const ledger = readLedger(scope === 'global'
118
+ ? stateRoot({ scope: 'global', homeDir: os.homedir() }) : stateRoot({ scope: 'project', projectRoot }));
119
+ const manifestServers = readInstallManifest({ scopeRoot: scope === 'global' ? os.homedir() : projectRoot })?.mcpServers ?? null;
120
+ const recorded = recordedMcpSelections({ registry, lock: resolved?.[scope] ?? null, ledger, targets: wanted, manifestServers });
121
+ return registryLifecycleView({
122
+ registry, repoRoot, targets: wanted, mcpSelections: recorded.selections, mcpAdoptable: recorded.adoptable,
123
+ scope: scope === 'global' ? { global: true } : { global: false, projectRoot },
124
+ });
125
+ };
126
+ const global = snapshot('global', read('global'), wanted);
127
+ const project = snapshot('project', read('project'), wanted);
128
+ return { global, project, scopes: [global, project] };
129
+ }
130
+
131
+ module.exports = { readScopes };
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+
8
+ /**
9
+ * C5 — sibling inspector (design C5, FR-007, R6).
10
+ *
11
+ * Reports files DoFlow does not manage that sit in a directory the harness loads **wholesale** —
12
+ * every file present becomes operative context — and that also holds a managed asset. Such a file
13
+ * is live context DoFlow did not put there, whatever it is called.
14
+ *
15
+ * **What this module deliberately does not report.** A directory the harness resolves **by name**
16
+ * is not inspected at all, not even to count its entries. There the harness loads the named file
17
+ * and nothing else, so a neighbouring file cannot take effect however it is named: a superseded
18
+ * `SKILL.md.pre-v1.2.0-backup-*` sitting beside `SKILL.md` is inert, and reporting it is noise.
19
+ * Within one scope DoFlow owns the path a same-named asset would occupy, so a same-scope name
20
+ * collision cannot arise either; genuine cross-scope outranking is FR-004's question, answered by
21
+ * `precedence.js`. This module therefore applies no filename heuristic whatsoever — in an inspected
22
+ * directory, presence is the whole test (requirement §9, 2026-09-27; design §9, C5).
23
+ *
24
+ * **The bound on cost (R6).** Only the parent directories of assets already enumerated from the
25
+ * ledger are read — one level, never recursed into. A managed tree that nests (Kiro's steering tree
26
+ * mirrors `core/shared/guidance/`, `rules/` and `mcp/` included) is still covered completely
27
+ * without a walk, because every subdirectory holding a managed file is itself the parent of a
28
+ * managed resource and so is inspected on its own account. `mcp-builder/` and `skill-creator/`
29
+ * sitting beside a managed skills directory are never enumerated: neither is the parent of a
30
+ * managed asset (requirement §5, "Out of Scope").
31
+ *
32
+ * Two hard rules follow directly from the design:
33
+ * - No currency verdict is ever attached to a candidate (data-model `UNMANAGED_CANDIDATE`): there
34
+ * is no recorded fingerprint for a file DoFlow does not manage, so it is reported as present,
35
+ * never as diverged.
36
+ * - Read-only throughout (NFR-001). Nothing here writes, moves, or deletes.
37
+ *
38
+ * Follows this repo's directory-reading idiom (`src/runtime/knowledge/index-store.js`): a bare
39
+ * `readdirSync(dir, { withFileTypes: true })` inside a try/catch that skips on failure. A managed
40
+ * asset's parent directory may legitimately not exist — a recorded resource whose file was since
41
+ * removed by hand — and that is not an error this read-only module raises.
42
+ */
43
+
44
+ /**
45
+ * The asset/harness pairs whose native directory the harness loads wholesale.
46
+ *
47
+ * **Nothing in the registry declares load semantics.** This set is derived from adapter behaviour,
48
+ * read off two locators in this repository:
49
+ *
50
+ * - `src/adapters/copy-tree.js` — the `copilot-rule-instructions` transform (line ~80) renders
51
+ * *every* file it copies under an `applyTo: '**'` front-matter header, so every file landing in
52
+ * `.github/instructions/` is always applied. Asset `instructions.copilot`, native dir
53
+ * `instructions`.
54
+ * - `src/adapters/kiro/index.js` (header comment, lines 8-12) — "Kiro receives DoFlow's *full*
55
+ * guidance tree as steering files", with no single-file instruction surface at all. Asset
56
+ * `guidance.context-layer` for the `kiro` harness only, native dir `steering`; the same asset's
57
+ * `../.doflow/guidance` projection for claude/codex/gemini/antigravity is name-resolved by
58
+ * explicit import and is not in this set.
59
+ *
60
+ * This is the single place to change. If the registry later declares load semantics per projection,
61
+ * replace this constant's contents with that lookup — do not add a second source of the fact, and
62
+ * do not infer it from a directory's name. An asset/harness pair absent here is name-resolved and
63
+ * its directory is skipped entirely.
64
+ *
65
+ * @type {Readonly<Object<string, ReadonlyArray<string>>>} harness id -> registry asset ids
66
+ */
67
+ const WHOLESALE_ASSETS = Object.freeze({
68
+ copilot: Object.freeze(['instructions.copilot']),
69
+ kiro: Object.freeze(['guidance.context-layer']),
70
+ });
71
+
72
+ /**
73
+ * @typedef {Object} UnmanagedCandidate The `UNMANAGED_CANDIDATE` entity of the feature's data model.
74
+ * @property {string} name The entry's own filename, exactly as read from the directory.
75
+ * @property {string} path The entry's absolute path.
76
+ * @property {string} parentDir The absolute directory it was found in — a managed asset's own
77
+ * parent, and one the harness loads wholesale.
78
+ * @property {'file'|'directory'} kind What the entry is. A `directory` is an unmanaged directory
79
+ * inside a wholesale tree: its contents are loaded too, and were not enumerated because this
80
+ * module does not recurse. A directory that itself holds a managed asset is not a candidate at
81
+ * all — it is the managed tree's own structure.
82
+ * @property {string} harness The harness whose wholesale load makes this file operative.
83
+ * @property {string} assetId The registry asset whose directory it shares.
84
+ *
85
+ * No `currency` field exists on this shape, deliberately: an unmanaged file carries no recorded
86
+ * fingerprint, so it can never be judged to match or diverge from anything.
87
+ */
88
+
89
+ /** Whether this harness loads this asset's native directory wholesale. `Array.isArray` also guards
90
+ * a harness id that collides with an `Object.prototype` key. */
91
+ function loadsWholesale(harness: any, assetId: any): boolean {
92
+ const assets = (WHOLESALE_ASSETS as any)[harness];
93
+ return Array.isArray(assets) && assets.includes(assetId);
94
+ }
95
+
96
+ /** Groups managed resources by parent directory. Every well-formed resource contributes its leaf
97
+ * filename (so a managed file is never mistaken for an unmanaged one) and its directory (so a
98
+ * subdirectory holding managed assets is recognised as structure, not as a foreign entry); only
99
+ * resources whose harness loads them wholesale contribute a `wholesale` target, and only a
100
+ * directory with at least one of those is ever read. */
101
+ function groupByParentDir(resources: any): any {
102
+ const byDir = new Map();
103
+ const managedDirs = new Set();
104
+ for (const resource of resources ?? []) {
105
+ const target = resource?.target;
106
+ if (typeof target !== 'string' || target === '') continue;
107
+ const dir = path.dirname(target);
108
+ managedDirs.add(dir);
109
+ if (!byDir.has(dir)) byDir.set(dir, { basenames: new Set(), wholesale: [] });
110
+ const entry = byDir.get(dir);
111
+ entry.basenames.add(path.basename(target));
112
+ if (loadsWholesale(resource.harness, resource.assetId)) {
113
+ entry.wholesale.push({ target, harness: resource.harness, assetId: resource.assetId });
114
+ }
115
+ }
116
+ return { byDir, managedDirs };
117
+ }
118
+
119
+ /** Whether a directory entry is part of the managed tree's own structure — it is, or contains at
120
+ * some depth, the parent directory of a managed resource. Scans `managedDirs`, which is bounded by
121
+ * the number of managed resources rather than by directory size (R6). */
122
+ function holdsManagedAsset(candidatePath: any, managedDirs: any): boolean {
123
+ const prefix = candidatePath + path.sep;
124
+ for (const dir of managedDirs) {
125
+ if (dir === candidatePath || (dir as string).startsWith(prefix)) return true;
126
+ }
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * Inspects the wholesale-loaded parent directories of already-enumerated managed resources for
132
+ * files DoFlow does not manage.
133
+ *
134
+ * @param {Object} options
135
+ * @param {Array<{harness: string, assetId: string, target: string}>} options.resources Managed
136
+ * resources exactly as recorded — from one scope or both concatenated; this module does not care
137
+ * which scope a resource came from, only where it lives on disk and how its harness loads it.
138
+ * @param {Object} [options.fsImpl] Injectable `node:fs`-shaped module, for hermetic tests.
139
+ * @returns {{
140
+ * candidatesByTarget: Object<string, UnmanagedCandidate[]>,
141
+ * directoriesInspected: number,
142
+ * }} `candidatesByTarget` maps a managed resource's absolute `target` to the unmanaged entries
143
+ * sharing its directory (present only for targets whose directory held at least one);
144
+ * `directoriesInspected` is how many wholesale directories actually existed and were read.
145
+ *
146
+ * **Nothing inspected is ever withheld, so no count of withheld entries is returned.** IC-002 names
147
+ * such a count, and this module used to return it as a literal 0 to satisfy that name. Under the
148
+ * narrowed FR-007 the two halves of this module's behaviour leave it no other value to hold: an
149
+ * inspected directory is read in full and every entry that is neither a managed basename nor
150
+ * managed structure is listed, and a directory that is not inspected is never read, so its entries
151
+ * are not inspected either. A field with one reachable value states nothing — and reporting 0
152
+ * beside eight reported siblings invited the reading that eight more had been hidden. IC-002's
153
+ * final field is dropped with it (task F.3); see `index.js#buildInventoryReport`.
154
+ */
155
+ function inspectSiblings({ resources, fsImpl = fs }: any = {}): any {
156
+ const { byDir, managedDirs } = groupByParentDir(resources);
157
+ const candidatesByTarget: any = {};
158
+ let directoriesInspected = 0;
159
+
160
+ for (const [dir, { basenames, wholesale }] of byDir) {
161
+ if ((wholesale as any[]).length === 0) continue; // name-resolved: never read, not even to count.
162
+
163
+ let entries;
164
+ try {
165
+ entries = fsImpl.readdirSync(dir, { withFileTypes: true });
166
+ } catch {
167
+ continue; // the managed asset's parent legitimately may not exist; not an error.
168
+ }
169
+ directoriesInspected += 1;
170
+
171
+ const unmanaged: any[] = [];
172
+ for (const entry of entries) {
173
+ const name = (entry as any).name;
174
+ if ((basenames as Set<string>).has(name)) continue; // the managed file itself.
175
+ const entryPath = path.join(dir as string, name);
176
+ const isDirectory = (entry as any).isDirectory?.() === true;
177
+ if (isDirectory && holdsManagedAsset(entryPath, managedDirs)) continue; // managed structure.
178
+ unmanaged.push({ name, path: entryPath, parentDir: dir, kind: isDirectory ? 'directory' : 'file' });
179
+ }
180
+ if (unmanaged.length === 0) continue;
181
+
182
+ // Attributed per managed target rather than shared, so each candidate names the harness and
183
+ // asset whose wholesale load is the reason it is operative at all.
184
+ for (const { target, harness, assetId } of (wholesale as any[])) {
185
+ candidatesByTarget[target] = unmanaged.map((found: any) => ({ ...found, harness, assetId }));
186
+ }
187
+ }
188
+
189
+ return { candidatesByTarget, directoriesInspected };
190
+ }
191
+
192
+ module.exports = { inspectSiblings, WHOLESALE_ASSETS };
@@ -0,0 +1,124 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ // Structure-aware markdown chunker. Chunks follow document
6
+ // structure — frontmatter becomes metadata, headings bound sections, fenced code blocks never
7
+ // split mid-block — because every downstream stage (indexing, ranking, prompting) inherits the
8
+ // coherence this stage preserves. Character budgets approximate common token targets (~150-token
9
+ // children ≈ 600 chars) without a tokenizer dependency: the corpus is
10
+ // prose-dominant English/markdown, where chars/4 tracks tokens closely enough to rank with.
11
+
12
+ const TARGET_CHUNK_CHARS = 600;
13
+ const HARD_MAX_CHUNK_CHARS = 1200;
14
+
15
+ /** Split raw markdown into chunks: {id, path, section, text}. Ids are stable path+ordinal pairs,
16
+ * not content hashes — the index manifest owns content addressing, and a chunk whose text changed
17
+ * should keep its identity so diffs are legible. */
18
+ function chunkMarkdown(rawText: any, { path: filePath }: { path?: string } = {}): any[] {
19
+ const { body, title } = stripFrontmatter(String(rawText ?? ''));
20
+ const sections = splitSections(body);
21
+ const chunks: any[] = [];
22
+ let ordinal = 0;
23
+ for (const section of sections) {
24
+ for (const piece of packSection(section.text)) {
25
+ chunks.push({
26
+ id: `${filePath ?? 'memory'}#${ordinal}`,
27
+ path: filePath ?? null,
28
+ title: section.heading ? `${title ? `${title} › ` : ''}${section.heading}` : (title ?? null),
29
+ ordinal,
30
+ text: piece,
31
+ });
32
+ ordinal += 1;
33
+ }
34
+ }
35
+ return chunks;
36
+ }
37
+
38
+ function stripFrontmatter(text: string): { body: string; title: string | null } {
39
+ const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
40
+ if (!match) return { body: text, title: null };
41
+ const meta = match[1];
42
+ const titleMatch = /^title:\s*(.+)$/m.exec(meta) ?? /^#\s+(.+)$/m.exec(text.slice(match[0].length));
43
+ return { body: text.slice(match[0].length), title: titleMatch ? titleMatch[1].trim().replace(/^["']|["']$/g, '') : null };
44
+ }
45
+
46
+ /** Heading-bounded sections; a leading H1 is metadata (the title), not a section boundary. */
47
+ function splitSections(body: string): Array<{ heading: string | null; text: string }> {
48
+ const lines = body.split(/\r?\n/);
49
+ const sections: Array<{ heading: string | null; text: string }> = [];
50
+ let current: { heading: string | null; lines: string[] } = { heading: null, lines: [] };
51
+ const flush = () => {
52
+ if (current.lines.some((l) => l.trim() !== '')) {
53
+ sections.push({ heading: current.heading, text: current.lines.join('\n') });
54
+ }
55
+ };
56
+ for (const line of lines) {
57
+ const heading = /^(#{2,6})\s+(.*)$/.exec(line);
58
+ if (heading) {
59
+ flush();
60
+ current = { heading: heading[2].trim(), lines: [] };
61
+ continue;
62
+ }
63
+ current.lines.push(line);
64
+ }
65
+ flush();
66
+ return sections.length ? sections : [{ heading: null, text: body }];
67
+ }
68
+
69
+ /** Group a section's blocks (paragraphs, lists, fenced code) into chunks near the target size.
70
+ * A block larger than the hard max (a long code fence) stands alone rather than being cut. */
71
+ function packSection(text: string): string[] {
72
+ const blocks = splitBlocks(text).filter((b) => b.trim() !== '');
73
+ const packed: string[] = [];
74
+ let buffer = '';
75
+ for (const block of blocks) {
76
+ if (block.length > HARD_MAX_CHUNK_CHARS) {
77
+ if (buffer.trim()) packed.push(buffer.trim());
78
+ packed.push(block.trim());
79
+ buffer = '';
80
+ continue;
81
+ }
82
+ if (buffer && buffer.length + block.length + 2 > TARGET_CHUNK_CHARS) {
83
+ packed.push(buffer.trim());
84
+ buffer = block;
85
+ } else {
86
+ buffer = buffer ? `${buffer}\n\n${block}` : block;
87
+ }
88
+ }
89
+ if (buffer.trim()) packed.push(buffer.trim());
90
+ return packed;
91
+ }
92
+
93
+ /** Split into blocks at blank lines, keeping fenced code blocks intact across their blank lines. */
94
+ function splitBlocks(text: string): string[] {
95
+ const lines = text.split(/\r?\n/);
96
+ const blocks: string[] = [];
97
+ let buffer: string[] = [];
98
+ let fence: string | null = null;
99
+ for (const line of lines) {
100
+ const opening = /^\s*(```|~~~)/.exec(line);
101
+ if (fence) {
102
+ buffer.push(line);
103
+ if (opening && opening[1] === fence) fence = null;
104
+ continue;
105
+ }
106
+ if (opening) {
107
+ if (buffer.some((l) => l.trim() !== '')) blocks.push(buffer.join('\n'));
108
+ buffer = [line];
109
+ fence = opening[1];
110
+ continue;
111
+ }
112
+ if (line.trim() === '' && !buffer.some((l) => l.trim() !== '')) continue;
113
+ if (line.trim() === '') {
114
+ blocks.push(buffer.join('\n'));
115
+ buffer = [];
116
+ continue;
117
+ }
118
+ buffer.push(line);
119
+ }
120
+ if (buffer.some((l) => l.trim() !== '')) blocks.push(buffer.join('\n'));
121
+ return blocks;
122
+ }
123
+
124
+ module.exports = { chunkMarkdown, TARGET_CHUNK_CHARS, HARD_MAX_CHUNK_CHARS };