elanous 0.2.4 → 0.2.5

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 (412) hide show
  1. package/README.md +3 -3
  2. package/apps/pwa/out/404/index.html +1 -1
  3. package/apps/pwa/out/404.html +1 -1
  4. package/apps/pwa/out/_next/static/chunks/06ef2eba-d1b99ac6d2ea663c.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/1413-7179a49332db5a80.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/150-25c277137a9638ab.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/1640-bc99ec23bbe29d12.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 1751.50b96ea9c37b49b6.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/2300-de059d0ab2a16630.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/2684-05b0a81e75298c16.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/3026-2ed7e317d9b02102.js +25 -0
  12. package/apps/pwa/out/_next/static/chunks/3611-77496ada38cd6358.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/3717-68957c4dc9ceec90.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/3929-46ba9af6f9d895cc.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/4015-62c5a3744698a8da.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/4217-3c4317a7905f9eeb.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/4240-3230f230fae2d7d5.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/4271-e53e075ebd48593b.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/4377.3b125fef79576dfd.js +5 -0
  20. package/apps/pwa/out/_next/static/chunks/4440-18bb160d95819f4e.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4853-35fdbe6bdbcdc0fc.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → 4daa2798-1363d725c79f3593.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/5007-6a3b30e23ff68a6e.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/5244-12b424c42070f5ff.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/{5791-bb82743fa6299562.js → 5348-bbf838e119661eee.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/5391-8793f88b03b30161.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5611-df00af3e4e4a61bb.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5890-dca2321773be829a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/6123-37c9dc57ddaa7ab0.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/6588-b7975304a302ab91.js +1 -0
  31. package/apps/pwa/out/_next/static/chunks/6604-c03961962b6273b6.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/7109-cf8c87e968e83a65.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7178-30f77877525a2efb.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/7262-871563fc9663730b.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/7294-f0eeebb92aa93459.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7429-881af44deed6958f.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/7527.943753b6ed829f09.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/764-a01e858525a190c0.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/7868-e35d5f7985ecd3f1.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8320-51484af7b01fdfa5.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 8617.465ca22a319c95b3.js} +1 -1
  42. package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8918-482b30ce2a8ebf4c.js} +3 -3
  43. package/apps/pwa/out/_next/static/chunks/9131-52f5fad51cceca04.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9238.2cd5f47f4bf7a2c3.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 9704-3221c102623c8d73.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-70c404c7bb6d7f0f.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/approvals/page-3ffd80166b1050aa.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f89e9a303515a033.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/board/page-4f403cd060442c95.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-9fa879ebf5959096.js → page-0ac5d053f0444d21.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/bots/page-4bcc63e272ae8205.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/chat/page-880734bd07d62882.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/control/{page-4472ddf1e2458e69.js → page-72bd384898d88955.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/design-check/page-1018bbc9586ecae5.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/editor/page-de86f8c38076dd7b.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/healthz/route-70c404c7bb6d7f0f.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/intake/page-cd6447196d8bafe6.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/layout-e733630c47fa102b.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/live/page-d1c1a8357b161875.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/market/page-60c514128e81d787.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-8b637b7e325e13c1.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/morning/{page-112f7e477bda77fd.js → page-bccce0babceae30f.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/not-found-63301d11dbe73836.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/observatory/page-a8cf0dc472060db2.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/page-02fba54881ccfa49.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/reflection/page-d5e8e90f0038f8de.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-b1ef6824f494466a.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/sessions/page-d589bb0f233b1009.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-c9bbaebef4cda059.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/settings/page-3639ce7916b92fed.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-7450ae7478955653.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/setup/layout-70c404c7bb6d7f0f.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/setup/page-b41ca4abfd618452.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/share/page-b333dd79ebee3fa9.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/showroom/page-8be1227492793655.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/tasks/page-6e03e9371b552e0d.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/term/page-911411e9fca18d03.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/trace/page-792170e649b37088.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/vault/page-c40463466937bc91.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-509c798b80384536.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/workflows/page-1cac2aa21d77c417.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9626799d9b4a2cd1.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-84cd419494e16b0f.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → f6f3850d-7f324c6566002113.js} +1 -1
  85. package/apps/pwa/out/_next/static/chunks/framework-361487d2da603917.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/main-app-607f5beb94cb2e07.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/main-d0222d219e29dddf.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/pages/_app-a26980a31126decc.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/pages/_error-edb2b37decf23e4d.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/{webpack-e5bea36f183b25e6.js → webpack-2bf59c720fe9542c.js} +1 -1
  91. package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +1 -0
  92. package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_buildManifest.js +1 -1
  93. package/apps/pwa/out/approvals/index.html +1 -1
  94. package/apps/pwa/out/approvals/index.txt +29 -27
  95. package/apps/pwa/out/autopilot/index.html +1 -1
  96. package/apps/pwa/out/autopilot/index.txt +29 -27
  97. package/apps/pwa/out/board/index.html +1 -1
  98. package/apps/pwa/out/board/index.txt +29 -27
  99. package/apps/pwa/out/botlab/index.html +1 -1
  100. package/apps/pwa/out/botlab/index.txt +26 -24
  101. package/apps/pwa/out/bots/index.html +1 -1
  102. package/apps/pwa/out/bots/index.txt +26 -24
  103. package/apps/pwa/out/chat/index.html +1 -1
  104. package/apps/pwa/out/chat/index.txt +26 -24
  105. package/apps/pwa/out/control/index.html +1 -1
  106. package/apps/pwa/out/control/index.txt +29 -27
  107. package/apps/pwa/out/design-check/index.html +1 -1
  108. package/apps/pwa/out/design-check/index.txt +29 -27
  109. package/apps/pwa/out/editor/index.html +1 -1
  110. package/apps/pwa/out/editor/index.txt +29 -27
  111. package/apps/pwa/out/healthz +1 -1
  112. package/apps/pwa/out/icons/elanous-16.png +0 -0
  113. package/apps/pwa/out/icons/elanous-180.png +0 -0
  114. package/apps/pwa/out/icons/elanous-192.png +0 -0
  115. package/apps/pwa/out/icons/elanous-32.png +0 -0
  116. package/apps/pwa/out/icons/elanous-512.png +0 -0
  117. package/apps/pwa/out/index.html +1 -1
  118. package/apps/pwa/out/index.txt +26 -24
  119. package/apps/pwa/out/intake/index.html +1 -1
  120. package/apps/pwa/out/intake/index.txt +29 -27
  121. package/apps/pwa/out/live/index.html +1 -1
  122. package/apps/pwa/out/live/index.txt +29 -27
  123. package/apps/pwa/out/manifest.webmanifest +5 -2
  124. package/apps/pwa/out/market/index.html +1 -1
  125. package/apps/pwa/out/market/index.txt +26 -24
  126. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  127. package/apps/pwa/out/missions/placeholder/index.txt +26 -24
  128. package/apps/pwa/out/morning/index.html +1 -1
  129. package/apps/pwa/out/morning/index.txt +29 -27
  130. package/apps/pwa/out/observatory/index.html +1 -1
  131. package/apps/pwa/out/observatory/index.txt +29 -27
  132. package/apps/pwa/out/reflection/index.html +1 -1
  133. package/apps/pwa/out/reflection/index.txt +27 -25
  134. package/apps/pwa/out/scheduler/index.html +1 -1
  135. package/apps/pwa/out/scheduler/index.txt +29 -27
  136. package/apps/pwa/out/sessions/index.html +1 -1
  137. package/apps/pwa/out/sessions/index.txt +26 -24
  138. package/apps/pwa/out/settings/devices/index.html +1 -1
  139. package/apps/pwa/out/settings/devices/index.txt +29 -27
  140. package/apps/pwa/out/settings/index.html +1 -1
  141. package/apps/pwa/out/settings/index.txt +29 -27
  142. package/apps/pwa/out/setup/done/index.html +1 -1
  143. package/apps/pwa/out/setup/done/index.txt +29 -27
  144. package/apps/pwa/out/setup/index.html +1 -1
  145. package/apps/pwa/out/setup/index.txt +29 -27
  146. package/apps/pwa/out/share/index.html +1 -1
  147. package/apps/pwa/out/share/index.txt +29 -27
  148. package/apps/pwa/out/showroom/index.html +1 -1
  149. package/apps/pwa/out/showroom/index.txt +26 -24
  150. package/apps/pwa/out/sw-strategies.js +44 -0
  151. package/apps/pwa/out/sw.js +6 -44
  152. package/apps/pwa/out/tasks/index.html +1 -1
  153. package/apps/pwa/out/tasks/index.txt +29 -27
  154. package/apps/pwa/out/term/index.html +1 -1
  155. package/apps/pwa/out/term/index.txt +29 -27
  156. package/apps/pwa/out/trace/index.html +1 -1
  157. package/apps/pwa/out/trace/index.txt +29 -27
  158. package/apps/pwa/out/vault/index.html +1 -1
  159. package/apps/pwa/out/vault/index.txt +29 -27
  160. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  161. package/apps/pwa/out/workflows/chat-ui/index.txt +29 -27
  162. package/apps/pwa/out/workflows/index.html +1 -1
  163. package/apps/pwa/out/workflows/index.txt +29 -27
  164. package/apps/pwa/out/workspace/index.html +1 -1
  165. package/apps/pwa/out/workspace/index.txt +29 -27
  166. package/apps/pwa/out/worktrees/index.html +1 -1
  167. package/apps/pwa/out/worktrees/index.txt +29 -27
  168. package/catalog/resources.yaml +6 -6
  169. package/docs/goal-context/steward-directives-2026-09-28.md +35 -37
  170. package/graphs/landing/landing-heal.yaml +1 -1
  171. package/graphs/release/recipes.yaml +8 -1
  172. package/graphs/release/release-loop.yaml +6 -1
  173. package/graphs/video/README.md +1 -0
  174. package/graphs/video/explainer-line.yaml +58 -0
  175. package/package.json +3 -1
  176. package/plugins/job-coach/.codex-plugin/plugin.json +18 -1
  177. package/plugins/job-coach/README.md +12 -0
  178. package/plugins/job-coach/connectors/ncs/server.ts +4 -3
  179. package/plugins/job-coach/graphs/gap-judge.ts +44 -0
  180. package/plugins/job-coach/graphs/recipes.yaml +27 -0
  181. package/plugins/job-coach/graphs/report-enterprise.yaml +28 -0
  182. package/plugins/job-coach/graphs/run-step.ts +211 -17
  183. package/plugins/job-coach/plugin.json +16 -3
  184. package/plugins/job-coach/skills/enterprise-ai-fit/SKILL.md +8 -0
  185. package/plugins/job-coach/skills/enterprise-competency/SKILL.md +8 -0
  186. package/plugins/job-coach/skills/enterprise-needs/SKILL.md +8 -0
  187. package/plugins/job-coach/skills/enterprise-roadmap/SKILL.md +8 -0
  188. package/plugins/job-coach/skills/enterprise-task-analysis/SKILL.md +8 -0
  189. package/plugins/job-coach/skills/interview-to-profile/SKILL.md +18 -1
  190. package/scripts/backup/elanous-backup.sh +4 -1
  191. package/scripts/build-docs-site.ts +4 -3
  192. package/scripts/coord-channel-watch.sh +23 -4
  193. package/scripts/coord-post.sh +49 -12
  194. package/scripts/coord-tracks.json +5 -5
  195. package/scripts/eval-bot-vs-grok.ts +79 -0
  196. package/scripts/eval-grok-live-baseline.ts +36 -0
  197. package/scripts/fixtures/front-route-corpus.sample.json +17 -0
  198. package/scripts/fleet/node-setup.sh +46 -7
  199. package/scripts/landing-heal/must-fix-match.ts +26 -0
  200. package/scripts/landing-heal/run-mission.ts +114 -20
  201. package/scripts/lib/front-route-measurement.ts +131 -0
  202. package/scripts/lib/tui-sim-keys.ts +42 -0
  203. package/scripts/measure-front-route-classifier.ts +56 -0
  204. package/scripts/media/ocr-leak-scan.sh +21 -0
  205. package/scripts/public-export.ts +2 -3
  206. package/scripts/public-import-pr.ts +80 -6
  207. package/scripts/publish-internal-dist.ts +2 -3
  208. package/scripts/red-sweep-diff.ts +1 -1
  209. package/scripts/release-loop/auto-approve-node.ts +72 -0
  210. package/scripts/release-loop/gate-node.ts +332 -44
  211. package/scripts/release-loop/publish-node.ts +7 -3
  212. package/scripts/release-loop/release-docs.ts +20 -13
  213. package/scripts/release-loop/test-durations.ts +63 -0
  214. package/scripts/release-loop/upgrade-node.ts +3 -2
  215. package/scripts/test-audit-coverage-per-file.sh +16 -0
  216. package/scripts/test-audit-meaningless.ts +88 -0
  217. package/scripts/test-audit-overlap-and-weak.ts +73 -0
  218. package/scripts/test-deterministic.ts +27 -5
  219. package/scripts/tui-sim.ts +59 -14
  220. package/scripts/ux/bump-installed.sh +20 -0
  221. package/scripts/ux/lib/cdp.ts +72 -0
  222. package/scripts/ux/record-page.ts +50 -0
  223. package/skills/explainer-video/SKILL.md +99 -0
  224. package/skills/explainer-video/engine/brand/elanous-mark-on-dark.svg +1 -0
  225. package/skills/explainer-video/engine/build.mjs +431 -0
  226. package/skills/explainer-video/engine/vo.ts +37 -0
  227. package/skills/explainer-video/examples/elanous-0.2.5/script.json +155 -0
  228. package/src/agent/nested-agent-env.ts +1 -0
  229. package/src/agent-mission/driver.ts +95 -16
  230. package/src/agent-mission/mission-cli.ts +6 -0
  231. package/src/agent-mission/resource-ladder.ts +73 -25
  232. package/src/auth/acp-token.ts +16 -0
  233. package/src/auth/github-app-token.ts +112 -0
  234. package/src/autopilot/gh-rest-fallback.ts +177 -0
  235. package/src/autopilot/pr-manager.ts +51 -4
  236. package/src/boot/acp-server.ts +2 -16
  237. package/src/boot/daemon-tools/web-search.ts +2 -1
  238. package/src/boot/ws-bridge.ts +11 -0
  239. package/src/chat/index.ts +37 -8
  240. package/src/chat/log-scroll-key.ts +12 -0
  241. package/src/chat/submit-trace.ts +30 -0
  242. package/src/cli/agent-cli.ts +7 -2
  243. package/src/cli/decisions-cli.ts +94 -0
  244. package/src/cli/doctor-cli.ts +40 -54
  245. package/src/cli/doctor-readiness.ts +16 -24
  246. package/src/cli/intake-cli.ts +1 -1
  247. package/src/cli/mcp-call.ts +9 -0
  248. package/src/cli/mcp-gateway-cli.ts +7 -3
  249. package/src/cli/plugin-cli.ts +109 -11
  250. package/src/cli/pr-lineage.ts +2 -1
  251. package/src/cli/pwa-start.ts +3 -0
  252. package/src/cli/release-cli.ts +94 -7
  253. package/src/cli/self-update.ts +7 -4
  254. package/src/cli/start-cli.ts +33 -14
  255. package/src/dashboard/acp-boot.ts +3 -0
  256. package/src/dashboard/index.ts +62 -13
  257. package/src/dashboard/input/no-provider-reply.ts +13 -0
  258. package/src/dashboard/slash-runtime/unknown-slash-reply.ts +78 -0
  259. package/src/dashboard/stale-install-check.ts +77 -0
  260. package/src/decisions/decision-ledger.ts +404 -0
  261. package/src/domains/outbound-alert.ts +8 -2
  262. package/src/domains/telegram-kind-route.ts +21 -0
  263. package/src/execution-loop/dispatch-task.ts +139 -0
  264. package/src/execution-loop/gate-decision-summary.ts +29 -0
  265. package/src/execution-loop/gates.ts +197 -0
  266. package/src/graph-kinds/installed-plugin-nodes.ts +95 -0
  267. package/src/graph-kinds/plugin-nodes.ts +10 -2
  268. package/src/graph-runner/runner.ts +35 -5
  269. package/src/grounding/token.ts +12 -6
  270. package/src/harness/harness-cli-command.ts +68 -9
  271. package/src/harness/harness-pod-dispatch.ts +8 -2
  272. package/src/harness/harness-stop.ts +147 -18
  273. package/src/harness/self-send-decide.ts +26 -17
  274. package/src/harness/self-send-target.ts +22 -1
  275. package/src/harness/worktree-branch-prefix.ts +4 -3
  276. package/src/index.ts +56 -13
  277. package/src/instance/resolve.ts +6 -0
  278. package/src/intake-plane/collect-telegram-saved.ts +5 -0
  279. package/src/intake-plane/digest.ts +11 -2
  280. package/src/live/detail-switch.ts +8 -3
  281. package/src/llm/usable-llm.ts +32 -0
  282. package/src/loops/registry.ts +27 -5
  283. package/src/market/publish.ts +59 -3
  284. package/src/native-tool-catalog.ts +2 -2
  285. package/src/nexus/api/graph-approvals.ts +125 -0
  286. package/src/nexus/api/http-server.ts +60 -6
  287. package/src/nexus/api/log-fabric.ts +10 -1
  288. package/src/nexus/api/mcp-oauth-hint.ts +10 -9
  289. package/src/nexus/api/merge-approvals.ts +2 -1
  290. package/src/nexus/api/meta-api.ts +14 -0
  291. package/src/nexus/api/plugins-credentials-api.ts +47 -0
  292. package/src/nexus/api/plugins-market.ts +87 -3
  293. package/src/nexus/api/pod-credential-api.ts +68 -14
  294. package/src/nexus/api/public-routes.ts +7 -0
  295. package/src/nexus/api/trace.ts +57 -6
  296. package/src/nexus/boot/register-mcp-clients.ts +99 -14
  297. package/src/nexus/index.ts +33 -32
  298. package/src/nexus/outbound/router.ts +1 -1
  299. package/src/onboarding.ts +25 -2
  300. package/src/plugins/core/host.ts +14 -0
  301. package/src/plugins/install/market-fetch.ts +4 -3
  302. package/src/plugins/install/plugin-credentials.ts +106 -0
  303. package/src/plugins/install/plugin-install.ts +163 -32
  304. package/src/prompt-library/universal-preamble.ts +8 -2
  305. package/src/release-loop/checklist.ts +191 -0
  306. package/src/release-loop/release-note.ts +6 -3
  307. package/src/self-dev/dev-cli.ts +40 -20
  308. package/src/self-dev/dev-pipeline.ts +77 -1
  309. package/src/self-implement/gate-baseline.ts +3 -1
  310. package/src/self-implement/goal-author.ts +18 -1
  311. package/src/self-implement/host-regate.ts +6 -2
  312. package/src/self-implement/orchestrator.ts +7 -2
  313. package/src/self-implement/seams.ts +4 -2
  314. package/src/skills/tools/dev-harness.ts +42 -0
  315. package/src/steward/steward-cards.ts +45 -0
  316. package/src/steward/triage.ts +50 -14
  317. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +61 -3
  318. package/src/task-orchestrator/surfaces/pod-command-job.ts +27 -6
  319. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +39 -9
  320. package/src/task-orchestrator/surfaces/pod-source-bundle.ts +16 -1
  321. package/src/task-orchestrator/surfaces/pod-source-receive.ts +51 -13
  322. package/src/task-orchestrator/surfaces/self-implement-pod.ts +172 -28
  323. package/src/telegram-report.ts +12 -6
  324. package/src/tui-client/acp-transport-ws-client.ts +72 -5
  325. package/src/tui.ts +3 -2
  326. package/src/user-config.ts +28 -2
  327. package/src/util/spawn-sync-output.ts +15 -0
  328. package/src/version/code-revision.ts +1 -1
  329. package/src/version/packed-revision.json +1 -1
  330. package/src/web-search/free.ts +77 -0
  331. package/src/web-search/index.ts +2 -0
  332. package/src/workflow-runtime/deps-bridge.ts +4 -3
  333. package/src/workflow-runtime/plugin-kind-node.ts +32 -4
  334. package/src/workflow-runtime/types.ts +2 -1
  335. package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/1191-7b13a24117a689fa.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/2375-3fcae5186c64fe6b.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
  346. package/apps/pwa/out/_next/static/chunks/319-d37df1b6e0cbeff4.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
  348. package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
  349. package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
  350. package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
  351. package/apps/pwa/out/_next/static/chunks/4836-3c93795ab834381e.js +0 -1
  352. package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
  353. package/apps/pwa/out/_next/static/chunks/6090-1bd6077591603947.js +0 -1
  354. package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
  355. package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
  356. package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
  357. package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
  358. package/apps/pwa/out/_next/static/chunks/7457-f5cb1cc8db9f7453.js +0 -1
  359. package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
  360. package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
  361. package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
  362. package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +0 -1
  363. package/apps/pwa/out/_next/static/chunks/8142-011e1f474efb8b33.js +0 -1
  364. package/apps/pwa/out/_next/static/chunks/8519-0059d82bdb4812bf.js +0 -1
  365. package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
  366. package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
  367. package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
  368. package/apps/pwa/out/_next/static/chunks/9740-52467f244755917f.js +0 -1
  369. package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +0 -1
  370. package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
  371. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
  372. package/apps/pwa/out/_next/static/chunks/app/approvals/page-21c3261af6ab377c.js +0 -1
  373. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-1991445b8ea86274.js +0 -1
  374. package/apps/pwa/out/_next/static/chunks/app/board/page-4ebfaee2369fe248.js +0 -1
  375. package/apps/pwa/out/_next/static/chunks/app/bots/page-ca7c3cc305c3285a.js +0 -1
  376. package/apps/pwa/out/_next/static/chunks/app/chat/page-9951930add3d2051.js +0 -1
  377. package/apps/pwa/out/_next/static/chunks/app/design-check/page-4de08d4b766f134b.js +0 -1
  378. package/apps/pwa/out/_next/static/chunks/app/editor/page-7a975e2b9db1dfb7.js +0 -1
  379. package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
  380. package/apps/pwa/out/_next/static/chunks/app/intake/page-49270714ef35bb96.js +0 -1
  381. package/apps/pwa/out/_next/static/chunks/app/layout-33fbdf353e2a5982.js +0 -1
  382. package/apps/pwa/out/_next/static/chunks/app/live/page-259f122e2dd64317.js +0 -1
  383. package/apps/pwa/out/_next/static/chunks/app/market/page-65b82726753658e1.js +0 -1
  384. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-4b8df3748dc06285.js +0 -1
  385. package/apps/pwa/out/_next/static/chunks/app/not-found-542a5c0006da637c.js +0 -1
  386. package/apps/pwa/out/_next/static/chunks/app/observatory/page-63ffb347831262ea.js +0 -1
  387. package/apps/pwa/out/_next/static/chunks/app/page-98e4e359d97263b4.js +0 -1
  388. package/apps/pwa/out/_next/static/chunks/app/reflection/page-97e24ac675ce576e.js +0 -1
  389. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-ac6391f26391d89d.js +0 -1
  390. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9f45c9d45ffc43bd.js +0 -1
  391. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-36995b9cb5d98218.js +0 -1
  392. package/apps/pwa/out/_next/static/chunks/app/settings/page-0c795b577988d63f.js +0 -1
  393. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b4311e2f71c6735a.js +0 -1
  394. package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
  395. package/apps/pwa/out/_next/static/chunks/app/setup/page-eb2381074df20dba.js +0 -1
  396. package/apps/pwa/out/_next/static/chunks/app/share/page-b01336e64eba01ae.js +0 -1
  397. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7af7f21c1f2858ef.js +0 -1
  398. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a98ecd364586c89b.js +0 -1
  399. package/apps/pwa/out/_next/static/chunks/app/term/page-57f0502dfbfa089b.js +0 -1
  400. package/apps/pwa/out/_next/static/chunks/app/trace/page-3467d2e1abd8a14a.js +0 -1
  401. package/apps/pwa/out/_next/static/chunks/app/vault/page-7842ca23d53ba682.js +0 -1
  402. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-ae9ac882912b0198.js +0 -1
  403. package/apps/pwa/out/_next/static/chunks/app/workflows/page-3a0d72e3f783bcef.js +0 -1
  404. package/apps/pwa/out/_next/static/chunks/app/workspace/page-f3b39fe8358c2a97.js +0 -1
  405. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-5a22af3181ac2afb.js +0 -1
  406. package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
  407. package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
  408. package/apps/pwa/out/_next/static/chunks/main-app-1457627bb302a22c.js +0 -1
  409. package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
  410. package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
  411. package/apps/pwa/out/_next/static/css/e66b7f5efc8df3f3.css +0 -1
  412. /package/apps/pwa/out/_next/static/{zoQX5iTvXOsIvCjxCe1Ji → ktMqfrBbf8BMGac0jqLrb}/_ssgManifest.js +0 -0
@@ -15,8 +15,12 @@
15
15
  // self 는 backend 리스트 항목이 아니라 transport 축을 붕괴시키는 특수 갈래. 감독 축도 self=turn 레벨.
16
16
 
17
17
  import { execFileSync } from 'node:child_process';
18
+ import { createHash } from 'node:crypto';
18
19
  import { readFileSync } from 'node:fs';
19
- import { appendEvidenceLocationRequirement, extractVerbatimOriginalAsk, formatGoalFileLintFinding, inspectAskDecisionSignalMarker, leadingGoalMetadata, lintGoalFile, parseGoalId, parseGoalType, SUPERSEDED_BY_LINE, type GoalFileLintFinding, type GoalFileLintResult } from '../self-implement/goal-author.js';
20
+ import { dispatchTask, type DispatchTaskDeps } from '../execution-loop/dispatch-task.js';
21
+ import { getNestDepth } from '../agent/nest-depth.js';
22
+ import { appendRunLedgerEntry } from '../self-implement/run-ledger.js';
23
+ import { appendEvidenceLocationRequirement, extractVerbatimOriginalAsk, formatGoalFileLintFinding, inspectAskDecisionSignalMarker, leadingGoalMetadata, lintGoalFile, parseGoalId, parseGoalType, tracedPathReferences, SUPERSEDED_BY_LINE, type GoalFileLintFinding, type GoalFileLintResult } from '../self-implement/goal-author.js';
20
24
  import { createRepositoryReferencedFileReader, type ReferencedFileReader } from '../self-implement/goal-file-reader.js';
21
25
  import { parseGoalDocumentClarifications } from '../self-implement/goal-author-clarification.js';
22
26
  import { requiredEvidenceFromGoal } from '../self-implement/off-diff-evidence.js';
@@ -55,6 +59,8 @@ export type OrchestrateRuntime = Omit<OrchestrateSelfDevOptions, 'goals' | 'conc
55
59
  export interface DevMissionOpts {
56
60
  chain?: readonly ('codex' | 'claude' | 'elanous')[];
57
61
  plugin?: { plugin: string; marketplace: string };
62
+ resources?: 'on' | 'off';
63
+ backendExplicit?: string;
58
64
  headless?: boolean;
59
65
  evidence?: EvidenceMode;
60
66
  maxRounds?: number;
@@ -619,6 +625,8 @@ export function toAgentMissionSpec(
619
625
  ...(m.headless === true ? { headless: true } : {}),
620
626
  ...(m.chain ? { chain: m.chain } : {}),
621
627
  ...(m.plugin ? { plugin: m.plugin } : {}),
628
+ ...(m.resources !== undefined ? { resources: m.resources } : {}),
629
+ ...(m.backendExplicit !== undefined ? { backendExplicit: m.backendExplicit } : {}),
622
630
  ...(plan.base ? { base: plan.base } : {}),
623
631
  ...(plan.enhance !== undefined ? { enhance: plan.enhance } : {}),
624
632
  ...(m.maxRounds !== undefined ? { maxRounds: m.maxRounds } : {}),
@@ -644,6 +652,8 @@ export function buildAgentMissionDevSpec(o: {
644
652
  headless?: boolean;
645
653
  chain?: readonly ('codex' | 'claude' | 'elanous')[];
646
654
  plugin?: { plugin: string; marketplace: string };
655
+ resources?: 'on' | 'off';
656
+ backendExplicit?: string;
647
657
  /** --no-enhance 시 false(그 외 미지정 → driver 가 entry 로 구동). */
648
658
  enhanceOff?: boolean;
649
659
  evidence: EvidenceMode;
@@ -663,6 +673,8 @@ export function buildAgentMissionDevSpec(o: {
663
673
  ...(o.headless === true ? { headless: true } : {}),
664
674
  ...(o.chain ? { chain: o.chain } : {}),
665
675
  ...(o.plugin ? { plugin: o.plugin } : {}),
676
+ ...(o.resources !== undefined ? { resources: o.resources } : {}),
677
+ ...(o.backendExplicit !== undefined ? { backendExplicit: o.backendExplicit } : {}),
666
678
  maxRounds: o.maxRounds,
667
679
  commit: o.commit,
668
680
  entry: 'elanous-apparatus',
@@ -918,6 +930,10 @@ export interface DevHarnessDispatchArgs extends Record<string, unknown> {
918
930
  }
919
931
 
920
932
  export interface DevPipelineDeps {
933
+ dispatchTask?: typeof dispatchTask;
934
+ dispatchTaskDeps?: DispatchTaskDeps;
935
+ /** Caller has already recorded this dispatch decision at an outer entrance. */
936
+ skipDispatchTask?: boolean;
921
937
  /** Caller-managed human progress sink (for example SurfaceUx.progress). Absent callers retain byte-for-byte CLI stdout. */
922
938
  progress?: SurfaceUx['progress'];
923
939
  runSelfImplement?: (o: SelfImplementOptions) => Promise<SelfImplementResult>;
@@ -1777,6 +1793,65 @@ export async function runDevPipeline(spec: DevPipelineSpec, deps: DevPipelineDep
1777
1793
  }
1778
1794
  }
1779
1795
 
1796
+ async function observeDevTaskDispatch(spec: DevPipelineSpec, plan: ResolvedDevPlan, deps: DevPipelineDeps): Promise<void> {
1797
+ const skipped = plan.dispatch === 'interactive' && getNestDepth() > 0 ? 'nested-interactive'
1798
+ : plan.relaunch === true ? 'relaunch'
1799
+ : plan.dispatch === 'parallel' ? 'parallel-parent'
1800
+ : deps.skipDispatchTask === true ? 'skipDispatchTask'
1801
+ : process.env.ELANOUS_DISPATCH_RECORDED === '1' ? 'dispatch-recorded'
1802
+ // `bun test` preload sets this so pipeline tests do not run the real gates (usage/recall
1803
+ // lookups) on every launch; tests that inject a dispatcher or gates still observe.
1804
+ : process.env.ELANOUS_EXECUTION_LOOP_DISPATCH === 'off' && !deps.dispatchTask && !deps.dispatchTaskDeps ? 'disabled'
1805
+ : undefined;
1806
+ if (skipped) {
1807
+ try { debug.log('execution-loop.gate', 'dispatch-skipped', { reason: skipped, ...(spec.runId ? { runId: spec.runId } : {}) }); } catch { /* fail-soft */ }
1808
+ return;
1809
+ }
1810
+ try {
1811
+ const goalText = 'text' in plan.input ? plan.input.text : (deps.readFile ?? ((path: string) => readFileSync(path, 'utf8')))(plan.input.file);
1812
+ const goalId = 'file' in plan.input ? parseGoalId(goalText) : null;
1813
+ let recordedCardId: string | undefined;
1814
+ const recordCard = (cardId: string): void => {
1815
+ if (!spec.runId || recordedCardId) return;
1816
+ recordedCardId = cardId;
1817
+ try {
1818
+ appendRunLedgerEntry({ timestamp: new Date().toISOString(), runId: spec.runId,
1819
+ event: 'execution-loop-card', data: { cardId } });
1820
+ } catch (error) {
1821
+ try { debug.log('execution-loop.gate', 'ledger-unavailable', { reason: String(error) }); } catch { /* fail-soft */ }
1822
+ }
1823
+ };
1824
+ const dispatchDeps: DispatchTaskDeps = {
1825
+ ...deps.dispatchTaskDeps,
1826
+ onCardCreated: (cardId) => {
1827
+ recordCard(cardId);
1828
+ deps.dispatchTaskDeps?.onCardCreated?.(cardId);
1829
+ },
1830
+ };
1831
+ const work = Promise.resolve().then(() => (deps.dispatchTask ?? dispatchTask)({
1832
+ goalId: goalId ?? `request-${createHash('sha256').update(goalText).digest('hex').slice(0, 32)}`,
1833
+ title: goalText.split(/\r?\n/, 1)[0]?.trim() || 'Untitled request',
1834
+ goalText,
1835
+ targetPaths: 'file' in plan.input ? tracedPathReferences(goalText).map((reference) => reference.path) : [],
1836
+ spec,
1837
+ ...(spec.runId ? { runKey: spec.runId } : {}),
1838
+ }, dispatchDeps));
1839
+ // The default dispatcher bounds its gates to five seconds. Await its completed
1840
+ // observation so short-lived callers cannot exit before decisions reach the card.
1841
+ // Injected dispatchers retain an outer timeout to protect the original launch.
1842
+ let timeout: ReturnType<typeof setTimeout> | undefined;
1843
+ const result = await (deps.dispatchTask
1844
+ ? Promise.race([
1845
+ work,
1846
+ new Promise<never>((_, reject) => { timeout = setTimeout(() => reject(new Error('dispatch timeout after 5000ms')), 5_000); }),
1847
+ ]).finally(() => { if (timeout) clearTimeout(timeout); })
1848
+ : work);
1849
+ recordCard(result.cardId);
1850
+ } catch (error) {
1851
+ try { debug.log('execution-loop.gate', 'dispatch-unavailable', { reason: String(error) }); } catch { /* fail-soft */ }
1852
+ }
1853
+ }
1854
+
1780
1855
  async function runDevPipelineDispatch(
1781
1856
  spec: DevPipelineSpec,
1782
1857
  deps: DevPipelineDeps,
@@ -1785,6 +1860,7 @@ async function runDevPipelineDispatch(
1785
1860
  ): Promise<DevPipelineRunResult> {
1786
1861
  const plan = planDevPipeline(spec);
1787
1862
  seen.plan = plan;
1863
+ await observeDevTaskDispatch(spec, plan, deps);
1788
1864
  if (!plan.wired) {
1789
1865
  throw new DevPipelineError(
1790
1866
  `runDevPipeline: '${plan.dispatch}' 경로는 U4 골격에서 미배선(NotYetUnified). ` +
@@ -682,7 +682,9 @@ function runIsolatedBunTest(cwd: string, args: string[], timeout: number): Proce
682
682
  return { status: null, stdout: '', stderr: message, error: error instanceof Error ? error : new Error(message) };
683
683
  }
684
684
  try {
685
- return spawnSync('bun', ['test', ...args], { cwd, env: isolated.env, encoding: 'utf8', timeout, maxBuffer: 32 * 1024 * 1024 });
685
+ // SIGKILL — a test that traps SIGTERM while blocked in a native wait never runs its handler, so spawnSync
686
+ // waited forever and its drive grandchildren lived on as ppid=1 orphans (09-30 O1: 19 processes, up to 11h).
687
+ return spawnSync('bun', ['test', ...args], { cwd, env: isolated.env, encoding: 'utf8', timeout, killSignal: 'SIGKILL', maxBuffer: 32 * 1024 * 1024 });
686
688
  } finally {
687
689
  isolated.cleanup();
688
690
  }
@@ -32,6 +32,7 @@ import { quoteShellArg } from '../cli/logs-cli.js';
32
32
  import type { HarnessGroundingProvenance } from './context-capsule.js';
33
33
  import { hasGoalStepCodeName } from './goal-step-code-name.js';
34
34
  import { targetScopedGoalText } from './goal-text-path-scope.js';
35
+ import { parseReleaseNoteSection } from '../release-loop/release-note.js';
35
36
 
36
37
  // ⭐ `situation`/`complication` 은 **선택**이다 — 안 오면 `scqaNarrative` 가 종전 문면을 쓴다.
37
38
  // ⛔ 필수로 만들지 않는다: 그러면 인핸싱이 폴백으로 떨어진 저작(LLM 실패·`disabled`)이 전부 죽는다.
@@ -334,7 +335,7 @@ function endGoalAuthorPhase(phase: GoalAuthorTimedPhase, startedAt: number, auth
334
335
 
335
336
  export type GoalFileLintLevel = 'ERROR' | 'WARN';
336
337
 
337
- export type GoalFileLintTag = 'canonical-structure' | 'evidence-section' | 'boundary-size' | 'launch-branch' | 'unanswered-clarification' | 'shell-damage' | 'traced-path' | 'grounding-evidence' | 'empty-result-population' | 'decision-signal-numeric-source' | 'decision-signal-numeric-coverage' | 'decision-signal-proxy-expectation' | 'out-of-target-requirement' | 'heading-form-marker' | 'blanket-invariant' | 'self-question-subject' | 'artifact-launch-declaration' | 'all-negative-signals' | 'unreadable-signals' | 'alternative-signals' | 'count-observation' | 'identifier-name-observation' | 'self-reported-observation' | 'default-invocation-observation';
338
+ export type GoalFileLintTag = 'canonical-structure' | 'evidence-section' | 'boundary-size' | 'launch-branch' | 'unanswered-clarification' | 'shell-damage' | 'traced-path' | 'grounding-evidence' | 'empty-result-population' | 'decision-signal-numeric-source' | 'decision-signal-numeric-coverage' | 'decision-signal-proxy-expectation' | 'out-of-target-requirement' | 'heading-form-marker' | 'blanket-invariant' | 'self-question-subject' | 'artifact-launch-declaration' | 'all-negative-signals' | 'unreadable-signals' | 'alternative-signals' | 'count-observation' | 'identifier-name-observation' | 'self-reported-observation' | 'default-invocation-observation' | 'release-note';
338
339
 
339
340
  type GoalFileLintOrigin =
340
341
  | { readonly kind: 'known-incident'; readonly incident: string; readonly reference: string }
@@ -369,6 +370,7 @@ export const GOAL_FILE_LINT_ORIGINS: Record<GoalFileLintTag, GoalFileLintOrigin>
369
370
  'identifier-name-observation': { kind: 'known-incident', incident: "an authored observation of 'the list of test names this file registers' produced a test that greps its own source for test-name strings, which passes with every named test body emptied; 59 of 3164 goal documents carried the same wording", reference: 'PR #15791 (closed; superseded by #15802); docs/harness/observability/ISSUES.md OBS-T415' },
370
371
  'self-reported-observation': { kind: 'known-incident', incident: "an authored observation of 'the captureScope field of the emitted JSON' stayed green across two failed implementations: a Chrome CLI flag Chrome silently ignores (capture stayed 1280x900 against a true 4651) and a CDP call that timed out leaving no file at all, both of which still emitted captureScope:'full-page'", reference: 'docs/manual/MANUAL-web-clone-to-reproducible-resource-2026-09-08.md section 6b; PR #16201; measured 2026-09-08: 190 of 2867 goal documents that carry a decision signal match this shape, three times the 63 that match identifier-name-observation' },
371
372
  'default-invocation-observation': { kind: 'known-incident', incident: 'a scripts/*.ts checker landed green from fixture tests and, run once with no arguments from the repository root, reported covered 1 and uncovered 22 because its default catalog path never opened the map the goal named', reference: 'PR #19126; measured 2026-09-20 against the same-day controls #19123 and #19127' },
373
+ 'release-note': { kind: 'unknown-origin', label: 'ORIGIN-UNKNOWN' },
372
374
  } as const satisfies Record<GoalFileLintTag, GoalFileLintOrigin>;
373
375
 
374
376
  /** Machine-countable reason for a finding that retains the canonical-structure tag. */
@@ -1452,6 +1454,21 @@ export function lintGoalFile(document: string, branch: string, deps: GoalFileLin
1452
1454
  for (const finding of askSectionCountLintFindings(document)) findings.push(finding);
1453
1455
  for (const finding of headingFormMarkerLintFindings(document)) findings.push(finding);
1454
1456
  for (const finding of blanketInvariantLintFindings(document)) findings.push(finding);
1457
+ const releaseNote = parseReleaseNoteSection(document);
1458
+ for (const problem of releaseNote.problems) {
1459
+ findings.push({
1460
+ level: 'WARN', tag: 'release-note',
1461
+ message: `## 릴리스 노트 ${problem} invalid or missing; use - 한 줄: English summary, - 종류: feat|fix|security|internal (feature → feat), - 문서: path or 없음(이유), - 대상: next|later`,
1462
+ });
1463
+ }
1464
+ if (releaseNote.fragment || releaseNote.problems.length) {
1465
+ const section = markdownSection(document, '릴리스 노트') ?? '';
1466
+ const kind = releaseNote.fragment?.kind ?? /^[ \t]*- 종류:[ \t]*(.*?)[ \t]*$/m.exec(section)?.[1];
1467
+ const line = releaseNote.fragment?.line ?? /^[ \t]*- 한 줄:[ \t]*(.*?)[ \t]*$/m.exec(section)?.[1];
1468
+ if (kind && ['feat', 'fix', 'security'].includes(kind) && line && /[가-힣ㄱ-ㅎㅏ-ㅣ]/u.test(line)) {
1469
+ findings.push({ level: 'WARN', tag: 'release-note', message: '## 릴리스 노트 한 줄: 공개 노트에 실리는 문장은 영어로(internal 은 한국어 무방)' });
1470
+ }
1471
+ }
1455
1472
  for (const violation of selfQuestionSubjectViolations) {
1456
1473
  findings.push({
1457
1474
  level: 'WARN',
@@ -27,9 +27,13 @@ export type HostRegateDeps = {
27
27
  log?: (event: 'passed' | 'failed' | 'unmeasured' | 'base-raced', data: Record<string, unknown>) => void;
28
28
  };
29
29
 
30
+ const SPAWN_MAX_BUFFER = 256 * 1024 * 1024;
31
+
30
32
  const defaultCommand: NonNullable<HostRegateDeps['command']> = (bin, args, cwd, env) => {
31
- const r = spawnSync(bin, [...args], { cwd, env: env ?? process.env, encoding: 'utf8', timeout: 600_000 });
32
- return { status: r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? `${r.error.message} (command unavailable)` : '') };
33
+ // bun ≥1.4 applies Node's 1MB default maxBuffer and silently truncates — test output here is often larger.
34
+ const r = spawnSync(bin, [...args], { cwd, env: env ?? process.env, encoding: 'utf8', timeout: 600_000, maxBuffer: SPAWN_MAX_BUFFER });
35
+ const overflow = (r.error as NodeJS.ErrnoException | undefined)?.code === 'ENOBUFS';
36
+ return { status: overflow ? null : r.status, stdout: r.stdout ?? '', stderr: (r.stderr ?? '') + (r.error ? `${r.error.message} (${overflow ? `output exceeded ${SPAWN_MAX_BUFFER} bytes` : 'command unavailable'})` : '') };
33
37
  };
34
38
 
35
39
  /** The informational pr-land gate always returns zero; enforce its measured report and the combined test result instead. */
@@ -3307,9 +3307,14 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3307
3307
  if (opts.childLlm && (typeof opts.childLlm.provider !== 'string' || !opts.childLlm.provider.trim() || typeof opts.childLlm.model !== 'string' || !opts.childLlm.model.trim())) {
3308
3308
  throw new Error('childLlm.provider and childLlm.model must both be non-empty');
3309
3309
  }
3310
- const branch = opts.branchName ?? plannedSelfImplBranch(opts.feature, opts.goalId);
3311
3310
  const identity = resolveRunIdentity({ explicit: opts.runId });
3312
3311
  const { runId } = identity;
3312
+ const branch = opts.branchName ?? plannedSelfImplBranch(opts.feature, opts.goalId, runId);
3313
+ try {
3314
+ debug.log('self-implement.branch', 'planned', {
3315
+ runId, goalId: opts.goalId, branch, runSuffix: opts.branchName === undefined ? branch.match(/-r[a-z0-9]{6}$/)?.[0] ?? null : null,
3316
+ });
3317
+ } catch { /* observation must not block the run */ }
3313
3318
  const attemptOrdinal = incrementRunAttemptOrdinal(runId);
3314
3319
  const shardIdentity = parseRunShardIdentity(opts.feature);
3315
3320
  const observeOuter = makeRunObserver(runId, opts.goalId, debug.log.bind(debug), opts.seams.writeRunLedger, shardIdentity);
@@ -4517,8 +4522,8 @@ async function runSelfImplementInner(
4517
4522
  roundClassifications.push(classification);
4518
4523
  onRoundClassification(classification);
4519
4524
  };
4520
- const branch = opts.branchName ?? plannedSelfImplBranch(opts.feature, opts.goalId);
4521
4525
  const { runId, source: runIdSource } = identity ?? resolveRunIdentity({ explicit: opts.runId });
4526
+ const branch = opts.branchName ?? plannedSelfImplBranch(opts.feature, opts.goalId, runId);
4522
4527
  try {
4523
4528
  debug.log('run-identity', 'own', {
4524
4529
  runId, branch, source: runIdSource, nestDepth: nestInfo().depth, ...originObservationFields(),
@@ -60,7 +60,7 @@ import { pwaReachableSrcFiles } from './pwa-import-graph.js';
60
60
  import { stageNonGitDir, stageFile, applyShadowToTarget, applyFileToTarget } from './shadow-stage.js';
61
61
  import type { TargetKind } from './target-kind.js';
62
62
  import { basename } from 'node:path';
63
- import { makePrManager, type PrManager } from '../autopilot/pr-manager.js';
63
+ import { ghAutomationEnv, makePrManager, type PrManager } from '../autopilot/pr-manager.js';
64
64
  import { defaultSpawnGh, lookupOpenDraftPrs } from '../cli/logs-abandoned-draft-prs.js';
65
65
  import { loadRunLedger, runLedgerDir } from './run-ledger.js';
66
66
  import { readdirSync } from 'node:fs';
@@ -2347,8 +2347,10 @@ export function defaultSeams(o: DefaultSeamsOptions = {}): SelfImplementSeams {
2347
2347
  closePr: (prUrl, comment) => (o.prManager ?? makePrManager()).closePr(prUrl, comment),
2348
2348
 
2349
2349
  postPrComment: async ({ number, body, cwd }) => {
2350
+ // Round comments go out as the GitHub App like the PR itself — a personal `gh` login made the owner a
2351
+ // participant and mailed them on merge (#22089).
2350
2352
  await execFileAsync('gh', ['pr', 'comment', String(number), '--body', body], {
2351
- cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024,
2353
+ cwd, encoding: 'utf8', timeout: GIT_TIMEOUT, maxBuffer: 64 * 1024 * 1024, env: ghAutomationEnv(process.env),
2352
2354
  });
2353
2355
  },
2354
2356
 
@@ -13,7 +13,9 @@
13
13
  // ★ 제1원칙: front door 결정 observe(harness.frontdoor) — objective digest·autoDrive·terminal.
14
14
  // autoDrive 'safe'(기본): 저위험 자율·고위험(PR open)만 ux.confirm(막 채널 없으면 fail-closed).
15
15
 
16
+ import { createHash } from 'node:crypto';
16
17
  import { tierModel } from '../../llm/model-defaults.js';
18
+ import { dispatchTask, type DispatchTaskDeps } from '../../execution-loop/dispatch-task.js';
17
19
  import type { LLMToolSpec } from '../../llm.js';
18
20
  import type { DaemonToolDispatchCtx } from '../../boot/daemon-tools/types.js';
19
21
  import type { SelfImplementSeams } from '../../self-implement/orchestrator.js';
@@ -96,6 +98,9 @@ export function buildRunDevHarnessTool(): LLMToolSpec {
96
98
  export interface DevHarnessDeps {
97
99
  /** 하니스 구동(기본 runStagedHarnessOnSurface). */
98
100
  runHarness?: (opts: RunHarnessOnSurfaceOptions) => Promise<HarnessResult>;
101
+ /** In-process 실행 직전의 관측 전용 디스패치 seam. */
102
+ dispatchTask?: typeof dispatchTask;
103
+ dispatchTaskDeps?: DispatchTaskDeps;
99
104
  /** seam 팩토리(기본 defaultSeams). */
100
105
  seamsFactory?: (o: DefaultSeamsOptions) => SelfImplementSeams;
101
106
  /** 비대화형 HITL relay 팩토리 seam(기본 buildLlmHitlRelay). */
@@ -468,6 +473,43 @@ export async function dispatchRunDevHarness(
468
473
  const runHarness = deps?.runHarness ?? runStagedHarnessOnSurface;
469
474
  const knobs = resolveHarnessKnobs(rawArgs, ctx?.userText); // ★ B1 관통 — C1~C4 노브를 surface 파라미터에서 막으로
470
475
  const harnessMention = harnessMentionState(ctx?.userText);
476
+ // Outer pipeline/Pod observations are not repeated; this decision never controls the harness.
477
+ const dispatchSkipped = rawArgs.plan_staged === true ? 'plan-staged'
478
+ : rawArgs.dispatch_recorded === true || process.env.ELANOUS_DISPATCH_RECORDED === '1' ? 'dispatch-recorded'
479
+ : process.env.ELANOUS_EXECUTION_LOOP_DISPATCH === 'off' && !deps?.dispatchTask && !deps?.dispatchTaskDeps ? 'disabled'
480
+ : undefined;
481
+ if (dispatchSkipped) {
482
+ try { debug.log('execution-loop.gate', 'dispatch-skipped', { reason: dispatchSkipped, runId }); } catch { /* fail-soft */ }
483
+ } else {
484
+ try {
485
+ const spec = { input: { text: objective }, executor: { kind: 'self' as const }, plan: true, runId };
486
+ const observer = new AbortController();
487
+ const timeoutMs = Math.min(5_000, Math.max(0, deps?.dispatchTaskDeps?.timeoutMs ?? 5_000));
488
+ let timer: ReturnType<typeof setTimeout> | undefined;
489
+ try {
490
+ await Promise.race([
491
+ Promise.resolve().then(() => (deps?.dispatchTask ?? dispatchTask)({
492
+ goalId: `request-${createHash('sha256').update(objective).digest('hex').slice(0, 32)}`,
493
+ title: objective.split(/\r?\n/, 1)[0]?.trim() || 'Untitled request',
494
+ goalText: objective,
495
+ targetPaths: [],
496
+ spec,
497
+ runKey: runId,
498
+ }, { ...deps?.dispatchTaskDeps, signal: observer.signal })),
499
+ new Promise<never>((_, reject) => {
500
+ timer = setTimeout(() => {
501
+ observer.abort();
502
+ reject(new Error(`dispatch observation timed out after ${timeoutMs}ms`));
503
+ }, timeoutMs);
504
+ }),
505
+ ]);
506
+ } finally {
507
+ if (timer) clearTimeout(timer);
508
+ }
509
+ } catch (error) {
510
+ try { debug.log('execution-loop.gate', 'dispatch-unavailable', { reason: String(error), runId }); } catch { /* fail-soft */ }
511
+ }
512
+ }
471
513
  const result = await runHarness({
472
514
  // ⛔⭐ `naturalLanguageDispatch` 를 «단정»하지 않는다 — 증거로 정한다.
473
515
  // 이 함수는 «둘» 이 지난다: ⑴ 모델이 부른 도구(사용자 턴 문면이 ctx.userText 로 온다)
@@ -0,0 +1,45 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { CardStore, redactSecrets, type AppendSectionInput, type CreateCardInput, type TaskCard } from '../task-cards/card-store.js';
3
+ import type { ScheduledDecision, TriageIssue } from './triage.js';
4
+
5
+ export interface StewardCardStore {
6
+ createCard(input: CreateCardInput): TaskCard;
7
+ appendSection(id: string, input: AppendSectionInput): TaskCard;
8
+ close(): void;
9
+ }
10
+
11
+ export interface StewardCardDeps {
12
+ store?: StewardCardStore;
13
+ root?: string;
14
+ now?: () => Date;
15
+ }
16
+
17
+ /** Record observe-only intake and the latest scheduled judgment without launching any work. */
18
+ export function recordTriageOnCards(decisions: ScheduledDecision[], issues: TriageIssue[], deps: StewardCardDeps = {}): void {
19
+ const store = deps.store ?? new CardStore(deps.root);
20
+ const byIssue = new Map(issues.map(issue => [issue.identifier, issue]));
21
+ try {
22
+ for (const decision of decisions) {
23
+ const issue = byIssue.get(decision.issue);
24
+ if (!issue) throw new Error(`Missing issue ${decision.issue}`);
25
+ const card = store.createCard({ goalId: `linear:${issue.identifier}`, title: redactSecrets(issue.title) });
26
+ const intakeKey = `intake:${issue.identifier}`;
27
+ if (!card.sections.some(section => section.key === intakeKey)) {
28
+ const directiveSource = /^출처: (telegram|pwa|tui|cli)$/m.exec(issue.body)?.[1];
29
+ const directiveTime = /^시각: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)$/m.exec(issue.body)?.[1];
30
+ store.appendSection(card.id, {
31
+ key: intakeKey, owner: 'steward',
32
+ content: JSON.stringify({ summary: redactSecrets(issue.title), source: directiveSource ?? `linear:${issue.identifier}`, at: directiveTime ?? (deps.now ?? (() => new Date()))().toISOString() }),
33
+ });
34
+ }
35
+ const { rung, dependsOn, priority, disposition, hitlReason, why, owner } = decision;
36
+ const judgment = { rung, dependsOn, priority, disposition: rung === 'hitl' || hitlReason ? 'hitl' : disposition, hitlReason: hitlReason ?? null, why: redactSecrets(why), ...(owner ? { owner } : {}) };
37
+ const key = `triage:${createHash('sha256').update(JSON.stringify(judgment)).digest('hex')}`;
38
+ if (!card.sections.some(section => section.key === key)) {
39
+ store.appendSection(card.id, { key, owner: 'steward', content: JSON.stringify(judgment) });
40
+ }
41
+ }
42
+ } finally {
43
+ if (!deps.store) store.close();
44
+ }
45
+ }
@@ -4,30 +4,39 @@ import { fetchLinearIssues } from '../connectors/linear.js';
4
4
  import { getSecretAsync } from '../nexus/config/secrets/index.js';
5
5
  import { getUserConfig } from '../user-config.js';
6
6
  import { emitDecision } from '../live/detail-switch.js';
7
+ import { debug, redactSecretText } from '../debug/log.js';
7
8
  import { effectiveInstanceRoot } from '../instance/resolve.js';
9
+ import { redactSecrets } from '../task-cards/card-store.js';
8
10
  import { directiveHash } from './directive.js';
11
+ import { recordTriageOnCards, type StewardCardDeps } from './steward-cards.js';
9
12
 
10
13
  export type Rung = 0 | 1 | 2 | 3 | 4 | 5 | 'hitl';
11
- export type HitlReason = 'money' | 'public' | 'security' | 'irreversible';
14
+ /** `other` = the judge chose human review for a reason outside the four named ones (kept as HITL, never downgraded). */
15
+ export type HitlReason = 'money' | 'public' | 'security' | 'irreversible' | 'other';
12
16
  export interface TriageIssue { identifier: string; ref: string; title: string; body: string }
13
17
  export interface TriageDecision {
14
18
  issue: string; rung: Rung; dependsOn: string[]; priority: number; duplicateOf?: string;
15
19
  hitlReason?: HitlReason; why: string; role?: string; cost?: number;
20
+ /** Owner track from `loops.steward.tracks` (absent when no table is configured or the judge picked none of its keys). */
21
+ owner?: string;
16
22
  }
17
23
  export interface ScheduledDecision extends TriageDecision { disposition: 'now' | 'wait' | 'hitl' }
18
24
  export interface StewardSettings {
19
25
  mode?: 'observe' | 'act'; linearTeam?: string;
20
26
  roles?: Record<string, { maxConcurrent?: number }>;
21
27
  budget?: number;
28
+ tracks?: Record<string, string>;
22
29
  }
23
30
  export interface StewardDeps {
24
31
  fetch?: typeof fetch;
25
32
  getSecret?: (id: string) => Promise<string | undefined>;
26
- judge?: (issue: TriageIssue, issues: TriageIssue[]) => Promise<unknown>;
33
+ judge?: (issue: TriageIssue, issues: TriageIssue[], tracks?: Record<string, string>) => Promise<unknown>;
27
34
  decide?: typeof emitDecision;
28
35
  sendDigest?: (text: string) => Promise<void>;
29
36
  now?: () => Date;
30
37
  root?: string;
38
+ cardStore?: StewardCardDeps['store'];
39
+ warn?: (message: string) => void;
31
40
  }
32
41
 
33
42
  export function stewardSettings(): StewardSettings {
@@ -41,7 +50,7 @@ const unsafe: Array<[HitlReason, RegExp]> = [
41
50
  ['irreversible', /비가역|강제\s*푸시|force.push|영구\s*삭제|리셋권\s*소비|drop\s*table|초기화/i],
42
51
  ];
43
52
 
44
- function parseJudgment(issue: TriageIssue, raw: unknown): TriageDecision {
53
+ function parseJudgment(issue: TriageIssue, raw: unknown, tracks?: Record<string, string>): TriageDecision {
45
54
  const value = typeof raw === 'string' ? JSON.parse(raw) as unknown : raw;
46
55
  if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error(`Invalid triage judgment: ${issue.identifier}`);
47
56
  const d = value as Record<string, unknown>;
@@ -51,8 +60,9 @@ function parseJudgment(issue: TriageIssue, raw: unknown): TriageDecision {
51
60
  throw new Error(`Invalid triage judgment: ${issue.identifier}`);
52
61
  }
53
62
  const forced = unsafe.find(([, pattern]) => pattern.test(`${issue.title}\n${issue.body}`))?.[0];
54
- const hitlReason = forced ?? (unsafe.some(([reason]) => reason === d.hitlReason) ? d.hitlReason as HitlReason : undefined);
55
- if (d.rung === 'hitl' && !hitlReason) throw new Error(`HITL reason required: ${issue.identifier}`);
63
+ const named = forced ?? (unsafe.some(([reason]) => reason === d.hitlReason) ? d.hitlReason as HitlReason : undefined);
64
+ // A HITL judgment without one of the four named reasons stays HITL (`other`) — one issue must not stop the stage.
65
+ const hitlReason = named ?? (d.rung === 'hitl' ? 'other' as const : undefined);
56
66
  return {
57
67
  issue: issue.identifier, rung: hitlReason ? 'hitl' : d.rung as Rung,
58
68
  dependsOn: d.dependsOn as string[], priority: d.priority as number,
@@ -60,24 +70,40 @@ function parseJudgment(issue: TriageIssue, raw: unknown): TriageDecision {
60
70
  ...(hitlReason ? { hitlReason } : {}), why: (d.why as string).trim().replace(/\s+/g, ' '),
61
71
  ...(typeof d.role === 'string' ? { role: d.role } : {}),
62
72
  ...(typeof d.cost === 'number' && Number.isFinite(d.cost) && d.cost >= 0 ? { cost: d.cost } : {}),
73
+ ...(tracks && typeof d.owner === 'string' && Object.hasOwn(tracks, d.owner) ? { owner: d.owner } : {}),
63
74
  };
64
75
  }
65
76
 
66
- async function judgeStewardIssue(issue: TriageIssue, issues: TriageIssue[]): Promise<unknown> {
67
- const prompt = `스튜어드 트리아지. JSON 객체만 출력: {"rung":0|1|2|3|4|5|"hitl","dependsOn":[],"priority":1,"duplicateOf":null,"hitlReason":null,"why":"한 문장","role":"builder","cost":0}.\n0=이미됨/중복, 1=셸, 2=CLI, 3=조사, 4=하니스 골, 5=외부 에이전트. 돈·공개·보안·비가역은 hitl. 확인되지 않은 완료/중복은 추측하지 마라. 의존은 제공된 이슈 키만 써라.\n이슈 목록: ${JSON.stringify(issues.map(i => ({ key: i.identifier, title: i.title })))}\n판정 대상: ${JSON.stringify(issue)}`;
77
+ /** The role table as prompt lines — read from config so a reorg edits one place. */
78
+ export function trackTablePrompt(tracks?: Record<string, string>): string {
79
+ if (!tracks || !Object.keys(tracks).length) return '';
80
+ const keys = Object.keys(tracks);
81
+ return `\n담당 트랙(owner) — 아래 중 하나(${keys.map(k => JSON.stringify(k)).join('|')}) · 이슈가 어느 트랙 일인지:\n${keys.map(k => `- ${k}: ${tracks[k]}`).join('\n')}`;
82
+ }
83
+
84
+ async function judgeStewardIssue(issue: TriageIssue, issues: TriageIssue[], tracks?: Record<string, string>): Promise<unknown> {
85
+ const ownerField = tracks && Object.keys(tracks).length ? `,"owner":${JSON.stringify(Object.keys(tracks)[0])}` : '';
86
+ const prompt = `스튜어드 트리아지. JSON 객체만 출력: {"rung":0|1|2|3|4|5|"hitl","dependsOn":[],"priority":1,"duplicateOf":null,"hitlReason":null,"why":"한 문장","role":"builder","cost":0${ownerField}}.\n0=이미됨/중복, 1=셸, 2=CLI, 3=조사, 4=하니스 골, 5=외부 에이전트. 돈·공개·보안·비가역은 hitl(hitlReason = "money"|"public"|"security"|"irreversible" · 그 밖의 이유로 사람 확인이 필요하면 "other"). 확인되지 않은 완료/중복은 추측하지 마라. 의존은 제공된 이슈 키만 써라.${trackTablePrompt(tracks)}\n이슈 목록: ${JSON.stringify(issues.map(i => ({ key: i.identifier, title: i.title })))}\n판정 대상: ${JSON.stringify(issue)}`;
68
87
  const { streamLLM } = await import('../llm.js');
69
88
  const { tierModel } = await import('../llm/model-defaults.js');
70
89
  const result = await streamLLM([{ role: 'user', content: prompt }], () => {}, { model: tierModel('best') });
71
90
  return JSON.parse(result.replace(/^```(?:json)?\s*|\s*```$/g, '').trim()) as unknown;
72
91
  }
73
92
 
74
- export async function triageIssues(issues: TriageIssue[], judge: NonNullable<StewardDeps['judge']> = judgeStewardIssue, decide: typeof emitDecision = emitDecision): Promise<TriageDecision[]> {
93
+ export async function triageIssues(issues: TriageIssue[], judge: NonNullable<StewardDeps['judge']> = judgeStewardIssue, decide: typeof emitDecision = emitDecision, tracks?: Record<string, string>): Promise<TriageDecision[]> {
75
94
  const decisions: TriageDecision[] = [];
76
95
  for (const issue of issues) {
77
- const result = parseJudgment(issue, await judge(issue, issues));
96
+ let result: TriageDecision;
97
+ try {
98
+ result = parseJudgment(issue, await judge(issue, issues, tracks), tracks);
99
+ } catch (error) {
100
+ // A malformed or failed judgment for one issue goes to a human; the other issues are still triaged.
101
+ try { debug.log('steward.stage', 'judgment-invalid', { issue: issue.identifier, reason: redactSecrets(String(error)) }); } catch { /* fail-soft */ }
102
+ result = { issue: issue.identifier, rung: 'hitl', hitlReason: 'other', dependsOn: [], priority: 0, why: 'triage judgment unavailable — human review' };
103
+ }
78
104
  decisions.push(result);
79
105
  decide({ kind: result.rung === 'hitl' ? 'ESCALATE' : 'ROUTE', what: issue.identifier, reason: result.why,
80
- purpose: 'steward triage', target: String(result.rung), refs: { issue: issue.identifier } });
106
+ purpose: 'steward triage', target: result.owner ? `${result.rung} · ${result.owner}` : String(result.rung), refs: { issue: issue.identifier } });
81
107
  }
82
108
  return decisions;
83
109
  }
@@ -99,7 +125,7 @@ export function scheduleTriage(decisions: TriageDecision[], settings: StewardSet
99
125
  const slots = { ...running };
100
126
  let remaining = settings.budget ?? Infinity;
101
127
  return sorted.map(item => {
102
- const role = item.role ?? String(item.rung);
128
+ const role = item.owner ?? item.role ?? String(item.rung);
103
129
  const max = settings.roles?.[role]?.maxConcurrent ?? Infinity;
104
130
  const blocked = item.dependsOn.some(dep => !completed.has(dep));
105
131
  const status: ScheduledDecision['disposition'] = item.rung === 'hitl' || item.hitlReason ? 'hitl' :
@@ -146,7 +172,7 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
146
172
  } else if (stage === 'triage') {
147
173
  const issues = JSON.parse(readFileSync(snapshot, 'utf8')) as TriageIssue[];
148
174
  const judge = deps.judge ?? judgeStewardIssue;
149
- writeFileSync(judgments, JSON.stringify(await triageIssues(issues, judge, deps.decide)));
175
+ writeFileSync(judgments, JSON.stringify(await triageIssues(issues, judge, deps.decide, settings.tracks)));
150
176
  } else if (stage === 'schedule') {
151
177
  const decisions = JSON.parse(readFileSync(judgments, 'utf8')) as TriageDecision[];
152
178
  const pending = new Set(decisions.flatMap(decision => decision.dependsOn));
@@ -163,6 +189,13 @@ export async function runStewardStage(stage: 'sync' | 'triage' | 'schedule' | 'r
163
189
  const state = readState(path);
164
190
  const at = (deps.now ?? (() => new Date()))().toISOString();
165
191
  const issues = JSON.parse(readFileSync(snapshot, 'utf8')) as TriageIssue[];
192
+ try {
193
+ recordTriageOnCards(rows, issues, { root, store: deps.cardStore, now: deps.now });
194
+ } catch (error) {
195
+ const message = `steward report: card write failed: ${redactSecrets(error instanceof Error ? error.message : String(error))}`;
196
+ debug.log('steward.cards', 'write-failed', { error: message });
197
+ (deps.warn ?? console.warn)(message);
198
+ }
166
199
  for (const row of rows) {
167
200
  const issue = issues.find(item => item.identifier === row.issue);
168
201
  if (!issue) throw new Error(`Missing issue ${row.issue}`);
@@ -195,8 +228,11 @@ if (import.meta.main) {
195
228
  console.error('steward: expected sync|triage|schedule|report');
196
229
  process.exitCode = 2;
197
230
  } else {
198
- runStewardStage(stage as 'sync' | 'triage' | 'schedule' | 'report').catch(() => {
199
- console.error(`steward ${stage}: stage failed`);
231
+ runStewardStage(stage as 'sync' | 'triage' | 'schedule' | 'report').catch((error: unknown) => {
232
+ // Say why (secrets masked) — 09-29 the first resident run failed as a bare «stage failed» on a missing Linear key.
233
+ const reason = redactSecretText(error instanceof Error ? error.message : String(error)).slice(0, 300);
234
+ console.error(`steward ${stage}: stage failed — ${reason}`);
235
+ debug.log('steward.stage', 'failed', { stage, reason });
200
236
  process.exitCode = 1;
201
237
  });
202
238
  }
@@ -2,6 +2,8 @@ import { lstatSync, mkdirSync, 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
+ import { getDefaultLogStore, type LogStore } from '../../mss/logging/log-store.js';
6
+ import type { LogRecord } from '../../mss/logging/record.js';
5
7
 
6
8
  const FILE_LIMIT = 5 * 1024 * 1024;
7
9
  const TOTAL_LIMIT = 20 * 1024 * 1024;
@@ -79,14 +81,63 @@ export function parsePodArtifactChunks(logs: string): ParseResult {
79
81
  return error.length ? { error, artifacts } : artifacts;
80
82
  }
81
83
 
84
+ const POD_LOGS_PATH = 'pod-logs/logs.jsonl';
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
+ };
113
+ }
114
+
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 } {
117
+ if (!store) return { imported: 0, skipped: 0 };
118
+ const text = bytes.toString('utf8');
119
+ const rows: Array<{ rec: LogRecord; surface: string }> = [];
120
+ let skipped = 0;
121
+ for (const line of text.split(/\r?\n/)) {
122
+ 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);
127
+ }
128
+ if (rows.length > 0) store.insertBatch(rows);
129
+ return { imported: rows.length, skipped };
130
+ }
131
+
82
132
  /** Reject symlink parents as well as lexical traversal; never replace a host file. */
83
- export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) => debug.log(c, e, d) }: {
133
+ export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) => debug.log(c, e, d), logStore = getDefaultLogStore() }: {
84
134
  dir: string;
85
135
  job: string;
86
136
  log?: (category: string, event: string, data: Record<string, unknown>) => void;
137
+ logStore?: LogStore | null;
87
138
  }): void {
88
139
  const parsed = parsePodArtifactChunks(logs);
89
- const podLogsPath = 'pod-logs/logs.jsonl';
140
+ const podLogsPath = POD_LOGS_PATH;
90
141
  let podLogsReturned = false;
91
142
  let podLogsMissingReason = logs.split(/\r?\n/).find((line) => line.startsWith(`ELANOUS_POD_ARTIFACT_SKIPPED ${podLogsPath} `))
92
143
  ? 'artifact skipped (size limit or encoding failure)'
@@ -123,7 +174,14 @@ export function collectPodArtifacts(logs: string, { dir, job, log = (c, e, d) =>
123
174
  log('self-implement.pod', 'artifact-collected', { job, path, bytes: bytes.length });
124
175
  if (path === podLogsPath) {
125
176
  podLogsReturned = true;
126
- log('self-implement.pod', 'pod-logs-returned', { job, path: destination, lines: bytes.length ? bytes.toString('utf8').split('\n').length - (bytes.at(-1) === 10 ? 1 : 0) : 0 });
177
+ const lines = bytes.length ? bytes.toString('utf8').split('\n').length - (bytes.at(-1) === 10 ? 1 : 0) : 0;
178
+ 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
+ }
127
185
  }
128
186
  } catch (e) {
129
187
  const reason = (e as NodeJS.ErrnoException).code === 'EEXIST' ? 'exists' : e instanceof Error ? e.message : String(e);