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
@@ -260,9 +260,56 @@ function skipOverlappingRun(root: string, open: FailureStreak['open'] & { pid?:
260
260
  }
261
261
  debug.log('steward.stage', 'skipped-overlap', { openStage: open.stage, pid: open.pid });
262
262
  writeFileSync(skippedPath(root), JSON.stringify({ openStage: open.stage, pid: open.pid }));
263
+ logTickDecision(root, 'skipped', `overlapping ${open.stage} (pid ${open.pid ?? 'unknown'})`);
263
264
  }
264
265
 
265
266
  function reportPath(root: string): string { return join(root, 'steward', 'observe.json'); }
267
+
268
+ type TickDecision = { verdict: 'launched' | 'failed'; reason: string; goalId?: string };
269
+
270
+ function logTickDecision(root: string | undefined, verdict: 'launched' | 'skipped' | 'reported' | 'failed', reason: string, goalId?: string): void {
271
+ const runId = graphRunId();
272
+ const marker = root && runId && existsSync(join(root, 'steward')) ? join(root, 'steward', `decision-logged-${runId}.json`) : undefined;
273
+ const decision = { verdict, reason: redactSecretText(reason), ...(goalId ? { goalId } : {}), ...(runId ? { runId } : {}) };
274
+ if (marker) try {
275
+ const previous = JSON.parse(readFileSync(marker, 'utf8')) as { verdict?: string; reason?: string };
276
+ // A replay keeps the first success; a failure replaces a success; a success after a failure replaces the failure.
277
+ if (previous.verdict === decision.verdict && previous.reason === decision.reason) return;
278
+ if (previous.verdict !== 'failed' && verdict !== 'failed') return;
279
+ } catch { /* Missing or stale marker: retry the observation. */ }
280
+ try {
281
+ debug.log('loop.steward', 'decision', decision);
282
+ if (marker) writeFileSync(marker, JSON.stringify(decision));
283
+ } catch { /* Observation must not change the tick outcome. */ }
284
+ }
285
+
286
+ function tickDecisionPath(root: string, runId: string): string {
287
+ return join(root, 'steward', `decision-${runId}.json`);
288
+ }
289
+ function rememberLaunchDecision(root: string, entry: TickDecision): void {
290
+ const runId = graphRunId();
291
+ if (!runId) return;
292
+ const path = tickDecisionPath(root, runId);
293
+ try {
294
+ let current: TickDecision | undefined;
295
+ try { current = JSON.parse(readFileSync(path, 'utf8')) as TickDecision; }
296
+ catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') return; }
297
+ // Keep the first failure (and its goal/cause), even if a retried schedule later succeeds.
298
+ if (current?.verdict === 'failed' || current?.verdict === entry.verdict) return;
299
+ writeFileSync(path, JSON.stringify(entry));
300
+ } catch { /* An unavailable observation store must not block execution. */ }
301
+ }
302
+ function readLaunchDecision(root: string): TickDecision | undefined {
303
+ const runId = graphRunId();
304
+ if (!runId) return undefined;
305
+ try { return JSON.parse(readFileSync(tickDecisionPath(root, runId), 'utf8')) as TickDecision; }
306
+ catch { return undefined; }
307
+ }
308
+ function logStageFailure(root: string, stage: StewardStage, reason: string): void {
309
+ const runId = graphRunId();
310
+ if (runId) try { rmSync(tickDecisionPath(root, runId), { force: true }); } catch { /* Observation must not change the failure. */ }
311
+ logTickDecision(root, 'failed', `${stage}: ${reason}`);
312
+ }
266
313
  function readState(path: string): { issues: Record<string, string>; digestDay?: string } {
267
314
  try { return JSON.parse(readFileSync(path, 'utf8')) as ReturnType<typeof readState>; }
268
315
  catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return { issues: {} }; throw error; }
@@ -396,10 +443,18 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
396
443
  const entry = raiseHitl(issue, row, launchDeps);
397
444
  recordHitlOnCard(issue, entry, cardDeps);
398
445
  }
446
+ let launchDecision: TickDecision | undefined;
399
447
  for (const item of plan.launches) {
400
448
  const entry = launch(item, launchDeps);
449
+ const decision: TickDecision | undefined = entry.status === 'launched'
450
+ ? { verdict: 'launched', reason: 'harness run spawned', goalId: `linear:${item.issue.identifier}` }
451
+ : entry.status === 'failed'
452
+ ? { verdict: 'failed', reason: entry.reason ?? 'launch failed', goalId: `linear:${item.issue.identifier}` }
453
+ : undefined;
454
+ if (decision && (!launchDecision || (decision.verdict === 'failed' && launchDecision.verdict !== 'failed'))) launchDecision = decision;
401
455
  recordLaunchOnCard(item.issue, entry, cardDeps);
402
456
  }
457
+ if (launchDecision) rememberLaunchDecision(root, launchDecision);
403
458
  if (settings.mode === 'shadow') {
404
459
  const { recordShadowTick } = await import('./readiness.js');
405
460
  const tick = recordShadowTick(root, rows, plan, (deps.now ?? (() => new Date()))());
@@ -459,12 +514,18 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
459
514
  }
460
515
  }
461
516
  const day = at.slice(0, 10);
517
+ let digested = false;
462
518
  if (state.digestDay !== day) {
463
519
  const send = deps.sendDigest ?? sendStewardDigest;
464
520
  await send(`스튜어드 ${day}: ${rows.map(row => `${row.issue} ${row.disposition}`).join(' · ')}`);
465
521
  state.digestDay = day;
466
522
  writeFileSync(path, JSON.stringify(state));
523
+ digested = true;
467
524
  }
525
+ const launched = readLaunchDecision(root);
526
+ if (launched) logTickDecision(root, launched.verdict, launched.reason, launched.goalId);
527
+ else if (outcomes.length || digested) logTickDecision(root, 'reported', outcomes.length ? `${outcomes.length} outcome(s) reported` : 'daily digest sent');
528
+ else logTickDecision(root, 'skipped', 'no new outcome or digest');
468
529
  }
469
530
  outcome = 'ok';
470
531
  } finally {
@@ -513,10 +574,14 @@ async function withStageSlot<T>(root: string, run: () => Promise<T>, onBusy?: ()
513
574
 
514
575
  /** The command-node boundary records both normal exits and an unclosed stage from a killed process. */
515
576
  export async function runStewardStageCommand(stage: StewardStage, deps: StewardCommandDeps = {}): Promise<0 | 1> {
516
- if (resolvedStewardSettings(deps.launchSettings).mode === 'off') return 0;
577
+ if (resolvedStewardSettings(deps.launchSettings).mode === 'off') {
578
+ if (stage === 'sync') logTickDecision(deps.root ?? effectiveInstanceRoot(), 'skipped', 'steward mode off');
579
+ return 0;
580
+ }
517
581
  const root = deps.root ?? effectiveInstanceRoot();
518
582
  if (stage !== 'sync' && existsSync(skippedPath(root))) return 0;
519
- return withStageSlot(root, async () => {
583
+ try {
584
+ return await withStageSlot(root, async () => {
520
585
  const now = deps.now ?? (() => new Date());
521
586
  const threshold = stewardSettings().alertAfterFailures ?? 3;
522
587
  const alertIfNeeded = async (state: FailureStreak): Promise<void> => {
@@ -564,10 +629,16 @@ export async function runStewardStageCommand(stage: StewardStage, deps: StewardC
564
629
  if (!ok) {
565
630
  console.error(`steward ${stage}: stage failed — ${reason}`);
566
631
  debug.log('steward.stage', 'failed', { stage, reason });
632
+ if (stage === 'sync' || !existsSync(skippedPath(root))) logStageFailure(root, stage, reason);
567
633
  return 1;
568
634
  }
569
635
  return 0;
570
- }, stage === 'sync' ? () => { skipOverlappingRun(root, currentOpen(root, stage)); return 0 as const; } : undefined);
636
+ }, stage === 'sync' ? () => { skipOverlappingRun(root, currentOpen(root, stage)); return 0 as const; } : undefined);
637
+ } catch (error) {
638
+ try { if (stage === 'sync' || !existsSync(skippedPath(root))) logStageFailure(root, stage, error instanceof Error ? error.message : String(error)); }
639
+ catch { /* Observation must not change the stage error. */ }
640
+ throw error;
641
+ }
571
642
  }
572
643
 
573
644
  export async function runStewardNodeCommand(
@@ -53,7 +53,10 @@ export async function recordWorkingBackwardsOnCards(
53
53
  }
54
54
  const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
55
55
  if (card.sections.some(section => section.key.startsWith('prfaq:')) && card.sections.some(section => section.key.startsWith('manual:'))) continue;
56
- if (card.sections.some(section => section.key.startsWith('launch:'))) throw new Error(`Working-backwards draft follows launch: ${issue.identifier}`);
56
+ if (card.sections.some(section => section.key.startsWith('launch:'))) {
57
+ debug.log('steward.working-backwards', 'skipped-launched-before-draft', { issueId: issue.identifier });
58
+ continue;
59
+ }
57
60
  const draft = await draftWorkingBackwards(issue, ask);
58
61
  for (const [section, content] of [['prfaq', draft.prfaq], ['manual', draft.manual]] as const) {
59
62
  const key = `${section}:${createHash('sha256').update(content).digest('hex')}`;
@@ -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 {
@@ -1,9 +1,9 @@
1
- import { lstatSync, mkdirSync, writeFileSync } from 'node:fs';
1
+ import { lstatSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
2
2
  import { isAbsolute, join, relative, resolve, sep } from 'node:path';
3
3
  import { gunzipSync } from 'node:zlib';
4
4
  import { debug } from '../../debug/log.js';
5
5
  import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
6
- import type { LogRecord } from '../../mss/logging/record.js';
6
+ import { fromJsonl, type LogRecord } from '../../mss/logging/record.js';
7
7
 
8
8
  const FILE_LIMIT = 5 * 1024 * 1024;
9
9
  const TOTAL_LIMIT = 20 * 1024 * 1024;
@@ -83,50 +83,58 @@ export function parsePodArtifactChunks(logs: string): ParseResult {
83
83
 
84
84
  const POD_LOGS_PATH = 'pod-logs/logs.jsonl';
85
85
 
86
- function childRunId(data: unknown): string | undefined {
87
- if (!data || typeof data !== 'object' || Array.isArray(data)) return undefined;
88
- const value = (data as Record<string, unknown>).runId;
89
- return typeof value === 'string' && value.length > 0 ? value : undefined;
90
- }
91
-
92
- function decisionRecord(line: string): { rec: LogRecord; surface: string } | undefined {
93
- let parsed: unknown;
94
- try { parsed = JSON.parse(line); } catch { return undefined; }
95
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) return undefined;
96
- const row = parsed as Record<string, unknown>;
97
- if (row.category !== 'harness.decision') return undefined;
98
- if (typeof row.ts !== 'string' || typeof row.event !== 'string') return undefined;
99
- const runId = childRunId(row.data);
100
- if (!runId) return undefined;
101
- const data = { ...(row.data as Record<string, unknown>), podJob: '', importedFrom: 'pod' as const };
102
- return {
103
- rec: {
104
- ts: row.ts,
105
- category: 'harness.decision',
106
- event: row.event,
107
- data,
108
- ...(typeof row.level === 'string' ? { level: row.level as LogRecord['level'] } : {}),
109
- ...(typeof row.trace_id === 'string' ? { trace_id: row.trace_id } : {}),
110
- },
111
- surface: typeof row.surface === 'string' && row.surface ? row.surface : 'pod',
112
- };
86
+ /** Last measured child ledger context when a failed Pod emitted no terminal JSON. */
87
+ export function podNoResultDiagnostic(ledger: string): string | null {
88
+ let stage: string | undefined;
89
+ let round: number | undefined;
90
+ let mustFix: string | undefined;
91
+ for (const line of ledger.split(/\r?\n/)) {
92
+ try {
93
+ const entry = JSON.parse(line) as { event?: unknown; data?: Record<string, unknown> };
94
+ if (!entry || typeof entry !== 'object' || !entry.data || typeof entry.data !== 'object' || typeof entry.event !== 'string' || entry.event === 'pod-ledger-incomplete') continue;
95
+ if (typeof entry.data.round === 'number' && Number.isSafeInteger(entry.data.round)) round = entry.data.round;
96
+ if (entry.event !== 'run-origin') stage = entry.event;
97
+ // `reviewed` carries pointers only (count + finding ids); the text stays in the review artifact.
98
+ if (entry.event === 'reviewed' && typeof entry.data.mustFix === 'number') {
99
+ const ids = Array.isArray(entry.data.findingIds) ? entry.data.findingIds.filter((id): id is string => typeof id === 'string') : [];
100
+ mustFix = `${entry.data.mustFix}${ids.length ? ` ${JSON.stringify(ids)}` : ''}`;
101
+ }
102
+ } catch { /* incomplete ledger line */ }
103
+ }
104
+ return stage ? `child terminal result missing; last ledger stage=${stage}; round=${round ?? 'unknown'}; mustFix=${mustFix ?? 'unknown'}` : null;
113
105
  }
114
106
 
115
- /** Import only harness.decision rows from a returned pod log into the launching host store. */
116
- export function importPodDecisionLogs(bytes: Buffer, job: string, store: LogStore | null = getDefaultLogStore()): { imported: number; skipped: number } {
107
+ /** Import valid JSONL log records into the launching host store, with durable per-job line receipts. */
108
+ export function importPodLogs(bytes: Buffer, job: string, store: LogStore | null = getDefaultLogStore()): { imported: number; skipped: number } {
117
109
  if (!store) return { imported: 0, skipped: 0 };
118
- const text = bytes.toString('utf8');
119
- const rows: Array<{ rec: LogRecord; surface: string }> = [];
110
+ const rows: Array<{ rec: LogRecord; surface: string; line: number }> = [];
120
111
  let skipped = 0;
121
- for (const line of text.split(/\r?\n/)) {
112
+ for (const [lineNumber, line] of bytes.toString('utf8').split(/\r?\n/).entries()) {
122
113
  if (!line) continue;
123
- const row = decisionRecord(line);
124
- if (!row) { skipped += 1; continue; }
125
- row.rec.data = { ...(row.rec.data as Record<string, unknown>), podJob: job };
126
- rows.push(row);
114
+ const rec = fromJsonl(line);
115
+ if (!rec || !Number.isFinite(Date.parse(rec.ts))) { skipped++; continue; }
116
+ const data = rec.data && typeof rec.data === 'object' && !Array.isArray(rec.data)
117
+ ? rec.data as Record<string, unknown>
118
+ : rec.data === undefined ? {} : { originalData: rec.data };
119
+ const surface = (rec as LogRecord & { surface?: unknown }).surface;
120
+ rows.push({
121
+ rec: { ...rec, data: { ...data, origin: 'pod', podJob: job } },
122
+ surface: typeof surface === 'string' && surface ? surface : 'pod',
123
+ line: lineNumber,
124
+ });
125
+ }
126
+ return { imported: store.insertPodBatch(job, rows), skipped };
127
+ }
128
+
129
+ function importReturnedLogs(bytes: Buffer, job: string, store: LogStore | null,
130
+ log: (category: string, event: string, data: Record<string, unknown>) => void): void {
131
+ try {
132
+ if (!store) throw new Error('host log store unavailable');
133
+ const imported = importPodLogs(bytes, job, store);
134
+ log('self-implement.pod', 'pod-logs-imported', { job, imported: imported.imported, skipped: imported.skipped });
135
+ } catch (error) {
136
+ debug.log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
127
137
  }
128
- if (rows.length > 0) store.insertBatch(rows);
129
- return { imported: rows.length, skipped };
130
138
  }
131
139
 
132
140
  /** Reject symlink parents as well as lexical traversal; never replace a host file. */
@@ -176,17 +184,24 @@ export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) =>
176
184
  podLogsReturned = true;
177
185
  const lines = bytes.length ? bytes.toString('utf8').split('\n').length - (bytes.at(-1) === 10 ? 1 : 0) : 0;
178
186
  log('self-implement.pod', 'pod-logs-returned', { job, path: destination, lines });
179
- try {
180
- const imported = importPodDecisionLogs(bytes, job, logStore);
181
- log('self-implement.pod', 'pod-decisions-imported', { job, imported: imported.imported, skipped: imported.skipped });
182
- } catch (error) {
183
- log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
184
- }
187
+ importReturnedLogs(bytes, job, logStore, log);
185
188
  }
186
189
  } catch (e) {
187
190
  const reason = (e as NodeJS.ErrnoException).code === 'EEXIST' ? 'exists' : e instanceof Error ? e.message : String(e);
188
- if ((e as NodeJS.ErrnoException).code === 'EEXIST') log('self-implement.pod', 'artifact-collect-skipped', { job, path, reason });
189
- else log('self-implement.pod', 'artifact-collect-incomplete', { job, path, reason });
191
+ if ((e as NodeJS.ErrnoException).code === 'EEXIST') {
192
+ log('self-implement.pod', 'artifact-collect-skipped', { job, path, reason });
193
+ if (path === podLogsPath) {
194
+ try {
195
+ const existing = resolve(dir, job, path);
196
+ if (!lstatSync(existing).isSymbolicLink() && readFileSync(existing).equals(bytes)) {
197
+ podLogsReturned = true;
198
+ importReturnedLogs(bytes, job, logStore, log);
199
+ }
200
+ } catch (error) {
201
+ debug.log('self-implement.pod', 'pod-logs-import-failed', { job, reason: error instanceof Error ? error.message : String(error) });
202
+ }
203
+ }
204
+ } else log('self-implement.pod', 'artifact-collect-incomplete', { job, path, reason });
190
205
  if (path === podLogsPath) podLogsMissingReason = reason;
191
206
  }
192
207
  }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { randomBytes } from 'node:crypto';
9
9
  import { spawnSync } from 'node:child_process';
10
+ import { mkdirSync, writeFileSync } from 'node:fs';
10
11
  import { isAbsolute, resolve } from 'node:path';
11
12
  import { getUserConfig } from '../../user-config.js';
12
13
  import { debug } from '../../debug/log.js';
@@ -35,6 +36,14 @@ export const POD_LITE_IMAGE = 'elanous-harness-lite:local';
35
36
 
36
37
  export const POD_LITE_MEMORY_LIMIT = '2Gi';
37
38
 
39
+ /** 요청은 한도를 넘을 수 없다(쿠버네티스가 Job 을 거부한다) — lite 2Gi 처럼 한도가 기본 요청보다 작으면 한도로 맞춘다. */
40
+ export function memoryRequestWithin(limit: string | undefined): string {
41
+ const gi = (value: string | undefined) => { const m = /^(\d+)Gi$/.exec(value ?? ''); return m ? Number(m[1]) : null; };
42
+ const request = gi(POD_CHILD_REQUESTS.memory);
43
+ const max = gi(limit);
44
+ return request !== null && max !== null && request > max ? limit! : POD_CHILD_REQUESTS.memory;
45
+ }
46
+
38
47
  /** POD7 must-fix: the skill list alone can't tell a network-only job from one that needs ffmpeg/browser — so lite is
39
48
  * only ever chosen when the caller says so (`lite`), and then only for network skills without a clone. Anything we
40
49
  * can't judge keeps the full image. A lite request that doesn't fit is refused with the reason, not silently widened. */
@@ -50,6 +59,26 @@ export function podCommandImageFor(skills: readonly string[], clone: boolean, li
50
59
 
51
60
  const COMMAND_LOG_CHARS = 80;
52
61
 
62
+ /** 자식 로그 전문 상한(바이트) — 넘으면 끝부분을 남긴다. config `pod.childLogMaxBytes` 가 양의 정수일 때만 그 값. */
63
+ export const POD_CHILD_LOG_MAX_BYTES_DEFAULT = 1_048_576;
64
+
65
+ export function podChildLogMaxBytes(read: () => number | undefined = () => getUserConfig().pod?.childLogMaxBytes): number {
66
+ const configured = read();
67
+ return typeof configured === 'number' && Number.isSafeInteger(configured) && configured > 0
68
+ ? configured
69
+ : POD_CHILD_LOG_MAX_BYTES_DEFAULT;
70
+ }
71
+
72
+ /** 크기 상한을 넘으면 끝부분을 남긴다(잘린 앞은 표시). */
73
+ export function tailBytes(text: string, maxBytes: number): string {
74
+ const bytes = Buffer.from(text);
75
+ if (bytes.length <= maxBytes) return text;
76
+ const cut = bytes.subarray(bytes.length - maxBytes);
77
+ let start = 0;
78
+ while (start < cut.length && (cut[start]! & 0xc0) === 0x80) start += 1;
79
+ return `…[앞 ${bytes.length - maxBytes}바이트 생략]\n${cut.subarray(start).toString('utf8')}`;
80
+ }
81
+
53
82
  const GATE = `ok=0
54
83
  for i in $(seq 1 60); do
55
84
  if curl -s -m 1 -o /dev/null http://host.orb.internal:31415/health || curl -s -m 1 -o /dev/null http://core.elanous-prod:8080/; then ok=0; else ok=$((ok+1)); fi
@@ -156,7 +185,7 @@ export function podCommandJobManifest(o: PodCommandJobInput): Record<string, unk
156
185
  initContainers: [{ name: 'isolation-gate', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never', command: ['bash', '-c'], args: [GATE] }],
157
186
  containers: [{
158
187
  name: 'child', image: o.image, imagePullPolicy: o.imagePullPolicy ?? 'Never',
159
- resources: { requests: { ...POD_CHILD_REQUESTS }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: '4' } },
188
+ resources: { requests: { ...POD_CHILD_REQUESTS, memory: memoryRequestWithin(o.memoryLimit) }, limits: { memory: o.memoryLimit ?? '16Gi', cpu: '4' } },
160
189
  command: ['bash', '-c'], args: [script],
161
190
  env: [
162
191
  ...(o.runId ? [{ name: 'ELANOUS_RUN_ID', value: o.runId }] : []),
@@ -214,12 +243,23 @@ export function podCommandSecretKeys(secret: Record<string, unknown> | null): st
214
243
  return Object.keys((secret.stringData as Record<string, string> | undefined) ?? {});
215
244
  }
216
245
 
246
+ export interface PodArtifactCollection {
247
+ /** 회수한 산출 파일 수(child.log 제외). 로그 조회 실패면 null. */
248
+ files: number | null;
249
+ /** 회수한 산출 파일의 상대 경로. */
250
+ names: string[];
251
+ /** 로그 조회 자체가 실패했을 때의 사유. 성공이면 null. */
252
+ error: string | null;
253
+ }
254
+
217
255
  export interface PodCommandResult {
218
256
  exitCode: number;
219
257
  artifactsDir: string;
220
258
  job: string;
221
259
  /** 이 런이 실제로 쓴 이미지(레지스트리 판 태그 또는 로컬 이름). */
222
260
  image?: string;
261
+ /** collectPodArtifacts 가 회수한 파일. 로그 조회 실패면 files=null · error=사유. runPodCommand 는 항상 채운다. */
262
+ artifacts?: PodArtifactCollection;
223
263
  }
224
264
 
225
265
  export interface RunPodCommandOptions {
@@ -255,6 +295,8 @@ export interface RunPodCommandOptions {
255
295
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
256
296
  env?: NodeJS.ProcessEnv;
257
297
  configHostMirror?: () => string | undefined;
298
+ /** 자식 로그 전문 상한(바이트). 생략 시 config `pod.childLogMaxBytes`, 없으면 1MiB. 넘으면 끝부분 우선. */
299
+ childLogMaxBytes?: number;
258
300
  }
259
301
 
260
302
  function jobFinished(types: string): 'complete' | 'failed' | null {
@@ -263,6 +305,19 @@ function jobFinished(types: string): 'complete' | 'failed' | null {
263
305
  return null;
264
306
  }
265
307
 
308
+ function artifactNames(logs: string): string[] {
309
+ const names = new Set<string>();
310
+ for (const line of logs.split(/\r?\n/)) {
311
+ const match = /^ELANOUS_POD_ARTIFACT (\S+) \d+\/\d+ /.exec(line);
312
+ if (!match) continue;
313
+ try {
314
+ const path = Buffer.from(match[1]!, 'base64url').toString('utf8');
315
+ if (path && !path.split('/').some((part) => part === '' || part === '.' || part === '..')) names.add(path);
316
+ } catch { /* 회수 쪽(collectPodArtifacts)이 불완전 사유를 남긴다 */ }
317
+ }
318
+ return [...names];
319
+ }
320
+
266
321
  function exitCodeFromPod(kubectl: Kubectl, namespace: string, job: string, state: 'complete' | 'failed'): number {
267
322
  const got = kubectl(['-n', namespace, 'get', 'pods', '-l', `job-name=${job}`, '-o', 'jsonpath={.items[0].status.containerStatuses[?(@.name=="child")].state.terminated.exitCode}']);
268
323
  const code = Number(got.stdout.trim());
@@ -316,7 +371,7 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
316
371
  }
317
372
  if (pool) {
318
373
  for (;;) {
319
- member = pool.tryAcquire();
374
+ member = await pool.tryAcquire();
320
375
  if (member) break;
321
376
  await sleep(options.pollMs ?? 15_000);
322
377
  }
@@ -387,12 +442,29 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
387
442
  if (finished) { state = finished; break; }
388
443
  await sleep(options.pollMs ?? 15_000);
389
444
  }
390
- const logs = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']).stdout;
391
- collectPodArtifacts(logs, {
392
- dir: options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`,
393
- job: name,
394
- log: (c, e, d) => log(c, e, d),
395
- });
445
+ const logsResult = kubectl(['-n', namespace, 'logs', `job/${name}`, '-c', 'child']);
446
+ const logsFailed = logsResult.status !== 0;
447
+ const logs = logsFailed ? '' : logsResult.stdout;
448
+ const artifactDir = options.artifactsRoot ?? `${effectiveInstanceRoot()}/pod-artifacts`;
449
+ let artifacts: PodArtifactCollection;
450
+ if (logsFailed) {
451
+ const reason = logsResult.stderr.trim() || `kubectl logs exit ${logsResult.status}`;
452
+ artifacts = { files: null, names: [], error: reason };
453
+ log('pod.command', 'artifact-collect-failed', { job: name, reason });
454
+ } else {
455
+ collectPodArtifacts(logs, {
456
+ dir: artifactDir,
457
+ job: name,
458
+ log: (c, e, d) => log(c, e, d),
459
+ });
460
+ const names = artifactNames(logs);
461
+ artifacts = { files: names.length, names, error: null };
462
+ }
463
+ // 표지가 없어도 알리는 경로는 실제로 존재해야 한다. 자식 로그 전문은 child.log (상한 넘으면 끝부분).
464
+ mkdirSync(artifactsDir, { recursive: true });
465
+ const maxBytes = options.childLogMaxBytes ?? podChildLogMaxBytes();
466
+ const childLog = logsFailed ? `산출 회수 못 함: ${artifacts.error}` : tailBytes(logs, maxBytes);
467
+ writeFileSync(resolve(artifactsDir, 'child.log'), childLog);
396
468
  const exitCode = exitCodeFromPod(kubectl, namespace, name, state);
397
469
  cleanupSecret();
398
470
  log('pod.command', 'job-finished', {
@@ -401,8 +473,11 @@ export async function runPodCommand(options: RunPodCommandOptions): Promise<PodC
401
473
  skills,
402
474
  llm: llm ?? null,
403
475
  command: command.join(' ').slice(0, COMMAND_LOG_CHARS),
476
+ artifactsDir,
477
+ artifacts,
478
+ ...(logsFailed ? { artifactError: `산출 회수 못 함: ${artifacts.error}` } : {}),
404
479
  });
405
- return { exitCode, artifactsDir, job: name, image: imageRef ?? image };
480
+ return { exitCode, artifactsDir, job: name, image: imageRef ?? image, artifacts };
406
481
  } catch (err) {
407
482
  cleanupSecret();
408
483
  throw err;
@@ -0,0 +1,48 @@
1
+ /** Extract a human-readable reason from a failed child Pod without treating transfer markers as child errors. */
2
+ export function extractPodFailureReason(input: {
3
+ logs: string;
4
+ logTailReason?: string;
5
+ containerReason?: string | null;
6
+ jobReason?: string;
7
+ deadlineSeconds?: number;
8
+ }): string {
9
+ if (input.jobReason === 'DeadlineExceeded') {
10
+ return `DeadlineExceeded: Job 수명 상한 ${input.deadlineSeconds ?? 'unknown'}초 초과`;
11
+ }
12
+ if (input.containerReason === 'OOMKilled') return 'OOMKilled: 컨테이너 메모리 한도 초과';
13
+
14
+ const isBookkeeping = (line: string): boolean =>
15
+ /^ELANOUS_(?:MEM|RUN_LEDGER|USAGE_ROLLUP|POD_)/u.test(line) || /^(?:at\s+\S|\.\.\.\s+\d+\s+more\b|cleanup\s+(?:complete|done|finished)\b)/iu.test(line);
16
+ const isError = (line: string): boolean =>
17
+ /\berror\b|\bfail(?:ed|ure)?\b|exception|fatal|denied|timed?\s*out|exceeded|overloaded|unavailable|exhausted|오류|실패/iu.test(line);
18
+ const meaningful: Array<{ text: string; terminal: boolean }> = [];
19
+ for (const line of input.logs.split(/\r\n|\n|\r/u)) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed || isBookkeeping(trimmed)) continue;
22
+ if (trimmed.startsWith('{')) {
23
+ let parsed: unknown;
24
+ try { parsed = JSON.parse(trimmed); } catch { /* An ordinary line may begin with a brace. */ }
25
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
26
+ const row = parsed as Record<string, unknown>;
27
+ if (typeof row.ok === 'boolean' && typeof row.stage === 'string') {
28
+ meaningful.length = 0;
29
+ if (row.ok === false && typeof row.error === 'string') {
30
+ const errorLines = row.error.split(/\r\n|\n|\r/u).map((part) => part.trim())
31
+ .filter((part) => part && !isBookkeeping(part));
32
+ const errorLine = [...errorLines].reverse().find(isError) ?? errorLines.at(-1);
33
+ if (errorLine) meaningful.push({ text: errorLine, terminal: true });
34
+ }
35
+ continue;
36
+ }
37
+ // A JSON transfer/observation is not a plain-text failure reason.
38
+ continue;
39
+ }
40
+ }
41
+ meaningful.push({ text: trimmed, terminal: false });
42
+ }
43
+ const errorLine = [...meaningful].reverse().find((line) => line.terminal || isError(line.text));
44
+ const reason = errorLine?.text.replace(/\/home\/[^/\s]+\//gu, '~/').slice(0, 240);
45
+ if (reason) return reason;
46
+ const why = input.logTailReason?.trim() || (input.logs.trim() ? '로그에 읽을 수 있는 오류 줄 없음' : '자식 로그 비어 있음');
47
+ return `사유 못 읽음: ${why.replace(/\/home\/[^/\s]+\//gu, '~/').slice(0, 240)}`;
48
+ }
@@ -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 {