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
@@ -0,0 +1,173 @@
1
+ import { existsSync, readdirSync, statSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { listCoordEvents } from '../../context-bus/coord-events.js';
4
+ import { CardStore, cardIndexPath, type TaskCard } from '../../task-cards/card-store.js';
5
+ import { loadRunLedger, resolveFederatedRunLedgerDirectories, type RunLedgerEntry } from '../../self-implement/run-ledger.js';
6
+ import { listLoops } from '../../loops/registry.js';
7
+ import { listSubSeatCharters } from './sub-seat-charters.js';
8
+ import { SEATS } from './ops-seats.js';
9
+ import { jsonResponse } from './json-response.js';
10
+
11
+ export interface LoopEdge {
12
+ at: string;
13
+ kind: 'request' | 'decision' | 'report' | 'card' | 'run';
14
+ from: string;
15
+ to: string;
16
+ ref: string;
17
+ }
18
+
19
+ export interface LoopEdgesDeps {
20
+ listCoord?: typeof listCoordEvents;
21
+ cardRoot?: string;
22
+ ledgerDir?: string;
23
+ listCards?: () => TaskCard[];
24
+ listRunStarts?: (since: string) => Array<{ runId: string; at: string; seat: string }>;
25
+ listLoopOwners?: () => Array<{ id: string; owner?: string | null }>;
26
+ seatIds?: () => string[];
27
+ }
28
+
29
+ const ID = /^[a-zA-Z0-9_-]{1,128}$/;
30
+ const SURFACES = new Set(['telegram', 'pwa', 'linear', 'tui', 'discord', 'api', 'cli', 'voice', 'folder', 'wish']);
31
+ const KINDS: Record<string, LoopEdge['kind']> = { '요청': 'request', '결정': 'decision', '보고': 'report' };
32
+
33
+ function seat(value: unknown, seats: ReadonlySet<string>): value is string {
34
+ return typeof value === 'string' && seats.has(value);
35
+ }
36
+ function surface(value: unknown): value is string {
37
+ return typeof value === 'string' && SURFACES.has(value);
38
+ }
39
+ function object(value: unknown): Record<string, unknown> | null {
40
+ return value && typeof value === 'object' && !Array.isArray(value) ? value as Record<string, unknown> : null;
41
+ }
42
+ function parse(content: string): Record<string, unknown> | null {
43
+ try { return object(JSON.parse(content) as unknown); } catch { return null; }
44
+ }
45
+ function inWindow(at: string, since: number, now: number): boolean {
46
+ const ms = Date.parse(at);
47
+ return Number.isFinite(ms) && ms >= since && ms <= now;
48
+ }
49
+
50
+ function cardEdges(card: TaskCard, since: number, now: number, seats: ReadonlySet<string>): LoopEdge[] {
51
+ if (!card.goalId.startsWith('wish:') || !ID.test(card.id)) return [];
52
+ const node = `card:${card.id}`;
53
+ const ref = card.id;
54
+ const edges: LoopEdge[] = [];
55
+ const intake = card.sections.find(s => s.key === 'intake:wish:0');
56
+ const intakeData = intake && parse(intake.content);
57
+ if (intake && surface(intakeData?.source) && inWindow(intake.createdAt, since, now)) {
58
+ edges.push({ at: intake.createdAt, kind: 'card', from: `surface:${intakeData.source}`, to: node, ref });
59
+ }
60
+ const split = card.sections.find(s => s.key === 'flow:split:0');
61
+ const splitData = split && parse(split.content);
62
+ const cells = Array.isArray(splitData?.cells) ? splitData.cells : [];
63
+ const owners = new Map<string, string>();
64
+ for (const item of cells) {
65
+ const cell = object(item);
66
+ if (typeof cell?.id === 'string' && ID.test(cell.id) && seat(cell.owner, seats)) owners.set(cell.id, cell.owner);
67
+ }
68
+ const placedOwners = new Set<string>();
69
+ for (const section of card.sections) {
70
+ if (!section.key.startsWith('flow:placed:')) continue;
71
+ const cellId = section.key.slice('flow:placed:'.length);
72
+ const saved = parse(section.content);
73
+ const placement = object(saved?.decision);
74
+ const owner = owners.get(cellId);
75
+ if (!owner || !placement || placement.id !== cellId || !ID.test(cellId)
76
+ || typeof placement.version !== 'string' || !/^\d+\.\d+\.\d+$/.test(placement.version)) continue;
77
+ placedOwners.add(owner);
78
+ if (inWindow(section.createdAt, since, now)) {
79
+ const cellNode = `loop:${cellId}`;
80
+ edges.push({ at: section.createdAt, kind: 'card', from: node, to: cellNode, ref });
81
+ edges.push({ at: section.createdAt, kind: 'card', from: cellNode, to: owner, ref });
82
+ }
83
+ }
84
+ const reply = card.sections.find(s => s.key === 'intake:reply:0');
85
+ const replyData = reply && parse(reply.content);
86
+ // The destination section alone is not a send; flow:result records a completed card flow.
87
+ const result = card.sections.find(s => s.key === 'flow:result:0');
88
+ if (result && reply && surface(replyData?.surface) && inWindow(result.createdAt, since, now)) {
89
+ for (const owner of placedOwners) {
90
+ edges.push({ at: result.createdAt, kind: 'card', from: owner, to: `surface:${replyData.surface}`, ref });
91
+ }
92
+ }
93
+ return edges;
94
+ }
95
+
96
+ function runStarts(since: string, dir: string, owners: ReadonlyMap<string, string>, seats: ReadonlySet<string>): Array<{ runId: string; at: string; seat: string }> {
97
+ if (!existsSync(dir)) return [];
98
+ const result: Array<{ runId: string; at: string; seat: string }> = [];
99
+ let names: string[];
100
+ try { names = readdirSync(dir); } catch { return []; }
101
+ for (const name of names) {
102
+ if (!/^run-[a-zA-Z0-9-]+\.jsonl$/.test(name)) continue;
103
+ const path = join(dir, name);
104
+ try { if (!statSync(path).isFile() || statSync(path).size > 5 * 1024 * 1024) continue; }
105
+ catch { continue; }
106
+ const runId = name.slice(0, -6);
107
+ let entries: RunLedgerEntry[] | null;
108
+ try { entries = loadRunLedger(runId, dir); } catch { continue; }
109
+ const start = entries?.find(entry => entry.event === 'start' && entry.runId === runId);
110
+ const feature = typeof start?.data.feature === 'string' ? start.data.feature : '';
111
+ const launchSeat = start?.data.launchSeat ?? start?.data.seat ?? owners.get(feature)
112
+ ?? start?.data.originAgent ?? start?.data.controller;
113
+ if (start?.timestamp && start.timestamp >= since && seat(launchSeat, seats)) {
114
+ result.push({ runId, at: start.timestamp, seat: launchSeat });
115
+ }
116
+ }
117
+ return result;
118
+ }
119
+
120
+ /** GET only; project an allowlist of fields from the three ledgers. Never serialize ledger records. */
121
+ export function handleLoopEdgesGet(req: Request, deps: LoopEdgesDeps = {}): Response {
122
+ const params = new URL(req.url).searchParams;
123
+ const rawSince = params.get('since');
124
+ const since = rawSince === null ? Date.now() - 60 * 60 * 1000 : Date.parse(rawSince);
125
+ const rawLimit = params.get('limit');
126
+ const limit = rawLimit === null ? 100 : Number(rawLimit);
127
+ const now = Date.now();
128
+ if (rawSince !== null && !/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d+)?(?:Z|[+-]\d\d:\d\d)$/.test(rawSince)
129
+ || !Number.isFinite(since) || since > now || !Number.isSafeInteger(limit) || limit < 1 || limit > 500) {
130
+ return jsonResponse({ error: 'usage: GET /v1/loops/edges?since=<ISO>&limit=<1..500>' }, 400);
131
+ }
132
+ const sinceIso = new Date(since).toISOString();
133
+ const seats = new Set((deps.seatIds ?? (() => [...SEATS.map(s => s.seat), ...listSubSeatCharters().map(s => s.id)]))());
134
+ const edges: LoopEdge[] = [];
135
+ for (const event of (deps.listCoord ?? listCoordEvents)({ since: sinceIso, channelOnly: true })) {
136
+ if (!seat(event.refs.seat, seats) || !inWindow(event.at, since, now)) continue;
137
+ const kind = KINDS[event.refs.kind ?? ''];
138
+ if (!kind || !Array.isArray(event.refs.recipients)) continue;
139
+ const ref = typeof event.refs.url === 'string' && /^https:\/\/github\.com\/[\w.-]+\/[\w.-]+\/(?:pull|issues)\/\d+#issuecomment-\d+$/.test(event.refs.url)
140
+ ? event.refs.url : event.id;
141
+ if (!ID.test(ref) && !ref.startsWith('https://github.com/')) continue;
142
+ for (const recipient of new Set(event.refs.recipients)) {
143
+ if (seat(recipient, seats)) edges.push({ at: event.at, kind, from: event.refs.seat, to: recipient, ref });
144
+ }
145
+ }
146
+ if (deps.listCards) {
147
+ for (const card of deps.listCards()) edges.push(...cardEdges(card, since, now, seats));
148
+ } else {
149
+ const root = deps.cardRoot;
150
+ if (existsSync(cardIndexPath(root))) {
151
+ const store = new CardStore(root, true);
152
+ try { for (const card of store.listCards()) edges.push(...cardEdges(card, since, now, seats)); }
153
+ finally { store.close(); }
154
+ }
155
+ }
156
+ const readStarts = deps.listRunStarts ?? ((from: string) => {
157
+ const owners = new Map<string, string>();
158
+ try {
159
+ for (const entry of (deps.listLoopOwners ?? (() => listLoops()))()) {
160
+ if (seat(entry.owner, seats)) owners.set(entry.id, entry.owner);
161
+ }
162
+ } catch { /* A registry outage does not erase explicit launch-seat evidence. */ }
163
+ return (deps.ledgerDir ? [deps.ledgerDir] : resolveFederatedRunLedgerDirectories({ includeTest: false }))
164
+ .flatMap(dir => runStarts(from, dir, owners, seats));
165
+ });
166
+ for (const run of readStarts(sinceIso)) {
167
+ if (seat(run.seat, seats) && ID.test(run.runId) && inWindow(run.at, since, now)) {
168
+ edges.push({ at: run.at, kind: 'run', from: run.seat, to: `loop:${run.runId}`, ref: run.runId });
169
+ }
170
+ }
171
+ edges.sort((a, b) => b.at.localeCompare(a.at) || a.kind.localeCompare(b.kind) || a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
172
+ return jsonResponse({ edges: edges.slice(0, limit) });
173
+ }
@@ -53,7 +53,7 @@ export function startApprovalGate(input: ApprovalGateInput, deps: ApprovalGateDe
53
53
  void Promise.resolve().then(() => (deps.runHostRegate ?? runHostRegate)({
54
54
  prNumber: input.prNumber, headCommit: input.headSha, repoRoot: input.repoRoot, verifyOnly: true,
55
55
  })).then(async (result) => {
56
- let status = result.status ?? (result.passed ? 'passed' : 'failed');
56
+ let status: ApprovalGateStatus = result.status === 'frozen' ? 'unmeasured' : result.status ?? (result.passed ? 'passed' : 'failed');
57
57
  let failures = result.failures.map(({ step, detail }) => `${step} — ${detail.replace(/\s+/g, ' ')}`.slice(0, 800));
58
58
  if (status === 'passed') {
59
59
  const current = await (deps.readPr ?? readPr)(input);
@@ -15,6 +15,7 @@ import { tasksDbPath } from '../../task-orchestrator/paths.js';
15
15
  import { jsonResponse } from './http-server.js';
16
16
  import { checkAuth, type MetaApiOpts } from './meta-api.js';
17
17
  import { listScheduleRuns } from '../../domains/schedule-runs.js';
18
+ import { listLoops } from '../../loops/registry.js';
18
19
 
19
20
  export interface SchedulesReadDeps {
20
21
  rows?: () => ScheduleRow[];
@@ -24,6 +25,7 @@ export interface SchedulesReadDeps {
24
25
  logsPath?: () => string;
25
26
  /** 발화 이력 원천(`schedule_runs`) — 기본은 schedules.db 를 읽기 전용으로 연다. */
26
27
  schedulesDbPath?: () => string;
28
+ loopOwners?: () => Array<{ id: string; title: string; owner?: string | null; enabled: boolean; lastRun: { at: string; status: string } | null; jobs: Array<{ id: string }> }>;
27
29
  now?: () => Date;
28
30
  }
29
31
 
@@ -193,10 +195,25 @@ function inventory(deps: SchedulesReadDeps): { cards: ScheduleCard[]; names: Map
193
195
  return { cards, names };
194
196
  }
195
197
 
198
+ export function readSchedulesInventory(deps: SchedulesReadDeps = {}): ScheduleCard[] {
199
+ return inventory(deps).cards;
200
+ }
201
+
196
202
  export function handleSchedulesList(req: Request, opts: MetaApiOpts, deps: SchedulesReadDeps = {}): Response {
197
203
  if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
198
- const { cards } = inventory(deps);
204
+ const cards = readSchedulesInventory(deps);
199
205
  const schedules = new URL(req.url).searchParams.get('includeOff') === '1' ? cards : cards.filter(c => c.state !== 'off');
206
+ if (new URL(req.url).searchParams.get('includeOwners') === '1') {
207
+ try {
208
+ const owners = (deps.loopOwners ?? (() => listLoops({ schedules: registryRows() })))().map(loop => ({ id: loop.id, title: redactSecretText(loop.title), owner: loop.owner ?? null,
209
+ enabled: loop.enabled, lastRun: loop.lastRun ? { at: loop.lastRun.at, status: loop.lastRun.status } : null,
210
+ jobs: loop.jobs.map(job => job.id) }));
211
+ return jsonResponse({ schedules, count: schedules.length, owners }, 200);
212
+ } catch {
213
+ // The ordinary inventory stays readable; an opted-in map must not mistake an outage for unowned loops.
214
+ return jsonResponse({ schedules, count: schedules.length, owners: null }, 200);
215
+ }
216
+ }
200
217
  return jsonResponse({ schedules, count: schedules.length }, 200);
201
218
  }
202
219
 
@@ -6,6 +6,7 @@ import { resolveAttachmentPath } from '../../boot/attachment-store.js';
6
6
  import { runGh } from '../../decisions/decision-cards.js';
7
7
  import { openMsgStore, canonicalSeatId } from '../../msg/msg-store.js';
8
8
  import { dispatchCeoTask, type CeoCommandDeps } from '../../seat-dispatch/ceo-commands.js';
9
+ import { appendSeatRequestRows, withSeatRequestLedgerLock } from '../../seat-dispatch/seat-request-ledger.js';
9
10
  import { acknowledgeSeatAnswers, askSeat, deliverSeatAnswers, parseSeatAsk, type SeatAskDeps } from '../../seat-dispatch/seat-ask.js';
10
11
  import { getUserConfig } from '../../user-config.js';
11
12
  import { effectiveInstanceRoot } from '../../instance/resolve.js';
@@ -36,10 +37,12 @@ interface PendingRequest {
36
37
  queuedAt: string;
37
38
  ref: string;
38
39
  attachments?: string[];
40
+ reason?: string;
41
+ closedAt?: string;
39
42
  }
40
43
 
41
44
  interface RejectedRequest extends Omit<PendingRequest, 'status'> {
42
- status: 'rejected';
45
+ status: 'rejected' | 'done';
43
46
  }
44
47
 
45
48
  type RecordEntry = (SeatRequestItem & { key: string }) | PendingRequest | RejectedRequest;
@@ -73,19 +76,7 @@ function file(root: string): string {
73
76
  }
74
77
 
75
78
  function append(path: string, entry: RecordEntry): void {
76
- mkdirSync(dirname(path), { recursive: true });
77
- // Atomically replace the journal so a failed write cannot leave a truncated JSON line.
78
- const previous = existsSync(path) ? readFileSync(path, 'utf8') : '';
79
- const temp = `${path}.${randomUUID()}.tmp`;
80
- try {
81
- writeFileSync(temp, `${previous}${JSON.stringify(entry)}\n`);
82
- const fd = openSync(temp, 'r');
83
- try { fsyncSync(fd); } finally { closeSync(fd); }
84
- renameSync(temp, path);
85
- } catch (error) {
86
- try { unlinkSync(temp); } catch { /* no temp file to remove */ }
87
- throw error;
88
- }
79
+ appendSeatRequestRows(path, [entry]);
89
80
  }
90
81
 
91
82
  function records(path: string): Map<string, RecordEntry> {
@@ -100,7 +91,7 @@ function records(path: string): Map<string, RecordEntry> {
100
91
  if (!line) continue;
101
92
  const entry = JSON.parse(line) as RecordEntry;
102
93
  if (typeof entry.key !== 'string' || typeof entry.seat !== 'string' || typeof entry.text !== 'string'
103
- || typeof entry.queuedAt !== 'string' || !['pending', 'queued', 'rejected'].includes(entry.status)
94
+ || typeof entry.queuedAt !== 'string' || !['pending', 'queued', 'rejected', 'done'].includes(entry.status)
104
95
  || (entry.status === 'queued' && typeof entry.receiptId !== 'string')
105
96
  || (entry.status !== 'queued' && typeof entry.ref !== 'string')) {
106
97
  throw new Error('invalid seat request journal');
@@ -121,7 +112,7 @@ function records(path: string): Map<string, RecordEntry> {
121
112
  throw new Error('invalid seat request outcome');
122
113
  }
123
114
  const current = entries.get(item.key);
124
- if (current?.status === 'queued') continue;
115
+ if (current?.status === 'queued' || current?.status === 'done' || current?.closedAt) continue;
125
116
  if (current?.status === 'pending') {
126
117
  if (item.status === 'rejected' && current.ref !== item.ref) continue;
127
118
  if (item.status === 'queued' && current.queuedAt !== item.queuedAt) continue;
@@ -135,27 +126,32 @@ function outcomeFile(path: string, key: string): string {
135
126
  return join(dirname(path), 'outcomes', `${key}.json`);
136
127
  }
137
128
 
138
- function persistOutcome(path: string, entry: RecordEntry, write: typeof append): void {
139
- try {
140
- write(path, entry);
141
- const sidecar = outcomeFile(path, entry.key);
142
- if (existsSync(sidecar)) unlinkSync(sidecar);
143
- } catch {
144
- // The primary journal may be unavailable while the outcome is already committed
145
- // to the intake queue. Keep a durable sidecar rather than a process-local receipt.
146
- const target = outcomeFile(path, entry.key);
147
- mkdirSync(dirname(target), { recursive: true });
148
- const temp = `${target}.${randomUUID()}.tmp`;
129
+ function persistOutcome(path: string, entry: RecordEntry, write: typeof append): RecordEntry {
130
+ return withSeatRequestLedgerLock(path, () => {
131
+ const current = records(path).get(entry.key);
132
+ if (current && ('closedAt' in current || current.status === 'done')) return current;
133
+ if (current?.status === 'queued') return current;
149
134
  try {
150
- writeFileSync(temp, JSON.stringify(entry));
151
- const fd = openSync(temp, 'r');
152
- try { fsyncSync(fd); } finally { closeSync(fd); }
153
- renameSync(temp, target);
154
- } catch (error) {
155
- try { unlinkSync(temp); } catch { /* no temp file */ }
156
- throw error;
135
+ write(path, entry);
136
+ const sidecar = outcomeFile(path, entry.key);
137
+ if (existsSync(sidecar)) unlinkSync(sidecar);
138
+ } catch {
139
+ // A committed external delivery still needs a durable outcome if the journal write failed.
140
+ const target = outcomeFile(path, entry.key);
141
+ mkdirSync(dirname(target), { recursive: true });
142
+ const temp = `${target}.${randomUUID()}.tmp`;
143
+ try {
144
+ writeFileSync(temp, JSON.stringify(entry));
145
+ const fd = openSync(temp, 'r');
146
+ try { fsyncSync(fd); } finally { closeSync(fd); }
147
+ renameSync(temp, target);
148
+ } catch (error) {
149
+ try { unlinkSync(temp); } catch { /* no temp file */ }
150
+ throw error;
151
+ }
157
152
  }
158
- }
153
+ return entry;
154
+ });
159
155
  }
160
156
 
161
157
  function receipt(item: SeatRequestItem): Response {
@@ -298,13 +294,18 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
298
294
  const gate = new Promise<void>((resolve) => { start = resolve; });
299
295
  const task = (async (): Promise<Response> => {
300
296
  await gate;
301
- let existing: RecordEntry | undefined;
302
- try { existing = records(path).get(key); }
297
+ // A settled request answers from the journal before attachments are resolved — a retry
298
+ // must return its receipt even when the uploaded file is gone. The locked re-check below still guards races.
299
+ let settled: RecordEntry | undefined;
300
+ try { settled = records(path).get(key); }
303
301
  catch { return unavailable(); }
304
- if (existing) {
305
- if (existing.seat !== seat.id || existing.text !== text
306
- || JSON.stringify(existing.attachments ?? []) !== JSON.stringify(attachmentIds)) return jsonResponse({ error: 'idempotency-conflict' }, 409);
307
- if (existing.status === 'queued') return receipt(existing);
302
+ if (settled) {
303
+ if (settled.seat !== seat.id || settled.text !== text
304
+ || JSON.stringify(settled.attachments ?? []) !== JSON.stringify(attachmentIds)) return jsonResponse({ error: 'idempotency-conflict' }, 409);
305
+ if ('closedAt' in settled || settled.status === 'done') {
306
+ return jsonResponse({ error: 'request-closed', status: settled.status, reason: 'reason' in settled ? settled.reason : undefined }, 409);
307
+ }
308
+ if (settled.status === 'queued') return receipt(settled);
308
309
  }
309
310
  const attachments: Array<{ name: string; path: string }> = [];
310
311
  for (const id of attachmentIds) {
@@ -316,18 +317,36 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
316
317
  const metadata = (input.attachments as Array<{ name?: unknown }>)[attachments.length];
317
318
  attachments.push({ name: typeof metadata?.name === 'string' && metadata.name.trim() ? metadata.name : basename(resolved), path: resolved });
318
319
  }
319
- const intent: PendingRequest = existing?.status === 'pending'
320
- ? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}`,
321
- ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
322
- if (existing?.status !== 'pending') {
323
- try {
324
- (deps.append ?? append)(path, intent);
325
- if (existing?.status === 'rejected') {
326
- const sidecar = outcomeFile(path, key);
327
- if (existsSync(sidecar)) unlinkSync(sidecar);
320
+ let initial: { intent?: PendingRequest; response?: Response };
321
+ try {
322
+ initial = withSeatRequestLedgerLock(path, () => {
323
+ const existing = records(path).get(key);
324
+ if (existing) {
325
+ if (existing.seat !== seat.id || existing.text !== text
326
+ || JSON.stringify(existing.attachments ?? []) !== JSON.stringify(attachmentIds)) {
327
+ return { response: jsonResponse({ error: 'idempotency-conflict' }, 409) };
328
+ }
329
+ if ('closedAt' in existing || existing.status === 'done') {
330
+ return { response: jsonResponse({ error: 'request-closed', status: existing.status,
331
+ reason: 'reason' in existing ? existing.reason : undefined }, 409) };
332
+ }
333
+ if (existing.status === 'queued') return { response: receipt(existing) };
328
334
  }
329
- } catch { return unavailable(); }
330
- }
335
+ const intent: PendingRequest = existing?.status === 'pending'
336
+ ? existing : { key, seat: seat.id, text, queuedAt: now, status: 'pending', ref: `pwa:${randomUUID()}`,
337
+ ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
338
+ if (existing?.status !== 'pending') {
339
+ (deps.append ?? append)(path, intent);
340
+ if (existing?.status === 'rejected') {
341
+ const sidecar = outcomeFile(path, key);
342
+ if (existsSync(sidecar)) unlinkSync(sidecar);
343
+ }
344
+ }
345
+ return { intent };
346
+ });
347
+ } catch { return unavailable(); }
348
+ if (initial.response) return initial.response;
349
+ const intent = initial.intent!;
331
350
  const ceoSeat = canonicalSeatId(seat.id);
332
351
  if (['OP', 'TC', 'MK', 'UX'].includes(ceoSeat)) {
333
352
  let result: Awaited<ReturnType<typeof dispatchCeoTask>>;
@@ -342,10 +361,12 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
342
361
  ...(attachmentIds.length ? { attachments: attachmentIds } : {}), channel: result.channel,
343
362
  ...(result.channelError ? { channelError: result.channelError } : {}),
344
363
  };
345
- try { persistOutcome(path, item, deps.append ?? append); }
364
+ let persisted: RecordEntry;
365
+ try { persisted = persistOutcome(path, item, deps.append ?? append); }
346
366
  catch { return unavailable(); }
367
+ if (persisted.status !== 'queued') return jsonResponse({ error: 'request-closed', status: persisted.status, reason: persisted.reason }, 409);
347
368
  publishInsideEvent({ kind: 'seat', seat: item.seat, receiptId: item.receiptId, status: item.status, ts: item.queuedAt });
348
- return receipt(item);
369
+ return receipt(persisted);
349
370
  }
350
371
  let result: Awaited<ReturnType<typeof submitIntakeWork>>;
351
372
  try {
@@ -363,11 +384,13 @@ export async function handleSeatRequests(req: Request, deps: SeatRequestsDeps =
363
384
  }
364
385
  const item: RecordEntry & SeatRequestItem = { key, receiptId: result.acceptanceId, seat: seat.id, text, queuedAt: intent.queuedAt,
365
386
  status: 'queued', ...(attachmentIds.length ? { attachments: attachmentIds } : {}) };
366
- try { persistOutcome(path, item, deps.append ?? append); }
387
+ let persisted: RecordEntry;
388
+ try { persisted = persistOutcome(path, item, deps.append ?? append); }
367
389
  catch { return unavailable(); }
390
+ if (persisted.status !== 'queued') return jsonResponse({ error: 'request-closed', status: persisted.status, reason: persisted.reason }, 409);
368
391
  debug.log('seat-address.pwa', 'enqueued', { seat: seat.id, receiptId: item.receiptId });
369
392
  publishInsideEvent({ kind: 'seat', seat: item.seat, receiptId: item.receiptId, status: item.status, ts: item.queuedAt });
370
- return receipt(item);
393
+ return receipt(persisted);
371
394
  })();
372
395
  inFlight.set(lock, { payload, task });
373
396
  start();
@@ -1,4 +1,8 @@
1
- import { CardStore } from '../../task-cards/card-store.js';
1
+ import { Database } from 'bun:sqlite';
2
+ import { existsSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { CardStore, type TaskCard } from '../../task-cards/card-store.js';
5
+ import { releaseLedgerRoot } from '../../instance/resolve.js';
2
6
  import { createWishCard } from '../../intake-plane/wish-card.js';
3
7
  import { jsonResponse } from './json-response.js';
4
8
 
@@ -7,19 +11,53 @@ export async function handleTaskCardsWishPost(req: Request, root?: string): Prom
7
11
  try { body = await req.json(); }
8
12
  catch { return jsonResponse({ error: 'bad_request' }, 400); }
9
13
  if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
10
- const { text, ref } = body as Record<string, unknown>;
11
- if (typeof text !== 'string' || !text.trim() || typeof ref !== 'string' || !ref.trim()) {
14
+ const { text, ref, sessionId } = body as Record<string, unknown>;
15
+ if (typeof text !== 'string' || !text.trim() || typeof ref !== 'string' || !ref.trim()
16
+ || typeof sessionId !== 'string' || !sessionId.trim()) {
12
17
  return jsonResponse({ error: 'bad_request' }, 400);
13
18
  }
14
19
  const store = new CardStore(root);
15
20
  try {
16
- const card = createWishCard({ text, source: 'pwa', ref }, store);
21
+ const card = createWishCard({ text, source: 'pwa', ref, replyTo: { surface: 'pwa', sessionId } }, store);
17
22
  return jsonResponse(card, card.created ? 201 : 200);
18
23
  } finally {
19
24
  store.close();
20
25
  }
21
26
  }
22
27
 
28
+ export interface WishPlacement { cellId: string; cellTitle: string; version: string; status: 'green' | 'yellow' | 'red' | 'done' }
29
+
30
+ export function isSplitCell(cell: unknown): cell is { id: string } {
31
+ return cell !== null && typeof cell === 'object' && 'id' in cell
32
+ && typeof cell.id === 'string' && cell.id.trim().length > 0;
33
+ }
34
+
35
+ /** Join the live checklist to the wish card by the split cell ID, never by title or card status. */
36
+ export function wishPlacements(card: TaskCard, root?: string): WishPlacement[] {
37
+ if (!card.goalId.startsWith('wish:')) return [];
38
+ const split = [...card.sections].reverse().find(section => section.key === 'orch:split');
39
+ if (!split) return [];
40
+ let cells: unknown;
41
+ try { cells = JSON.parse(split.content); } catch { return []; }
42
+ if (!Array.isArray(cells)) return [];
43
+ const ids = [...new Set(cells.filter(isSplitCell).map(cell => cell.id.trim()))];
44
+ if (!ids.length) return [];
45
+ const ledgerRoot = root ?? releaseLedgerRoot();
46
+ const path = join(ledgerRoot, 'release', 'features.sqlite');
47
+ if (!existsSync(path)) return [];
48
+ const db = new Database(path, { readonly: true, strict: true });
49
+ try {
50
+ const placements: WishPlacement[] = [];
51
+ for (const cellId of ids) {
52
+ const rows = db.query('SELECT a.version, a.status, COALESCE(a.title_override, f.title) AS title FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.feature_id = ? ORDER BY a.version').all(cellId) as Array<{ version: string; status: string; title: string }>;
53
+ for (const row of rows) if (['green', 'yellow', 'red', 'done'].includes(row.status)) {
54
+ placements.push({ cellId, cellTitle: row.title, version: row.version, status: row.status as WishPlacement['status'] });
55
+ }
56
+ }
57
+ return placements;
58
+ } finally { db.close(); }
59
+ }
60
+
23
61
  /** Read-only card API; route registration and authorization belong to the HTTP server. */
24
62
  export function handleTaskCardsGet(pathname: string, root?: string): Response {
25
63
  if (pathname !== '/v1/task-cards' && !/^\/v1\/task-cards\/[^/]+$/.test(pathname)) {
@@ -46,7 +84,8 @@ export function handleTaskCardsGet(pathname: string, root?: string): Response {
46
84
  const store = new CardStore(root);
47
85
  try {
48
86
  const card = store.getCard(id);
49
- return card ? jsonResponse({ card }) : jsonResponse({ error: 'not_found' }, 404);
87
+ return card ? jsonResponse({ card, ...(card.goalId.startsWith('wish:') ? { placements: wishPlacements(card, root) } : {}) })
88
+ : jsonResponse({ error: 'not_found' }, 404);
50
89
  } finally {
51
90
  store.close();
52
91
  }
@@ -302,6 +302,7 @@ export async function handleWorkflowPut(
302
302
  }
303
303
  const r = saveWorkflow(name, yamlText, { scope });
304
304
  if (!r.ok) {
305
+ if (r.readonly) return jsonResponse({ error: 'readonly_workflow', reason: r.error }, 409);
305
306
  return jsonResponse(
306
307
  {
307
308
  error: 'invalid_workflow',
@@ -323,7 +324,7 @@ export function handleWorkflowDelete(
323
324
  const url = new URL(req.url);
324
325
  const scope = url.searchParams.get('scope') === 'global' ? 'global' : 'project';
325
326
  const r = deleteWorkflow(name, { scope });
326
- if (!r.ok) return jsonResponse({ error: 'delete_failed', reason: r.error }, 404);
327
+ if (!r.ok) return jsonResponse({ error: r.readonly ? 'readonly_workflow' : 'delete_failed', reason: r.error }, r.readonly ? 409 : 404);
327
328
  return jsonResponse({ ok: true, path: r.path, scope }, 200);
328
329
  }
329
330
 
@@ -99,6 +99,8 @@ export interface EnhanceOpts {
99
99
  reasoningEffort?: 'low' | 'medium' | 'high';
100
100
  /** 산출물 유형 힌트(선택) — 예: 'PPT 발표덱', 'PLAN 문서'. 제약 문구 조정. */
101
101
  deliverableHint?: string;
102
+ /** Optional usage observer for the caller's authoring phase; missing usage stays unknown. */
103
+ onUsage?: (usage: LLMUsage) => void;
102
104
  /** 인핸싱 끄기(순수 verbatim). true 면 원문만 fenced 로 감싸 반환. */
103
105
  disabled?: boolean;
104
106
  /** Ask 원문 밖 스캐폴드에만 렌더할, 저작기가 관측한 디렉터리 실물 수. */
@@ -368,7 +370,10 @@ export async function enhancePrompt(raw: string, opts: EnhanceOpts = {}): Promis
368
370
  const rawOut = await streamLLM(messages, () => {}, {
369
371
  model,
370
372
  reasoningEffort: opts.reasoningEffort ?? 'high',
371
- onUsage: (usage) => logLlmUsage(model, usage),
373
+ onUsage: (usage) => {
374
+ logLlmUsage(model, usage);
375
+ try { opts.onUsage?.(usage); } catch { /* usage observation must not change enhancement */ }
376
+ },
372
377
  });
373
378
  const parsed = parseEnhanceJson(rawOut);
374
379
  if (parsed) {
@@ -0,0 +1,51 @@
1
+ import { releaseLedgerRoot } from '../instance/resolve.js';
2
+ import { releaseReadiness, type ReleaseReadiness } from '../../scripts/release-loop/release-readiness.js';
3
+ import { listSchedules, type ReleaseSchedule } from './release-schedule.js';
4
+
5
+ /** Cuts become eligible at cutAt and remain eligible through the end of the window. */
6
+ export function selectScheduledCut(schedules: readonly ReleaseSchedule[], now: Date, windowMinutes: number): ReleaseSchedule | null {
7
+ if (!Number.isFinite(windowMinutes) || windowMinutes <= 0) throw new Error('windowMinutes must be positive and finite');
8
+ const current = now.getTime();
9
+ if (!Number.isFinite(current)) throw new Error('now must be a valid date');
10
+ return schedules
11
+ .filter((schedule) => {
12
+ const cut = Date.parse(schedule.cutAt);
13
+ return Number.isFinite(cut) && cut <= current && current - cut <= windowMinutes * 60_000;
14
+ })
15
+ .sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt) || a.version.localeCompare(b.version))[0] ?? null;
16
+ }
17
+
18
+ export type AutoStartDeferral = { deferred: true; reason: Exclude<ReleaseReadiness, { ready: true }>['reason'] | 'frozen' };
19
+
20
+ export type AutoStartResult =
21
+ | { status: 'no-cut' }
22
+ | { status: 'planned'; version: string; cutAt: string }
23
+ | { status: 'skipped'; version: string; reason: AutoStartDeferral['reason'] }
24
+ | { status: 'started'; version: string; cutAt: string };
25
+
26
+ export interface AutoStartOptions {
27
+ windowMinutes: number;
28
+ apply?: boolean;
29
+ now?: Date;
30
+ ledgerRoot?: string;
31
+ schedules?: (root: string) => ReleaseSchedule[];
32
+ readiness?: (version: string, ledgerRoot: string, now: Date) => ReleaseReadiness;
33
+ launch?: (version: string) => void | AutoStartDeferral | Promise<void | AutoStartDeferral>;
34
+ }
35
+
36
+ /** Select from the release schedule; the existing `release run --if-ready` owns the launch-time lock and guards. */
37
+ export async function autoStartScheduledRelease(options: AutoStartOptions): Promise<AutoStartResult> {
38
+ const root = options.ledgerRoot ?? releaseLedgerRoot();
39
+ const now = options.now ?? new Date();
40
+ // Validate the window even when the ledger has no rows.
41
+ const schedule = selectScheduledCut((options.schedules ?? listSchedules)(root), now, options.windowMinutes);
42
+ if (!schedule) return { status: 'no-cut' };
43
+ const { version, cutAt } = schedule;
44
+ if (!options.apply) return { status: 'planned', version, cutAt };
45
+ const readiness = (options.readiness ?? ((v, ledgerRoot, at) => releaseReadiness(v, { ledgerRoot, now: () => at })))(version, root, now);
46
+ if (!readiness.ready) return { status: 'skipped', version, reason: readiness.reason };
47
+ if (!options.launch) throw new Error('auto-start apply requires a launch callback');
48
+ const launched = await options.launch(version);
49
+ if (launched?.deferred) return { status: 'skipped', version, reason: launched.reason };
50
+ return { status: 'started', version, cutAt };
51
+ }