elanous 0.2.13 → 0.2.14

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 (445) 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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  9. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.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-2df489622a52a33a.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/9070.067c106528ec1ae6.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  48. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
  69. package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
  81. package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
  89. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
  91. package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  97. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  98. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  99. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  100. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  104. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
  105. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
  106. package/apps/pwa/out/approvals/index.html +1 -1
  107. package/apps/pwa/out/approvals/index.txt +23 -23
  108. package/apps/pwa/out/autopilot/index.html +1 -1
  109. package/apps/pwa/out/autopilot/index.txt +23 -23
  110. package/apps/pwa/out/board/index.html +1 -1
  111. package/apps/pwa/out/board/index.txt +23 -23
  112. package/apps/pwa/out/botlab/index.html +1 -1
  113. package/apps/pwa/out/botlab/index.txt +22 -22
  114. package/apps/pwa/out/bots/index.html +1 -1
  115. package/apps/pwa/out/bots/index.txt +22 -22
  116. package/apps/pwa/out/chat/index.html +1 -1
  117. package/apps/pwa/out/chat/index.txt +22 -22
  118. package/apps/pwa/out/consult/index.html +1 -1
  119. package/apps/pwa/out/consult/index.txt +23 -23
  120. package/apps/pwa/out/control/index.html +1 -1
  121. package/apps/pwa/out/control/index.txt +23 -23
  122. package/apps/pwa/out/design-check/index.html +1 -1
  123. package/apps/pwa/out/design-check/index.txt +23 -23
  124. package/apps/pwa/out/editor/index.html +1 -1
  125. package/apps/pwa/out/editor/index.txt +23 -23
  126. package/apps/pwa/out/exec/index.html +1 -1
  127. package/apps/pwa/out/exec/index.txt +23 -23
  128. package/apps/pwa/out/field/index.html +1 -1
  129. package/apps/pwa/out/field/index.txt +23 -23
  130. package/apps/pwa/out/healthz +1 -1
  131. package/apps/pwa/out/index.html +1 -1
  132. package/apps/pwa/out/index.txt +22 -22
  133. package/apps/pwa/out/inside/index.html +1 -1
  134. package/apps/pwa/out/inside/index.txt +22 -22
  135. package/apps/pwa/out/intake/index.html +1 -1
  136. package/apps/pwa/out/intake/index.txt +23 -23
  137. package/apps/pwa/out/live/index.html +1 -1
  138. package/apps/pwa/out/live/index.txt +23 -23
  139. package/apps/pwa/out/loops/index.html +1 -0
  140. package/apps/pwa/out/loops/index.txt +30 -0
  141. package/apps/pwa/out/market/index.html +1 -1
  142. package/apps/pwa/out/market/index.txt +22 -22
  143. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  144. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  145. package/apps/pwa/out/morning/index.html +1 -1
  146. package/apps/pwa/out/morning/index.txt +23 -23
  147. package/apps/pwa/out/observatory/index.html +1 -1
  148. package/apps/pwa/out/observatory/index.txt +23 -23
  149. package/apps/pwa/out/ops/checklist/index.html +1 -1
  150. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  151. package/apps/pwa/out/ops/release/index.html +1 -1
  152. package/apps/pwa/out/ops/release/index.txt +22 -22
  153. package/apps/pwa/out/ops/seats/index.html +1 -1
  154. package/apps/pwa/out/ops/seats/index.txt +22 -22
  155. package/apps/pwa/out/outputs/index.html +1 -1
  156. package/apps/pwa/out/outputs/index.txt +22 -22
  157. package/apps/pwa/out/reflection/index.html +1 -1
  158. package/apps/pwa/out/reflection/index.txt +23 -23
  159. package/apps/pwa/out/scheduler/index.html +1 -1
  160. package/apps/pwa/out/scheduler/index.txt +23 -23
  161. package/apps/pwa/out/sessions/index.html +1 -1
  162. package/apps/pwa/out/sessions/index.txt +22 -22
  163. package/apps/pwa/out/settings/devices/index.html +1 -1
  164. package/apps/pwa/out/settings/devices/index.txt +23 -23
  165. package/apps/pwa/out/settings/index.html +1 -1
  166. package/apps/pwa/out/settings/index.txt +23 -23
  167. package/apps/pwa/out/setup/done/index.html +1 -1
  168. package/apps/pwa/out/setup/done/index.txt +23 -23
  169. package/apps/pwa/out/setup/index.html +1 -1
  170. package/apps/pwa/out/setup/index.txt +23 -23
  171. package/apps/pwa/out/share/index.html +1 -1
  172. package/apps/pwa/out/share/index.txt +23 -23
  173. package/apps/pwa/out/showroom/index.html +1 -1
  174. package/apps/pwa/out/showroom/index.txt +22 -22
  175. package/apps/pwa/out/tasks/index.html +1 -1
  176. package/apps/pwa/out/tasks/index.txt +23 -23
  177. package/apps/pwa/out/term/index.html +1 -1
  178. package/apps/pwa/out/term/index.txt +23 -23
  179. package/apps/pwa/out/today/index.html +1 -1
  180. package/apps/pwa/out/today/index.txt +23 -23
  181. package/apps/pwa/out/trace/index.html +1 -1
  182. package/apps/pwa/out/trace/index.txt +23 -23
  183. package/apps/pwa/out/vault/index.html +1 -1
  184. package/apps/pwa/out/vault/index.txt +23 -23
  185. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  186. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  187. package/apps/pwa/out/workflows/index.html +1 -1
  188. package/apps/pwa/out/workflows/index.txt +23 -23
  189. package/apps/pwa/out/workspace/index.html +1 -1
  190. package/apps/pwa/out/workspace/index.txt +23 -23
  191. package/apps/pwa/out/worktrees/index.html +1 -1
  192. package/apps/pwa/out/worktrees/index.txt +23 -23
  193. package/graphs/orchestrator/orchestrator.yaml +59 -0
  194. package/graphs/orchestrator/recipes.yaml +18 -0
  195. package/graphs/release/recipes.yaml +3 -0
  196. package/graphs/release/release-loop.yaml +4 -1
  197. package/graphs/rhythm/daily.yaml +38 -0
  198. package/graphs/rhythm/recipes.yaml +18 -0
  199. package/graphs/rhythm/weekly.yaml +39 -0
  200. package/graphs/video/recipes.yaml +3 -0
  201. package/graphs/video/youtube-summary.yaml +17 -0
  202. package/package.json +1 -1
  203. package/scripts/audit-test-state-writes.ts +72 -17
  204. package/scripts/brand/check.ts +186 -0
  205. package/scripts/coord-post.sh +11 -1
  206. package/scripts/docs-cli-check.ts +23 -6
  207. package/scripts/expose-rubric-check.ts +118 -0
  208. package/scripts/expose-rubric-suggest.ts +163 -0
  209. package/scripts/heal/run-role.ts +14 -0
  210. package/scripts/hq/standby-verify.ts +11 -7
  211. package/scripts/lessons/handbook-page.ts +85 -0
  212. package/scripts/release-loop/gate-node.ts +9 -0
  213. package/scripts/release-loop/publish-node.ts +14 -1
  214. package/scripts/release-loop/unattended-release.ts +23 -1
  215. package/scripts/release-loop/vault-note-node.ts +196 -0
  216. package/scripts/release-story/draft.ts +172 -0
  217. package/scripts/rhythm/daily.ts +383 -0
  218. package/scripts/rhythm/weekly.ts +294 -0
  219. package/scripts/skills/public-check.ts +169 -0
  220. package/scripts/test-deterministic.ts +4 -8
  221. package/scripts/test-diet/effectiveness.ts +195 -0
  222. package/scripts/test-diet/lib.ts +16 -8
  223. package/scripts/test-diet/node.ts +77 -9
  224. package/scripts/ux/demo-rehearsal.ts +2 -2
  225. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  226. package/scripts/video/youtube-summary.ts +133 -0
  227. package/skills/diagram-master/SKILL.md +42 -49
  228. package/skills/diagram-master/references/pyproject.toml +4 -1
  229. package/skills/diagram-master/references/routing-guide.md +4 -2
  230. package/skills/grill-me/SKILL.md +1 -0
  231. package/skills/youtube-master/SKILL.md +13 -13
  232. package/skills/youtube-master/package-lock.json +0 -545
  233. package/skills/youtube-master/package.json +2 -5
  234. package/skills/youtube-master/scripts/main.ts +2 -2
  235. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  236. package/skills/youtube-master/src/util.ts +30 -7
  237. package/src/agent-mission/driver.ts +3 -3
  238. package/src/autopilot/build/llm-conflict-merge.ts +45 -8
  239. package/src/autopilot/build/merge-intent.ts +53 -0
  240. package/src/chat/index.ts +2 -0
  241. package/src/claims/claims-ledger.ts +232 -0
  242. package/src/claims/claims-render.ts +157 -0
  243. package/src/cli/claims-cli.ts +94 -0
  244. package/src/cli/decisions-cli.ts +35 -3
  245. package/src/cli/docs-cli.ts +2 -0
  246. package/src/cli/flow-cli.ts +11 -0
  247. package/src/cli/freeze-cli.ts +45 -0
  248. package/src/cli/lesson-cli.ts +108 -0
  249. package/src/cli/logs-cli.ts +104 -2
  250. package/src/cli/logs-model-input.ts +131 -0
  251. package/src/cli/pr-cli.ts +29 -1
  252. package/src/cli/private-ledger-commands.ts +54 -4
  253. package/src/cli/pty-drive-verdict.ts +11 -2
  254. package/src/cli/release-cli.ts +144 -21
  255. package/src/cli/rfc-status.ts +91 -0
  256. package/src/connectors/linear.ts +5 -3
  257. package/src/context-bus/condense.ts +15 -20
  258. package/src/context-bus/context-now-public.ts +86 -0
  259. package/src/context-bus/context-now-surfaces.ts +55 -2
  260. package/src/context-bus/context-now.ts +4 -3
  261. package/src/context-bus/long-term-memory.ts +92 -11
  262. package/src/dashboard/index.ts +75 -22
  263. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  264. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  265. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  266. package/src/decisions/decision-ledger.ts +40 -1
  267. package/src/discord/sprint21-runtime.ts +16 -0
  268. package/src/discord-self-message.ts +8 -0
  269. package/src/discord-slash-wire.ts +31 -2
  270. package/src/domains/outbound-alert.ts +1 -1
  271. package/src/elanous-config-dir.ts +29 -4
  272. package/src/execution-loop/launch-gate.ts +29 -14
  273. package/src/flow/cards.ts +239 -0
  274. package/src/graph-runner/graph-tick.ts +4 -0
  275. package/src/graph-runner/runner.ts +30 -5
  276. package/src/harness/harness-cli-command.ts +152 -27
  277. package/src/harness/harness-incidents.ts +43 -12
  278. package/src/harness/harness-pod-dispatch.ts +2 -0
  279. package/src/harness/harness-queue-child.ts +3 -3
  280. package/src/harness/harness-queue.ts +216 -28
  281. package/src/harness/helper-scan.ts +139 -0
  282. package/src/harness/model-input-observation.ts +37 -0
  283. package/src/hq/hq.ts +1 -1
  284. package/src/hq/promote.ts +301 -0
  285. package/src/index.ts +83 -21
  286. package/src/intake-plane/linear-wish.ts +39 -11
  287. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  288. package/src/intake-plane/wish-card.ts +53 -2
  289. package/src/intake-plane/wish-folder.ts +5 -2
  290. package/src/intake-plane/wish-reply.ts +72 -0
  291. package/src/lessons/lesson-import.ts +106 -0
  292. package/src/lessons/lesson-ledger.ts +206 -0
  293. package/src/loops/activity.ts +203 -0
  294. package/src/loops/budget.ts +32 -0
  295. package/src/loops/checker.ts +5 -3
  296. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  297. package/src/loops/loop-cli.ts +36 -4
  298. package/src/loops/neighbors.ts +40 -0
  299. package/src/loops/observe.ts +10 -0
  300. package/src/loops/orchestrator/author-depth.ts +164 -0
  301. package/src/loops/orchestrator/author-input-check.ts +77 -0
  302. package/src/loops/orchestrator/author-ledger.ts +230 -0
  303. package/src/loops/orchestrator/finish-rate.ts +175 -0
  304. package/src/loops/orchestrator/overlap.ts +51 -12
  305. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  306. package/src/loops/orchestrator/tick.ts +302 -0
  307. package/src/loops/orchestrator/traffic.ts +18 -3
  308. package/src/loops/registry.ts +454 -9
  309. package/src/loops/status-rows.ts +118 -0
  310. package/src/loops/verdict.ts +17 -5
  311. package/src/maturity/bot-slash-catalog.ts +66 -0
  312. package/src/maturity/feature-maturity.ts +7 -4
  313. package/src/nexus/api/dashboard.ts +6 -1
  314. package/src/nexus/api/http-server.ts +18 -2
  315. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  316. package/src/nexus/api/schedules-read.ts +5 -1
  317. package/src/nexus/api/task-cards-api.ts +44 -5
  318. package/src/nexus/api/workflows.ts +2 -1
  319. package/src/release-loop/auto-start.ts +48 -0
  320. package/src/release-loop/checklist.ts +13 -4
  321. package/src/release-loop/feature-store.ts +28 -16
  322. package/src/release-loop/landing-freeze.ts +121 -0
  323. package/src/release-loop/placement.ts +242 -0
  324. package/src/release-loop/release-schedule.ts +3 -1
  325. package/src/seat-dispatch/ceo-commands.ts +2 -2
  326. package/src/seat-dispatch/seat-ask.ts +16 -3
  327. package/src/seat-dispatch/seat-questions.ts +58 -0
  328. package/src/seat-loop/seat-loop.ts +406 -31
  329. package/src/self-dev/dev-pipeline.ts +6 -2
  330. package/src/self-dev/draft-sweep.ts +76 -0
  331. package/src/self-dev/run-store.ts +73 -1
  332. package/src/self-dev/run-supervisor.ts +8 -0
  333. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  334. package/src/self-implement/budget-gate.ts +29 -0
  335. package/src/self-implement/expose-gate.ts +47 -0
  336. package/src/self-implement/frozen-merges.ts +294 -0
  337. package/src/self-implement/gate-cli.ts +10 -4
  338. package/src/self-implement/gate-scope.ts +46 -9
  339. package/src/self-implement/goal-archive.ts +222 -0
  340. package/src/self-implement/goal-lookup.ts +147 -0
  341. package/src/self-implement/heal-intake.ts +79 -0
  342. package/src/self-implement/host-regate.ts +25 -2
  343. package/src/self-implement/orchestrator.ts +114 -10
  344. package/src/self-implement/revert-guard.ts +62 -0
  345. package/src/self-implement/run-ledger.ts +1 -1
  346. package/src/self-implement/seams.ts +28 -8
  347. package/src/session/index.ts +2 -0
  348. package/src/task-cards/card-cli.ts +10 -2
  349. package/src/task-cards/card-store.ts +10 -6
  350. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  351. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  352. package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
  353. package/src/telegram-commands.ts +27 -3
  354. package/src/telegram-loops-command.ts +44 -0
  355. package/src/user-config.ts +120 -16
  356. package/src/version/packed-revision.json +1 -1
  357. package/src/workflow-runtime/storage.ts +31 -13
  358. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  369. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  373. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  411. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  412. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  413. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  414. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  415. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  416. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  417. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  418. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  419. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  420. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  421. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  422. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  423. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  424. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  425. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  426. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  427. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  428. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  429. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  430. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  431. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  432. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  433. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  434. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  444. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  445. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
@@ -68,6 +68,7 @@ import { decideLineageSupersede, lineageSupersedeCloseComment, type LineageSuper
68
68
  import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
69
69
  import { enqueueControlMemo, readSoftStopRequestStatus, type SoftStopRequestRead } from '../harness/control-inbox.js';
70
70
  import { debug } from '../debug/log.js';
71
+ import { recordFailureEvent } from './heal-intake.js';
71
72
  import { releasePathHold, releasePathHoldComment, RELEASE_PATH_LABEL } from '../self-dev/release-path-guard.js';
72
73
  import { emitDecision } from '../live/detail-switch.js';
73
74
  import { reworkDecision } from './decision-events.js';
@@ -83,6 +84,7 @@ import { decomposeSelfDevGoal, inferHotPaths, type SelfDevDecomposeOptions } fro
83
84
  import { boundaryGlobs, boundaryViolations } from './goal-boundary.js';
84
85
  import { askAtExecution, type ExecutionClarificationResult } from './execution-clarification.js';
85
86
  import { targetScopedGoalText } from './goal-text-path-scope.js';
87
+ import { dropStaleReverts, type RevertGuardResult } from './revert-guard.js';
86
88
  import type { SelfDevGoalType } from '../self-dev/orchestrate.js';
87
89
  import { DEFAULT_BRANCH_WORKTREE_BASE, gateGitResidue, observeGitResidue } from '../git-fs/worktree.js';
88
90
  import { runGitCommand } from '../git-fs/runner.js';
@@ -96,6 +98,8 @@ import { getHarnessSpace, normalizeSpaceId, resolveRunIdentity } from '../harnes
96
98
  import { getElanousConfigDir } from '../elanous-config-dir.js';
97
99
  import { elanousStateRoot } from '../autopilot/state-paths.js';
98
100
  import { addNextMdReleaseNote, harnessReleaseNote, parseReleaseNoteSection, releaseNotesDir, renderReleaseNoteSection, writeReleaseNote, type ReleaseNoteFragment, type ReleaseNoteSection } from '../release-loop/release-note.js';
101
+ import { landingFreezeMessage } from '../release-loop/landing-freeze.js';
102
+ import { admitLandingMerge, owningRepoRoot } from './frozen-merges.js';
99
103
  import { plannedSelfImplBranch } from '../harness/worktree-branch-prefix.js';
100
104
  export { slugifyFeature } from '../harness/worktree-branch-prefix.js';
101
105
  import { AUTO_REVIEW_LABEL, resolveAutoReviewLabels } from './context-capsule.js';
@@ -757,6 +761,33 @@ function declaredPathsFromGoalDocumentText(document: string): { readable: boolea
757
761
  return parseDeclaredTargetPathsFromAsk(originalAsk);
758
762
  }
759
763
 
764
+ /** Only paths explicitly designated as targets in the ask or piece text protect an intentional revert. */
765
+ function declaredRevertGuardPaths(text: string): string[] {
766
+ const lines = targetScopedGoalText(text).split(/\r?\n/);
767
+ const pieceTargets: string[] = [];
768
+ for (let index = 0; index < lines.length; index++) {
769
+ const line = lines[index]!.replace(/^\s*(?:[-*+]\s+|>\s*)+/, '');
770
+ const declaration = /^(?:조각\s+)?(?:대상\s*경로|target\s*paths?)\s*[::]\s*(.*)$/i.exec(line);
771
+ if (!declaration) continue;
772
+ const fragments = [declaration[1]!];
773
+ while (index + 1 < lines.length) {
774
+ const next = lines[index + 1]!.trim().replace(/^(?:[-*+]\s+|>\s*)+/, '');
775
+ const paths = parseDeclaredTargetPathsFromAsk(`대상 경로: ${next}`).paths;
776
+ const tokens = next.split(/[·,]/).map((part) => part.trim().replace(/^`+|`+$/g, ''));
777
+ if (!tokens.length || !tokens.every((token) => token && paths.includes(normalizeDeclaredScopePath(token)))) break;
778
+ fragments.push(next);
779
+ index++;
780
+ }
781
+ pieceTargets.push(...parseDeclaredTargetPathsFromAsk(`대상 경로: ${fragments.join(',')}`).paths);
782
+ }
783
+ return pieceTargets;
784
+ }
785
+
786
+ function revertGuardTargetPaths(feature: string, goalDocument?: string): string[] {
787
+ const originalAsk = goalDocument ? extractVerbatimOriginalAsk(goalDocument)?.ask : undefined;
788
+ return [...new Set([...declaredRevertGuardPaths(feature), ...(originalAsk ? declaredRevertGuardPaths(originalAsk) : [])])];
789
+ }
790
+
760
791
  export function detectDeclaredScopeDiff(input: {
761
792
  goalDocument?: string | null;
762
793
  changedFiles?: readonly string[] | null;
@@ -972,7 +1003,7 @@ export interface SelfImplementSeams {
972
1003
  /** 변경 전 ref의 runtime source 전체. 생략 시 git에서 읽으며, 읽기 실패는 판정 미지로 남긴다. */
973
1004
  reviewBaselineObservationSource?: (cwd: string, base: string) => Promise<string | undefined>;
974
1005
  /** ④ gate — cwd 에서 bun test/build. postsync=정합 후 재게이트(merge-base 실행 범위). */
975
- gate: (cwd: string, ctx?: { runId?: string; mode?: 'postsync' }) => Promise<SelfImplementGateResult>;
1006
+ gate: (cwd: string, ctx?: { runId?: string; mode?: 'postsync'; round?: number }) => Promise<SelfImplementGateResult>;
976
1007
  /** Gate가 실행된 worktree HEAD가 local origin/main보다 뒤처진 커밋 수. 실패는 undefined(unknown)로 정직하게 남긴다. */
977
1008
  gateWorktreeBehindMain?: (cwd: string) => Promise<number | undefined>;
978
1009
  /** 변경 파일 목록 — 게이트 «라우터»가 「코드가 바뀌었나」를 판정하는 입력.
@@ -1029,6 +1060,8 @@ export interface SelfImplementSeams {
1029
1060
  /** ★ ⑦ 병합 seam(2026-07-21·auto-merge) — 열린 PR 을 실제 병합(gh pr merge --squash). `matchHeadCommit`이 있으면
1030
1061
  * 검사한 고정 head 뒤 변경을 gh가 거부한다. autoMerge + 리뷰 clean 일 때만 호출(outward-facing·main 자율병합). */
1031
1062
  mergePr?: (opts: { number: number; cwd: string; matchHeadCommit?: string }) => Promise<{ merged: boolean; baseRefName?: string; mergeCommit?: string; detail?: string }>;
1063
+ /** Only successful merges trigger this injected, fail-closed draft inventory and mutation seam. */
1064
+ supersedeDraftsOnMerge?: (number: number, cwd: string) => Promise<void>;
1032
1065
  /** Post-merge cleanup is opt-in and injectable so tests never touch the filesystem. */
1033
1066
  postMergeCleanup?: {
1034
1067
  enabled: boolean;
@@ -2551,12 +2584,16 @@ function releaseNoteForRun(goalFile: string | undefined, feature: string): Relea
2551
2584
  }
2552
2585
 
2553
2586
  /** GATE-CALLERS (OP 10-03): caller tests that exceeded the gate cap must stay visible in the PR body even when the gate log is cut. */
2587
+ export function exposeGateLines(gateLog?: string): string[] {
2588
+ return (gateLog ?? '').split('\n').filter((line) => line.startsWith('[expose]'));
2589
+ }
2590
+
2554
2591
  export function gateNotRunSection(gateLog?: string): string[] {
2555
2592
  const lines = (gateLog ?? '').split('\n').filter((line) => line.startsWith('⚠️ caller test cap exceeded'));
2556
2593
  return lines.length ? ['', '## Gate · 안 돈 소비자 시험(상한 초과)', ...lines.map((line) => `- ${line.replace(/^⚠️ caller test cap exceeded — not run: /, '')}`)] : [];
2557
2594
  }
2558
2595
 
2559
- function prBody(feature: string, implSummary: string, gateLog?: string, review?: SelfImplementReview, reviewIntent?: string, autoReviewDeclineReasons?: readonly string[], evidence?: string, goalFile?: string, planRevision?: ReworkPlanRevision, releaseNote?: ReturnType<typeof harnessReleaseNote>): string {
2596
+ function prBody(feature: string, implSummary: string, gateLog?: string, review?: SelfImplementReview, reviewIntent?: string, autoReviewDeclineReasons?: readonly string[], evidence?: string, goalFile?: string, planRevision?: ReworkPlanRevision, releaseNote?: ReturnType<typeof harnessReleaseNote>, revertGuard?: RevertGuardResult): string {
2560
2597
  const note = releaseNote ?? releaseNoteForRun(goalFile, feature);
2561
2598
  const relaxation = planRevision?.relaxation
2562
2599
  ? ['', '## 감독 수용 기준 완화', `- 대상: ${planRevision.relaxation.target}`, `- 이전: ${planRevision.relaxation.expected}`, `- 완화: ${planRevision.relaxation.replacement}`, `- 이유: ${planRevision.reason}`, `- 적용: ${planRevision.application?.status ?? 'failed'}${planRevision.application?.detail ? ` (${planRevision.application.detail})` : ''}`, ...(planRevision.disposition ? [`- 충돌 처분: ${planRevision.disposition}`] : [])]
@@ -2577,7 +2614,14 @@ function prBody(feature: string, implSummary: string, gateLog?: string, review?:
2577
2614
  ...(review.shouldFix.length ? ['', '**should-fix (비블로킹):**', ...review.shouldFix.slice(0, 8).map((f) => `- ${f}`)] : [])] : []),
2578
2615
  ...(autoReviewDeclineReasons?.length ? ['', '## Auto-review label not applied', 'The requested auto-review label was declined by the autonomy gate:', ...autoReviewDeclineReasons.map((reason) => `- ${reason}`)] : []),
2579
2616
  ...gateNotRunSection(gateLog),
2580
- ...(gateLog ? ['', '## Gate', '```', gateLog.trim().slice(0, 3000), '```'] : []),
2617
+ ...(gateLog || revertGuard ? ['', '## Gate',
2618
+ // 경고는 잘린 gate 로그의 코드블록 밖에서도 한눈에 보인다.
2619
+ ...((gateLog ?? '').split('\n').filter((line) => line.startsWith('⚠️ 동작 검증 0:'))),
2620
+ ...exposeGateLines(gateLog),
2621
+ ...(gateLog ? ['```', gateLog.trim().slice(0, 3000), '```'] : []),
2622
+ ...(revertGuard?.reverted.length ? [`- 되돌림 방지: ${revertGuard.reverted.length}파일 제외`] : []),
2623
+ ...(revertGuard?.protected.length ? [`- 되돌림 경고: 대상 경로 ${revertGuard.protected.length}파일 유지 (${revertGuard.protected.join(', ')})`] : []),
2624
+ ...(revertGuard?.warning ? [`- 되돌림 검사 실패(커밋 계속): ${revertGuard.warning}`] : [])] : []),
2581
2625
  ...relaxation,
2582
2626
  '',
2583
2627
  '🤖 self-implement 오케스트레이터 (elanous 자율 구현·리뷰-게이트 병합)',
@@ -2807,7 +2851,7 @@ function blockedDraftPrBody(feature: string, implSummary: string, state: Blocked
2807
2851
  ...(state.gate === undefined
2808
2852
  ? ['(게이트 미실행)']
2809
2853
  : state.gate.log
2810
- ? ['```', state.gate.log.trim().slice(0, 3000), '```']
2854
+ ? [...state.gate.log.split('\n').filter((line) => line.startsWith('⚠️ 동작 검증 0:')), ...exposeGateLines(state.gate.log), '```', state.gate.log.trim().slice(0, 3000), '```'] // 경고는 절단된 로그 밖에도 보존한다.
2811
2855
  : ['(게이트 실행됨 · 로그 없음)']),
2812
2856
  '',
2813
2857
  '## Main-sync typecheck',
@@ -4087,6 +4131,19 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4087
4131
  throw e;
4088
4132
  } finally {
4089
4133
  if (loopRegistered) registerRunLoop('ended');
4134
+ const terminalStage = terminalResult?.stage ?? 'crashed';
4135
+ try {
4136
+ const mappedStatus = mapStageToRunStatus(terminalStage)?.runStatus;
4137
+ if (!terminalResult || mappedStatus === 'failed' || (mappedStatus === undefined && terminalResult.ok === false) || signalIncompleteState(roundClassifications).latestSignalIncomplete) {
4138
+ recordFailureEvent({
4139
+ source: 'harness-run', kind: terminalStage, ref: runId,
4140
+ summary: opts.feature.split(/\r?\n/, 1)[0]?.trim() || `Harness run ${runId} failed`,
4141
+ at: new Date().toISOString(),
4142
+ });
4143
+ }
4144
+ } catch (error) {
4145
+ logRunAwareFailSoft(runId, 'heal-intake-record-failed', { stage: terminalStage, error: safeErrorDescription(error) });
4146
+ }
4090
4147
  if (terminalResult) await recordGoalExecution(terminalResult);
4091
4148
  // ⛔⭐⭐⭐ 종결을 «못 적고» 죽는 길을 막는다 (2026-08-11 · 🅣 71차 · 대표 「종료 때 정리가 안 되면 설계 문제」).
4092
4149
  // `terminalResult` 는 «두 자리»에서만 선다 — 정상 종료 ⊕ `StepTimeoutError` 분기.
@@ -5279,6 +5336,13 @@ async function runSelfImplementInner(
5279
5336
  let finishedChildCommitMissing = false;
5280
5337
  let finishedChildCommitFailure: string | undefined;
5281
5338
  let finishedChildCommitted = false;
5339
+ const revertGuard: RevertGuardResult = { reverted: [], protected: [] };
5340
+ const inspectStaleReverts = (targets: readonly string[]): void => {
5341
+ const inspected = dropStaleReverts(wt.path, targets, runId, getUserConfig().harness?.revertGuard?.depth);
5342
+ for (const file of inspected.reverted) if (!revertGuard.reverted.includes(file)) revertGuard.reverted.push(file);
5343
+ for (const file of inspected.protected) if (!revertGuard.protected.includes(file)) revertGuard.protected.push(file);
5344
+ if (inspected.warning) revertGuard.warning = inspected.warning;
5345
+ };
5282
5346
  const classifyUnfinishedRun = (stage: SelfImplementStage, verdict?: ReworkBudgetVerdict, decomposition?: TerminalDecomposition): AbandonedClassificationResult => {
5283
5347
  const quotaExhaustionAssessment = quotaExhaustionAssessmentForRun();
5284
5348
  const worktree = unfinishedWorktreeObservation(wt.path);
@@ -6306,6 +6370,7 @@ async function runSelfImplementInner(
6306
6370
  .filter((path) => !harnessSeededPaths.includes(path)
6307
6371
  && targets.some((target) => changedFileIsInsideDeclaredTarget(path, target)));
6308
6372
  if (eligible.length) {
6373
+ inspectStaleReverts(revertGuardTargetPaths(opts.feature, goalDocument));
6309
6374
  const message = `self-implement: ${prTitle(opts.feature)}`;
6310
6375
  let outcome: { ok: boolean; out: string };
6311
6376
  try {
@@ -6514,7 +6579,7 @@ async function runSelfImplementInner(
6514
6579
  return { ...stoppedBeforeGate, ...resolveRunOutcome({ termination: 'abandoned' }), sessionId, quotaExhaustionAssessment: quotaExhaustionAssessmentForRun(), ...(review ? { review } : {}) };
6515
6580
  }
6516
6581
  onNodeEntry(node('gate'), round);
6517
- gate = await stepTimeout(s.gate(wt.path, { runId }), T.gate, 'gate');
6582
+ gate = await stepTimeout(s.gate(wt.path, { runId, round }), T.gate, 'gate');
6518
6583
  } else {
6519
6584
  observe('gate-skipped-by-graph', { ...graphAuthorityFields(graphAuthority, graphTemplate), round, reason: gateRoute.reason, changedFileCount: gateRouteFiles?.length ?? null });
6520
6585
  gate = {
@@ -7106,6 +7171,11 @@ async function runSelfImplementInner(
7106
7171
 
7107
7172
  if (s.mergeMain && s.commitWork) {
7108
7173
  onNodeEntry('main-sync', round);
7174
+ let goalDocument: string | undefined;
7175
+ if (opts.goalFile) {
7176
+ try { goalDocument = readFileSync(opts.goalFile, 'utf8'); } catch { /* no documented targets */ }
7177
+ }
7178
+ inspectStaleReverts(revertGuardTargetPaths(opts.feature, goalDocument));
7109
7179
  s.commitWork(wt.path, prTitle(opts.feature));
7110
7180
  // ⛔⭐ resume 경로와 «같은» 해석기를 쓴다 — 두 자리가 갈리면 같은 원인이 다른 얼굴로 나온다.
7111
7181
  const resolvedTarget = resolveDefaultBranchTarget(s.defaultBranchRef ?? defaultBranchRef, wt.path);
@@ -7139,7 +7209,7 @@ async function runSelfImplementInner(
7139
7209
  ? `${mergeTarget} 충돌해결됨 — ${formatLlmMergeOutcome(sync)} — 통합 결과 full 재-gate…`
7140
7210
  : `${mergeTarget} 정합됨 — 통합 결과 full 재-gate…`);
7141
7211
  onNodeEntry('regate', round);
7142
- const regate = await withStepTimeout(s.gate(wt.path, { runId, mode: 'postsync' }), T.gate, 'gate');
7212
+ const regate = await withStepTimeout(s.gate(wt.path, { runId, mode: 'postsync', round }), T.gate, 'gate');
7143
7213
  if (!regate.passed) await onFailureVerdict('gate');
7144
7214
  // `mode` name/meaning stay: `full` = existing gate including tests. Both
7145
7215
  // conflict-resolved and clean-merge reuse this same `s.gate` invocation.
@@ -7269,7 +7339,7 @@ async function runSelfImplementInner(
7269
7339
  addNextMdReleaseNote(wt.path, releaseNote);
7270
7340
  const preparedPrBody = preparePrBody(
7271
7341
  [
7272
- prBody(opts.feature, impl.summary, gate.log, review, reviewIntent, declineReasons, harvestedForPr(), opts.goalFile, lastPlanRevision, releaseNote),
7342
+ prBody(opts.feature, impl.summary, gate.log, review, reviewIntent, declineReasons, harvestedForPr(), opts.goalFile, lastPlanRevision, releaseNote, revertGuard),
7273
7343
  ...(prEvidenceDecision.body ? ['', '---', '', prEvidenceDecision.body] : []),
7274
7344
  ].join('\n'),
7275
7345
  s.persistPrBodyArtifact,
@@ -7418,11 +7488,45 @@ async function runSelfImplementInner(
7418
7488
  progress('pr-opened', `호스트 재게이트 대기 (#${pr.number})`);
7419
7489
  return { ok: true, stage: 'merge-ready', node: 'open-pr', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), checkedHeadCommit: checkedHeadCommit!, prUrl: pr.url, prNumber: pr.number };
7420
7490
  }
7421
- onNodeEntry('merge', round);
7422
- progress('merging', '자동 병합 (리뷰 clean·squash)…');
7423
- const m = await withStepTimeout(s.mergePr!({ number: pr.number, cwd: wt.path, matchHeadCommit: checkedHeadCommit! }), T.pr, 'pr');
7491
+ // Landing freeze: the in-flight marker is written before the freeze is read, so a `freeze on` racing this merge waits.
7492
+ // The resume sweep merges from the repository that owns the run worktree (the worktree itself may be cleaned up).
7493
+ const heldEntry = () => {
7494
+ if (!checkedHeadCommit || !/^[0-9a-f]{40}$/i.test(checkedHeadCommit)) return null;
7495
+ const owner = owningRepoRoot(wt.path);
7496
+ // Without a known owning repository the held PR stays queued for a person; no sweep resumes it from a worktree.
7497
+ return owner ? { prNumber: pr.number, headCommit: checkedHeadCommit, repoRoot: owner } : { prNumber: pr.number, headCommit: checkedHeadCommit, repoRoot: wt.path, manual: true as const };
7498
+ };
7499
+ const landing = admitLandingMerge(heldEntry, undefined, {}, { prNumber: pr.number, repoRoot: owningRepoRoot(wt.path) ?? wt.path, ...(checkedHeadCommit ? { headCommit: checkedHeadCommit } : {}) });
7500
+ if (landing.kind === 'held' && !(checkedHeadCommit && /^[0-9a-f]{40}$/i.test(checkedHeadCommit))) {
7501
+ return { ok: true, stage: 'pr-opened', node: 'open-pr', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), mergeReason: 'merge-guard-unevaluated', prUrl: pr.url, prNumber: pr.number, detail: 'freeze: checked head SHA unavailable; PR left ready for review' };
7502
+ }
7503
+ if (landing.kind !== 'merge') {
7504
+ const detail = landing.kind === 'held' ? landingFreezeMessage(landing.freeze) : 'freeze lifted; a resume sweep is merging this PR';
7505
+ debug.log('harness.merge', 'frozen', { pr: pr.number, ...(landing.kind === 'held' ? { reason: landing.freeze.reason, until: landing.freeze.until } : { resumedElsewhere: true }) });
7506
+ progress('pr-opened', `#${pr.number} ready · ${detail}`);
7507
+ return { ok: true, stage: 'pr-opened', node: 'open-pr', ...resolveRunOutcome({ termination: 'completed' }), ...decisionSignalResult, ...preservedCompletionResult, ...(completionStatus ? { completionStatus } : {}), sessionId, worktreePath: wt.path, branch: wt.branch, gate, ...(review ? { review } : {}), mergeReason: landing.kind === 'held' ? 'frozen' : 'resumed-elsewhere', prUrl: pr.url, prNumber: pr.number, detail };
7508
+ }
7509
+ // The marker and claim live until the merge call itself settles — a step timeout does not stop a merge already
7510
+ // sent — and anything that throws before the merge call is made releases them.
7511
+ let merging: ReturnType<NonNullable<typeof s.mergePr>>;
7512
+ try {
7513
+ onNodeEntry('merge', round);
7514
+ progress('merging', '자동 병합 (리뷰 clean·squash)…');
7515
+ merging = s.mergePr!({ number: pr.number, cwd: wt.path, matchHeadCommit: checkedHeadCommit! });
7516
+ } catch (error) { landing.end(); throw error; }
7517
+ // The default merge seam resolves only after `gh pr view` reads MERGED, so settling is the confirmation.
7518
+ const release = (merged: boolean) => {
7519
+ try { landing.end(merged); } catch (error) { observe('landing-release-failed', { number: pr.number, error: safeErrorDescription(error) }, { level: 'error' }); }
7520
+ };
7521
+ void merging.then((outcome) => release(outcome.merged === true), () => release(false));
7522
+ const m = await withStepTimeout(merging, T.pr, 'pr');
7424
7523
  observe('merged', { number: pr.number, merged: m.merged, detail: m.detail ?? null });
7425
7524
  if (m.merged) {
7525
+ try {
7526
+ await s.supersedeDraftsOnMerge?.(pr.number, wt.path);
7527
+ } catch (error) {
7528
+ observe('draft-supersede-on-merge-failed', { number: pr.number, error: safeErrorDescription(error) }, { level: 'warn' });
7529
+ }
7426
7530
  const directory = releaseNotesDir(elanousStateRoot());
7427
7531
  const fragment: ReleaseNoteFragment = {
7428
7532
  pr: pr.number,
@@ -0,0 +1,62 @@
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[]; warning?: string };
7
+
8
+ /** Compare the worktree's exact bytes with blobs from recent HEAD ancestry, before add -A. */
9
+ export function dropStaleReverts(cwd: string, targets: readonly string[], runId: string, depth = 50): RevertGuardResult {
10
+ const result: RevertGuardResult = { reverted: [], protected: [] };
11
+ const git = (...args: string[]): Buffer => execFileSync('git', args, { cwd, timeout: 20_000, maxBuffer: 16 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'] });
12
+ const reason = (error: unknown): string => (error instanceof Error ? error.message : String(error)).replace(/\s+/g, ' ').slice(0, 240);
13
+ try {
14
+ const limit = Number.isSafeInteger(depth) && depth > 0 ? depth : 50;
15
+ const commits = git('rev-list', `--max-count=${limit + 1}`, 'HEAD').toString('utf8').trim().split('\n');
16
+ const changed = git('diff', '--name-only', '--diff-filter=M', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean);
17
+ const staged = git('diff', '--cached', '--name-only', '--diff-filter=M', '-z', 'HEAD', '--').toString('utf8').split('\0').filter(Boolean);
18
+ for (const file of new Set([...changed, ...staged])) {
19
+ try {
20
+ if (!lstatSync(join(cwd, file)).isFile()) continue;
21
+ const current = readFileSync(join(cwd, file));
22
+ const rawOid = execFileSync('git', ['hash-object', '--stdin'], { cwd, input: current, timeout: 20_000 }).toString('utf8').trim();
23
+ const headEntry = git('ls-tree', 'HEAD', '--', file).toString('utf8').trim();
24
+ const head = /^\d+ blob ([0-9a-f]+)\t/.exec(headEntry)?.[1];
25
+ if (!head) continue;
26
+ const sameBytes = (oid: string): boolean => oid === rawOid || git('cat-file', 'blob', oid).equals(current);
27
+ if (sameBytes(head)) continue;
28
+ let matchedCommit: string | undefined;
29
+ const inspectedOids = new Set<string>();
30
+ for (const commit of commits.slice(1)) {
31
+ // A path may not have existed in an ancestor; that is not a failed inspection.
32
+ const blob = git('ls-tree', commit, '--', file).toString('utf8').trim();
33
+ const oid = /^\d+ blob ([0-9a-f]+)\t/.exec(blob)?.[1];
34
+ if (oid && !inspectedOids.has(oid)) {
35
+ if (sameBytes(oid)) { matchedCommit = commit; break; }
36
+ inspectedOids.add(oid);
37
+ }
38
+ }
39
+ if (!matchedCommit) continue;
40
+ const scoped = targets.some((target) => {
41
+ const path = target.replace(/^\.\//, '').replace(/\/+$/, '');
42
+ return path !== '' && (file === path || file.startsWith(`${path}/`));
43
+ });
44
+ if (scoped) {
45
+ result.protected.push(file);
46
+ debug.log('self-implement.revert-guard', 'protected-target', { runId, files: [file], matchedCommit }, { level: 'warn' });
47
+ } else {
48
+ git('checkout', 'HEAD', '--', file);
49
+ result.reverted.push(file);
50
+ debug.log('self-implement.revert-guard', 'reverted-stale', { runId, files: [file], matchedCommit });
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
+ } catch (error) {
58
+ result.warning = `git inspection failed: ${reason(error)}`;
59
+ debug.log('self-implement.revert-guard', 'fail-open', { runId, reason: result.warning }, { level: 'warn' });
60
+ }
61
+ return result;
62
+ }
@@ -276,7 +276,7 @@ interface RunLedgerLookupTarget {
276
276
  targetName: string | null;
277
277
  }
278
278
 
279
- function resolveFederatedRunLedgerTargets(options: Pick<FederatedRunLedgerLookupOptions, 'includeTest' | 'targets'>): RunLedgerLookupTarget[] {
279
+ export function resolveFederatedRunLedgerTargets(options: Pick<FederatedRunLedgerLookupOptions, 'includeTest' | 'targets'>): RunLedgerLookupTarget[] {
280
280
  const resolved = options.targets ? { targets: [...options.targets] } : resolveLogTargets({ all: true, includeTest: options.includeTest });
281
281
  if (resolved.error) throw new Error(resolved.error);
282
282
  const targets = new Map<string, string>();
@@ -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,
@@ -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. */
@@ -1,7 +1,7 @@
1
1
  import type { Command } from 'commander';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { scanWishFolder } from '../intake-plane/wish-folder.js';
4
- import { LINEAR_WISH_KEY_MISSING, scanLinearWishes } from '../intake-plane/linear-wish.js';
4
+ import { LINEAR_WISH_KEY_MISSING, scanLinearWishes, suggestLinearWishes } from '../intake-plane/linear-wish.js';
5
5
  import type { fetchLinearIssues } from '../connectors/linear.js';
6
6
  import { CardStore, type TaskCard } from './card-store.js';
7
7
 
@@ -145,10 +145,18 @@ export function registerCardCommand(program: Command, deps: CardCliDeps = {}): C
145
145
  .description('Create wish cards from open Linear issues')
146
146
  .requiredOption('--team <key>', 'Linear team key')
147
147
  .option('--label <name>', 'Wish label or title prefix', 'wish')
148
+ .option('--suggest', 'Suggest wish labels without writing to Linear or creating cards')
148
149
  .option('--json', 'Output JSON')
149
- .action(async (options: { team: string; label: string; json?: boolean }) => {
150
+ .action(async (options: { team: string; label: string; suggest?: boolean; json?: boolean }) => {
150
151
  let store: CardStore | undefined;
151
152
  try {
153
+ if (options.suggest) {
154
+ const suggestions = await suggestLinearWishes({ teamKey: options.team,
155
+ deps: { getApiKey: deps.getApiKey, fetchIssues: deps.fetchIssues } });
156
+ write(options.json ? `${JSON.stringify(suggestions)}\n` :
157
+ suggestions.map(item => `${item.identifier}\t${item.title}\t${item.reason}\n`).join(''));
158
+ return;
159
+ }
152
160
  store = createStore();
153
161
  const result = await scanLinearWishes({ teamKey: options.team, label: options.label, store,
154
162
  deps: { getApiKey: deps.getApiKey, fetchIssues: deps.fetchIssues } });
@@ -42,7 +42,7 @@ export const SECTION_OWNERS: Readonly<Record<string, string | null>> = {
42
42
  gates: 'execution-loop', relations: 'execution-loop', memory: 'execution-loop',
43
43
  workspace: 'executor', run: 'executor',
44
44
  landing: 'landing-loop', release: 'release-loop', 'test-diet': 'test-diet',
45
- incidents: null,
45
+ incidents: null, orch: 'orchestrator', flow: 'flow',
46
46
  };
47
47
 
48
48
  export function sectionOf(key: string): string {
@@ -80,10 +80,11 @@ export class CardStore {
80
80
  private readonly db: Database;
81
81
  readonly root: string;
82
82
 
83
- constructor(root: string = elanousStateRoot()) {
83
+ constructor(root: string = elanousStateRoot(), private readonly readOnly = false) {
84
84
  this.root = root;
85
- mkdirSync(taskCardsDir(root), { recursive: true });
86
- this.db = new Database(cardIndexPath(root));
85
+ if (!readOnly) mkdirSync(taskCardsDir(root), { recursive: true });
86
+ this.db = readOnly ? new Database(cardIndexPath(root), { readonly: true }) : new Database(cardIndexPath(root));
87
+ if (readOnly) return;
87
88
  try {
88
89
  this.db.exec('PRAGMA busy_timeout = 5000');
89
90
  this.db.exec('PRAGMA journal_mode = WAL');
@@ -127,6 +128,7 @@ export class CardStore {
127
128
  }
128
129
 
129
130
  private reconcile(): void {
131
+ if (this.readOnly) return;
130
132
  for (const file of readdirSync(taskCardsDir(this.root))) {
131
133
  if (!/^[a-zA-Z0-9_-]+\.jsonl$/.test(file)) continue;
132
134
  const id = file.slice(0, -'.jsonl'.length);
@@ -220,6 +222,7 @@ export class CardStore {
220
222
  }
221
223
 
222
224
  getCard(id: string): TaskCard | null {
225
+ if (this.readOnly) return this.readCard(id);
223
226
  return this.db.transaction(() => {
224
227
  this.reconcile();
225
228
  return this.readCard(id);
@@ -227,12 +230,13 @@ export class CardStore {
227
230
  }
228
231
 
229
232
  listCards(options: { open?: boolean } = {}): TaskCard[] {
230
- return this.db.transaction(() => {
233
+ const list = () => {
231
234
  this.reconcile();
232
235
  const rows = this.db.query(`SELECT id FROM cards ${options.open ? "WHERE status = 'open'" : ''} ORDER BY created_at DESC, id DESC`)
233
236
  .all() as Array<{ id: string }>;
234
237
  return rows.map(({ id }) => this.readCard(id)!);
235
- }).immediate();
238
+ };
239
+ return this.readOnly ? list() : this.db.transaction(list).immediate();
236
240
  }
237
241
 
238
242
  close(): void {
@@ -12,12 +12,15 @@ export const POD_HOST_LEASE_ANNOTATION = 'elanous.dev/host-lease-admitted';
12
12
 
13
13
  // `get pods --all-namespaces -o json` on a busy cluster is several MB — the default spawnSync
14
14
  // buffer fails with ENOBUFS (10-03 node-b measured). Same proxy stripping as the Pod launch kubectl.
15
- const LEASE_KUBECTL_MAX_BUFFER = 256 * 1024 * 1024;
15
+ export const LEASE_KUBECTL_MAX_BUFFER = 256 * 1024 * 1024;
16
16
  export function leaseKubectl(args: readonly string[], input?: string): { status: number | null; stdout: string; stderr: string } {
17
17
  const env = { ...process.env };
18
18
  for (const k of ['HTTPS_PROXY', 'https_proxy', 'HTTP_PROXY', 'http_proxy', 'ALL_PROXY', 'all_proxy']) delete env[k];
19
19
  const r = spawnSync('kubectl', [...args], { encoding: 'utf8', input, env, timeout: 120_000, maxBuffer: LEASE_KUBECTL_MAX_BUFFER });
20
- return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? String(r.error) : '') };
20
+ const error = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ENOBUFS'
21
+ ? `kubectl 출력 너무 큼 (ENOBUFS; maxBuffer=${LEASE_KUBECTL_MAX_BUFFER} bytes)`
22
+ : r.error ? String(r.error) : '';
23
+ return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + error };
21
24
  }
22
25
 
23
26
  export interface PodLeaseMember {