@telora/daemon 0.19.47 → 0.19.82

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 (597) hide show
  1. package/build-info.json +8 -3
  2. package/dist/assembly-engine.d.ts +9 -0
  3. package/dist/assembly-engine.d.ts.map +1 -1
  4. package/dist/assembly-engine.js.map +1 -1
  5. package/dist/audit-hooks.js +1 -1
  6. package/dist/branch-status.js +2 -2
  7. package/dist/branch-status.js.map +1 -1
  8. package/dist/ci-sensor-loop.d.ts +47 -0
  9. package/dist/ci-sensor-loop.d.ts.map +1 -0
  10. package/dist/ci-sensor-loop.js +116 -0
  11. package/dist/ci-sensor-loop.js.map +1 -0
  12. package/dist/ci-status-git.d.ts +52 -0
  13. package/dist/ci-status-git.d.ts.map +1 -0
  14. package/dist/ci-status-git.js +86 -0
  15. package/dist/ci-status-git.js.map +1 -0
  16. package/dist/ci-status.d.ts +122 -0
  17. package/dist/ci-status.d.ts.map +1 -0
  18. package/dist/ci-status.js +114 -0
  19. package/dist/ci-status.js.map +1 -0
  20. package/dist/cli/connect/agents.d.ts +24 -0
  21. package/dist/cli/connect/agents.d.ts.map +1 -0
  22. package/dist/cli/connect/agents.js +65 -0
  23. package/dist/cli/connect/agents.js.map +1 -0
  24. package/dist/cli/connect/claude.d.ts +31 -0
  25. package/dist/cli/connect/claude.d.ts.map +1 -0
  26. package/dist/cli/connect/claude.js +104 -0
  27. package/dist/cli/connect/claude.js.map +1 -0
  28. package/dist/cli/connect/cline.d.ts +37 -0
  29. package/dist/cli/connect/cline.d.ts.map +1 -0
  30. package/dist/cli/connect/cline.js +115 -0
  31. package/dist/cli/connect/cline.js.map +1 -0
  32. package/dist/cli/connect/codex.d.ts +44 -0
  33. package/dist/cli/connect/codex.d.ts.map +1 -0
  34. package/dist/cli/connect/codex.js +111 -0
  35. package/dist/cli/connect/codex.js.map +1 -0
  36. package/dist/cli/connect/ide.d.ts +69 -0
  37. package/dist/cli/connect/ide.d.ts.map +1 -0
  38. package/dist/cli/connect/ide.js +134 -0
  39. package/dist/cli/connect/ide.js.map +1 -0
  40. package/dist/cli/connect/scaffolding.d.ts +31 -0
  41. package/dist/cli/connect/scaffolding.d.ts.map +1 -0
  42. package/dist/cli/connect/scaffolding.js +52 -0
  43. package/dist/cli/connect/scaffolding.js.map +1 -0
  44. package/dist/cli/connect/shared.d.ts +30 -0
  45. package/dist/cli/connect/shared.d.ts.map +1 -0
  46. package/dist/cli/connect/shared.js +11 -0
  47. package/dist/cli/connect/shared.js.map +1 -0
  48. package/dist/cli/connect.d.ts +9 -210
  49. package/dist/cli/connect.d.ts.map +1 -1
  50. package/dist/cli/connect.js +10 -526
  51. package/dist/cli/connect.js.map +1 -1
  52. package/dist/cli/open.js +2 -2
  53. package/dist/completion/event.d.ts +2 -0
  54. package/dist/completion/event.d.ts.map +1 -1
  55. package/dist/completion/event.js +19 -2
  56. package/dist/completion/event.js.map +1 -1
  57. package/dist/completion/merge-phase.d.ts +3 -0
  58. package/dist/completion/merge-phase.d.ts.map +1 -1
  59. package/dist/completion/merge-phase.js +35 -3
  60. package/dist/completion/merge-phase.js.map +1 -1
  61. package/dist/completion/read-only-pristine.d.ts +1 -1
  62. package/dist/completion/read-only-pristine.d.ts.map +1 -1
  63. package/dist/completion/read-only-pristine.js +1 -1
  64. package/dist/completion/read-only-pristine.js.map +1 -1
  65. package/dist/completion/review-exit-phase.d.ts +30 -3
  66. package/dist/completion/review-exit-phase.d.ts.map +1 -1
  67. package/dist/completion/review-exit-phase.js +203 -15
  68. package/dist/completion/review-exit-phase.js.map +1 -1
  69. package/dist/completion/status-advance-phase.js +1 -1
  70. package/dist/completion/status-advance-phase.js.map +1 -1
  71. package/dist/completion/team-completion.d.ts.map +1 -1
  72. package/dist/completion/team-completion.js +22 -2
  73. package/dist/completion/team-completion.js.map +1 -1
  74. package/dist/completion/team-lifecycle.js +1 -1
  75. package/dist/completion/terminated-work-reconcile.js +1 -1
  76. package/dist/completion/terminated-work-reconcile.js.map +1 -1
  77. package/dist/condition-evaluators.d.ts +41 -2
  78. package/dist/condition-evaluators.d.ts.map +1 -1
  79. package/dist/condition-evaluators.js +37 -288
  80. package/dist/condition-evaluators.js.map +1 -1
  81. package/dist/config.d.ts.map +1 -1
  82. package/dist/config.js +23 -0
  83. package/dist/config.js.map +1 -1
  84. package/dist/context-audit.d.ts +1 -1
  85. package/dist/context-audit.d.ts.map +1 -1
  86. package/dist/context-lock.d.ts +1 -1
  87. package/dist/context-lock.d.ts.map +1 -1
  88. package/dist/control-state.d.ts +2 -2
  89. package/dist/control-state.d.ts.map +1 -1
  90. package/dist/control-state.js +4 -3
  91. package/dist/control-state.js.map +1 -1
  92. package/dist/daemon-process.d.ts +1 -1
  93. package/dist/daemon-process.d.ts.map +1 -1
  94. package/dist/delivery-advance-block.d.ts +1 -1
  95. package/dist/delivery-advance-block.d.ts.map +1 -1
  96. package/dist/delivery-advance-block.js +1 -1
  97. package/dist/delivery-advance-block.js.map +1 -1
  98. package/dist/delivery-lifecycle.js +1 -1
  99. package/dist/delivery-reconciler-shadow.d.ts +1 -1
  100. package/dist/delivery-reconciler-shadow.d.ts.map +1 -1
  101. package/dist/delivery-reconciler-shadow.js +1 -1
  102. package/dist/delivery-reconciler-shadow.js.map +1 -1
  103. package/dist/directive-executor.js +1 -1
  104. package/dist/directive-executor.js.map +1 -1
  105. package/dist/drift-eval-loop.d.ts.map +1 -1
  106. package/dist/drift-eval-loop.js +6 -14
  107. package/dist/drift-eval-loop.js.map +1 -1
  108. package/dist/escalations.d.ts +1 -1
  109. package/dist/escalations.d.ts.map +1 -1
  110. package/dist/escalations.js +1 -1
  111. package/dist/escalations.js.map +1 -1
  112. package/dist/evaluation-context.d.ts +2 -33
  113. package/dist/evaluation-context.d.ts.map +1 -1
  114. package/dist/evaluation-context.js +2 -56
  115. package/dist/evaluation-context.js.map +1 -1
  116. package/dist/focus-engine.d.ts +14 -5
  117. package/dist/focus-engine.d.ts.map +1 -1
  118. package/dist/focus-engine.js +35 -289
  119. package/dist/focus-engine.js.map +1 -1
  120. package/dist/focus-executor.d.ts +10 -791
  121. package/dist/focus-executor.d.ts.map +1 -1
  122. package/dist/focus-executor.js +81 -1572
  123. package/dist/focus-executor.js.map +1 -1
  124. package/dist/focus-lifecycle.js +2 -2
  125. package/dist/focus-lifecycle.js.map +1 -1
  126. package/dist/focus-loop-registry.d.ts +52 -0
  127. package/dist/focus-loop-registry.d.ts.map +1 -0
  128. package/dist/focus-loop-registry.js +328 -0
  129. package/dist/focus-loop-registry.js.map +1 -0
  130. package/dist/focus-merge.d.ts +1 -1
  131. package/dist/focus-merge.d.ts.map +1 -1
  132. package/dist/focus-merge.js +6 -6
  133. package/dist/focus-merge.js.map +1 -1
  134. package/dist/focus-phase.d.ts +4 -0
  135. package/dist/focus-phase.d.ts.map +1 -1
  136. package/dist/focus-phase.js +3 -3
  137. package/dist/focus-phase.js.map +1 -1
  138. package/dist/focus-prompt-builder.d.ts.map +1 -1
  139. package/dist/focus-prompt-builder.js +4 -1
  140. package/dist/focus-prompt-builder.js.map +1 -1
  141. package/dist/focus-stage-lifecycle/planning-release.d.ts +197 -0
  142. package/dist/focus-stage-lifecycle/planning-release.d.ts.map +1 -0
  143. package/dist/focus-stage-lifecycle/planning-release.js +312 -0
  144. package/dist/focus-stage-lifecycle/planning-release.js.map +1 -0
  145. package/dist/focus-stage-lifecycle/review-convergence.d.ts +133 -0
  146. package/dist/focus-stage-lifecycle/review-convergence.d.ts.map +1 -0
  147. package/dist/focus-stage-lifecycle/review-convergence.js +235 -0
  148. package/dist/focus-stage-lifecycle/review-convergence.js.map +1 -0
  149. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts +133 -0
  150. package/dist/focus-stage-lifecycle/wedge-watchdog.d.ts.map +1 -0
  151. package/dist/focus-stage-lifecycle/wedge-watchdog.js +365 -0
  152. package/dist/focus-stage-lifecycle/wedge-watchdog.js.map +1 -0
  153. package/dist/focus-stage-lifecycle.d.ts +22 -421
  154. package/dist/focus-stage-lifecycle.d.ts.map +1 -1
  155. package/dist/focus-stage-lifecycle.js +24 -832
  156. package/dist/focus-stage-lifecycle.js.map +1 -1
  157. package/dist/{git-activity.d.ts → git/activity.d.ts} +1 -1
  158. package/dist/git/activity.d.ts.map +1 -0
  159. package/dist/{git-activity.js → git/activity.js} +2 -2
  160. package/dist/git/activity.js.map +1 -0
  161. package/dist/{git-branch.d.ts → git/branch.d.ts} +2 -2
  162. package/dist/git/branch.d.ts.map +1 -0
  163. package/dist/{git-branch.js → git/branch.js} +2 -2
  164. package/dist/git/branch.js.map +1 -0
  165. package/dist/{git-fetch.d.ts → git/fetch.d.ts} +2 -2
  166. package/dist/git/fetch.d.ts.map +1 -0
  167. package/dist/{git-fetch.js → git/fetch.js} +1 -1
  168. package/dist/git/fetch.js.map +1 -0
  169. package/dist/{git-genesis.d.ts → git/genesis.d.ts} +3 -3
  170. package/dist/git/genesis.d.ts.map +1 -0
  171. package/dist/{git-genesis.js → git/genesis.js} +5 -5
  172. package/dist/git/genesis.js.map +1 -0
  173. package/dist/git/index.d.ts +36 -0
  174. package/dist/git/index.d.ts.map +1 -0
  175. package/dist/git/index.js +36 -0
  176. package/dist/git/index.js.map +1 -0
  177. package/dist/{git-integration.d.ts → git/integration.d.ts} +2 -2
  178. package/dist/git/integration.d.ts.map +1 -0
  179. package/dist/{git-integration.js → git/integration.js} +4 -4
  180. package/dist/git/integration.js.map +1 -0
  181. package/dist/{git-merge-helpers.d.ts → git/merge-helpers.d.ts} +3 -3
  182. package/dist/git/merge-helpers.d.ts.map +1 -0
  183. package/dist/{git-merge-helpers.js → git/merge-helpers.js} +3 -3
  184. package/dist/git/merge-helpers.js.map +1 -0
  185. package/dist/{git-merge-lock.d.ts → git/merge-lock.d.ts} +1 -1
  186. package/dist/git/merge-lock.d.ts.map +1 -0
  187. package/dist/{git-merge-lock.js → git/merge-lock.js} +1 -1
  188. package/dist/git/merge-lock.js.map +1 -0
  189. package/dist/{git-merge-strategies.d.ts → git/merge-strategies.d.ts} +2 -2
  190. package/dist/git/merge-strategies.d.ts.map +1 -0
  191. package/dist/{git-merge-strategies.js → git/merge-strategies.js} +3 -3
  192. package/dist/git/merge-strategies.js.map +1 -0
  193. package/dist/{git-merge.d.ts → git/merge.d.ts} +4 -4
  194. package/dist/git/merge.d.ts.map +1 -0
  195. package/dist/{git-merge.js → git/merge.js} +4 -4
  196. package/dist/git/merge.js.map +1 -0
  197. package/dist/{git-state-detector.d.ts → git/state-detector.d.ts} +2 -2
  198. package/dist/git/state-detector.d.ts.map +1 -0
  199. package/dist/{git-state-detector.js → git/state-detector.js} +7 -7
  200. package/dist/git/state-detector.js.map +1 -0
  201. package/dist/{git-types.d.ts → git/types.d.ts} +2 -2
  202. package/dist/git/types.d.ts.map +1 -0
  203. package/dist/{git-types.js → git/types.js} +1 -1
  204. package/dist/git/types.js.map +1 -0
  205. package/dist/{git-utils.d.ts → git/utils.d.ts} +1 -1
  206. package/dist/git/utils.d.ts.map +1 -0
  207. package/dist/{git-utils.js → git/utils.js} +2 -2
  208. package/dist/git/utils.js.map +1 -0
  209. package/dist/git.d.ts +5 -5
  210. package/dist/git.d.ts.map +1 -1
  211. package/dist/git.js +10 -10
  212. package/dist/git.js.map +1 -1
  213. package/dist/guard-engine.d.ts +3 -2
  214. package/dist/guard-engine.d.ts.map +1 -1
  215. package/dist/guard-engine.js +2 -2
  216. package/dist/guard-engine.js.map +1 -1
  217. package/dist/install-probe-tick.d.ts +63 -0
  218. package/dist/install-probe-tick.d.ts.map +1 -0
  219. package/dist/install-probe-tick.js +137 -0
  220. package/dist/install-probe-tick.js.map +1 -0
  221. package/dist/install-probe.d.ts +115 -0
  222. package/dist/install-probe.d.ts.map +1 -0
  223. package/dist/install-probe.js +197 -0
  224. package/dist/install-probe.js.map +1 -0
  225. package/dist/listener-auto-advance.d.ts.map +1 -1
  226. package/dist/listener-auto-advance.js +31 -0
  227. package/dist/listener-auto-advance.js.map +1 -1
  228. package/dist/listener.js +5 -5
  229. package/dist/listener.js.map +1 -1
  230. package/dist/{loop-context-audit-pass.d.ts → loops/context-audit-pass.d.ts} +4 -4
  231. package/dist/loops/context-audit-pass.d.ts.map +1 -0
  232. package/dist/{loop-context-audit-pass.js → loops/context-audit-pass.js} +7 -7
  233. package/dist/loops/context-audit-pass.js.map +1 -0
  234. package/dist/{loop-engine-types.d.ts → loops/engine-types.d.ts} +1 -1
  235. package/dist/loops/engine-types.d.ts.map +1 -0
  236. package/dist/{loop-engine-types.js → loops/engine-types.js} +1 -1
  237. package/dist/loops/engine-types.js.map +1 -0
  238. package/dist/{loop-engine.d.ts → loops/engine.d.ts} +6 -6
  239. package/dist/loops/engine.d.ts.map +1 -0
  240. package/dist/{loop-engine.js → loops/engine.js} +13 -13
  241. package/dist/loops/engine.js.map +1 -0
  242. package/dist/{loop-event-bus.d.ts → loops/event-bus.d.ts} +1 -1
  243. package/dist/loops/event-bus.d.ts.map +1 -0
  244. package/dist/{loop-event-bus.js → loops/event-bus.js} +1 -1
  245. package/dist/loops/event-bus.js.map +1 -0
  246. package/dist/loops/index.d.ts +33 -0
  247. package/dist/loops/index.d.ts.map +1 -0
  248. package/dist/loops/index.js +32 -0
  249. package/dist/loops/index.js.map +1 -0
  250. package/dist/{loop-llm-client.d.ts → loops/llm-client.d.ts} +3 -3
  251. package/dist/loops/llm-client.d.ts.map +1 -0
  252. package/dist/{loop-llm-client.js → loops/llm-client.js} +3 -3
  253. package/dist/loops/llm-client.js.map +1 -0
  254. package/dist/{loop-llm-flag.d.ts → loops/llm-flag.d.ts} +1 -1
  255. package/dist/loops/llm-flag.d.ts.map +1 -0
  256. package/dist/{loop-llm-flag.js → loops/llm-flag.js} +1 -1
  257. package/dist/loops/llm-flag.js.map +1 -0
  258. package/dist/{loop-operational-block.d.ts → loops/operational-block.d.ts} +1 -1
  259. package/dist/loops/operational-block.d.ts.map +1 -0
  260. package/dist/{loop-operational-block.js → loops/operational-block.js} +1 -1
  261. package/dist/loops/operational-block.js.map +1 -0
  262. package/dist/{loop-prompt-builder.d.ts → loops/prompt-builder.d.ts} +2 -2
  263. package/dist/loops/prompt-builder.d.ts.map +1 -0
  264. package/dist/{loop-prompt-builder.js → loops/prompt-builder.js} +1 -1
  265. package/dist/loops/prompt-builder.js.map +1 -0
  266. package/dist/{loop-variance.d.ts → loops/variance.d.ts} +1 -1
  267. package/dist/loops/variance.d.ts.map +1 -0
  268. package/dist/{loop-variance.js → loops/variance.js} +1 -1
  269. package/dist/loops/variance.js.map +1 -0
  270. package/dist/merge-back-loop.d.ts.map +1 -1
  271. package/dist/merge-back-loop.js +4 -3
  272. package/dist/merge-back-loop.js.map +1 -1
  273. package/dist/merge-detector.js +2 -2
  274. package/dist/merge-detector.js.map +1 -1
  275. package/dist/{nexus-endpoint-clearance.d.ts → nexus/endpoint-clearance.d.ts} +2 -2
  276. package/dist/nexus/endpoint-clearance.d.ts.map +1 -0
  277. package/dist/{nexus-endpoint-clearance.js → nexus/endpoint-clearance.js} +2 -2
  278. package/dist/nexus/endpoint-clearance.js.map +1 -0
  279. package/dist/{nexus-escalation-bridge.d.ts → nexus/escalation-bridge.d.ts} +1 -1
  280. package/dist/nexus/escalation-bridge.d.ts.map +1 -0
  281. package/dist/{nexus-escalation-bridge.js → nexus/escalation-bridge.js} +2 -2
  282. package/dist/nexus/escalation-bridge.js.map +1 -0
  283. package/dist/{nexus-incoming.d.ts → nexus/incoming.d.ts} +2 -2
  284. package/dist/nexus/incoming.d.ts.map +1 -0
  285. package/dist/{nexus-incoming.js → nexus/incoming.js} +2 -2
  286. package/dist/nexus/incoming.js.map +1 -0
  287. package/dist/nexus/index.d.ts +20 -0
  288. package/dist/nexus/index.d.ts.map +1 -0
  289. package/dist/nexus/index.js +20 -0
  290. package/dist/nexus/index.js.map +1 -0
  291. package/dist/{nexus-intake-spawn.d.ts → nexus/intake-spawn.d.ts} +3 -3
  292. package/dist/nexus/intake-spawn.d.ts.map +1 -0
  293. package/dist/{nexus-intake-spawn.js → nexus/intake-spawn.js} +1 -1
  294. package/dist/nexus/intake-spawn.js.map +1 -0
  295. package/dist/{nexus-intake.d.ts → nexus/intake.d.ts} +2 -2
  296. package/dist/nexus/intake.d.ts.map +1 -0
  297. package/dist/{nexus-intake.js → nexus/intake.js} +4 -4
  298. package/dist/nexus/intake.js.map +1 -0
  299. package/dist/{nexus-return.d.ts → nexus/return.d.ts} +3 -3
  300. package/dist/nexus/return.d.ts.map +1 -0
  301. package/dist/{nexus-return.js → nexus/return.js} +4 -4
  302. package/dist/nexus/return.js.map +1 -0
  303. package/dist/otlp/index.d.ts +19 -0
  304. package/dist/otlp/index.d.ts.map +1 -0
  305. package/dist/otlp/index.js +19 -0
  306. package/dist/otlp/index.js.map +1 -0
  307. package/dist/{otlp-log-parser.d.ts → otlp/log-parser.d.ts} +2 -2
  308. package/dist/otlp/log-parser.d.ts.map +1 -0
  309. package/dist/{otlp-log-parser.js → otlp/log-parser.js} +5 -6
  310. package/dist/otlp/log-parser.js.map +1 -0
  311. package/dist/{otlp-metric-parser.d.ts → otlp/metric-parser.d.ts} +2 -2
  312. package/dist/otlp/metric-parser.d.ts.map +1 -0
  313. package/dist/{otlp-metric-parser.js → otlp/metric-parser.js} +4 -5
  314. package/dist/otlp/metric-parser.js.map +1 -0
  315. package/dist/{otlp-port-manager.d.ts → otlp/port-manager.d.ts} +1 -1
  316. package/dist/otlp/port-manager.d.ts.map +1 -0
  317. package/dist/{otlp-port-manager.js → otlp/port-manager.js} +1 -1
  318. package/dist/otlp/port-manager.js.map +1 -0
  319. package/dist/{otlp-receiver.d.ts → otlp/receiver.d.ts} +2 -2
  320. package/dist/otlp/receiver.d.ts.map +1 -0
  321. package/dist/{otlp-receiver.js → otlp/receiver.js} +5 -33
  322. package/dist/otlp/receiver.js.map +1 -0
  323. package/dist/{otlp-types.d.ts → otlp/types.d.ts} +1 -9
  324. package/dist/otlp/types.d.ts.map +1 -0
  325. package/dist/{otlp-types.js → otlp/types.js} +1 -27
  326. package/dist/otlp/types.js.map +1 -0
  327. package/dist/pipeline-config.d.ts +1 -1
  328. package/dist/{prd-controller.d.ts → prd/controller.d.ts} +7 -7
  329. package/dist/prd/controller.d.ts.map +1 -0
  330. package/dist/{prd-controller.js → prd/controller.js} +11 -11
  331. package/dist/prd/controller.js.map +1 -0
  332. package/dist/{prd-divergence.d.ts → prd/divergence.d.ts} +2 -2
  333. package/dist/prd/divergence.d.ts.map +1 -0
  334. package/dist/{prd-divergence.js → prd/divergence.js} +1 -1
  335. package/dist/prd/divergence.js.map +1 -0
  336. package/dist/{prd-focus-pushdown.d.ts → prd/focus-pushdown.d.ts} +2 -2
  337. package/dist/prd/focus-pushdown.d.ts.map +1 -0
  338. package/dist/{prd-focus-pushdown.js → prd/focus-pushdown.js} +3 -3
  339. package/dist/prd/focus-pushdown.js.map +1 -0
  340. package/dist/{prd-frontier.d.ts → prd/frontier.d.ts} +2 -2
  341. package/dist/prd/frontier.d.ts.map +1 -0
  342. package/dist/{prd-frontier.js → prd/frontier.js} +1 -1
  343. package/dist/prd/frontier.js.map +1 -0
  344. package/dist/prd/index.d.ts +26 -0
  345. package/dist/prd/index.d.ts.map +1 -0
  346. package/dist/prd/index.js +25 -0
  347. package/dist/prd/index.js.map +1 -0
  348. package/dist/{prd-steward.d.ts → prd/steward.d.ts} +4 -4
  349. package/dist/prd/steward.d.ts.map +1 -0
  350. package/dist/{prd-steward.js → prd/steward.js} +4 -4
  351. package/dist/prd/steward.js.map +1 -0
  352. package/dist/{prd-viability.d.ts → prd/viability.d.ts} +2 -2
  353. package/dist/prd/viability.d.ts.map +1 -0
  354. package/dist/{prd-viability.js → prd/viability.js} +1 -1
  355. package/dist/prd/viability.js.map +1 -0
  356. package/dist/product-scaffold.js +2 -2
  357. package/dist/product-scaffold.js.map +1 -1
  358. package/dist/prompt-contradictions.d.ts +55 -0
  359. package/dist/prompt-contradictions.d.ts.map +1 -0
  360. package/dist/prompt-contradictions.js +114 -0
  361. package/dist/prompt-contradictions.js.map +1 -0
  362. package/dist/prompt-listing.d.ts +19 -0
  363. package/dist/prompt-listing.d.ts.map +1 -1
  364. package/dist/prompt-listing.js +49 -0
  365. package/dist/prompt-listing.js.map +1 -1
  366. package/dist/prompt-sections/execution-sections.d.ts +3 -2
  367. package/dist/prompt-sections/execution-sections.d.ts.map +1 -1
  368. package/dist/prompt-sections/execution-sections.js +70 -97
  369. package/dist/prompt-sections/execution-sections.js.map +1 -1
  370. package/dist/prompt-sections/persona-sections.d.ts +12 -0
  371. package/dist/prompt-sections/persona-sections.d.ts.map +1 -1
  372. package/dist/prompt-sections/persona-sections.js +29 -2
  373. package/dist/prompt-sections/persona-sections.js.map +1 -1
  374. package/dist/prompt-sections/role-sections.js +2 -2
  375. package/dist/prompt-sections/role-sections.js.map +1 -1
  376. package/dist/queries/context-assembly.d.ts +14 -4
  377. package/dist/queries/context-assembly.d.ts.map +1 -1
  378. package/dist/queries/context-assembly.js +14 -4
  379. package/dist/queries/context-assembly.js.map +1 -1
  380. package/dist/queries/context-lock-queries.js +1 -1
  381. package/dist/queries/context-lock-queries.js.map +1 -1
  382. package/dist/queries/deliveries.d.ts +45 -0
  383. package/dist/queries/deliveries.d.ts.map +1 -1
  384. package/dist/queries/deliveries.js +2 -0
  385. package/dist/queries/deliveries.js.map +1 -1
  386. package/dist/queries/focuses.d.ts +17 -7
  387. package/dist/queries/focuses.d.ts.map +1 -1
  388. package/dist/queries/focuses.js +27 -9
  389. package/dist/queries/focuses.js.map +1 -1
  390. package/dist/queries/index.d.ts +1 -1
  391. package/dist/queries/index.d.ts.map +1 -1
  392. package/dist/queries/index.js +1 -1
  393. package/dist/queries/index.js.map +1 -1
  394. package/dist/queries/schemas.d.ts +4 -0
  395. package/dist/queries/schemas.d.ts.map +1 -1
  396. package/dist/queries/schemas.js +8 -0
  397. package/dist/queries/schemas.js.map +1 -1
  398. package/dist/reality-pole.js +1 -1
  399. package/dist/resolvers/escalation-answers.d.ts +40 -0
  400. package/dist/resolvers/escalation-answers.d.ts.map +1 -1
  401. package/dist/resolvers/escalation-answers.js +54 -35
  402. package/dist/resolvers/escalation-answers.js.map +1 -1
  403. package/dist/resolvers/git-diff-against-base.d.ts.map +1 -1
  404. package/dist/resolvers/git-diff-against-base.js +4 -1
  405. package/dist/resolvers/git-diff-against-base.js.map +1 -1
  406. package/dist/resolvers/prd-context.js +1 -1
  407. package/dist/resolvers/shared/wiki.d.ts +27 -0
  408. package/dist/resolvers/shared/wiki.d.ts.map +1 -1
  409. package/dist/resolvers/shared/wiki.js +39 -0
  410. package/dist/resolvers/shared/wiki.js.map +1 -1
  411. package/dist/sensor-sweep.js +1 -1
  412. package/dist/spawn/delivered-manifest.d.ts +42 -0
  413. package/dist/spawn/delivered-manifest.d.ts.map +1 -0
  414. package/dist/spawn/delivered-manifest.js +64 -0
  415. package/dist/spawn/delivered-manifest.js.map +1 -0
  416. package/dist/spawn/model-routing.d.ts +277 -0
  417. package/dist/spawn/model-routing.d.ts.map +1 -0
  418. package/dist/spawn/model-routing.js +464 -0
  419. package/dist/spawn/model-routing.js.map +1 -0
  420. package/dist/spawn/process-spawn.d.ts +137 -0
  421. package/dist/spawn/process-spawn.d.ts.map +1 -0
  422. package/dist/spawn/process-spawn.js +385 -0
  423. package/dist/spawn/process-spawn.js.map +1 -0
  424. package/dist/spawn/prompt-assembly.d.ts +170 -0
  425. package/dist/spawn/prompt-assembly.d.ts.map +1 -0
  426. package/dist/spawn/prompt-assembly.js +272 -0
  427. package/dist/spawn/prompt-assembly.js.map +1 -0
  428. package/dist/spawn/shared.d.ts +96 -0
  429. package/dist/spawn/shared.d.ts.map +1 -0
  430. package/dist/spawn/shared.js +14 -0
  431. package/dist/spawn/shared.js.map +1 -0
  432. package/dist/spawn/spawn-decision.d.ts +239 -0
  433. package/dist/spawn/spawn-decision.d.ts.map +1 -0
  434. package/dist/spawn/spawn-decision.js +437 -0
  435. package/dist/spawn/spawn-decision.js.map +1 -0
  436. package/dist/spawn/worktree-session.d.ts +105 -0
  437. package/dist/spawn/worktree-session.d.ts.map +1 -0
  438. package/dist/spawn/worktree-session.js +216 -0
  439. package/dist/spawn/worktree-session.js.map +1 -0
  440. package/dist/spawner/lifecycle.js +5 -5
  441. package/dist/spawner/lifecycle.js.map +1 -1
  442. package/dist/spawner/resolution.js +4 -4
  443. package/dist/spawner/resolution.js.map +1 -1
  444. package/dist/stall-detector-config.d.ts +4 -0
  445. package/dist/stall-detector-config.d.ts.map +1 -1
  446. package/dist/stall-detector-config.js +6 -0
  447. package/dist/stall-detector-config.js.map +1 -1
  448. package/dist/state-cascade.js +1 -1
  449. package/dist/state-cascade.js.map +1 -1
  450. package/dist/trigger-executor.d.ts +5 -9
  451. package/dist/trigger-executor.d.ts.map +1 -1
  452. package/dist/trigger-executor.js +14 -36
  453. package/dist/trigger-executor.js.map +1 -1
  454. package/dist/types/config.d.ts +44 -0
  455. package/dist/types/config.d.ts.map +1 -1
  456. package/dist/types/focus.d.ts +13 -0
  457. package/dist/types/focus.d.ts.map +1 -1
  458. package/dist/types/workflow.d.ts +1 -3
  459. package/dist/types/workflow.d.ts.map +1 -1
  460. package/dist/unified-engine-lifecycle.js +1 -1
  461. package/dist/unified-init.d.ts +3 -3
  462. package/dist/unified-init.d.ts.map +1 -1
  463. package/dist/unified-init.js +9 -25
  464. package/dist/unified-init.js.map +1 -1
  465. package/dist/unified-shell.d.ts +3 -3
  466. package/dist/unified-shell.js +4 -4
  467. package/dist/unified-shell.js.map +1 -1
  468. package/dist/verification-baseline.d.ts +112 -0
  469. package/dist/verification-baseline.d.ts.map +1 -0
  470. package/dist/verification-baseline.js +264 -0
  471. package/dist/verification-baseline.js.map +1 -0
  472. package/dist/verification-config.d.ts +53 -0
  473. package/dist/verification-config.d.ts.map +1 -0
  474. package/dist/verification-config.js +72 -0
  475. package/dist/verification-config.js.map +1 -0
  476. package/dist/verification-default-on.d.ts +87 -0
  477. package/dist/verification-default-on.d.ts.map +1 -0
  478. package/dist/verification-default-on.js +186 -0
  479. package/dist/verification-default-on.js.map +1 -0
  480. package/dist/verification-deterministic.d.ts +56 -0
  481. package/dist/verification-deterministic.d.ts.map +1 -0
  482. package/dist/verification-deterministic.js +123 -0
  483. package/dist/verification-deterministic.js.map +1 -0
  484. package/dist/verification-engine.d.ts +22 -15
  485. package/dist/verification-engine.d.ts.map +1 -1
  486. package/dist/verification-engine.js +94 -65
  487. package/dist/verification-engine.js.map +1 -1
  488. package/dist/verification-failure-parser.d.ts +48 -0
  489. package/dist/verification-failure-parser.d.ts.map +1 -0
  490. package/dist/verification-failure-parser.js +102 -0
  491. package/dist/verification-failure-parser.js.map +1 -0
  492. package/dist/verification-feedback.d.ts +58 -0
  493. package/dist/verification-feedback.d.ts.map +1 -0
  494. package/dist/verification-feedback.js +110 -0
  495. package/dist/verification-feedback.js.map +1 -0
  496. package/dist/{worktree-focus.d.ts → worktree/focus.d.ts} +2 -2
  497. package/dist/worktree/focus.d.ts.map +1 -0
  498. package/dist/{worktree-focus.js → worktree/focus.js} +7 -7
  499. package/dist/worktree/focus.js.map +1 -0
  500. package/dist/worktree/index.d.ts +16 -0
  501. package/dist/worktree/index.d.ts.map +1 -0
  502. package/dist/worktree/index.js +16 -0
  503. package/dist/worktree/index.js.map +1 -0
  504. package/dist/{worktree-safety.d.ts → worktree/safety.d.ts} +1 -1
  505. package/dist/worktree/safety.d.ts.map +1 -0
  506. package/dist/{worktree-safety.js → worktree/safety.js} +2 -2
  507. package/dist/worktree/safety.js.map +1 -0
  508. package/dist/{worktree-state.d.ts → worktree/state.d.ts} +1 -1
  509. package/dist/worktree/state.d.ts.map +1 -0
  510. package/dist/{worktree-state.js → worktree/state.js} +6 -6
  511. package/dist/worktree/state.js.map +1 -0
  512. package/dist/worktree.d.ts +1 -1
  513. package/dist/worktree.js +5 -5
  514. package/dist/worktree.js.map +1 -1
  515. package/package.json +5 -4
  516. package/dist/git-activity.d.ts.map +0 -1
  517. package/dist/git-activity.js.map +0 -1
  518. package/dist/git-branch.d.ts.map +0 -1
  519. package/dist/git-branch.js.map +0 -1
  520. package/dist/git-fetch.d.ts.map +0 -1
  521. package/dist/git-fetch.js.map +0 -1
  522. package/dist/git-genesis.d.ts.map +0 -1
  523. package/dist/git-genesis.js.map +0 -1
  524. package/dist/git-integration.d.ts.map +0 -1
  525. package/dist/git-integration.js.map +0 -1
  526. package/dist/git-merge-helpers.d.ts.map +0 -1
  527. package/dist/git-merge-helpers.js.map +0 -1
  528. package/dist/git-merge-lock.d.ts.map +0 -1
  529. package/dist/git-merge-lock.js.map +0 -1
  530. package/dist/git-merge-strategies.d.ts.map +0 -1
  531. package/dist/git-merge-strategies.js.map +0 -1
  532. package/dist/git-merge.d.ts.map +0 -1
  533. package/dist/git-merge.js.map +0 -1
  534. package/dist/git-state-detector.d.ts.map +0 -1
  535. package/dist/git-state-detector.js.map +0 -1
  536. package/dist/git-types.d.ts.map +0 -1
  537. package/dist/git-types.js.map +0 -1
  538. package/dist/git-utils.d.ts.map +0 -1
  539. package/dist/git-utils.js.map +0 -1
  540. package/dist/loop-context-audit-pass.d.ts.map +0 -1
  541. package/dist/loop-context-audit-pass.js.map +0 -1
  542. package/dist/loop-engine-types.d.ts.map +0 -1
  543. package/dist/loop-engine-types.js.map +0 -1
  544. package/dist/loop-engine.d.ts.map +0 -1
  545. package/dist/loop-engine.js.map +0 -1
  546. package/dist/loop-event-bus.d.ts.map +0 -1
  547. package/dist/loop-event-bus.js.map +0 -1
  548. package/dist/loop-llm-client.d.ts.map +0 -1
  549. package/dist/loop-llm-client.js.map +0 -1
  550. package/dist/loop-llm-flag.d.ts.map +0 -1
  551. package/dist/loop-llm-flag.js.map +0 -1
  552. package/dist/loop-operational-block.d.ts.map +0 -1
  553. package/dist/loop-operational-block.js.map +0 -1
  554. package/dist/loop-prompt-builder.d.ts.map +0 -1
  555. package/dist/loop-prompt-builder.js.map +0 -1
  556. package/dist/loop-variance.d.ts.map +0 -1
  557. package/dist/loop-variance.js.map +0 -1
  558. package/dist/nexus-endpoint-clearance.d.ts.map +0 -1
  559. package/dist/nexus-endpoint-clearance.js.map +0 -1
  560. package/dist/nexus-escalation-bridge.d.ts.map +0 -1
  561. package/dist/nexus-escalation-bridge.js.map +0 -1
  562. package/dist/nexus-incoming.d.ts.map +0 -1
  563. package/dist/nexus-incoming.js.map +0 -1
  564. package/dist/nexus-intake-spawn.d.ts.map +0 -1
  565. package/dist/nexus-intake-spawn.js.map +0 -1
  566. package/dist/nexus-intake.d.ts.map +0 -1
  567. package/dist/nexus-intake.js.map +0 -1
  568. package/dist/nexus-return.d.ts.map +0 -1
  569. package/dist/nexus-return.js.map +0 -1
  570. package/dist/otlp-log-parser.d.ts.map +0 -1
  571. package/dist/otlp-log-parser.js.map +0 -1
  572. package/dist/otlp-metric-parser.d.ts.map +0 -1
  573. package/dist/otlp-metric-parser.js.map +0 -1
  574. package/dist/otlp-port-manager.d.ts.map +0 -1
  575. package/dist/otlp-port-manager.js.map +0 -1
  576. package/dist/otlp-receiver.d.ts.map +0 -1
  577. package/dist/otlp-receiver.js.map +0 -1
  578. package/dist/otlp-types.d.ts.map +0 -1
  579. package/dist/otlp-types.js.map +0 -1
  580. package/dist/prd-controller.d.ts.map +0 -1
  581. package/dist/prd-controller.js.map +0 -1
  582. package/dist/prd-divergence.d.ts.map +0 -1
  583. package/dist/prd-divergence.js.map +0 -1
  584. package/dist/prd-focus-pushdown.d.ts.map +0 -1
  585. package/dist/prd-focus-pushdown.js.map +0 -1
  586. package/dist/prd-frontier.d.ts.map +0 -1
  587. package/dist/prd-frontier.js.map +0 -1
  588. package/dist/prd-steward.d.ts.map +0 -1
  589. package/dist/prd-steward.js.map +0 -1
  590. package/dist/prd-viability.d.ts.map +0 -1
  591. package/dist/prd-viability.js.map +0 -1
  592. package/dist/worktree-focus.d.ts.map +0 -1
  593. package/dist/worktree-focus.js.map +0 -1
  594. package/dist/worktree-safety.d.ts.map +0 -1
  595. package/dist/worktree-safety.js.map +0 -1
  596. package/dist/worktree-state.d.ts.map +0 -1
  597. package/dist/worktree-state.js.map +0 -1
@@ -20,843 +20,35 @@
20
20
  * (Issue 3: review_nonconvergent escalation, focus
21
21
  * stays at review until a human intervenes).
22
22
  *
23
- * This module owns all four daemon-side transitions. It does not advance
24
- * `current_workflow_stage_id` directly the existing `syncFocusPhase` does
25
- * that on the next poll, after the daemon's `stage` write lands.
23
+ * This module owns the poll-loop dispatcher `runStageLifecycleTransitions` and
24
+ * re-exports the decomposed transition logic, which lives in three cohesive
25
+ * submodules:
26
+ *
27
+ * - `focus-stage-lifecycle/review-convergence` -- the verify->review and
28
+ * review->close_loop transitions plus their pure convergence helpers.
29
+ * - `focus-stage-lifecycle/planning-release` -- the ratify-to-execute and
30
+ * writable-focus planning->queued delivery releases.
31
+ * - `focus-stage-lifecycle/wedge-watchdog` -- the planning-wedge watchdog +
32
+ * zero-actionable log.
33
+ *
34
+ * It does not advance `current_workflow_stage_id` directly -- the existing
35
+ * `syncFocusPhase` does that on the next poll, after the daemon's `stage` write
36
+ * lands.
26
37
  *
27
38
  * @module focus-stage-lifecycle
28
39
  */
29
40
  import { configForProduct } from './config.js';
30
41
  import { getActiveFocuses as defaultGetActiveFocuses } from './supabase.js';
31
- import { getFocusDeliveries as defaultGetFocusDeliveries, getLatestCompletedFocusReview as defaultGetLatestCompletedFocusReview, getInFlightRemediationCount as defaultGetInFlightRemediationCount, getFocusReviewIterationCount as defaultGetFocusReviewIterationCount, updateFocusStage as defaultUpdateFocusStage, updateFocusReadOnly as defaultUpdateFocusReadOnly, } from './queries/focuses.js';
32
- import { updateDeliveryStatus as defaultUpdateDeliveryStatus } from './queries/deliveries.js';
33
- import { createEscalation as defaultCreateEscalation, hasOpenWedgedFocusEscalation as defaultHasOpenWedgedFocusEscalation, WEDGED_FOCUS_ESCALATION_KIND, } from './queries/issues.js';
34
- import { ESCALATION_REASONS, ESCALATION_KINDS, ESCALATION_DISPOSITIONS, FOCUS_STAGE, DELIVERY_STATUS, } from '@telora/daemon-core';
35
- import { emitLoopTrigger } from './loop-event-bus.js';
36
- import { isReviewEnabled } from './pipeline-config.js';
37
- import { deriveFocusPhase } from './focus-phase.js';
38
- import { hasActiveTeam as defaultHasActiveTeam } from './focus-team-state.js';
39
- import { isAuditPhaseFocus } from './audit-shape.js';
40
- // ── Constants ─────────────────────────────────────────────────────
41
- /**
42
- * Default cap on review iterations before the daemon files a
43
- * `review_nonconvergent` escalation and pauses the focus.
44
- *
45
- * Configurable per focus via `pipeline_config.review.maxIterations`.
46
- */
47
- export const DEFAULT_REVIEW_MAX_ITERATIONS = 3;
48
- // ── Pure helpers (Issue 2 + Issue 3) ──────────────────────────────
49
- /**
50
- * Decide whether a focus has converged out of review. Convergence is
51
- * deterministic and state-based:
52
- *
53
- * - If there are no in-flight remediation deliveries created during/after
54
- * the latest review pass, the focus has converged.
55
- *
56
- * Pure function: takes the in-flight count and returns the decision.
57
- * Exported so tests can drive it with synthetic counts.
58
- */
59
- export function hasReviewConverged(inFlightRemediationCount) {
60
- return inFlightRemediationCount <= 0;
61
- }
62
- /**
63
- * Compute the configured review iteration cap for a focus. Pulls
64
- * `pipeline_config.review.maxIterations` and falls back to
65
- * `DEFAULT_REVIEW_MAX_ITERATIONS` when unset or non-positive.
66
- *
67
- * Exported for unit testing.
68
- */
69
- export function getReviewIterationCap(pipelineConfig) {
70
- const raw = pipelineConfig?.review?.maxIterations;
71
- if (typeof raw !== 'number' || !Number.isFinite(raw) || raw <= 0) {
72
- return DEFAULT_REVIEW_MAX_ITERATIONS;
73
- }
74
- return Math.floor(raw);
75
- }
76
- // ── Convergence check (Issue 2) ───────────────────────────────────
77
- /**
78
- * Returns true when the focus has any in-flight remediation deliveries
79
- * created during/after the latest review pass.
80
- *
81
- * "Latest review pass" is the `started_at` of the most recent focus_reviews
82
- * row; when no review has run yet, this returns false (no remediation
83
- * deliveries can exist before a review pass).
84
- *
85
- * In-flight means execution_status is NOT one of done/cancelled/verify.
86
- *
87
- * Exported for unit testing the convergence query independently of the
88
- * surrounding transition logic.
89
- */
90
- export async function hasInFlightRemediations(focusId, latestReviewPassTimestamp, deps = { getInFlightRemediationCount: defaultGetInFlightRemediationCount }) {
91
- if (!latestReviewPassTimestamp) {
92
- return false;
93
- }
94
- const count = await deps.getInFlightRemediationCount(focusId, latestReviewPassTimestamp);
95
- return count > 0;
96
- }
97
- const defaultVerifyToReviewDeps = {
98
- updateFocusStage: defaultUpdateFocusStage,
99
- emitLoopTrigger,
100
- };
101
- /**
102
- * When a focus's stage is `verify` and auto-review is enabled, advance it to
103
- * `review`. Auto-review is default-on (`isReviewEnabled`): only an explicit
104
- * `pipeline_config.review.enabled === false` keeps the focus at `verify` until
105
- * a human signals review. A focus with no stored review key auto-advances.
106
- *
107
- * Returns true when an advance was performed.
108
- */
109
- export async function advanceVerifyToReviewIfEnabled(focus, deps = defaultVerifyToReviewDeps) {
110
- if (focus.stage !== FOCUS_STAGE.VERIFY)
111
- return false;
112
- // Default-on: only an explicit review.enabled === false keeps the focus at
113
- // verify. Missing / undefined review reads as enabled (mirrors isCiEnabled).
114
- if (!isReviewEnabled(focus.pipelineConfig))
115
- return false;
116
- try {
117
- await deps.updateFocusStage(focus.focusId, 'review');
118
- console.log(`[focus-stage-lifecycle] Focus "${focus.focusName}" stage verify -> review ` +
119
- `(auto: pipeline_config.review.enabled)`);
120
- deps.emitLoopTrigger({
121
- type: 'focus_status_changed',
122
- focusId: focus.focusId,
123
- detail: 'stage:verify->review',
124
- });
125
- return true;
126
- }
127
- catch (err) {
128
- console.warn(`[focus-stage-lifecycle] Failed to advance "${focus.focusName}" ` +
129
- `verify -> review: ${err.message}`);
130
- return false;
131
- }
132
- }
133
- const defaultReviewConvergeDeps = {
134
- getLatestCompletedFocusReview: defaultGetLatestCompletedFocusReview,
135
- getInFlightRemediationCount: defaultGetInFlightRemediationCount,
136
- getFocusReviewIterationCount: defaultGetFocusReviewIterationCount,
137
- updateFocusStage: defaultUpdateFocusStage,
138
- createEscalation: defaultCreateEscalation,
139
- emitLoopTrigger,
140
- };
141
- /**
142
- * Evaluate the review -> close_loop transition for a focus and apply the
143
- * appropriate action. Returns a structured outcome so callers can log /
144
- * test the decision path.
145
- *
146
- * Logic:
147
- * 1. If stage is not `review`, do nothing.
148
- * 2. If the review iteration count is >= the cap, file a
149
- * `review_nonconvergent` escalation (idempotent best-effort) and
150
- * LEAVE the focus stage at `review`. The focus is paused until a
151
- * human intervenes.
152
- * 3. Otherwise, check in-flight remediations against the latest review
153
- * pass. If zero, advance stage `review -> close_loop`.
154
- * 4. Otherwise, do nothing -- remediations are still in flight.
155
- *
156
- * Exported with injectable deps for unit testing.
157
- */
158
- export async function advanceReviewToCloseLoopIfConverged(focus, deps = defaultReviewConvergeDeps) {
159
- if (focus.stage !== FOCUS_STAGE.REVIEW) {
160
- return { kind: 'no_action', reason: 'stage_not_review' };
161
- }
162
- const cap = getReviewIterationCap(focus.pipelineConfig);
163
- let iterations = 0;
164
- try {
165
- iterations = await deps.getFocusReviewIterationCount(focus.focusId);
166
- }
167
- catch (err) {
168
- console.warn(`[focus-stage-lifecycle] Failed to read review iteration count for ` +
169
- `"${focus.focusName}": ${err.message}`);
170
- // On read failure, fall through to the convergence check rather than
171
- // false-escalating; if iterations were actually high the next tick
172
- // will catch it.
173
- }
174
- if (iterations >= cap) {
175
- // N-iteration cap exceeded -- file an escalation and leave the focus
176
- // at review. The escalation is best-effort; downstream dedup keeps it
177
- // from spamming via the agent_escalations idx_kind_status partial index
178
- // (status in ('pending', 'in_review')).
179
- try {
180
- await deps.createEscalation({
181
- organizationId: focus.organizationId,
182
- sessionId: '',
183
- issueId: null,
184
- reasonType: ESCALATION_REASONS.DECISION_NEEDED,
185
- // The intent owner must decide: descope, extend the cap, or close.
186
- disposition: ESCALATION_DISPOSITIONS.NEEDS_INTENT,
187
- anchor: { focusId: focus.focusId },
188
- description: `Focus "${focus.focusName}" has not converged after ${iterations} ` +
189
- `review iteration(s) (cap: ${cap}). The review self-loop has been ` +
190
- `paused; a human must decide whether to descope remaining gaps, ` +
191
- `extend the cap via pipeline_config.review.maxIterations, or close ` +
192
- `the focus.`,
193
- escalationKind: ESCALATION_KINDS.REVIEW_NONCONVERGENT,
194
- // Evidence only -- focus provenance lives on the columns via the anchor.
195
- metadata: {
196
- iterations,
197
- cap,
198
- },
199
- });
200
- console.warn(`[focus-stage-lifecycle] review_nonconvergent escalation filed for ` +
201
- `"${focus.focusName}" (iterations=${iterations}, cap=${cap}); focus ` +
202
- `remains at stage=review.`);
203
- }
204
- catch (err) {
205
- console.warn(`[focus-stage-lifecycle] Failed to file review_nonconvergent ` +
206
- `escalation for "${focus.focusName}": ${err.message}`);
207
- }
208
- return { kind: 'nonconvergent', iterations, cap };
209
- }
210
- // Look up the latest completed review's started_at. If no review has
211
- // completed yet, there are no remediations created against it; treat as
212
- // "not yet converged" rather than "converged on zero" -- the agent must
213
- // produce a review before we can claim convergence.
214
- let latestReviewStartedAt = null;
215
- try {
216
- const latest = await deps.getLatestCompletedFocusReview(focus.focusId);
217
- latestReviewStartedAt = latest?.startedAt ?? null;
218
- }
219
- catch (err) {
220
- console.warn(`[focus-stage-lifecycle] Failed to read latest focus_review for ` +
221
- `"${focus.focusName}": ${err.message}`);
222
- return { kind: 'no_action', reason: 'review_read_failed' };
223
- }
224
- if (!latestReviewStartedAt) {
225
- return { kind: 'no_action', reason: 'no_completed_review_yet' };
226
- }
227
- let inFlightCount = 0;
228
- try {
229
- inFlightCount = await deps.getInFlightRemediationCount(focus.focusId, latestReviewStartedAt);
230
- }
231
- catch (err) {
232
- console.warn(`[focus-stage-lifecycle] Failed to count in-flight remediations for ` +
233
- `"${focus.focusName}": ${err.message}`);
234
- return { kind: 'no_action', reason: 'remediation_query_failed' };
235
- }
236
- if (!hasReviewConverged(inFlightCount)) {
237
- return { kind: 'no_action', reason: 'remediations_in_flight' };
238
- }
239
- try {
240
- await deps.updateFocusStage(focus.focusId, 'close_loop');
241
- console.log(`[focus-stage-lifecycle] Focus "${focus.focusName}" stage review -> ` +
242
- `close_loop (converged after ${iterations} iteration(s))`);
243
- deps.emitLoopTrigger({
244
- type: 'focus_status_changed',
245
- focusId: focus.focusId,
246
- detail: 'stage:review->close_loop',
247
- });
248
- return { kind: 'converged', from: 'review', to: 'close_loop' };
249
- }
250
- catch (err) {
251
- console.warn(`[focus-stage-lifecycle] Failed to advance "${focus.focusName}" ` +
252
- `review -> close_loop: ${err.message}`);
253
- return { kind: 'no_action', reason: 'stage_write_failed' };
254
- }
255
- }
256
- // ── Ratify-to-execute release (Safe audit-to-remediation D3) ──────
257
- /**
258
- * Pure predicate: a read-only focus is ready to cross planning->execution
259
- * exactly when a human has ratified its remediation plan.
260
- *
261
- * read_only === true => the focus is in the audit/read-only phase, its
262
- * remediation deliveries held in 'planning'.
263
- * ratified_at != null => a human stamped the distinct ratification signal.
264
- *
265
- * Both must hold. read_only=false (already crossed / a normal execute focus)
266
- * fails the predicate, which is what makes the release idempotent: after the
267
- * crossing flips read_only=false the predicate no longer holds, so re-runs are
268
- * no-ops. A null ratified_at means the gate has NOT been crossed -- the human
269
- * signal is absent -- so the focus stays held.
270
- *
271
- * Exported so tests can drive it with synthetic focus shapes.
272
- */
273
- export function isFocusRatifiedForExecute(focus) {
274
- return focus.readOnly === true && focus.ratifiedAt != null;
275
- }
276
- const defaultRatifyReleaseDeps = {
277
- getFocusDeliveries: defaultGetFocusDeliveries,
278
- updateFocusReadOnly: defaultUpdateFocusReadOnly,
279
- updateDeliveryStatus: defaultUpdateDeliveryStatus,
280
- emitLoopTrigger,
281
- };
282
- /**
283
- * The SOLE planning->execution crossing for a read-only audit focus.
284
- *
285
- * When `isFocusRatifiedForExecute(focus)` holds (read_only=true AND a human
286
- * stamped ratified_at), atomically-in-effect:
287
- * (a) flip read_only=false, and
288
- * (b) release every held delivery (execution_status 'planning') to 'queued'.
289
- *
290
- * This is the ONLY path that does either, which is what makes the gate
291
- * non-procedural: the gate cannot be crossed by an agent or a stage transition;
292
- * it requires the distinct human ratification signal that this function reads.
293
- *
294
- * Idempotent by construction:
295
- * - flipping read_only=false first means the predicate fails on re-entry, so a
296
- * second tick is a no-op;
297
- * - the delivery move is guarded to ONLY touch 'planning' rows, so deliveries
298
- * already queued (or further along) are left untouched.
299
- *
300
- * Returns the number of deliveries released (0 when the predicate does not hold
301
- * or there were no held deliveries).
302
- *
303
- * Runs regardless of stage: a read-only audit focus may sit at planning/queued/
304
- * in_progress, so this is NOT gated on focus.stage.
305
- */
306
- export async function releaseRatifiedReadOnlyFocus(focus, deps = defaultRatifyReleaseDeps) {
307
- if (!isFocusRatifiedForExecute(focus))
308
- return 0;
309
- // Crash-safety ordering: release held deliveries FIRST, and flip
310
- // read_only=false ONLY after every held 'planning' delivery has been queued.
311
- // The flip is what makes the predicate fail on re-entry, so deferring it
312
- // until the release is complete means a crash (or a partial failure)
313
- // mid-release leaves the predicate TRUE (read_only still true) and the next
314
- // tick retries the whole release. Flipping first would orphan any
315
- // not-yet-released held delivery: the predicate would already be false and
316
- // those deliveries would never be queued.
317
- // (a) Release held deliveries: planning -> queued. Guarded so only 'planning'
318
- // rows move; anything already queued/further-along is left untouched
319
- // (idempotency + safety).
320
- let deliveries;
321
- try {
322
- deliveries = await deps.getFocusDeliveries(focus.focusId);
323
- }
324
- catch (err) {
325
- console.warn(`[focus-stage-lifecycle] Ratify-release: failed to fetch deliveries to ` +
326
- `release for "${focus.focusName}" (leaving read_only=true to retry next ` +
327
- `tick): ${err.message}`);
328
- return 0;
329
- }
330
- let released = 0;
331
- let allReleased = true;
332
- for (const d of deliveries) {
333
- if (d.executionStatus !== DELIVERY_STATUS.PLANNING)
334
- continue;
335
- try {
336
- await deps.updateDeliveryStatus(d.id, DELIVERY_STATUS.QUEUED, undefined, undefined, undefined, { organizationId: focus.organizationId, fromStatus: DELIVERY_STATUS.PLANNING });
337
- released += 1;
338
- }
339
- catch (err) {
340
- allReleased = false;
341
- console.warn(`[focus-stage-lifecycle] Ratify-release: failed to queue held delivery ` +
342
- `${d.id.slice(0, 8)} on "${focus.focusName}": ${err.message}`);
343
- }
344
- }
345
- // (b) Flip read_only=false ONLY when every held delivery was queued. If any
346
- // release failed, leave read_only=true so the predicate stays satisfied and
347
- // the next tick retries the (planning-only, idempotent) release -- never
348
- // crossing the gate with held deliveries still stuck in planning.
349
- if (!allReleased) {
350
- console.warn(`[focus-stage-lifecycle] Ratify-release: not all held deliveries queued ` +
351
- `for "${focus.focusName}" (released ${released}); leaving read_only=true ` +
352
- `to retry next tick.`);
353
- return released;
354
- }
355
- try {
356
- await deps.updateFocusReadOnly(focus.focusId, false);
357
- }
358
- catch (err) {
359
- console.warn(`[focus-stage-lifecycle] Ratify-release: held deliveries queued for ` +
360
- `"${focus.focusName}" but failed to flip read_only=false (will retry ` +
361
- `next tick): ${err.message}`);
362
- return released;
363
- }
364
- console.log(`[focus-stage-lifecycle] Focus "${focus.focusName}" ratified -> execute: ` +
365
- `read_only=false, released ${released} held planning ` +
366
- `deliver${released === 1 ? 'y' : 'ies'} to queued.`);
367
- deps.emitLoopTrigger({
368
- type: 'focus_status_changed',
369
- focusId: focus.focusId,
370
- detail: 'ratify:read_only->execute',
371
- });
372
- return released;
373
- }
374
- // ── Writable-focus planning->queued release (planning-wedge prevention D1) ──
375
- /**
376
- * Pure predicate: a writable focus is PAST its planning/kickoff window -- i.e. a
377
- * lingering 'planning' delivery on it is a WEDGE to release, not work a scoping
378
- * team is still shaping.
379
- *
380
- * Stage-column-INDEPENDENT by design. The previous version gated on
381
- * `isPostPlanning(focus.stage)`, but `product_focuses.stage` is a documented-stale
382
- * column: NO live path advances it to 'queued'/'in_progress' (the daemon writes
383
- * only review/close_loop/done; everything else keeps the 'planning' default).
384
- * supabase/.../focus/lifecycle.ts says so explicitly ("nothing advances `stage`
385
- * to 'verify'"), which is why the newer code (checkAutoReview, focus_transition_stage)
386
- * gates on the DERIVED phase instead. The old stage gate therefore NEVER fired for
387
- * the founding wedge -- a post-backfill writable focus whose stage stays 'planning'
388
- * forever (review finding, delivery 18055ddd). Worse, it was self-defeating: a
389
- * delivery is born 'planning' PRECISELY because focus.stage was not post-planning
390
- * (MCP resolveDefaultExecutionStatus reads the same column), and nothing changes
391
- * the column before this tick -- so the release could never see a post-planning
392
- * stage for exactly the focuses that have born-planning deliveries.
393
- *
394
- * The replacement uses two stage-free signals:
395
- * 1. read_only === false -- a NORMAL execute focus, not a read-only audit focus
396
- * (whose held planning deliveries are released only by the human-ratified
397
- * `releaseRatifiedReadOnlyFocus`).
398
- * 2. "past kickoff" via the delivery aggregate + active-team:
399
- * - deriveFocusPhase(deliveries) !== 'kickoff' => real work already exists
400
- * (some delivery is queued/coding/verify/done), so a lingering 'planning'
401
- * row is a wedge behind/ahead of it -> past kickoff.
402
- * - phase === 'kickoff' (all deliveries planning/draft, or none): genuinely
403
- * ambiguous between a live scoping window and a full wedge. An ACTIVE TEAM
404
- * is the discriminator -- a scoping team running for the focus is (or just
405
- * was) creating/releasing deliveries, so DON'T pre-empt it; no team means
406
- * nothing can make progress (the rank-0 planning delivery blocks the ready
407
- * selector so no team will ever spawn) -> wedge -> past kickoff.
408
- *
409
- * Second discovery-only signal (injection #7, TEL-3): signal 1 is widened from a
410
- * bare `read_only === false` to "NOT audit-phase". An audit-origin, not-yet-
411
- * ratified focus whose `read_only` is silently absent (coerced false) is STILL an
412
- * audit focus -- its born-planning remediation deliveries are owned by the ratify
413
- * path and must NOT be swept to queued here, or the remediation executes ahead of
414
- * the human ratify gate (2f253da6). `isAuditPhaseFocus` captures read_only===true
415
- * OR (audit_origin AND !ratified); a RATIFIED audit focus (read_only=false,
416
- * ratified_at set) is NOT audit-phase and IS released normally, and a NORMAL focus
417
- * (audit_origin=false) is unaffected. Keyed on audit_origin + ratified_at
418
- * (provenance + phase) -- it never writes read_only, so it is not a second source
419
- * of truth for the flag.
420
- *
421
- * Exported so tests can drive it with synthetic shapes.
422
- */
423
- export function isWritableFocusPastKickoff(input) {
424
- // Audit-phase focuses (explicit read_only=true OR the second structural signal:
425
- // audit_origin && not-yet-ratified) are owned by the ratify-release path.
426
- if (isAuditPhaseFocus({
427
- readOnly: input.readOnly === true,
428
- auditOrigin: input.auditOrigin === true,
429
- ratified: input.ratifiedAt != null,
430
- })) {
431
- return false;
432
- }
433
- if (input.readOnly !== false)
434
- return false;
435
- const phase = deriveFocusPhase({
436
- deliveries: input.deliveries.map((d) => ({ executionStatus: d.executionStatus })),
437
- reviewRequestedAt: input.reviewRequestedAt ?? null,
438
- });
439
- // Any non-kickoff phase => real work exists => a held planning row is a wedge.
440
- if (phase !== 'kickoff')
441
- return true;
442
- // Pure all-planning/draft (or empty) 'kickoff': a wedge only when no team is
443
- // (or can be) working it. An active scoping team is shaping deliveries -- leave it.
444
- return !input.hasActiveTeam;
445
- }
446
- const defaultWritableReleaseDeps = {
447
- getFocusDeliveries: defaultGetFocusDeliveries,
448
- updateDeliveryStatus: defaultUpdateDeliveryStatus,
449
- emitLoopTrigger,
450
- hasActiveTeam: defaultHasActiveTeam,
451
- };
452
- /**
453
- * The writable-focus analogue of `releaseRatifiedReadOnlyFocus`: the missing
454
- * planning->queued release for a NORMAL (read_only=false) executing focus.
455
- *
456
- * Closes the silent wedge: a writable focus whose deliveries were born
457
- * 'planning' (created during the planning stage, or via the MCP focus_list
458
- * lookup falling back to 'planning') has NO transition that releases them once
459
- * the focus is past planning -- the only planning->queued release in the
460
- * codebase (`releaseRatifiedReadOnlyFocus`) is gated to ratified read-only audit
461
- * focuses, and listener-auto-advance only moves queued/coding -> verify. So
462
- * `daemon_get_ready_focuses` hits the rank-0 'planning' delivery, treats it as a
463
- * hard block, and the focus never spawns a team.
464
- *
465
- * Fetch the focus's deliveries, and when `isWritableFocusPastKickoff` holds (the
466
- * stage-column-independent gate), sweep every held delivery (execution_status
467
- * 'planning') to 'queued'. Guarded so ONLY 'planning' rows move --
468
- * draft/paused/queued/further-along are left untouched, which also makes the
469
- * release idempotent: once swept, there are no 'planning' rows left so a re-run is
470
- * a no-op. Unlike the ratify path there is no read_only flip to defer, so each
471
- * delivery move stands on its own; a per-delivery failure is logged and retried on
472
- * the next tick.
473
- *
474
- * FRT effect #6 (a completed/merged delivery stuck in 'planning' must advance out
475
- * of 'planning' and never block at rank 0): planning's ONLY valid forward
476
- * transition is 'queued' (DELIVERY_STATUS machine: planning -> queued|cancelled;
477
- * planning -> done is illegal). So 'queued' is the correct -- and only -- forward
478
- * move here even for a complete-but-mislabeled row: it advances out of 'planning'
479
- * and re-enters the normal lifecycle, which re-settles already-done work quickly
480
- * (its issues are already Done -> coding -> verify -> done). Re-running merged work
481
- * is bounded by that; skipping the row instead would leave it 'planning' and
482
- * re-wedge the focus, which is worse.
483
- *
484
- * Returns the number of deliveries released (0 when the gate does not hold or
485
- * there were no held deliveries).
486
- */
487
- export async function releaseWritableFocusPlanningDeliveries(focus, deps = defaultWritableReleaseDeps) {
488
- // Audit-phase focuses -- explicit read_only=true OR the second structural signal
489
- // (audit_origin && not-yet-ratified) -- are owned by the ratify-release path.
490
- // Cheap exit before the fetch (isWritableFocusPastKickoff re-checks). Without
491
- // this a flag-absent audit focus (read_only silently false) would sweep its
492
- // born-planning remediation delivery to queued ahead of ratification (2f253da6).
493
- if (isAuditPhaseFocus({
494
- readOnly: focus.readOnly === true,
495
- auditOrigin: focus.auditOrigin === true,
496
- ratified: focus.ratifiedAt != null,
497
- })) {
498
- return 0;
499
- }
500
- if (focus.readOnly !== false)
501
- return 0;
502
- let deliveries;
503
- try {
504
- deliveries = await deps.getFocusDeliveries(focus.focusId);
505
- }
506
- catch (err) {
507
- console.warn(`[focus-stage-lifecycle] Writable-release: failed to fetch deliveries to ` +
508
- `release for "${focus.focusName}" (retry next tick): ${err.message}`);
509
- return 0;
510
- }
511
- // Stage-column-INDEPENDENT gate: derived phase + active-team, never focus.stage.
512
- if (!isWritableFocusPastKickoff({
513
- readOnly: focus.readOnly,
514
- auditOrigin: focus.auditOrigin,
515
- ratifiedAt: focus.ratifiedAt,
516
- deliveries,
517
- reviewRequestedAt: focus.reviewRequestedAt,
518
- hasActiveTeam: deps.hasActiveTeam(focus.focusId),
519
- })) {
520
- return 0;
521
- }
522
- let released = 0;
523
- for (const d of deliveries) {
524
- if (d.executionStatus !== DELIVERY_STATUS.PLANNING)
525
- continue;
526
- try {
527
- await deps.updateDeliveryStatus(d.id, DELIVERY_STATUS.QUEUED, undefined, undefined, undefined, { organizationId: focus.organizationId, fromStatus: DELIVERY_STATUS.PLANNING });
528
- released += 1;
529
- }
530
- catch (err) {
531
- console.warn(`[focus-stage-lifecycle] Writable-release: failed to queue held delivery ` +
532
- `${d.id.slice(0, 8)} on "${focus.focusName}": ${err.message}`);
533
- }
534
- }
535
- if (released > 0) {
536
- console.log(`[focus-stage-lifecycle] Focus "${focus.focusName}" (writable, past kickoff): ` +
537
- `released ${released} born-planning deliver${released === 1 ? 'y' : 'ies'} to queued.`);
538
- deps.emitLoopTrigger({
539
- type: 'focus_status_changed',
540
- focusId: focus.focusId,
541
- detail: 'writable:planning->queued',
542
- });
543
- }
544
- return released;
545
- }
546
- // ── Planning-wedge watchdog + zero-actionable log (D2 + D3) ───────
547
- /**
548
- * Delivery execution_status sets that mirror the server-side ready-focus
549
- * selector (supabase/functions/product-api/handlers/daemon-focus-handlers.ts:
550
- * NON_BLOCKING_STATUSES / ACTIONABLE_STATUSES). The edge is the SSOT; this
551
- * daemon-side mirror exists because the daemon receives an empty ready list for
552
- * a blocked focus and has no other window onto WHY. The status->class mapping is
553
- * kept in lockstep with the edge -- a divergence there would make the wedge log /
554
- * escalation disagree with what the daemon actually surfaces.
555
- *
556
- * non-blocking = terminal-ish; never blocks a focus (skipped in the walk).
557
- * actionable = a delivery the daemon can spawn a team for.
558
- * everything else (planning, draft, paused) is non-terminal + non-actionable:
559
- * it BLOCKS the focus when encountered first in rank order.
560
- *
561
- * DELIBERATE simplification vs the edge (review finding, deliveries 66c7d4d8 /
562
- * bbe01d7d): the edge ALSO skips an actionable delivery that already has an active
563
- * agent_session (`if (activeDeliveryIds.has(d.id)) continue;`), continuing the
564
- * rank walk past it. This daemon mirror does NOT model per-delivery active
565
- * sessions -- it has no equivalent of that DB query, and its in-memory team map is
566
- * focus-grained, not delivery-grained. The omission is BENIGN for wedge detection:
567
- * an actionable delivery with an active session means a team IS running, i.e. the
568
- * focus is making progress and is definitionally NOT wedged -- exactly what
569
- * `surfacesActionable: true` already reports. So the mirror can over-report
570
- * "surfaces actionable" relative to the edge's "ready" list, but never under-report
571
- * a wedge. We do not claim byte-for-byte parity, only that the wedge verdict agrees.
572
- */
573
- const WEDGE_NON_BLOCKING_STATUSES = new Set([
574
- DELIVERY_STATUS.DONE,
575
- DELIVERY_STATUS.CANCELLED,
576
- DELIVERY_STATUS.VERIFY,
577
- ]);
578
- const WEDGE_ACTIONABLE_STATUSES = new Set([
579
- DELIVERY_STATUS.QUEUED,
580
- DELIVERY_STATUS.CODING,
581
- DELIVERY_STATUS.VERIFY_FAILED,
582
- ]);
583
- /**
584
- * Held statuses that constitute a WEDGE worth escalating: work that should be
585
- * advancing but is stuck. Deliberately EXCLUDES 'paused' -- a pause is an
586
- * intentional human hold, not a wedge (AC: the watchdog must not fire for a
587
- * focus paused by a human).
588
- */
589
- const WEDGE_HELD_STATUSES = new Set([
590
- DELIVERY_STATUS.PLANNING,
591
- DELIVERY_STATUS.DRAFT,
592
- ]);
593
- /**
594
- * Replicate the server-side ready-focus rank-walk to decide whether a focus
595
- * surfaces actionable work, and if not, which delivery blocks it.
596
- *
597
- * The walk (rank-ascending, matching the edge): skip terminal-ish deliveries;
598
- * the FIRST non-terminal delivery decides -- actionable => the focus surfaces
599
- * (not wedged); non-actionable (planning/draft/paused) => that delivery blocks
600
- * the focus and everything behind it. This is why a low-rank 'planning' delivery
601
- * wedges a focus even when a higher-rank 'queued' delivery exists.
602
- *
603
- * Pure and exported for unit testing. Expects deliveries pre-sorted by
604
- * priorityRank ascending (as daemon_get_focus_deliveries returns them); it does
605
- * not re-sort, mirroring the edge which relies on the query's ORDER BY.
606
- */
607
- export function classifyFocusActionability(deliveries) {
608
- const totalCount = deliveries.length;
609
- for (const d of deliveries) {
610
- const status = d.executionStatus ?? '';
611
- if (WEDGE_NON_BLOCKING_STATUSES.has(status))
612
- continue;
613
- if (WEDGE_ACTIONABLE_STATUSES.has(status)) {
614
- return { totalCount, surfacesActionable: true, blockingDelivery: null, allTerminal: false };
615
- }
616
- // First non-terminal, non-actionable delivery -- it blocks the focus.
617
- return {
618
- totalCount,
619
- surfacesActionable: false,
620
- blockingDelivery: {
621
- id: d.id,
622
- name: d.name,
623
- status,
624
- priorityRank: d.priorityRank,
625
- updatedAt: d.updatedAt ?? null,
626
- },
627
- allTerminal: false,
628
- };
629
- }
630
- // Every delivery was terminal-ish (or there were none): not a wedge.
631
- return { totalCount, surfacesActionable: false, blockingDelivery: null, allTerminal: true };
632
- }
633
- // Per-focus watchdog state. Module-level so it persists across poll ticks.
634
- // firstSeenAt: in-memory first-seen (ms) FALLBACK clock, used only when the
635
- // blocking delivery has no updatedAt to ground the wedge age on.
636
- // logSig: last D3 log signature (blockingDeliveryId:status:char), for dampening.
637
- const focusWedgeFirstSeenAt = new Map();
638
- const focusZeroActionableLogSig = new Map();
639
- /** Clear a focus's watchdog state (called when it is no longer zero-actionable). */
640
- function clearFocusWedgeState(focusId) {
641
- focusWedgeFirstSeenAt.delete(focusId);
642
- focusZeroActionableLogSig.delete(focusId);
643
- }
644
- /**
645
- * Evict watchdog state for any focus NOT in `liveFocusIds`. A focus that goes
646
- * terminal (complete/cancelled) or whose product is unclaimed simply stops being
647
- * returned by getActiveFocuses, so `evaluateFocusWedge` never runs again to clear
648
- * its entry -- leaving a slow unbounded leak keyed by focusId (review finding,
649
- * delivery 66c7d4d8). The poll loop calls this once per run with the set of
650
- * focusIds it actually saw, bounding both maps to currently-active focuses.
651
- */
652
- export function pruneWedgeWatchdogState(liveFocusIds) {
653
- for (const id of focusWedgeFirstSeenAt.keys()) {
654
- if (!liveFocusIds.has(id))
655
- focusWedgeFirstSeenAt.delete(id);
656
- }
657
- for (const id of focusZeroActionableLogSig.keys()) {
658
- if (!liveFocusIds.has(id))
659
- focusZeroActionableLogSig.delete(id);
660
- }
661
- }
662
- /** Test seam: reset all watchdog state between cases. */
663
- export function __resetWedgeWatchdogState() {
664
- focusWedgeFirstSeenAt.clear();
665
- focusZeroActionableLogSig.clear();
666
- }
667
- /** Default wedge threshold (minutes) when the env var is unset / invalid. */
668
- const DEFAULT_WEDGED_FOCUS_THRESHOLD_MIN = 30;
669
- /**
670
- * Resolve the wedge threshold (ms).
671
- *
672
- * The canonical typed declaration of this knob lives in @telora/daemon-core
673
- * env-config (TELORA_WEDGED_FOCUS_THRESHOLD_MIN, positiveInt default 30) -- the
674
- * single source of truth for every operational knob. We read it directly from
675
- * process.env here (rather than via loadEnvConfig) so the daemon works against
676
- * the currently-installed daemon-core (a registry dep, not a workspace symlink)
677
- * before that package is republished with the new field -- the same pattern the
678
- * drift-eval list backoff and the stuck-shutdown watchdog use. Non-numeric /
679
- * non-positive falls back to the 30-minute default.
680
- */
681
- function defaultWedgeThresholdMs() {
682
- const raw = process.env.TELORA_WEDGED_FOCUS_THRESHOLD_MIN;
683
- const parsed = raw === undefined || raw === '' ? NaN : Number(raw);
684
- const minutes = Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_WEDGED_FOCUS_THRESHOLD_MIN;
685
- return minutes * 60_000;
686
- }
687
- function defaultWedgeWatchdogDeps() {
688
- return {
689
- getFocusDeliveries: defaultGetFocusDeliveries,
690
- hasOpenWedgedFocusEscalation: defaultHasOpenWedgedFocusEscalation,
691
- createEscalation: defaultCreateEscalation,
692
- hasActiveTeam: defaultHasActiveTeam,
693
- now: () => Date.now(),
694
- thresholdMs: defaultWedgeThresholdMs(),
695
- };
696
- }
697
- /**
698
- * Per-focus, per-tick evaluation of the planning-wedge condition. Does two
699
- * things off ONE actionability classification:
700
- *
701
- * D3 (log): when an active, role-assigned focus surfaces zero actionable
702
- * deliveries, name the rank-0 blocking delivery + its state in the daemon
703
- * log -- so a wedge is visible in ~/.telora/daemon.log, not only the
704
- * edge-function log. Dampened to once per focus per state change.
705
- *
706
- * D2 (escalate): when that focus is ALSO writable, the block is a WEDGE
707
- * (planning/draft, not a human pause), NO team is running, and the wedge
708
- * persists past the threshold, file a deduped wedged_focus escalation --
709
- * defense-in-depth for wedge classes the rank-respecting release (D1) does
710
- * not resolve (e.g. a 'draft' block, which D1 does not sweep).
711
- *
712
- * Runs AFTER the D1 release in the poll loop, so a focus D1 just unwedged is
713
- * already surfacing actionable work here and neither logs nor escalates.
714
- *
715
- * Gating (escalation): status active + assigned_agent_role_id set + read_only
716
- * false + wedge-held block + NO active team. The "no active team" check replaces
717
- * the old (broken) isPostPlanning(stage) gate -- the stage column is stale and
718
- * never reaches post-planning, so that gate never fired for the founding wedge
719
- * (review finding, delivery 66c7d4d8). It is the same kickoff-window discriminator
720
- * D1 uses: a running scoping team is shaping/releasing deliveries (don't escalate);
721
- * no team means nothing can make progress (the rank-0 block stops the ready
722
- * selector from spawning one) -- a true wedge. Zero-delivery focuses (planning
723
- * teams) and all-paused focuses (human hold) are excluded by the classifier (no
724
- * wedge-held blocking delivery).
725
- */
726
- export async function evaluateFocusWedge(focus, deps = defaultWedgeWatchdogDeps()) {
727
- // Only active, role-assigned focuses can wedge an execution team. An
728
- // unassigned or non-active focus has nothing to spawn -- not a wedge.
729
- if (focus.status !== 'active' || !focus.assignedAgentRoleId) {
730
- clearFocusWedgeState(focus.focusId);
731
- return { kind: 'skipped', reason: 'not_active_or_unassigned' };
732
- }
733
- let deliveries;
734
- try {
735
- deliveries = await deps.getFocusDeliveries(focus.focusId);
736
- }
737
- catch (err) {
738
- console.warn(`[focus-stage-lifecycle] Wedge eval: failed to fetch deliveries for ` +
739
- `"${focus.focusName}": ${err.message}`);
740
- return { kind: 'skipped', reason: 'delivery_fetch_failed' };
741
- }
742
- const actionability = classifyFocusActionability(deliveries);
743
- // The focus surfaces actionable work (or has only terminal/zero deliveries):
744
- // not zero-actionable-with-a-block -> clear any prior wedge state and return.
745
- if (actionability.surfacesActionable || !actionability.blockingDelivery) {
746
- clearFocusWedgeState(focus.focusId);
747
- return actionability.surfacesActionable
748
- ? { kind: 'surfaces_actionable' }
749
- : { kind: 'skipped', reason: 'all_terminal_or_empty' };
750
- }
751
- const block = actionability.blockingDelivery;
752
- const isWedgeHeld = WEDGE_HELD_STATUSES.has(block.status); // planning/draft vs paused
753
- const teamActive = deps.hasActiveTeam(focus.focusId);
754
- // ── D3: daemon-side zero-actionable log (dampened to state change) ──
755
- // Characterization distinguishes THREE states (review finding, delivery
756
- // bbe01d7d): a human pause (idle), a live kickoff/scoping window (team active,
757
- // deliveries being shaped -- NOT a wedge), and a true wedge (held work, no team
758
- // running). The old two-way split mislabeled a legitimate kickoff focus as
759
- // "wedged"; the active-team signal separates them.
760
- const characterization = !isWedgeHeld
761
- ? 'idle (delivery paused by a human)'
762
- : teamActive
763
- ? 'scoping in progress (team active, deliveries being shaped -- not a wedge)'
764
- : 'wedged (work held, no team running)';
765
- // Fold team-active into the dampening signature so a kickoff->wedge transition
766
- // (team exits while the block persists) re-logs with the updated characterization.
767
- const logSig = `${block.id}:${block.status}:${teamActive ? 'team' : 'noteam'}`;
768
- if (focusZeroActionableLogSig.get(focus.focusId) !== logSig) {
769
- focusZeroActionableLogSig.set(focus.focusId, logSig);
770
- console.log(`[focus-stage-lifecycle] Focus "${focus.focusName}" (stage=${focus.stage ?? 'unknown'}) ` +
771
- `surfaced zero actionable deliveries: rank-${block.priorityRank} "${block.name}" in ` +
772
- `"${block.status}" state -- ${characterization}.`);
773
- }
774
- // ── D2: wedged_focus escalation (writable + wedge-held + no active team) ──
775
- // No isPostPlanning(stage) gate -- the stage column is stale (see header). The
776
- // "no active team" check is the kickoff-window discriminator: a running scoping
777
- // team means the focus is making progress, not wedged.
778
- const escalationEligible = focus.readOnly === false &&
779
- isWedgeHeld &&
780
- !teamActive;
781
- if (!escalationEligible) {
782
- // Zero-actionable but not an escalation-worthy wedge (read-only audit, human
783
- // pause, or a live scoping team). Logged above; clear the fallback timer.
784
- focusWedgeFirstSeenAt.delete(focus.focusId);
785
- return { kind: 'zero_actionable', escalated: false, wedged: false, blockingStatus: block.status };
786
- }
787
- // Threshold: escalate only once the wedge has persisted past the window --
788
- // giving the D1 release time to fix it. Prefer the blocking delivery's
789
- // updatedAt as a RESTART-DURABLE wedge-age proxy (how long the row has actually
790
- // sat held; survives a daemon restart, fixing the process-memory reset --
791
- // review finding, delivery 66c7d4d8). Fall back to the in-memory first-seen
792
- // clock only when updatedAt is unavailable (older backends).
793
- const now = deps.now();
794
- const updatedAtMs = block.updatedAt ? Date.parse(block.updatedAt) : NaN;
795
- let firstSeen;
796
- if (Number.isFinite(updatedAtMs)) {
797
- firstSeen = updatedAtMs;
798
- focusWedgeFirstSeenAt.delete(focus.focusId); // DB-grounded; no in-memory clock needed
799
- }
800
- else {
801
- firstSeen = focusWedgeFirstSeenAt.get(focus.focusId) ?? now;
802
- if (!focusWedgeFirstSeenAt.has(focus.focusId)) {
803
- focusWedgeFirstSeenAt.set(focus.focusId, now);
804
- }
805
- }
806
- if (now - firstSeen < deps.thresholdMs) {
807
- return { kind: 'zero_actionable', escalated: false, wedged: true, blockingStatus: block.status };
808
- }
809
- // Dedup: at most one open wedged_focus escalation per focus (survives restart).
810
- try {
811
- if (await deps.hasOpenWedgedFocusEscalation(focus.focusId)) {
812
- return { kind: 'zero_actionable', escalated: false, wedged: true, blockingStatus: block.status };
813
- }
814
- }
815
- catch (err) {
816
- console.warn(`[focus-stage-lifecycle] Wedge eval: dedup check failed for ` +
817
- `"${focus.focusName}" (skipping escalation this tick): ${err.message}`);
818
- return { kind: 'zero_actionable', escalated: false, wedged: true, blockingStatus: block.status };
819
- }
820
- const wedgedMinutes = Math.round((now - firstSeen) / 60_000);
821
- try {
822
- await deps.createEscalation({
823
- organizationId: focus.organizationId,
824
- sessionId: '',
825
- issueId: null,
826
- reasonType: ESCALATION_REASONS.BLOCKED_BY_EXTERNAL,
827
- disposition: ESCALATION_DISPOSITIONS.AGENT_RESOLVABLE,
828
- anchor: { focusId: focus.focusId },
829
- description: `Focus "${focus.focusName}" is wedged: it is active, role-assigned, ` +
830
- `writable, and has no running team, but has zero actionable deliveries -- ` +
831
- `the rank-${block.priorityRank} delivery "${block.name}" is stuck in ` +
832
- `"${block.status}" state, blocking the focus, so no execution team ` +
833
- `spawns. It has been wedged for ~${wedgedMinutes} minute(s). The ` +
834
- `writable planning->queued release should normally clear a 'planning' ` +
835
- `block; investigate why the delivery is not being released ` +
836
- `(a 'draft' block is not swept by that release and needs attention).`,
837
- // Local string const (mirrors ESCALATION_KINDS.WEDGED_FOCUS in
838
- // daemon-core, the SSOT) so the write works against the currently-
839
- // installed daemon-core before it is republished with the new enum value.
840
- escalationKind: WEDGED_FOCUS_ESCALATION_KIND,
841
- metadata: {
842
- focusId: focus.focusId,
843
- kind: WEDGED_FOCUS_ESCALATION_KIND,
844
- blockingDeliveryId: block.id,
845
- blockingDeliveryStatus: block.status,
846
- wedgedMinutes,
847
- },
848
- });
849
- console.warn(`[focus-stage-lifecycle] wedged_focus escalation filed for ` +
850
- `"${focus.focusName}" (rank-${block.priorityRank} "${block.name}" in ` +
851
- `"${block.status}", wedged ~${wedgedMinutes}m).`);
852
- return { kind: 'zero_actionable', escalated: true, wedged: true, blockingStatus: block.status };
853
- }
854
- catch (err) {
855
- console.warn(`[focus-stage-lifecycle] Failed to file wedged_focus escalation for ` +
856
- `"${focus.focusName}": ${err.message}`);
857
- return { kind: 'zero_actionable', escalated: false, wedged: true, blockingStatus: block.status };
858
- }
859
- }
42
+ import { getFocusDeliveries as defaultGetFocusDeliveries } from './queries/focuses.js';
43
+ import { FOCUS_STAGE } from '@telora/daemon-core';
44
+ import { advanceVerifyToReviewIfEnabled, advanceReviewToCloseLoopIfConverged, } from './focus-stage-lifecycle/review-convergence.js';
45
+ import { releaseRatifiedReadOnlyFocus, releaseWritableFocusPlanningDeliveries, } from './focus-stage-lifecycle/planning-release.js';
46
+ import { evaluateFocusWedge, pruneWedgeWatchdogState, } from './focus-stage-lifecycle/wedge-watchdog.js';
47
+ // Re-export the decomposed surface so every importer + test keeps resolving
48
+ // these names from './focus-stage-lifecycle.js' unchanged.
49
+ export * from './focus-stage-lifecycle/review-convergence.js';
50
+ export * from './focus-stage-lifecycle/planning-release.js';
51
+ export * from './focus-stage-lifecycle/wedge-watchdog.js';
860
52
  // ── Poll-loop entry point ─────────────────────────────────────────
861
53
  /**
862
54
  * Per-product per-focus tick for the new stage lifecycle transitions.