elanous 0.2.13 → 0.2.15

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 (524) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -16,7 +16,7 @@ export const TERMINAL_STAGES_BY_NODE = {
16
16
  implement: ['aborted', 'timed-out', 'soft-stopped', 'parked'],
17
17
  gate: [],
18
18
  review: ['review-blocked'],
19
- rework: ['gate-failed', 'review-blocked'],
19
+ rework: ['gate-failed', 'review-blocked', 'no-changes'],
20
20
  'main-sync': ['merge-conflict'],
21
21
  regate: ['gate-failed'],
22
22
  'open-pr': ['pr-declined', 'pr-opened', 'worktree-completed'],
@@ -0,0 +1,97 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { lstatSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { debug } from '../debug/log.js';
5
+
6
+ export type RevertGuardResult = { reverted: string[]; protected: string[]; staleDeleted?: string[]; outsideDeleted?: string[]; warning?: string };
7
+
8
+ function isTargetPath(file: string, targets: readonly string[]): boolean {
9
+ return targets.some((target) => {
10
+ const path = target.replace(/^\.\//, '').replace(/\/+$/, '');
11
+ return path !== '' && (file === path || file.startsWith(`${path}/`));
12
+ });
13
+ }
14
+
15
+ /** Compare the worktree's exact bytes with blobs from recent HEAD ancestry, before add -A. */
16
+ export function dropStaleReverts(cwd: string, targets: readonly string[], runId: string, depth = 50, base?: string): RevertGuardResult {
17
+ const result: RevertGuardResult = { reverted: [], protected: [] };
18
+ const git = (...args: string[]): Buffer => execFileSync('git', args, { cwd, timeout: 20_000, maxBuffer: 16 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
19
+ const reason = (error: unknown): string => (error instanceof Error ? error.message : String(error)).replace(/\s+/g, ' ').slice(0, 240);
20
+ try {
21
+ const limit = Number.isSafeInteger(depth) && depth > 0 ? depth : 50;
22
+ const commits = git('rev-list', `--max-count=${limit + 1}`, 'HEAD').toString('utf8').trim().split('\n');
23
+ const changed = git('diff', '--name-only', '--diff-filter=M', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean);
24
+ const staged = git('diff', '--cached', '--name-only', '--diff-filter=M', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean);
25
+ const deleted = new Set([
26
+ ...git('diff', '--name-only', '--diff-filter=D', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean),
27
+ ...git('diff', '--cached', '--name-only', '--diff-filter=D', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean),
28
+ ]);
29
+ for (const file of deleted) {
30
+ if (isTargetPath(file, targets)) continue;
31
+ try {
32
+ // add -A stages the worktree: even a dangling symlink replaces the staged deletion.
33
+ try {
34
+ lstatSync(join(cwd, file));
35
+ continue;
36
+ } catch (error) {
37
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
38
+ }
39
+ // ls-tree returning an empty entry means absent; a failed lookup must not look like absence.
40
+ const headEntry = git('ls-tree', 'HEAD', '--', file).toString('utf8').trim();
41
+ if (!/^\d+ blob [0-9a-f]+\t/.test(headEntry)) continue;
42
+ const baseEntry = base ? git('ls-tree', base, '--', file).toString('utf8').trim() : null;
43
+ if (baseEntry === '' && base) {
44
+ git('checkout', 'HEAD', '--', file);
45
+ (result.staleDeleted ??= []).push(file);
46
+ result.reverted.push(file);
47
+ debug.log('self-implement.revert-guard', 'reverted-stale-delete', { runId, files: [file] });
48
+ } else {
49
+ (result.outsideDeleted ??= []).push(file);
50
+ debug.log('self-implement.revert-guard', 'outside-delete', { runId, files: [file] }, { level: 'warn' });
51
+ }
52
+ } catch (error) {
53
+ result.warning = `git inspection/checkout failed for ${file}: ${reason(error)}`;
54
+ debug.log('self-implement.revert-guard', 'fail-open', { runId, reason: result.warning }, { level: 'warn' });
55
+ }
56
+ }
57
+ for (const file of new Set([...changed, ...staged])) {
58
+ try {
59
+ if (!lstatSync(join(cwd, file)).isFile()) continue;
60
+ const current = readFileSync(join(cwd, file));
61
+ const rawOid = execFileSync('git', ['hash-object', '--stdin'], { cwd, input: current, timeout: 20_000 }).toString('utf8').trim();
62
+ const headEntry = git('ls-tree', 'HEAD', '--', file).toString('utf8').trim();
63
+ const head = /^\d+ blob ([0-9a-f]+)\t/.exec(headEntry)?.[1];
64
+ if (!head) continue;
65
+ const sameBytes = (oid: string): boolean => oid === rawOid || git('cat-file', 'blob', oid).equals(current);
66
+ if (sameBytes(head)) continue;
67
+ let matchedCommit: string | undefined;
68
+ const inspectedOids = new Set<string>();
69
+ for (const commit of commits.slice(1)) {
70
+ // A path may not have existed in an ancestor; that is not a failed inspection.
71
+ const blob = git('ls-tree', commit, '--', file).toString('utf8').trim();
72
+ const oid = /^\d+ blob ([0-9a-f]+)\t/.exec(blob)?.[1];
73
+ if (oid && !inspectedOids.has(oid)) {
74
+ if (sameBytes(oid)) { matchedCommit = commit; break; }
75
+ inspectedOids.add(oid);
76
+ }
77
+ }
78
+ if (!matchedCommit) continue;
79
+ if (isTargetPath(file, targets)) {
80
+ result.protected.push(file);
81
+ debug.log('self-implement.revert-guard', 'protected-target', { runId, files: [file], matchedCommit }, { level: 'warn' });
82
+ } else {
83
+ git('checkout', 'HEAD', '--', file);
84
+ result.reverted.push(file);
85
+ debug.log('self-implement.revert-guard', 'reverted-stale', { runId, files: [file], matchedCommit });
86
+ }
87
+ } catch (error) {
88
+ result.warning = `git inspection/checkout failed for ${file}: ${reason(error)}`;
89
+ debug.log('self-implement.revert-guard', 'fail-open', { runId, reason: result.warning }, { level: 'warn' });
90
+ }
91
+ }
92
+ } catch (error) {
93
+ result.warning = `git inspection failed: ${reason(error)}`;
94
+ debug.log('self-implement.revert-guard', 'fail-open', { runId, reason: result.warning }, { level: 'warn' });
95
+ }
96
+ return result;
97
+ }
@@ -1,5 +1,6 @@
1
1
  import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, unlinkSync } from 'node:fs';
2
2
  import { dirname, isAbsolute, join, resolve } from 'node:path';
3
+ import { refuseProductionLedgerWriteInTest } from '../harness/ledger-write-guard.js';
3
4
  import { tracedPathReferences, verbatimOriginalAsk } from './goal-author.js';
4
5
  import { parseAskTargetPathHintsResult } from '../self-dev/launch-preflight.js';
5
6
  import { isRunStatus } from './run-status-mapping.js';
@@ -276,7 +277,7 @@ interface RunLedgerLookupTarget {
276
277
  targetName: string | null;
277
278
  }
278
279
 
279
- function resolveFederatedRunLedgerTargets(options: Pick<FederatedRunLedgerLookupOptions, 'includeTest' | 'targets'>): RunLedgerLookupTarget[] {
280
+ export function resolveFederatedRunLedgerTargets(options: Pick<FederatedRunLedgerLookupOptions, 'includeTest' | 'targets'>): RunLedgerLookupTarget[] {
280
281
  const resolved = options.targets ? { targets: [...options.targets] } : resolveLogTargets({ all: true, includeTest: options.includeTest });
281
282
  if (resolved.error) throw new Error(resolved.error);
282
283
  const targets = new Map<string, string>();
@@ -402,6 +403,7 @@ export function describeFederatedMissingRunLedger(runId: string, options: Federa
402
403
 
403
404
  /** Append one observer event. Callers own fail-soft handling. */
404
405
  export function appendRunLedgerEntry(entry: RunLedgerEntry, dir = runLedgerDir()): void {
406
+ if (refuseProductionLedgerWriteInTest(dir, 'run-ledger')) return;
405
407
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
406
408
  const live = runLedgerPath(entry.runId, dir);
407
409
  withRunLedgerLock(dir, entry.runId, () => {
@@ -16,6 +16,7 @@ export type SelfImplementStage =
16
16
  | 'worktree-completed'
17
17
  | 'gate-failed'
18
18
  | 'review-blocked'
19
+ | 'no-changes'
19
20
  | 'merge-conflict'
20
21
  | 'aborted'
21
22
  | 'timed-out'
@@ -30,6 +31,7 @@ const STAGE_OUTCOMES = {
30
31
  'worktree-completed': { runStatus: 'completed' },
31
32
  'gate-failed': { runStatus: 'failed', failureKind: 'gate' },
32
33
  'review-blocked': { runStatus: 'failed', failureKind: 'review' },
34
+ 'no-changes': { runStatus: 'failed', failureKind: 'review' },
33
35
  'merge-conflict': { runStatus: 'failed', failureKind: 'merge-conflict' },
34
36
  aborted: { runStatus: 'failed', failureKind: 'aborted' },
35
37
  'timed-out': { runStatus: 'failed', failureKind: 'timed-out' },
@@ -37,7 +37,8 @@ import { iosFilesIn, runIosUnitTestGate } from '../../scripts/ci-ios-unit-tests.
37
37
  import { detectGateCommand } from './detect-gate.js';
38
38
  import { writeScopedTypecheckConfig } from './scoped-typecheck-config.js';
39
39
  // ⭐ 게이트 스코프 근본 수리(2026-07-26) — 풀 `bun test` 폴백 제거 + 연관 테스트 유도(순수).
40
- import { resolveGateScope } from './gate-scope.js';
40
+ import { formatNoRelatedTestsWarning, resolveGateScope } from './gate-scope.js';
41
+ import { publicExposureFiles, runExposeGate } from './expose-gate.js';
41
42
  import { buildImporterTestIndex, isTestPath } from './importer-test-index.js';
42
43
  import { buildRouteConsumerTestIndex } from './route-consumer-test-index.js';
43
44
  import { countTestDeclarations } from './test-declarations.js';
@@ -976,6 +977,7 @@ export interface DefaultSeamsOptions {
976
977
  runIosUnitTestGate?: (io: { args: readonly string[]; cwd: string }) => number;
977
978
  /** Source-policy gate adapter for focused seam tests. */
978
979
  runHarnessPolicyGates?: typeof runHarnessPolicyGates;
980
+ runExposeGate?: typeof runExposeGate;
979
981
  /** Dependency-wide validation adapter for focused seam tests. */
980
982
  runDependencyChangeGate?: typeof runDependencyChangeGate;
981
983
  /** 워크트리 시험 묶음(없으면 실패 파일)을 base에서 재실행하는 seam. 실패 경로에서만 호출된다. */
@@ -1409,6 +1411,10 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1409
1411
  },
1410
1412
  removeMatchingGoalCopy,
1411
1413
  },
1414
+ supersedeDraftsOnMerge: async (number, cwd) => {
1415
+ const { supersedeMergedGoalDrafts } = await import('../harness/harness-cli-command.js');
1416
+ await supersedeMergedGoalDrafts(number, cwd);
1417
+ },
1412
1418
  lineageSupersede: {
1413
1419
  listOpenDrafts: () => listOpenDraftsForLineage(),
1414
1420
  readRunLedger: (runId) => {
@@ -1810,8 +1816,13 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1810
1816
  const observationChanges = gateChangedFiles(cwd, comparisonBase);
1811
1817
  const observationChangedFiles = observationChanges.files;
1812
1818
  const postsync = ctx?.mode === 'postsync';
1813
- // 정합 전 일반 게이트는 worktree-only. 정합 후 재게이트만 이미 계산된 merge-base 목록을 실행 범위로 쓴다.
1814
- const changedAll = postsync ? observationChangedFiles : gitChangedFiles(cwd);
1819
+ // 일반 게이트는 기존 worktree 범위를 유지하되 fork 이후 커밋된 시험을 더한다.
1820
+ // HEAD 대비 미커밋 목록만 쓰면 릴리스 노트만 남은 재작업 라운드에서
1821
+ // 커밋된 시험을 놓쳐 docs-only 로 건너뛴다. postsync는 종전처럼 전 변경을 잰다.
1822
+ const changedAll = postsync ? observationChangedFiles : [...new Set([
1823
+ ...gitChangedFiles(cwd),
1824
+ ...observationChangedFiles.filter((file) => isTestPath(file)),
1825
+ ])];
1815
1826
  // ⚠️ **regular-file 판정**(리뷰 should-fix 5R) — `existsSync` 는 **디렉터리도 true** 다.
1816
1827
  // 계약은 "실존 테스트 **파일**"이므로 파일 여부까지 확인한다(디렉터리를 testArgs 로 넘기면
1817
1828
  // 필터 무매치 → "0 files ran" 부당 실패).
@@ -1824,11 +1835,19 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1824
1835
  trackedFiles.filter(isTestPath),
1825
1836
  [...new Set([...trackedFiles, ...changedAll, ...observationChangedFiles])].filter((file) => /\.(?:[cm]?[jt]sx?)$/.test(file)),
1826
1837
  );
1827
- const routeConsumerTestIndex = buildRouteConsumerTestIndex(cwd, trackedFiles.filter(isTestPath), changedAll, comparisonBase ?? 'HEAD');
1838
+ // 런 전체 재탐색 시 앞 라운드의 변경·삭제 서버 경로도 비교-base 소비자 색인에 있어야 한다.
1839
+ const routeConsumerTestIndex = buildRouteConsumerTestIndex(cwd, trackedFiles.filter(isTestPath),
1840
+ [...new Set([...changedAll, ...observationChangedFiles])], comparisonBase ?? 'HEAD');
1828
1841
  // 워크트리에 없는 변경 경로 = 지운 파일 — 미검증으로 세지 않는다(gate-scope `isDeleted` · 2026-09-26 run-7dd4cce6).
1829
1842
  const isDeleted = (path: string): boolean => !isFile(path);
1830
- const scopeOpts = { isDeleted, routeConsumerTestIndex: routeConsumerTestIndex ?? undefined };
1843
+ const scopeOpts = { isDeleted, routeConsumerTestIndex: routeConsumerTestIndex ?? undefined,
1844
+ runChanged: observationChangedFiles, round: ctx?.round };
1831
1845
  const scope = resolveGateScope(changedAll, isFile, importerTestIndex ?? undefined, postsync ? { ...scopeOpts, mode: 'postsync' } : scopeOpts);
1846
+ // The landing scope is the PR diff, not only this round's uncommitted test files.
1847
+ const expose = publicExposureFiles(observationChangedFiles).length > 0
1848
+ ? (o.runExposeGate ?? runExposeGate)(cwd, observationChangedFiles,
1849
+ getUserConfig().harness?.exposeGate === 'strict' ? 'strict' : 'warn')
1850
+ : { passed: true, log: '' };
1832
1851
  const comparisonFailureReason = observationChanges.comparisonBaseStatus === 'unavailable'
1833
1852
  ? 'comparison-base-unavailable'
1834
1853
  : observationChanges.comparisonBaseStatus === 'comparison-failed'
@@ -1865,6 +1884,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
1865
1884
  debug.log('self-implement', 'gate.scope', {
1866
1885
  ...(ctx?.runId ? { runId: ctx.runId } : {}),
1867
1886
  targeted: gateOpts.testArgs?.length ?? 0, files: (gateOpts.testArgs ?? []).slice(0, 8),
1887
+ 'evaluated-changes': changedAll.slice(0, 8), evaluatedChangeCount: changedAll.length,
1868
1888
  changedTests: observedChangedTestFiles.slice(0, 8), changedTestCount: observedChangedTestFiles.length,
1869
1889
  srcChanged: scope.sourceFiles.length, testStepSkipped,
1870
1890
  scopeReason, derived: scope.derived.length, ignoredOutsideSrc: scope.ignoredOutsideSrc,
@@ -2053,7 +2073,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2053
2073
  // `tc`(변경파일 tsc) **전에** 실행해서, tsc 가 깨져 **최종 게이트가 실패한 런에도**
2054
2074
  // base 를 다시 돌리고 `gate.log` 를 바꿨다 ⇒ *"게이트 실패 경로 무변경"* 경계 위반.
2055
2075
  // ⇒ 조건을 최종 판정과 같은 식으로 묶는다.
2056
- const gatePassed = !comparisonFailureReason && !unmeasuredPostsync && integrityPassed && tc.passed && policy.passed && (dependencyGate?.passed ?? true);
2076
+ const gatePassed = !comparisonFailureReason && !unmeasuredPostsync && integrityPassed && tc.passed && policy.passed && expose.passed && (dependencyGate?.passed ?? true);
2057
2077
  const verifyIdentity = gateVerifyIdentity(ctx?.runId);
2058
2078
  if (gatePassed && scope.skipTestStep) {
2059
2079
  verifyByBreaking = { ran: false, distinguishes: 0, 'does-not-distinguish': 0, unknown: 0, skippedReason: scope.reason };
@@ -2204,7 +2224,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2204
2224
  + (scope.reason === 'no-related-tests'
2205
2225
  // ⭐ **미검증 파일 목록**(2026-07-27 사후 리뷰 수리) — 종전엔 `sourceFiles` 를 실어
2206
2226
  // `package.json`·설정·스크립트 변경 시 **빈 목록**이 나갔다(그 파일들은 소스 확장자가 아니다).
2207
- ? `동작 검증을 못 한 변경: ${scope.unverified.slice(0, 8).join(', ')}`
2227
+ ? `\n\n${formatNoRelatedTestsWarning(scope.unverified)}\n\n동작 검증을 못 한 변경: ${scope.unverified.slice(0, 8).join(', ')}`
2208
2228
  + `\n⚠️ 이 라운드는 **동작 검증이 되지 않았다**(변경파일 tsc 만 통과) — "게이트 통과=검증됨"으로 결론내지 말 것.`
2209
2229
  // ⚠️ 안내를 일반화(리뷰 should-fix) — `package.json`·lockfile·설정·스크립트에는
2210
2230
  // "해당 소스의 테스트"라는 표현이 부정확하다(그 변경엔 대응 테스트 관례가 없다).
@@ -2229,7 +2249,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2229
2249
  : '';
2230
2250
  return {
2231
2251
  passed: gatePassed,
2232
- log: `${baseLog}${policyFailureLog}${dependencyFailureLog}${dependencyUnmeasuredNote}${comparisonFailureLog}${baselineNote}${verifyByBreakingNote}${scopeNote}${uncoveredNote}${pulledInRelatedTestsNote}${callerOverflowNote}${routeLookupNote}`,
2252
+ log: `${baseLog}${policyFailureLog}${dependencyFailureLog}${dependencyUnmeasuredNote}${comparisonFailureLog}${baselineNote}${verifyByBreakingNote}${scopeNote}${uncoveredNote}${pulledInRelatedTestsNote}${callerOverflowNote}${routeLookupNote}${expose.log ? `\n\n${expose.log}` : ''}`,
2233
2253
  testStepExecuted: !testStepSkipped && testStep !== undefined && !testStep.skipped,
2234
2254
  scopeReason,
2235
2255
  comparisonBaseStatus: observationChanges.comparisonBaseStatus,
@@ -2493,7 +2513,7 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2493
2513
  verdict: rr.verdict,
2494
2514
  mustFix: rr.mustFix,
2495
2515
  shouldFix: rr.shouldFix,
2496
- summary: renderReview(rr).slice(0, 2000),
2516
+ summary: rr.reviewed ? renderReview(rr).slice(0, 2000) : `리뷰 못 함(사유: ${rr.failureReason ?? '사유 미상'})`,
2497
2517
  reviewed: rr.reviewed ?? false,
2498
2518
  // ⛔⭐ 이 한 줄이 `orchestrator.ts` 의 「찍는 줄」에 값을 «흘린다». 빼면 그 줄은 살아 있는데 값이 안 온다.
2499
2519
  ...(rr.failureReason !== undefined ? { failureReason: rr.failureReason } : {}),
@@ -108,6 +108,7 @@ export function singleRunAsJobResult(feature: string, r: SelfImplementResultWith
108
108
  stage: r.stage,
109
109
  ...(!r.ok && r.detail ? { error: { message: r.detail } } : {}),
110
110
  ...(r.branch ? { branch: r.branch } : {}),
111
+ ...(r.harvestable === true && r.branch ? { harvestable: true } : {}),
111
112
  ...(r.worktreePath ? { worktreePath: r.worktreePath } : {}),
112
113
  ...(r.prUrl ? { prUrl: r.prUrl } : {}),
113
114
  ...(r.merged !== undefined ? { merged: r.merged } : {}),
@@ -0,0 +1,338 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { readFileSync, readdirSync } from 'node:fs';
3
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { prodInstanceRoot } from '../instance/resolve.js';
6
+ import { seatOfTree } from '../loops/orchestrator/traffic.js';
7
+ import { getUserConfig, ORCHESTRATOR_DEFAULTS } from '../user-config.js';
8
+ import { isRunStatus } from './run-status-mapping.js';
9
+ import { loadRunLedger, runLedgerDir, type RunLedgerEntry, type RunLedgerReader } from './run-ledger.js';
10
+
11
+ export type SpeedClaimsOutcome = 'merged' | 'failed' | 'abandoned' | 'completed-without-merge';
12
+ export type SpeedClaimsSeat = 'OP' | 'TC' | 'MK' | 'UX' | 'unknown';
13
+ export interface SpeedClaimsRow {
14
+ seat: SpeedClaimsSeat;
15
+ outcome: SpeedClaimsOutcome;
16
+ count: number;
17
+ underOneMinuteCount: number;
18
+ medianMinutes: number;
19
+ q1Minutes: number;
20
+ q3Minutes: number;
21
+ }
22
+ export interface SpeedClaimsMeasurement {
23
+ scope: 'self-implement-run-ledger';
24
+ ledgerDirectory: string;
25
+ status: 'measured' | 'cannot-measure';
26
+ completedCount: number;
27
+ inProgressCount: number;
28
+ cannotMeasureCount: number;
29
+ unknownSeatCount: number;
30
+ rows: SpeedClaimsRow[];
31
+ note: string;
32
+ parent: ParentSpeedClaimsMeasurement;
33
+ }
34
+ export interface ParentSpeedClaimsEvent {
35
+ ts: string;
36
+ category: string;
37
+ event: string;
38
+ data: unknown;
39
+ }
40
+
41
+ export interface ParentSpeedClaimsRow {
42
+ seat: 'OP' | 'TC' | 'MK' | 'UX' | 'direct' | 'unknown';
43
+ stage: string;
44
+ merged: boolean;
45
+ count: number;
46
+ medianMinutes: number;
47
+ q1Minutes: number;
48
+ q3Minutes: number;
49
+ }
50
+
51
+ export interface ParentSpeedClaimsMeasurement {
52
+ scope: 'self-dev.orchestrate-parent-logs';
53
+ status: 'measured' | 'cannot-measure';
54
+ completedCount: number;
55
+ inProgressCount: number;
56
+ cannotMeasureCount: number;
57
+ rows: ParentSpeedClaimsRow[];
58
+ note: string;
59
+ }
60
+
61
+ export interface SpeedClaimsOptions {
62
+ dir?: string;
63
+ list?: (path: string) => string[];
64
+ read?: RunLedgerReader;
65
+ readSeat?: (runId: string, ledgerDirectory: string, entries: readonly RunLedgerEntry[]) => SpeedClaimsSeat;
66
+ readParentEvents?: () => readonly ParentSpeedClaimsEvent[];
67
+ readParentQueue?: () => unknown;
68
+ }
69
+
70
+ const NOTE = 'Child-ledger elapsed minutes from the first child event (or checkpoint creation when unavailable) to the recorded child end, including retries and resumes; this is not the parent launch-to-done interval. Only finished runs enter quartiles (linear interpolation at ranks (n-1)*p). In-progress runs (including parked) and missing, unreadable or invalid timestamps are excluded, not assigned zero. Seats come from the same state root checkpoint, launch queue or recorded launch tree; unresolved seats are counted separately. A completed run without a recorded merge is not a merge.';
71
+ const CANONICAL_RUN_ID = /^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
72
+ const SEATS: readonly SpeedClaimsSeat[] = ['OP', 'TC', 'MK', 'UX', 'unknown'];
73
+
74
+ function checkpointFor(runId: string, ledgerDirectory: string): Record<string, unknown> | null {
75
+ try {
76
+ const value: unknown = JSON.parse(readFileSync(join(dirname(ledgerDirectory), 'self-dev-runs', `${runId}.json`), 'utf8'));
77
+ return value && typeof value === 'object' && !Array.isArray(value) && 'runId' in value && value.runId === runId
78
+ ? value as Record<string, unknown> : null;
79
+ } catch { return null; }
80
+ }
81
+
82
+ function seatFromLaunch(runId: string, ledgerDirectory: string, entries: readonly RunLedgerEntry[]): SpeedClaimsSeat {
83
+ const checkpoint = checkpointFor(runId, ledgerDirectory);
84
+ if (SEATS.includes(checkpoint?.seat as SpeedClaimsSeat) && checkpoint?.seat !== 'unknown') return checkpoint!.seat as SpeedClaimsSeat;
85
+ // Queue ids and launch ids are not run ids. Only an exact, unique ask-file match can attribute a queued launch.
86
+ const goalFiles = new Set(entries.filter((entry) => entry.event === 'start')
87
+ .map((entry) => entry.data.goalFile).filter((file): file is string => typeof file === 'string' && isAbsolute(file)));
88
+ if (goalFiles.size === 1) {
89
+ try {
90
+ const queue: unknown = JSON.parse(readFileSync(join(dirname(ledgerDirectory), 'harness', 'queue.json'), 'utf8'));
91
+ if (Array.isArray(queue)) {
92
+ const matched = queue.filter((row) => row && typeof row === 'object' && row.kind === 'ask'
93
+ && (row.status === 'launching' || row.status === 'launched' || row.status === 'finished')
94
+ && row.input === [...goalFiles][0] && SEATS.includes(row.seat));
95
+ if (matched.length === 1 && matched[0]!.seat !== 'unknown') return matched[0]!.seat as SpeedClaimsSeat;
96
+ }
97
+ } catch { /* An absent or unreadable queue cannot establish attribution. */ }
98
+ }
99
+ try {
100
+ const cfg = getUserConfig().loops?.orchestrator ?? ORCHESTRATOR_DEFAULTS;
101
+ const paths = entries.flatMap((entry) => entry.event === 'start' ? [entry.data.targetRoot] : entry.event === 'worktree' ? [entry.data.path] : []);
102
+ const seats = new Set<SpeedClaimsSeat>();
103
+ for (const path of paths) {
104
+ if (typeof path !== 'string' || !isAbsolute(path)) continue;
105
+ try {
106
+ const seat = seatOfTree(path, cfg);
107
+ if (seat) seats.add(seat);
108
+ } catch { /* A removed or unreadable launch tree does not prove a seat. */ }
109
+ }
110
+ if (seats.size === 1) return [...seats][0]!;
111
+ } catch { /* Unavailable seat configuration does not erase a readable run. */ }
112
+ return 'unknown';
113
+ }
114
+
115
+ function parseTime(timestamp: unknown): number | null {
116
+ if (typeof timestamp !== 'string') return null;
117
+ const match = /^(\d{4})-(\d\d)-(\d\d)T\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.exec(timestamp);
118
+ if (!match) return null;
119
+ const year = Number(match[1]);
120
+ const month = Number(match[2]);
121
+ const day = Number(match[3]);
122
+ const days = [31, year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
123
+ if (month < 1 || month > 12 || day < 1 || day > days[month - 1]!) return null;
124
+ const parsed = Date.parse(timestamp);
125
+ return Number.isFinite(parsed) ? parsed : null;
126
+ }
127
+
128
+ function time(entry: RunLedgerEntry | undefined): number | null {
129
+ return parseTime(entry?.timestamp);
130
+ }
131
+
132
+ function percentile(sorted: readonly number[], p: number): number {
133
+ const rank = (sorted.length - 1) * p;
134
+ const low = Math.floor(rank);
135
+ return sorted[low]! + (sorted[Math.ceil(rank)]! - sorted[low]!) * (rank - low);
136
+ }
137
+
138
+ function endOfRun(entries: readonly RunLedgerEntry[]): { entry: RunLedgerEntry; outcome: SpeedClaimsOutcome } | null {
139
+ let end: { entry: RunLedgerEntry; outcome: SpeedClaimsOutcome } | null = null;
140
+ for (const entry of entries) {
141
+ if (entry.event === 'start') { end = null; continue; }
142
+ if (entry.event === 'merged' && entry.data.merged === true) end = { entry, outcome: 'merged' };
143
+ if (end?.outcome === 'merged') continue;
144
+ if (entry.event === 'human-stop') end = { entry, outcome: 'abandoned' };
145
+ if (entry.event === 'run-status' && isRunStatus(entry.data.runStatus)) {
146
+ const status = entry.data.runStatus;
147
+ if (status === 'completed') end = { entry, outcome: 'completed-without-merge' };
148
+ else if (status === 'failed') end = { entry, outcome: entry.data.stage === 'aborted' ? 'abandoned' : 'failed' };
149
+ else if (status === 'cancelled') end = { entry, outcome: 'abandoned' };
150
+ else end = null;
151
+ }
152
+ }
153
+ return end;
154
+ }
155
+
156
+ const PARENT_NOTE = 'Elapsed minutes from the earliest valid parent self-dev.orchestrate start log to the latest valid done log, grouped by that done.data.outcomes[].stage and merged flag. Only runs with both events and valid timestamps enter quartiles. A start without done is in progress. Seats use the same-runId queue seat when available, or direct when a readable queue has no matching entry; an unreadable queue remains unknown. Parent log queries are read-only and independent of child run ledgers.';
157
+ type ParentSeat = ParentSpeedClaimsRow['seat'];
158
+ const PARENT_SEATS: readonly ParentSeat[] = ['OP', 'TC', 'MK', 'UX', 'direct', 'unknown'];
159
+
160
+ function object(value: unknown): Record<string, unknown> | null {
161
+ return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
162
+ }
163
+
164
+ /** Read the first-class logs CLI rather than opening logs.db in the measurement process. */
165
+ function readParentEventsFromLogs(): ParentSpeedClaimsEvent[] {
166
+ const command = fileURLToPath(new URL('../../bin/elanous.mjs', import.meta.url));
167
+ const events: ParentSpeedClaimsEvent[] = [];
168
+ let before: string | undefined;
169
+ const seenCursors = new Set<string>();
170
+ for (;;) {
171
+ const result = spawnSync(process.execPath, [command, `--test=${fileURLToPath(new URL('../../', import.meta.url))}`, 'logs', '--instance', 'prod', '--category', 'self-dev.orchestrate', '--json', '--json-data', '--limit', '5000', ...(before ? ['--before', before] : [])], {
172
+ encoding: 'utf8', maxBuffer: 64 * 1024 * 1024,
173
+ });
174
+ if (result.error || result.status !== 0) throw new Error(result.stderr || result.error?.message || 'parent logs query failed');
175
+ const lines = result.stdout.trim().split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
176
+ if (lines.some((line) => object(line._meta)?.type === 'log-query-unreadable-instances')) throw new Error('parent logs query incomplete');
177
+ events.push(...lines.filter((line): line is Record<string, unknown> & ParentSpeedClaimsEvent =>
178
+ line.store === 'prod' && line.category === 'self-dev.orchestrate' && typeof line.ts === 'string' && typeof line.event === 'string'));
179
+ const limit = lines.find((line) => object(line._meta)?.type === 'log-query-limit');
180
+ if (!limit) return events;
181
+ const meta = object(limit._meta);
182
+ const cursor = meta?.nextCursors ?? meta?.nextCursor;
183
+ if (typeof cursor !== 'number' && !object(cursor)) throw new Error('parent logs pagination incomplete');
184
+ const next = typeof cursor === 'number' ? String(cursor) : JSON.stringify(cursor);
185
+ if (seenCursors.has(next)) throw new Error('parent logs pagination repeated a cursor');
186
+ seenCursors.add(next);
187
+ before = next;
188
+ }
189
+ }
190
+
191
+ function readParentQueueFromState(): unknown {
192
+ return JSON.parse(readFileSync(join(prodInstanceRoot(), 'harness', 'queue.json'), 'utf8')) as unknown;
193
+ }
194
+
195
+ /** Parent launch-to-done census; no child ledger id is used as a join key. */
196
+ export function measureParentSpeedClaims(
197
+ events: readonly ParentSpeedClaimsEvent[], queue?: unknown,
198
+ ): ParentSpeedClaimsMeasurement {
199
+ const result: ParentSpeedClaimsMeasurement = {
200
+ scope: 'self-dev.orchestrate-parent-logs', status: 'measured', completedCount: 0,
201
+ inProgressCount: 0, cannotMeasureCount: 0, rows: [], note: PARENT_NOTE,
202
+ };
203
+ const queueRows = Array.isArray(queue) ? queue : object(queue)?.items;
204
+ const queueByRun = new Map<string, ParentSeat | null>();
205
+ if (Array.isArray(queueRows)) for (const raw of queueRows) {
206
+ const entry = object(raw);
207
+ if (entry && typeof entry.runId === 'string' && PARENT_SEATS.includes(entry.seat as ParentSeat) && entry.seat !== 'direct') {
208
+ const seat = entry.seat as ParentSeat;
209
+ queueByRun.set(entry.runId, queueByRun.has(entry.runId) && queueByRun.get(entry.runId) !== seat ? null : seat);
210
+ }
211
+ }
212
+ const runs = new Map<string, { start?: number; end?: number; outcomes?: unknown }>();
213
+ for (const event of events) {
214
+ if (event.category !== 'self-dev.orchestrate' || (event.event !== 'start' && event.event !== 'done')) continue;
215
+ const data = object(event.data);
216
+ const runId = data?.runId;
217
+ if (typeof runId !== 'string' || !CANONICAL_RUN_ID.test(runId)) { result.cannotMeasureCount++; continue; }
218
+ const run = runs.get(runId) ?? {};
219
+ const at = parseTime(event.ts);
220
+ if (event.event === 'start') {
221
+ if (at !== null) run.start = run.start === undefined || !Number.isFinite(run.start) ? at : Math.min(run.start, at);
222
+ else if (run.start === undefined) run.start = NaN;
223
+ } else if (at !== null && (run.end === undefined || !Number.isFinite(run.end) || at > run.end)) {
224
+ run.end = at;
225
+ run.outcomes = data?.outcomes;
226
+ } else if (at === null && run.end === undefined) {
227
+ run.end = NaN;
228
+ }
229
+ runs.set(runId, run);
230
+ }
231
+ const groups = new Map<string, { seat: ParentSeat; stage: string; merged: boolean; values: number[] }>();
232
+ for (const [runId, run] of runs) {
233
+ if (run.start === undefined && run.end !== undefined) { result.cannotMeasureCount++; continue; }
234
+ if (run.end === undefined) { result.inProgressCount++; continue; }
235
+ if (run.start === undefined || !Number.isFinite(run.start) || !Number.isFinite(run.end) || run.end < run.start || !Array.isArray(run.outcomes) || run.outcomes.length === 0) {
236
+ result.cannotMeasureCount++;
237
+ continue;
238
+ }
239
+ const seat = queueByRun.has(runId) ? (queueByRun.get(runId) ?? 'unknown') : Array.isArray(queueRows) ? 'direct' : 'unknown';
240
+ const outcomes = run.outcomes.map(object);
241
+ if (outcomes.some((outcome) => !outcome)) {
242
+ result.cannotMeasureCount++;
243
+ continue;
244
+ }
245
+ for (const outcome of outcomes) {
246
+ const stage = typeof outcome!.stage === 'string' ? outcome!.stage : 'unknown';
247
+ const merged = outcome!.merged === true;
248
+ const key = JSON.stringify([seat, stage, merged]);
249
+ const group = groups.get(key) ?? { seat, stage, merged, values: [] };
250
+ group.values.push((run.end - run.start!) / 60_000);
251
+ groups.set(key, group);
252
+ }
253
+ result.completedCount++;
254
+ }
255
+ result.rows = [...groups.values()].map(({ seat, stage, merged, values }) => {
256
+ values.sort((a, b) => a - b);
257
+ return { seat, stage, merged, count: values.length, medianMinutes: percentile(values, 0.5), q1Minutes: percentile(values, 0.25), q3Minutes: percentile(values, 0.75) };
258
+ }).sort((a, b) => PARENT_SEATS.indexOf(a.seat) - PARENT_SEATS.indexOf(b.seat) || a.stage.localeCompare(b.stage));
259
+ if (result.cannotMeasureCount > 0) result.status = 'cannot-measure';
260
+ return result;
261
+ }
262
+
263
+ /** Read-only elapsed-time census of canonical local run ledgers, including unsuccessful runs. */
264
+ export function measureSpeedClaims(options: SpeedClaimsOptions = {}): SpeedClaimsMeasurement {
265
+ const ledgerDirectory = resolve(options.dir ?? runLedgerDir());
266
+ let parent: ParentSpeedClaimsMeasurement;
267
+ try {
268
+ const events = (options.readParentEvents ?? readParentEventsFromLogs)();
269
+ let queue: unknown;
270
+ try { queue = (options.readParentQueue ?? readParentQueueFromState)(); } catch { /* Queue absence does not erase parent observations or establish a direct launch. */ }
271
+ parent = measureParentSpeedClaims(events, queue);
272
+ } catch {
273
+ parent = { ...measureParentSpeedClaims([]), status: 'cannot-measure' };
274
+ }
275
+ const result: SpeedClaimsMeasurement = {
276
+ scope: 'self-implement-run-ledger', ledgerDirectory, status: 'measured',
277
+ completedCount: 0, inProgressCount: 0, cannotMeasureCount: 0, unknownSeatCount: 0, rows: [], note: NOTE, parent,
278
+ };
279
+ let files: string[];
280
+ try { files = (options.list ?? readdirSync)(ledgerDirectory); }
281
+ catch { return { ...result, status: 'cannot-measure' }; }
282
+
283
+ const groups = new Map<string, { seat: SpeedClaimsSeat; outcome: SpeedClaimsOutcome; values: number[] }>();
284
+ for (const file of files.sort()) {
285
+ const runId = file.endsWith('.jsonl') ? file.slice(0, -'.jsonl'.length) : '';
286
+ if (!CANONICAL_RUN_ID.test(runId)) continue;
287
+ try {
288
+ const entries = loadRunLedger(runId, ledgerDirectory, options.read);
289
+ if (!entries || entries.length === 0) { result.cannotMeasureCount++; continue; }
290
+ const end = endOfRun(entries);
291
+ const first = time(entries[0]);
292
+ const createdAt = checkpointFor(runId, ledgerDirectory)?.createdAt;
293
+ const creation = typeof createdAt === 'number' && Number.isFinite(createdAt) && createdAt >= 0 ? createdAt : null;
294
+ const start = first === null ? creation : first;
295
+ const endTime = end ? time(end.entry) : null;
296
+ if (start === null || (end && (endTime === null || endTime < start))) {
297
+ result.cannotMeasureCount++;
298
+ continue;
299
+ }
300
+ if (!end) { result.inProgressCount++; continue; }
301
+ const seat = (options.readSeat ?? seatFromLaunch)(runId, ledgerDirectory, entries);
302
+ const safeSeat = SEATS.includes(seat) ? seat : 'unknown';
303
+ if (safeSeat === 'unknown') result.unknownSeatCount++;
304
+ const key = `${safeSeat}:${end.outcome}`;
305
+ const group = groups.get(key) ?? { seat: safeSeat, outcome: end.outcome, values: [] };
306
+ group.values.push((endTime! - start) / 60_000);
307
+ groups.set(key, group);
308
+ result.completedCount++;
309
+ } catch { result.cannotMeasureCount++; }
310
+ }
311
+ result.rows = [...groups.values()].map(({ seat, outcome, values }) => {
312
+ values.sort((a, b) => a - b);
313
+ return { seat, outcome, count: values.length, underOneMinuteCount: values.filter((minutes) => minutes < 1).length, medianMinutes: percentile(values, 0.5), q1Minutes: percentile(values, 0.25), q3Minutes: percentile(values, 0.75) };
314
+ }).sort((a, b) => SEATS.indexOf(a.seat) - SEATS.indexOf(b.seat) || a.outcome.localeCompare(b.outcome));
315
+ if (result.cannotMeasureCount > 0) result.status = 'cannot-measure';
316
+ return result;
317
+ }
318
+
319
+ export function renderSpeedClaims(result: SpeedClaimsMeasurement): string {
320
+ return [
321
+ 'child run-ledger (child start → child end):',
322
+ `ledger directory: ${result.ledgerDirectory}`,
323
+ `status: ${result.status}`,
324
+ `finished (quartile denominator): ${result.completedCount}`,
325
+ `in progress (excluded): ${result.inProgressCount}`,
326
+ `cannot measure (excluded): ${result.cannotMeasureCount}`,
327
+ `unknown seat (finished): ${result.unknownSeatCount}`,
328
+ ...result.rows.map((row) => `seat=${row.seat} outcome=${row.outcome} count=${row.count} underOneMinute=${row.underOneMinuteCount} median=${row.medianMinutes}m q1=${row.q1Minutes}m q3=${row.q3Minutes}m`),
329
+ `note: ${result.note}`,
330
+ 'parent launch → done (self-dev.orchestrate):',
331
+ `parent status: ${result.parent.status}`,
332
+ `parent finished (quartile denominator): ${result.parent.completedCount}`,
333
+ `parent in progress (excluded): ${result.parent.inProgressCount}`,
334
+ `parent cannot measure (excluded): ${result.parent.cannotMeasureCount}`,
335
+ ...result.parent.rows.map((row) => `seat=${row.seat} stage=${row.stage} merged=${row.merged} count=${row.count} median=${row.medianMinutes}m q1=${row.q1Minutes}m q3=${row.q3Minutes}m`),
336
+ `parent note: ${result.parent.note}`,
337
+ ].join('\n');
338
+ }
@@ -213,6 +213,8 @@ export interface SessionMeta {
213
213
  tgBotId?: string;
214
214
  /** Stable identity for a persona's resident conversation. */
215
215
  personaId?: string;
216
+ /** Optional persona selected for this conversation's replies (not resident identity). */
217
+ chatPersonaId?: string;
216
218
  /** Telegram session-key used for lookup by chat, e.g.
217
219
  * `telegram:8799226199:dm:123:0` (bot-scoped) or legacy
218
220
  * `telegram:dm:123:0`. Constructed once at create time. */