@khoavu882/doflow 2.0.0-beta.4 → 2.0.0

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 (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration: run directly by Node native type-stripping.
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+ const crypto = require('node:crypto');
7
+
8
+ function sha256(text ) {
9
+ return `sha256:${crypto.createHash('sha256').update(text, 'utf8').digest('hex')}`;
10
+ }
11
+
12
+ function inside(root , file , fsImpl = fs) {
13
+ const absolute = path.resolve(file);
14
+ let realRoot ;
15
+ let realFile ;
16
+ try {
17
+ realRoot = fsImpl.realpathSync(path.resolve(root));
18
+ realFile = fsImpl.realpathSync(absolute);
19
+ } catch { return null; }
20
+ const relative = path.relative(realRoot, realFile);
21
+ if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) return null;
22
+ const normalized = relative.split(path.sep).join('/');
23
+ if (normalized.split('/').includes('state')) return null;
24
+ return normalized;
25
+ }
26
+
27
+ function lineSlice(lines , startLine , endLine ) {
28
+ return lines.slice(startLine - 1, endLine).join('\n') + '\n';
29
+ }
30
+
31
+ function rangesFor(file , text , requirement ) {
32
+ const lines = text.split(/\r?\n/);
33
+ const result = [];
34
+ if (requirement) {
35
+ let section = '';
36
+ for (let i = 0; i < lines.length; i++) {
37
+ const heading = lines[i].match(/^##\s+(.*)$/);
38
+ if (heading) section = heading[1];
39
+ if (!/^##\s+[34](?:[.)]|\s|$)/.test(lines[i])) continue;
40
+ let end = i + 1;
41
+ while (end < lines.length && !/^##\s+/.test(lines[end])) end++;
42
+ for (let j = i + 1; j < end; j++) {
43
+ if (!/^\s*\*\*Detail:?\*\*/i.test(lines[j])) continue;
44
+ let blockEnd = j + 1;
45
+ while (blockEnd < end && !/^\s*\*\*[A-Za-z][^*]*:?\*\*/.test(lines[blockEnd])) blockEnd++;
46
+ result.push({ startLine: j + 1, endLine: blockEnd, heading: section || null });
47
+ j = blockEnd - 1;
48
+ }
49
+ i = end - 1;
50
+ }
51
+ return result;
52
+ }
53
+ let start = -1;
54
+ let title = null;
55
+ const flush = (end ) => {
56
+ if (start >= 0 && end >= start) result.push({ startLine: start + 1, endLine: end, heading: title });
57
+ };
58
+ for (let i = 0; i < lines.length; i++) {
59
+ const heading = lines[i].match(/^##\s+(.+)$/);
60
+ if (heading) {
61
+ flush(i);
62
+ start = i;
63
+ title = heading[1].trim();
64
+ }
65
+ }
66
+ flush(lines.length);
67
+ return result;
68
+ }
69
+
70
+ function createPointer({ path: rel, heading, range, slice } ) {
71
+ const bytes = Buffer.byteLength(slice, 'utf8');
72
+ return {
73
+ id: `ptr:${rel}:${range.startLine}-${range.endLine}`,
74
+ path: rel,
75
+ heading,
76
+ range,
77
+ digest: sha256(slice),
78
+ bytes,
79
+ tokensEst: { value: Math.ceil(bytes / 4), basis: 'estimated:bytes/4' },
80
+ inlineBytes: Buffer.byteLength(`- ptr:${rel}:${range.startLine}-${range.endLine} ${rel}:${range.startLine}-${range.endLine} ${bytes} B\n`, 'utf8'),
81
+ load: `read ${rel} lines ${range.startLine}-${range.endLine}`,
82
+ };
83
+ }
84
+
85
+ function sourceFiles({ projectRoot, feature, fsImpl = fs } ) {
86
+ const root = path.resolve(projectRoot);
87
+ const featureDir = feature?.featureDir ? path.resolve(feature.featureDir) : null;
88
+ const candidates = [];
89
+ const add = (file , requirement = false) => {
90
+ if (!file) return;
91
+ const absolute = path.isAbsolute(file) ? file : path.resolve(feature?.repoRoot || root, file);
92
+ let stat ;
93
+ try { stat = fsImpl.statSync(absolute); } catch { return; }
94
+ if (!inside(root, absolute, fsImpl) || !stat.isFile()) return;
95
+ if (!candidates.some((item) => item.absolute === absolute)) candidates.push({ absolute, requirement });
96
+ };
97
+ if (featureDir) {
98
+ add(feature.paths?.requirement || path.join(featureDir, 'intention', 'requirement.md'), true);
99
+ for (const file of [
100
+ feature.paths?.design,
101
+ feature.paths?.plan,
102
+ path.join(featureDir, 'design', 'design.md'),
103
+ path.join(featureDir, 'design', 'specs.md'),
104
+ path.join(featureDir, 'design', 'data-model.md'),
105
+ path.join(featureDir, 'plan', 'plan.md'),
106
+ path.join(featureDir, 'design.md'),
107
+ path.join(featureDir, 'plan.md'),
108
+ ]) add(file);
109
+ }
110
+ return candidates;
111
+ }
112
+
113
+ function collectPointers({ projectRoot, feature, fsImpl = fs } ) {
114
+ const root = path.resolve(projectRoot);
115
+ const pointers = [];
116
+ for (const source of sourceFiles({ projectRoot: root, feature, fsImpl })) {
117
+ const rel = inside(root, source.absolute, fsImpl);
118
+ if (!rel) continue;
119
+ let text ;
120
+ try { text = fsImpl.readFileSync(source.absolute, 'utf8'); } catch { continue; }
121
+ const lines = text.split(/\r?\n/);
122
+ for (const range of rangesFor(rel, text, source.requirement)) {
123
+ const slice = lineSlice(lines, range.startLine, range.endLine);
124
+ if (!slice.trim()) continue;
125
+ pointers.push(createPointer({ path: rel, heading: range.heading, range: { startLine: range.startLine, endLine: range.endLine }, slice }));
126
+ }
127
+ }
128
+ return pointers;
129
+ }
130
+
131
+ function readPointerSlice({ projectRoot, pointer, fsImpl = fs } ) {
132
+ const rel = inside(projectRoot, path.resolve(projectRoot, pointer.path), fsImpl);
133
+ if (!rel || rel !== pointer.path) return null;
134
+ const file = path.resolve(projectRoot, rel);
135
+ try {
136
+ const lines = fsImpl.readFileSync(file, 'utf8').split(/\r?\n/);
137
+ const slice = lineSlice(lines, pointer.range.startLine, pointer.range.endLine);
138
+ return { slice, digest: sha256(slice) };
139
+ } catch { return null; }
140
+ }
141
+
142
+ module.exports = { collectPointers, createPointer, readPointerSlice, sha256 };
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `doflow primary` (120 IC-008): the read-only view of which harness and model the main session runs.
5
+ * `--action resolve` prints `resolvePrimary` (IC-007); `--action observed` prints `observedSessions`
6
+ * (093 IC-018 a). Neither writes anything: no state, no config home, no upkeep.
7
+ * Loaded only when the verb, `status` or `doctor` prints the primary, never on an install path.
8
+ */
9
+
10
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
11
+ const { loadRegistry } = require('../../registry/index.js');
12
+ const { finishRuntime, captureCaught } = require('../cli-result.js');
13
+ const { resolvePrimary, observedSessions } = require('./primary.js');
14
+
15
+ const ALLOWED_FLAGS = new Set(['--action', '--json']);
16
+
17
+ function dash(value ) {
18
+ return value === null || value === undefined ? '-' : String(value);
19
+ }
20
+
21
+ /** The text form: one line per field, then each warning. */
22
+ function primaryLines(primary ) {
23
+ const { declared, observed } = primary;
24
+ const declaredText = declared
25
+ ? `${declared.harness}/${dash(declared.model)} (${declared.origin.harness}${declared.origin.model ? `, model ${declared.origin.model}` : ''})`
26
+ : '-';
27
+ const observedText = observed ? `${observed.model} (${observed.event}, ${observed.observedAt}, sessions ${observed.sessions})` : '-';
28
+ return [
29
+ `state: ${primary.state}`,
30
+ `harness: ${dash(primary.harness)}`,
31
+ `model: ${dash(primary.model)}`,
32
+ `family: ${primary.family}`,
33
+ `declared: ${declaredText}`,
34
+ `observed: ${observedText}`,
35
+ `probe: ${primary.probe}`,
36
+ `reason: ${dash(primary.reason)}`,
37
+ ...primary.warnings.map((warning ) => `warning: ${warning}`),
38
+ ];
39
+ }
40
+
41
+ /** The origin layer of the declared harness; `default` when nothing declared one. */
42
+ function primaryOrigin(primary ) {
43
+ return primary.declared?.origin?.harness ?? 'default';
44
+ }
45
+
46
+ /** `<state> <harness>/<model> (<origin>)`, the line `status` and `doctor` share. */
47
+ function primarySummary(primary ) {
48
+ return `${primary.state} ${dash(primary.harness)}/${dash(primary.model)} (${primaryOrigin(primary)})`;
49
+ }
50
+
51
+ /** The `doctor` object: the probe label is `declared-only` without a probe, else the harness's recorded probe status. */
52
+ function primaryDoctorView({ primary, registry } ) {
53
+ const entry = (registry.harnesses || []).find((harness ) => harness?.id === primary.harness);
54
+ const probe = primary.probe === 'none' ? 'declared-only' : (entry?.primaryProbe?.status ?? 'unverified');
55
+ return { state: primary.state, harness: primary.harness, model: primary.model, origin: primaryOrigin(primary), probe };
56
+ }
57
+
58
+ /** The text form of `observed`: the store, one line per checkout, one per session; no path or raw id. */
59
+ function observedLines(body ) {
60
+ return [
61
+ `store: ${body.store}`,
62
+ ...body.checkouts.map((c ) => `checkout ${c.worktreeKey} ${c.role}${c.current ? ' current' : ''} ${c.branch === null ? 'detached' : c.branch} ${dash(c.head)} ${dash(c.headCommittedAt)}`),
63
+ ...body.sessions.map((s ) => {
64
+ const unit = s.launched ? ` L.${s.launched.run}.${s.launched.lineage}.${s.launched.attempt}` : '';
65
+ return `session ${s.sessionKey} ${s.worktreeKey} ${s.kind}${unit} harness ${dash(s.harness)} model ${dash(s.model)} started ${dash(s.startedAt)} last-hook-record ${dash(s.lastHookRecordAt)}`;
66
+ }),
67
+ ...(body.truncated ? ['truncated: true'] : []),
68
+ ...(body.unreadable > 0 ? [`unreadable: ${body.unreadable}`] : []),
69
+ ];
70
+ }
71
+
72
+ function handlePrimaryCommand({ action, json = false, flags = [], env = process.env, cwd = process.cwd(), registry, out = (line ) => console.log(line) } = {}) {
73
+ const usage = (message ) => {
74
+ out(json ? JSON.stringify({ ok: false, error: 'usage', message }) : `doflow primary: ${message}`);
75
+ return finishRuntime(2);
76
+ };
77
+ if (action !== 'resolve' && action !== 'observed') return usage('action must be resolve');
78
+ const extra = (flags ).map((flag) => flag.split('=')[0]).find((flag) => !ALLOWED_FLAGS.has(flag));
79
+ if (extra !== undefined) return usage(`unsupported flag ${extra}; only --action and --json apply`);
80
+
81
+ if (action === 'observed') {
82
+ try {
83
+ const observed = observedSessions({ env, cwd });
84
+ if (!observed.ok) {
85
+ if (json) out(JSON.stringify({ v: 1, error: observed.error }));
86
+ else console.error('[ERROR] primary: cannot list checkouts');
87
+ return finishRuntime(1);
88
+ }
89
+ if (json) out(JSON.stringify(observed.body));
90
+ else for (const line of observedLines(observed.body)) out(line);
91
+ return finishRuntime(0);
92
+ } catch (error ) {
93
+ captureCaught(error, 'primary', 1);
94
+ console.error(`[ERROR] primary: ${error?.message || String(error)}`);
95
+ return finishRuntime(1);
96
+ }
97
+ }
98
+
99
+ try {
100
+ const primary = resolvePrimary({ env, projectRoot: cwd, registry });
101
+ if (json) out(JSON.stringify(primary));
102
+ else for (const line of primaryLines(primary)) out(line);
103
+ return finishRuntime(primary.state === 'refused' ? 1 : 0);
104
+ } catch (error ) {
105
+ captureCaught(error, 'primary', 1);
106
+ console.error(`[ERROR] primary: ${error?.message || String(error)}`);
107
+ return finishRuntime(1);
108
+ }
109
+ }
110
+
111
+ /** The `status` and `doctor` reader: one resolution with the shipped registry; the caller handles a fault. */
112
+ function resolveForReport({ env, projectRoot, repoRoot = REPO_ROOT } ) {
113
+ const registry = loadRegistry({ repoRoot });
114
+ return { primary: resolvePrimary({ env, projectRoot, registry }), registry };
115
+ }
116
+
117
+ module.exports = { handlePrimaryCommand, primaryLines, primarySummary, primaryDoctorView, resolveForReport };
@@ -0,0 +1,344 @@
1
+ 'use strict';
2
+
3
+ // A.3 (IC-005 and IC-007): read-only resolution of the main session's declared and observed model.
4
+
5
+ const crypto = require('node:crypto');
6
+ const fs = require('node:fs');
7
+ const path = require('node:path');
8
+ const { spawnSync } = require('node:child_process');
9
+ const { cwdHash, stateDir, ID_PATTERN, ATTEMPT_PATTERN } = require('./identity.js');
10
+ const { loadSettings } = require('../settings/resolve.js');
11
+ const { validateValue } = require('../settings/files.js');
12
+ const { loadRegistry } = require('../../registry/index.js');
13
+ const { REPO_ROOT } = require('../../helper/repo-root.js');
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+ function isObject(value ) {
33
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
34
+ }
35
+
36
+ function sameModel(left , right ) {
37
+ return left.trim().toLowerCase() === right.trim().toLowerCase();
38
+ }
39
+
40
+ /** IC-005: the common observation shape, without a reader's lifetime policy. */
41
+ function parseObservation(value , harness ) {
42
+ if (!isObject(value)) return null;
43
+ const keys = Object.keys(value).sort();
44
+ if (keys.join(',') !== 'event,harness,model,observedAt,schema,session') return null;
45
+ if (value.schema !== 1 || value.harness !== harness) return null;
46
+ if (typeof value.model !== 'string' || validateValue({ type: 'model-id' }, value.model)) return null;
47
+ if (typeof value.event !== 'string' || value.event.length === 0) return null;
48
+ if (typeof value.observedAt !== 'string' || !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z$/.test(value.observedAt)) return null;
49
+ if (!Number.isFinite(Date.parse(value.observedAt))) return null;
50
+ if (typeof value.session !== 'string' || !/^[0-9a-f]{16}$/.test(value.session)) return null;
51
+ return { model: value.model, event: value.event, observedAt: value.observedAt, session: value.session };
52
+ }
53
+
54
+ /** IC-005: read valid main-session model observations for one project and harness. A file that
55
+ * cannot be read or parsed adds one to `counts.unreadable` when `counts` is given. */
56
+ function readObservations({ root, projectRoot, harness, now, maxAgeMs = null, counts } ) {
57
+ if (!root) return [];
58
+ const dir = path.join(root, 'primary', cwdHash(projectRoot), harness);
59
+ let entries ;
60
+ try { entries = fs.readdirSync(dir); } catch { return []; }
61
+ const observations = [];
62
+
63
+ for (const name of entries) {
64
+ if (!name.endsWith('.json')) continue;
65
+ const file = path.join(dir, name);
66
+ try {
67
+ if (!fs.lstatSync(file).isFile()) continue;
68
+ const value = parseObservation(JSON.parse(fs.readFileSync(file, 'utf8')), harness);
69
+ if (!value) { if (counts) counts.unreadable += 1; continue; }
70
+ const observedAt = Date.parse(value.observedAt);
71
+ if (observedAt > now) continue;
72
+ if (maxAgeMs !== null && now - observedAt > maxAgeMs) continue;
73
+ observations.push({ ...value, time: observedAt });
74
+ } catch {
75
+ // Malformed, unreadable, and non-file entries do not count as observations.
76
+ if (counts) counts.unreadable += 1;
77
+ }
78
+ }
79
+ return observations;
80
+ }
81
+
82
+ /** The `model-observations` store's age bound in milliseconds, or null when it declares none. */
83
+ function observationMaxAgeMs(storage ) {
84
+ const store = (storage.stores || []).find((entry ) => entry?.id === 'model-observations');
85
+ const maxAgeDays = store?.bound?.maxAgeDays;
86
+ return Number.isFinite(maxAgeDays) ? maxAgeDays * 24 * 60 * 60 * 1000 : null;
87
+ }
88
+
89
+ /** IC-013 / IC-005: read one worker's observation; the storage store owns its lifetime. */
90
+ function readWorkerObservation({ root, identity, harness, fsImpl = fs } ) {
91
+ try {
92
+ if (typeof root !== 'string' || !isObject(identity)) return null;
93
+ if (typeof identity.run !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,55}$/.test(identity.run)) return null;
94
+ if (typeof identity.lineage !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,55}$/.test(identity.lineage)) return null;
95
+ if (typeof identity.attempt !== 'string' || !/^[1-9][0-9]{0,3}$/.test(identity.attempt)) return null;
96
+ const file = path.join(root, 'worker-model', `L.${identity.run}.${identity.lineage}.${identity.attempt}.json`);
97
+ if (!fsImpl.lstatSync(file).isFile()) return null;
98
+ return parseObservation(JSON.parse(fsImpl.readFileSync(file, 'utf8')), harness);
99
+ } catch {
100
+ return null;
101
+ }
102
+ }
103
+
104
+ /** IC-007: resolve the primary harness and model using settings and read-only hook observations. */
105
+ function resolvePrimary({
106
+ env,
107
+ projectRoot = process.cwd(),
108
+ registry,
109
+ models,
110
+ storage,
111
+ now = Date.now,
112
+ observationsRoot,
113
+ }
114
+
115
+
116
+
117
+
118
+
119
+
120
+
121
+ = {}) {
122
+ const runtimeEnv = env ?? {};
123
+ const role = (runtimeEnv.DOFLOW_SESSION_ROLE || '').replace(/[A-Z]/g, (c) => c.toLowerCase());
124
+ const nullDeclared = { harness: null, model: null, origin: { harness: null, model: null } };
125
+ const result = (state , fields = {}) => ({
126
+ state,
127
+ harness: null,
128
+ model: null,
129
+ family: 'unknown',
130
+ declared: null,
131
+ observed: null,
132
+ probe: 'none',
133
+ warnings: [],
134
+ reason: null,
135
+ ...fields,
136
+ });
137
+
138
+ if (role !== '' && role !== 'main') return result('not-primary', { reason: 'worker-role' });
139
+
140
+ const settings = loadSettings({ projectRoot, env: runtimeEnv });
141
+ const harnessSetting = settings.get('primary.harness');
142
+ const modelSetting = settings.get('primary.model');
143
+ const contradictionSetting = settings.get('primary.onContradiction');
144
+ const harness = harnessSetting.value;
145
+ const declaredModel = modelSetting.value;
146
+ const declared = harness === 'unknown' ? null : {
147
+ harness,
148
+ model: declaredModel,
149
+ origin: { harness: harnessSetting.origin.layer, model: declaredModel === null ? null : modelSetting.origin.layer },
150
+ };
151
+ const warnings = [];
152
+ if (harness === 'unknown') {
153
+ if (declaredModel !== null) warnings.push('primary-model-without-harness');
154
+ return result('unknown', { declared: null, warnings });
155
+ }
156
+
157
+ const { REPO_ROOT: repoRoot } = require('../../helper/repo-root.js');
158
+ const registryValue = registry ?? loadRegistry({ repoRoot });
159
+ const harnessEntry = (registryValue.harnesses || []).find((entry ) => entry?.id === harness);
160
+ const probe = harnessEntry?.primaryProbe?.model === 'payload' ? 'payload' : 'none';
161
+ if (probe === 'none') return result('declared-only', { harness, model: declaredModel, declared, probe, warnings });
162
+
163
+ let loadedModels = models;
164
+ if (!loadedModels) loadedModels = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core/registry/models.json'), 'utf8'));
165
+ let loadedStorage = storage;
166
+ if (!loadedStorage) loadedStorage = JSON.parse(fs.readFileSync(path.join(REPO_ROOT, 'core/registry/storage.json'), 'utf8'));
167
+ const maxAgeMs = observationMaxAgeMs(loadedStorage);
168
+ const root = observationsRoot === undefined ? stateDir(runtimeEnv) : observationsRoot;
169
+ const observations = readObservations({ root, projectRoot, harness, now: now(), maxAgeMs });
170
+ if (observations.length === 0) {
171
+ return result('unconfirmed', { harness, model: declaredModel, declared, probe, warnings, reason: 'no-observation' });
172
+ }
173
+
174
+ const groups = new Map ();
175
+ for (const observation of observations) {
176
+ const key = observation.model.trim().toLowerCase();
177
+ if (!groups.has(key)) groups.set(key, []);
178
+ groups.get(key) .push(observation);
179
+ }
180
+ if (groups.size > 1) {
181
+ return result('unconfirmed', { harness, model: declaredModel, declared, probe, warnings, reason: 'ambiguous-sessions' });
182
+ }
183
+
184
+ const group = [...groups.values()][0];
185
+ group.sort((a, b) => b.time - a.time);
186
+ const newest = group[0];
187
+ const observed = {
188
+ model: newest.model,
189
+ event: newest.event,
190
+ observedAt: newest.observedAt,
191
+ sessions: group.length,
192
+ };
193
+ const observedRecord = { ...observed, session: newest.session };
194
+ if (declaredModel === null || sameModel(declaredModel, newest.model)) {
195
+ const provider = (loadedModels.providers || []).find((entry ) => entry?.id === harness);
196
+ const families = Array.isArray(provider?.families) ? provider.families.filter((family ) => family !== 'unknown') : [];
197
+ const family = families.length === 1 && provider.families.length === 1 ? families[0] : 'unknown';
198
+ return result('confirmed', { harness, model: newest.model, family, declared, observed: observedRecord, probe, warnings });
199
+ }
200
+
201
+ warnings.push(`primary-contradicted: declared ${declaredModel}, observed ${newest.model}`);
202
+ const refused = contradictionSetting.value === 'refuse';
203
+ return result(refused ? 'refused' : 'contradicted', {
204
+ harness,
205
+ model: newest.model,
206
+ declared,
207
+ observed: observedRecord,
208
+ probe,
209
+ warnings,
210
+ reason: refused ? 'primary-contradicted' : null,
211
+ });
212
+ }
213
+
214
+ const SESSION_DIR_LIMIT = 2000;
215
+ const SESSION_DIR = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/; // the session-dirs store's match
216
+ const HARNESS_DIR = /^[a-z0-9][a-z0-9-]*$/; // observe-model.sh's harness check
217
+
218
+ const keyOf = (id ) => crypto.createHash('sha256').update(id).digest('hex').slice(0, 16);
219
+ const isoOrNull = (value ) => (typeof value === 'string' && /^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?Z$/.test(value) && Number.isFinite(Date.parse(value)) ? value : null);
220
+
221
+ /** `L.<run>.<lineage>.<attempt>` (identity.ts), split; null for any other name. */
222
+ function launchedUnit(name ) {
223
+ const parts = name.split('.');
224
+ if (parts.length !== 4 || parts[0] !== 'L') return null;
225
+ const [, run, lineage, attempt] = parts;
226
+ return ID_PATTERN.test(run) && ID_PATTERN.test(lineage) && ATTEMPT_PATTERN.test(attempt) ? { run, lineage, attempt } : null;
227
+ }
228
+
229
+ /** One checkout's HEAD commit time as UTC ISO, from `git log -1 --format=%cI HEAD`; null when git cannot say. */
230
+ function headCommittedAt(top , exec , env ) {
231
+ try {
232
+ const res = exec('git', ['-C', top, 'log', '-1', '--format=%cI', 'HEAD'], { encoding: 'utf8', timeout: 10000, env: { ...env, GIT_OPTIONAL_LOCKS: '0' } });
233
+ if (!res || res.error || res.status !== 0) return null;
234
+ const at = Date.parse(String(res.stdout || '').trim());
235
+ return Number.isFinite(at) ? new Date(at).toISOString() : null;
236
+ } catch {
237
+ return null;
238
+ }
239
+ }
240
+
241
+ /**
242
+ * IC-018 a: the project's checkouts and the hook records of the sessions that worked in them.
243
+ * Checkouts come from `listCheckouts` (sandboxes left out); records are the main-session model
244
+ * observations at each checkout top and the `sessions/<id>/git-context.json` files whose `cwd` lies
245
+ * in a checkout (longest prefix; at most SESSION_DIR_LIMIT directories, newest first by mtime). An
246
+ * observation and a session directory with the same session key are one entry. No path, pid or raw
247
+ * session id is returned, and nothing is written: no lock, no mkdir, no upkeep.
248
+ * @returns {{ok: false, error: 'cannot-list-checkouts'} | {ok: true, body: Object}}
249
+ */
250
+ function observedSessions({
251
+ env = process.env, cwd = process.cwd(), exec = spawnSync, fsImpl = fs, now = Date.now, storage,
252
+ } = {}) {
253
+ // Required here, not at the top: `resolve` (and status, doctor) never lists checkouts.
254
+ const { listCheckouts, checkoutContaining } = require('../checkouts.js');
255
+ const listed = listCheckouts({ cwd, exec, fsImpl });
256
+ if (!listed.ok && listed.reason !== 'not-a-git-repository') return { ok: false, error: 'cannot-list-checkouts' };
257
+ const entries = listed.ok ? listed.entries : [];
258
+ const checkouts = entries.filter((e) => !e.sandbox).map((e) => ({
259
+ worktreeKey: cwdHash(e.path),
260
+ role: e.path === listed.main ? 'main' : 'linked',
261
+ current: e.path === listed.current,
262
+ branch: e.branch,
263
+ head: e.head === null ? null : e.head.slice(0, 12),
264
+ headCommittedAt: e.head === null ? null : headCommittedAt(e.path, exec, env),
265
+ }));
266
+ const root = stateDir(env);
267
+ const body = { v: 1, store: root === null ? 'absent' : 'present', checkouts, sessions: [], truncated: false, unreadable: 0 };
268
+ if (root === null) return { ok: true, body };
269
+
270
+ const counts = { unreadable: 0 };
271
+ const sessions = new Map ();
272
+ const entry = (sessionKey , worktreeKey ) => {
273
+ if (!sessions.has(sessionKey)) {
274
+ sessions.set(sessionKey, {
275
+ sessionKey, worktreeKey, kind: 'hook', harness: null, model: null, modelFirstSeenAt: null,
276
+ startedAt: null, branchAtStart: null, lastHookRecordAt: null, launched: null,
277
+ });
278
+ }
279
+ return sessions.get(sessionKey);
280
+ };
281
+
282
+ const loadedStorage = storage ?? JSON.parse(fsImpl.readFileSync(path.join(REPO_ROOT, 'core/registry/storage.json'), 'utf8'));
283
+ const maxAgeMs = observationMaxAgeMs(loadedStorage);
284
+ const nowMs = now();
285
+ for (const top of entries.filter((e) => !e.sandbox).map((e) => e.path)) {
286
+ const worktreeKey = cwdHash(top);
287
+ let harnesses ;
288
+ try { harnesses = fsImpl.readdirSync(path.join(root, 'primary', worktreeKey), { withFileTypes: true }); } catch { continue; }
289
+ for (const dirent of harnesses) {
290
+ if (!dirent.isDirectory() || !HARNESS_DIR.test(dirent.name)) continue;
291
+ for (const o of readObservations({ root, projectRoot: top, harness: dirent.name, now: nowMs, maxAgeMs, counts })) {
292
+ const e = entry(o.session, worktreeKey);
293
+ if (e.modelFirstSeenAt !== null && Date.parse(e.modelFirstSeenAt) >= o.time) continue; // the newest observation of a key wins
294
+ Object.assign(e, { harness: dirent.name, model: o.model, modelFirstSeenAt: o.observedAt });
295
+ }
296
+ }
297
+ }
298
+
299
+ const sessionsRoot = path.join(root, 'sessions');
300
+ let dirs = [];
301
+ try {
302
+ for (const dirent of fsImpl.readdirSync(sessionsRoot, { withFileTypes: true })) {
303
+ if (!dirent.isDirectory() || !SESSION_DIR.test(dirent.name)) continue;
304
+ try { dirs.push({ name: dirent.name, mtimeMs: fsImpl.lstatSync(path.join(sessionsRoot, dirent.name)).mtimeMs }); } catch { /* gone meanwhile */ }
305
+ }
306
+ } catch { /* no session directories */ }
307
+ dirs.sort((a, b) => b.mtimeMs - a.mtimeMs || (a.name < b.name ? -1 : 1));
308
+ if (dirs.length > SESSION_DIR_LIMIT) { body.truncated = true; dirs = dirs.slice(0, SESSION_DIR_LIMIT); }
309
+ for (const { name } of dirs) {
310
+ const dir = path.join(sessionsRoot, name);
311
+ const file = path.join(dir, 'git-context.json');
312
+ let context ;
313
+ try {
314
+ if (!fsImpl.lstatSync(file).isFile()) continue;
315
+ } catch { continue; }
316
+ try { context = JSON.parse(fsImpl.readFileSync(file, 'utf8')); } catch { counts.unreadable += 1; continue; }
317
+ if (!isObject(context)) { counts.unreadable += 1; continue; }
318
+ const at = checkoutContaining(entries, context.cwd, fsImpl);
319
+ if (!at || at.sandbox) continue; // another project's session, or a sandbox's: never listed
320
+ let newest = -Infinity;
321
+ try {
322
+ for (const child of fsImpl.readdirSync(dir)) {
323
+ try { const st = fsImpl.lstatSync(path.join(dir, child)); if (st.isFile()) newest = Math.max(newest, st.mtimeMs); } catch { /* gone */ }
324
+ }
325
+ } catch { /* unreadable directory: no last record */ }
326
+ const unit = launchedUnit(name);
327
+ const e = entry(keyOf(name), cwdHash(at.path));
328
+ Object.assign(e, {
329
+ worktreeKey: cwdHash(at.path),
330
+ kind: unit ? 'launched' : 'hook',
331
+ startedAt: isoOrNull(context.captured_at),
332
+ branchAtStart: typeof context.branch === 'string' && context.branch !== '' ? context.branch : null,
333
+ lastHookRecordAt: Number.isFinite(newest) ? new Date(newest).toISOString() : null,
334
+ launched: unit,
335
+ });
336
+ }
337
+
338
+ const time = (e ) => Math.max(...[e.lastHookRecordAt, e.startedAt, e.modelFirstSeenAt].map((v) => (v === null ? -Infinity : Date.parse(v))));
339
+ body.sessions = [...sessions.values()].sort((a, b) => time(b) - time(a) || (a.sessionKey < b.sessionKey ? -1 : 1));
340
+ body.unreadable = counts.unreadable;
341
+ return { ok: true, body };
342
+ }
343
+
344
+ module.exports = { readObservations, readWorkerObservation, resolvePrimary, observedSessions };