elanous 0.2.13 → 0.2.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (524) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
  4. package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
  10. package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
  18. package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
  20. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  28. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
  31. package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
  42. package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/approvals/page-0e2c57fe6add4b2a.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
  53. package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
  54. package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
  55. package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
  56. package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
  59. package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.js +1 -0
  63. package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/app/inside/page-13c7b53e0c2519a9.js +1 -0
  65. package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
  66. package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
  67. package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
  68. package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
  69. package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
  70. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
  71. package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
  72. package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
  73. package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
  74. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
  75. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
  76. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
  77. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
  78. package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
  79. package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
  80. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
  81. package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
  82. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
  83. package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
  84. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.js +1 -0
  85. package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
  86. package/apps/pwa/out/_next/static/chunks/app/setup/page-7d4182964407c09c.js +1 -0
  87. package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
  88. package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
  89. package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
  90. package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
  91. package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
  92. package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
  93. package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
  94. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
  95. package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
  96. package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
  97. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.js +1 -0
  98. package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
  99. package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
  100. package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
  101. package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
  102. package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
  103. package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
  104. package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
  105. package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
  106. package/apps/pwa/out/_next/static/css/1f7c958c41ffa3b9.css +1 -0
  107. package/apps/pwa/out/approvals/index.html +1 -1
  108. package/apps/pwa/out/approvals/index.txt +23 -23
  109. package/apps/pwa/out/autopilot/index.html +1 -1
  110. package/apps/pwa/out/autopilot/index.txt +23 -23
  111. package/apps/pwa/out/board/index.html +1 -1
  112. package/apps/pwa/out/board/index.txt +23 -23
  113. package/apps/pwa/out/botlab/index.html +1 -1
  114. package/apps/pwa/out/botlab/index.txt +22 -22
  115. package/apps/pwa/out/bots/index.html +1 -1
  116. package/apps/pwa/out/bots/index.txt +22 -22
  117. package/apps/pwa/out/ceo/index.html +1 -0
  118. package/apps/pwa/out/ceo/index.txt +33 -0
  119. package/apps/pwa/out/chat/index.html +1 -1
  120. package/apps/pwa/out/chat/index.txt +22 -22
  121. package/apps/pwa/out/consult/index.html +1 -1
  122. package/apps/pwa/out/consult/index.txt +23 -23
  123. package/apps/pwa/out/control/index.html +1 -1
  124. package/apps/pwa/out/control/index.txt +23 -23
  125. package/apps/pwa/out/design-check/index.html +1 -1
  126. package/apps/pwa/out/design-check/index.txt +23 -23
  127. package/apps/pwa/out/editor/index.html +1 -1
  128. package/apps/pwa/out/editor/index.txt +23 -23
  129. package/apps/pwa/out/exec/index.html +1 -1
  130. package/apps/pwa/out/exec/index.txt +23 -23
  131. package/apps/pwa/out/field/index.html +1 -1
  132. package/apps/pwa/out/field/index.txt +23 -23
  133. package/apps/pwa/out/healthz +1 -1
  134. package/apps/pwa/out/index.html +1 -1
  135. package/apps/pwa/out/index.txt +22 -22
  136. package/apps/pwa/out/inside/index.html +1 -1
  137. package/apps/pwa/out/inside/index.txt +22 -22
  138. package/apps/pwa/out/intake/index.html +1 -1
  139. package/apps/pwa/out/intake/index.txt +23 -23
  140. package/apps/pwa/out/live/index.html +1 -1
  141. package/apps/pwa/out/live/index.txt +23 -23
  142. package/apps/pwa/out/loops/index.html +1 -0
  143. package/apps/pwa/out/loops/index.txt +30 -0
  144. package/apps/pwa/out/market/index.html +1 -1
  145. package/apps/pwa/out/market/index.txt +22 -22
  146. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  147. package/apps/pwa/out/missions/placeholder/index.txt +22 -22
  148. package/apps/pwa/out/morning/index.html +1 -1
  149. package/apps/pwa/out/morning/index.txt +23 -23
  150. package/apps/pwa/out/observatory/index.html +1 -1
  151. package/apps/pwa/out/observatory/index.txt +23 -23
  152. package/apps/pwa/out/ops/checklist/index.html +1 -1
  153. package/apps/pwa/out/ops/checklist/index.txt +22 -22
  154. package/apps/pwa/out/ops/release/index.html +1 -1
  155. package/apps/pwa/out/ops/release/index.txt +22 -22
  156. package/apps/pwa/out/ops/seats/index.html +1 -1
  157. package/apps/pwa/out/ops/seats/index.txt +22 -22
  158. package/apps/pwa/out/outputs/index.html +1 -1
  159. package/apps/pwa/out/outputs/index.txt +22 -22
  160. package/apps/pwa/out/reflection/index.html +1 -1
  161. package/apps/pwa/out/reflection/index.txt +23 -23
  162. package/apps/pwa/out/scheduler/index.html +1 -1
  163. package/apps/pwa/out/scheduler/index.txt +23 -23
  164. package/apps/pwa/out/sessions/index.html +1 -1
  165. package/apps/pwa/out/sessions/index.txt +22 -22
  166. package/apps/pwa/out/settings/devices/index.html +1 -1
  167. package/apps/pwa/out/settings/devices/index.txt +23 -23
  168. package/apps/pwa/out/settings/index.html +1 -1
  169. package/apps/pwa/out/settings/index.txt +23 -23
  170. package/apps/pwa/out/setup/done/index.html +1 -1
  171. package/apps/pwa/out/setup/done/index.txt +23 -23
  172. package/apps/pwa/out/setup/index.html +1 -1
  173. package/apps/pwa/out/setup/index.txt +23 -23
  174. package/apps/pwa/out/share/index.html +1 -1
  175. package/apps/pwa/out/share/index.txt +23 -23
  176. package/apps/pwa/out/showroom/index.html +1 -1
  177. package/apps/pwa/out/showroom/index.txt +22 -22
  178. package/apps/pwa/out/tasks/index.html +1 -1
  179. package/apps/pwa/out/tasks/index.txt +23 -23
  180. package/apps/pwa/out/term/index.html +1 -1
  181. package/apps/pwa/out/term/index.txt +23 -23
  182. package/apps/pwa/out/today/index.html +1 -1
  183. package/apps/pwa/out/today/index.txt +23 -23
  184. package/apps/pwa/out/trace/index.html +1 -1
  185. package/apps/pwa/out/trace/index.txt +23 -23
  186. package/apps/pwa/out/vault/index.html +1 -1
  187. package/apps/pwa/out/vault/index.txt +23 -23
  188. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  189. package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
  190. package/apps/pwa/out/workflows/index.html +1 -1
  191. package/apps/pwa/out/workflows/index.txt +23 -23
  192. package/apps/pwa/out/workspace/index.html +1 -1
  193. package/apps/pwa/out/workspace/index.txt +23 -23
  194. package/apps/pwa/out/worktrees/index.html +1 -1
  195. package/apps/pwa/out/worktrees/index.txt +23 -23
  196. package/graphs/heal/heal-loop.yaml +1 -1
  197. package/graphs/implement-loop.yaml +1 -1
  198. package/graphs/orchestrator/orchestrator.yaml +59 -0
  199. package/graphs/orchestrator/recipes.yaml +18 -0
  200. package/graphs/release/recipes.yaml +6 -0
  201. package/graphs/release/release-loop.yaml +6 -1
  202. package/graphs/rhythm/daily.yaml +38 -0
  203. package/graphs/rhythm/recipes.yaml +18 -0
  204. package/graphs/rhythm/weekly.yaml +39 -0
  205. package/graphs/video/recipes.yaml +3 -0
  206. package/graphs/video/youtube-summary.yaml +17 -0
  207. package/package.json +1 -1
  208. package/scripts/audit-test-state-writes.ts +72 -17
  209. package/scripts/brand/check.ts +188 -0
  210. package/scripts/ci-typecheck-changed.ts +50 -18
  211. package/scripts/coord-post.sh +11 -1
  212. package/scripts/docs/generate-graph-anatomy.ts +315 -0
  213. package/scripts/docs-cli-check.ts +23 -6
  214. package/scripts/expose-rubric-check.ts +118 -0
  215. package/scripts/expose-rubric-suggest.ts +169 -0
  216. package/scripts/heal/run-role.ts +14 -0
  217. package/scripts/hq/standby-verify.ts +11 -7
  218. package/scripts/lecture-note/node.ts +31 -11
  219. package/scripts/lessons/handbook-page.ts +112 -0
  220. package/scripts/marketing/generate-ax-brief.ts +140 -0
  221. package/scripts/marketing/generate-feature-map.ts +118 -0
  222. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  223. package/scripts/release-loop/gate-node.ts +89 -14
  224. package/scripts/release-loop/publish-node.ts +14 -1
  225. package/scripts/release-loop/unattended-release.ts +23 -1
  226. package/scripts/release-loop/vault-note-node.ts +428 -0
  227. package/scripts/release-story/draft.ts +220 -0
  228. package/scripts/rhythm/daily.ts +570 -0
  229. package/scripts/rhythm/weekly.ts +315 -0
  230. package/scripts/skills/public-check.ts +169 -0
  231. package/scripts/smart-merge/resync-shadow.ts +147 -0
  232. package/scripts/test-deterministic.ts +4 -8
  233. package/scripts/test-diet/effectiveness.ts +195 -0
  234. package/scripts/test-diet/lib.ts +16 -8
  235. package/scripts/test-diet/node.ts +77 -9
  236. package/scripts/ux/demo-rehearsal.ts +2 -2
  237. package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
  238. package/scripts/video/youtube-summary.ts +133 -0
  239. package/skills/diagram-master/SKILL.md +42 -49
  240. package/skills/diagram-master/references/pyproject.toml +4 -1
  241. package/skills/diagram-master/references/routing-guide.md +4 -2
  242. package/skills/grill-me/SKILL.md +1 -0
  243. package/skills/youtube-master/SKILL.md +13 -13
  244. package/skills/youtube-master/package-lock.json +0 -545
  245. package/skills/youtube-master/package.json +2 -5
  246. package/skills/youtube-master/scripts/main.ts +2 -2
  247. package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
  248. package/skills/youtube-master/src/util.ts +30 -7
  249. package/src/agent-env/profile.ts +161 -0
  250. package/src/agent-mission/driver.ts +3 -3
  251. package/src/agent-substrate/pr-reviewer.ts +2 -0
  252. package/src/ask-user-question/pending-questions.ts +5 -0
  253. package/src/auth/github-app-token.ts +112 -13
  254. package/src/autopilot/build/llm-conflict-merge.ts +73 -10
  255. package/src/autopilot/build/merge-intent.ts +53 -0
  256. package/src/briefing/brief-items.ts +121 -0
  257. package/src/chat/index.ts +2 -0
  258. package/src/claims/claims-ledger.ts +232 -0
  259. package/src/claims/claims-render.ts +166 -0
  260. package/src/cli/agent-env-cli.ts +21 -0
  261. package/src/cli/claims-cli.ts +94 -0
  262. package/src/cli/decisions-cli.ts +44 -3
  263. package/src/cli/docs-cli.ts +2 -0
  264. package/src/cli/doctor-cli.ts +23 -1
  265. package/src/cli/doctor-node-modules.ts +147 -0
  266. package/src/cli/flow-cli.ts +11 -0
  267. package/src/cli/freeze-cli.ts +45 -0
  268. package/src/cli/intake-cli.ts +46 -17
  269. package/src/cli/lesson-cli.ts +108 -0
  270. package/src/cli/logs-cli.ts +104 -2
  271. package/src/cli/logs-model-input.ts +131 -0
  272. package/src/cli/pod-cli.ts +2 -0
  273. package/src/cli/pr-cli.ts +29 -1
  274. package/src/cli/private-ledger-commands.ts +54 -4
  275. package/src/cli/pty-drive-verdict.ts +11 -2
  276. package/src/cli/release-cli.ts +190 -33
  277. package/src/cli/rfc-status.ts +91 -0
  278. package/src/cli/seat-requests-cli.ts +52 -0
  279. package/src/connectors/linear.ts +5 -3
  280. package/src/context-bus/condense.ts +15 -20
  281. package/src/context-bus/context-now-public.ts +96 -0
  282. package/src/context-bus/context-now-surfaces.ts +55 -2
  283. package/src/context-bus/context-now.ts +6 -3
  284. package/src/context-bus/coord-events.ts +6 -4
  285. package/src/context-bus/long-term-memory.ts +92 -11
  286. package/src/dashboard/index.ts +75 -22
  287. package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
  288. package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
  289. package/src/dashboard/slash-runtime/loops-table.ts +77 -0
  290. package/src/decisions/decision-cards.ts +33 -7
  291. package/src/decisions/decision-ledger.ts +40 -1
  292. package/src/decisions/proact-meter.ts +286 -0
  293. package/src/decisions/proact1-lite.ts +86 -0
  294. package/src/decisions/web-push-decision-cards.ts +1 -1
  295. package/src/discord/sprint21-runtime.ts +16 -0
  296. package/src/discord-self-message.ts +8 -0
  297. package/src/discord-slash-wire.ts +31 -2
  298. package/src/domains/alert-audit.ts +271 -0
  299. package/src/domains/core-tools.ts +4 -3
  300. package/src/domains/ops-facts-tool.ts +31 -1
  301. package/src/domains/outbound-alert.ts +1 -1
  302. package/src/elanous-config-dir.ts +29 -4
  303. package/src/execution-loop/launch-gate.ts +29 -14
  304. package/src/flow/cards.ts +239 -0
  305. package/src/graph-runner/graph-tick.ts +4 -0
  306. package/src/graph-runner/runner.ts +30 -5
  307. package/src/harness/harness-cli-command.ts +259 -38
  308. package/src/harness/harness-incidents.ts +45 -12
  309. package/src/harness/harness-pod-dispatch.ts +102 -11
  310. package/src/harness/harness-queue-child.ts +3 -3
  311. package/src/harness/harness-queue.ts +583 -65
  312. package/src/harness/helper-repair.ts +87 -0
  313. package/src/harness/helper-scan.ts +151 -0
  314. package/src/harness/launch-stamp.ts +236 -0
  315. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  316. package/src/harness/ledger-write-guard.ts +35 -0
  317. package/src/harness/model-input-observation.ts +37 -0
  318. package/src/harness/pod-memory-policy.ts +15 -0
  319. package/src/harness/repository-name.ts +55 -0
  320. package/src/hq/fence-audit.ts +90 -0
  321. package/src/hq/hq.ts +63 -5
  322. package/src/hq/lease.ts +4 -4
  323. package/src/hq/move-plan.ts +126 -0
  324. package/src/hq/promote.ts +332 -0
  325. package/src/index.ts +159 -27
  326. package/src/intake-plane/check.ts +63 -0
  327. package/src/intake-plane/digest.ts +37 -15
  328. package/src/intake-plane/front-classifier.ts +6 -0
  329. package/src/intake-plane/items.ts +3 -2
  330. package/src/intake-plane/lens.ts +177 -0
  331. package/src/intake-plane/linear-wish.ts +39 -11
  332. package/src/intake-plane/route.ts +145 -1
  333. package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
  334. package/src/intake-plane/wish-card.ts +53 -2
  335. package/src/intake-plane/wish-folder.ts +5 -2
  336. package/src/intake-plane/wish-reply.ts +72 -0
  337. package/src/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
  338. package/src/launch-head/seat-goal-queue.ts +198 -0
  339. package/src/lessons/lesson-import.ts +106 -0
  340. package/src/lessons/lesson-ledger.ts +206 -0
  341. package/src/loops/activity.ts +203 -0
  342. package/src/loops/budget.ts +63 -0
  343. package/src/loops/checker.ts +5 -3
  344. package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
  345. package/src/loops/loop-cli.ts +36 -4
  346. package/src/loops/neighbors.ts +40 -0
  347. package/src/loops/observe.ts +10 -0
  348. package/src/loops/orchestrator/author-depth.ts +220 -0
  349. package/src/loops/orchestrator/author-input-check.ts +82 -0
  350. package/src/loops/orchestrator/author-ledger.ts +232 -0
  351. package/src/loops/orchestrator/finish-rate.ts +175 -0
  352. package/src/loops/orchestrator/overlap.ts +51 -12
  353. package/src/loops/orchestrator/resync-shadow.ts +227 -0
  354. package/src/loops/orchestrator/seat-share.ts +56 -0
  355. package/src/loops/orchestrator/tick.ts +309 -0
  356. package/src/loops/orchestrator/traffic.ts +134 -13
  357. package/src/loops/registry.ts +535 -9
  358. package/src/loops/status-rows.ts +118 -0
  359. package/src/loops/verdict.ts +17 -5
  360. package/src/maturity/bot-slash-catalog.ts +66 -0
  361. package/src/maturity/feature-maturity.ts +9 -5
  362. package/src/maturity/tui-slash-availability.ts +31 -0
  363. package/src/mss/logging/log-store.ts +35 -4
  364. package/src/nexus/api/dashboard.ts +6 -1
  365. package/src/nexus/api/http-server.ts +33 -3
  366. package/src/nexus/api/loop-edges.ts +173 -0
  367. package/src/nexus/api/merge-approvals-gate.ts +1 -1
  368. package/src/nexus/api/schedules-read.ts +18 -1
  369. package/src/nexus/api/seat-requests.ts +79 -56
  370. package/src/nexus/api/task-cards-api.ts +44 -5
  371. package/src/nexus/api/workflows.ts +2 -1
  372. package/src/prompt-enhance/enhance.ts +6 -1
  373. package/src/release-loop/auto-start.ts +51 -0
  374. package/src/release-loop/checklist.ts +22 -4
  375. package/src/release-loop/feature-store.ts +71 -32
  376. package/src/release-loop/landing-freeze.ts +121 -0
  377. package/src/release-loop/merged-pr-checklist.ts +125 -0
  378. package/src/release-loop/placement.ts +302 -0
  379. package/src/release-loop/release-schedule.ts +3 -1
  380. package/src/seat-dispatch/ceo-commands.ts +2 -2
  381. package/src/seat-dispatch/seat-ask.ts +16 -3
  382. package/src/seat-dispatch/seat-questions.ts +58 -0
  383. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  384. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  385. package/src/seat-loop/seat-loop.ts +531 -40
  386. package/src/self-dev/dev-pipeline.ts +14 -6
  387. package/src/self-dev/draft-sweep.ts +162 -6
  388. package/src/self-dev/draft-triage-rules.ts +25 -8
  389. package/src/self-dev/orchestrate.ts +15 -1
  390. package/src/self-dev/review-provider-fallback.ts +6 -1
  391. package/src/self-dev/run-store.ts +73 -1
  392. package/src/self-dev/run-supervisor.ts +57 -2
  393. package/src/self-dev/self-orchestrate-runtime.ts +5 -1
  394. package/src/self-implement/budget-gate.ts +29 -0
  395. package/src/self-implement/expose-gate.ts +47 -0
  396. package/src/self-implement/frozen-merges.ts +295 -0
  397. package/src/self-implement/gate-cli.ts +10 -4
  398. package/src/self-implement/gate-scope.ts +46 -9
  399. package/src/self-implement/goal-archive.ts +222 -0
  400. package/src/self-implement/goal-author.ts +43 -5
  401. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  402. package/src/self-implement/goal-lookup.ts +147 -0
  403. package/src/self-implement/heal-intake.ts +197 -0
  404. package/src/self-implement/host-regate.ts +32 -2
  405. package/src/self-implement/l8-merge-queue.ts +132 -0
  406. package/src/self-implement/orchestrator.ts +299 -48
  407. package/src/self-implement/pipeline-shape.ts +1 -1
  408. package/src/self-implement/revert-guard.ts +97 -0
  409. package/src/self-implement/run-ledger.ts +3 -1
  410. package/src/self-implement/run-status-mapping.ts +2 -0
  411. package/src/self-implement/seams.ts +29 -9
  412. package/src/self-implement/self-implement-cli.ts +1 -0
  413. package/src/self-implement/speed-claims.ts +338 -0
  414. package/src/session/index.ts +2 -0
  415. package/src/steward/triage.ts +74 -3
  416. package/src/steward/working-backwards.ts +4 -1
  417. package/src/task-cards/card-cli.ts +10 -2
  418. package/src/task-cards/card-store.ts +10 -6
  419. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  420. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  421. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  422. package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
  423. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
  424. package/src/task-orchestrator/surfaces/pod-pool.ts +60 -8
  425. package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
  426. package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
  427. package/src/telegram-commands.ts +27 -3
  428. package/src/telegram-loops-command.ts +44 -0
  429. package/src/telegram.ts +25 -4
  430. package/src/user-config.ts +146 -18
  431. package/src/version/packed-revision.json +1 -1
  432. package/src/workflow-runtime/nodes/bash.ts +9 -1
  433. package/src/workflow-runtime/storage.ts +31 -13
  434. package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
  435. package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
  436. package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
  437. package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
  438. package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
  439. package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
  440. package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
  441. package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
  442. package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
  443. package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
  444. package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
  445. package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
  446. package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
  447. package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
  448. package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
  449. package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
  450. package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
  451. package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
  452. package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
  453. package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
  454. package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
  455. package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
  456. package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
  457. package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
  458. package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
  459. package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
  460. package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
  461. package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
  462. package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
  463. package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
  464. package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
  465. package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
  466. package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
  467. package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
  468. package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
  469. package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
  470. package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
  471. package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
  472. package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
  473. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
  474. package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
  475. package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +0 -1
  476. package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
  477. package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
  478. package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
  479. package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
  480. package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
  481. package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
  482. package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
  483. package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
  484. package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
  485. package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
  486. package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
  487. package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
  488. package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
  489. package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
  490. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +0 -1
  491. package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
  492. package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
  493. package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
  494. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
  495. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
  496. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
  497. package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
  498. package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
  499. package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
  500. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
  501. package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
  502. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-78b40a344ed22f24.js +0 -1
  503. package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
  504. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
  505. package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
  506. package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
  507. package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
  508. package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
  509. package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
  510. package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
  511. package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
  512. package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
  513. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
  514. package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
  515. package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
  516. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
  517. package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
  518. package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
  519. package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
  520. package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
  521. package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
  522. package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
  523. package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
  524. /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → LZcstaO9_NXypPFmujolv}/_ssgManifest.js +0 -0
@@ -0,0 +1,87 @@
1
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs';
2
+ import { createHash } from 'node:crypto';
3
+ import { dirname, join } from 'node:path';
4
+ import { debug } from '../debug/log.js';
5
+ import type { RunGh, ScannedStoppedPr, StoppedPrCategory } from './helper-scan.js';
6
+
7
+ export interface RepairGoalInput {
8
+ pr: number;
9
+ category: StoppedPrCategory;
10
+ /** The original user's request, not the supervisor's stop report or a paraphrase. */
11
+ originalRequest: string;
12
+ /** Remaining reviewer must-fix items, in their original wording and order. */
13
+ mustFix: readonly string[];
14
+ }
15
+
16
+ /** Author a single follow-up goal without changing the original ask or reviewer findings. */
17
+ export function authorRepairGoal({ pr, category, originalRequest, mustFix }: RepairGoalInput): string | null {
18
+ if ((category !== 'review-budget' && category !== 'review-oscillation') || !originalRequest.trim() || !mustFix.length || mustFix.some((item) => !item.trim())) {
19
+ return null;
20
+ }
21
+
22
+ const guidance = category === 'review-oscillation'
23
+ ? '기존 시험 설계를 보존하고 리뷰 진동·설계 역행을 확인한 뒤 남은 must-fix를 수리하라.'
24
+ : '남은 must-fix를 수리하라. 원래 요청의 범위와 기존 시험 설계를 보존하라.';
25
+ return `${originalRequest}${originalRequest.endsWith('\n') ? '' : '\n'}\n## PR #${pr} 수리\n${guidance}\n\n## 남은 must-fix (원문)\n${mustFix.join('\n')}`;
26
+ }
27
+
28
+ /** PR 본문에서 원 요청(## 요청 절)과 남은 must-fix(## 마지막 리뷰 must-fix · ## Follow-up must-fix 의 «- » 항목)를 원문 그대로 뽑는다. */
29
+ export function repairInputsFromBody(body: string): { originalRequest: string; mustFix: string[] } {
30
+ const section = (title: RegExp) => {
31
+ const lines = body.split('\n');
32
+ const start = lines.findIndex((line) => title.test(line));
33
+ if (start < 0) return [] as string[];
34
+ const end = lines.findIndex((line, index) => index > start && /^## /.test(line));
35
+ return lines.slice(start + 1, end < 0 ? undefined : end);
36
+ };
37
+ const requestLines = section(/^## 요청\s*$/);
38
+ while (requestLines[0] === '') requestLines.shift();
39
+ while (requestLines.length > 1 && requestLines.at(-1) === '' && requestLines.at(-2) === '') requestLines.pop();
40
+ if (requestLines.length === 2 && requestLines[1] === '') requestLines.pop();
41
+ const originalRequest = requestLines.join('\n');
42
+ const mustFix: string[] = [];
43
+ for (const lines of [section(/^## 마지막 리뷰 must-fix/), section(/^## Follow-up must-fix/)]) {
44
+ let item: string[] = [];
45
+ const flush = () => {
46
+ while (item.at(-1) === '') item.pop();
47
+ if (item.length) mustFix.push(item.join('\n'));
48
+ item = [];
49
+ };
50
+ for (const line of lines) {
51
+ if (/^- /.test(line)) {
52
+ flush();
53
+ item.push(line);
54
+ } else if (item.length) {
55
+ item.push(line);
56
+ }
57
+ }
58
+ flush();
59
+ }
60
+ return { originalRequest, mustFix };
61
+ }
62
+
63
+ export type RepairShadowRow = { pr: number; category: StoppedPrCategory; goalHash: string; at: string };
64
+
65
+ /** 그림자: 수리 골을 저작해 헬퍼 원장에만 남긴다(발사·라벨 0) · 같은 PR 은 한 번만. */
66
+ export async function recordRepairShadows(rows: readonly ScannedStoppedPr[], deps: { runGh: RunGh; root: string; now?: Date }): Promise<RepairShadowRow[]> {
67
+ const ledger = join(deps.root, 'helper', 'repairs.jsonl');
68
+ const seen = new Set<number>();
69
+ if (existsSync(ledger)) for (const line of readFileSync(ledger, 'utf8').split('\n')) {
70
+ if (!line.trim()) continue;
71
+ try { seen.add((JSON.parse(line) as { pr: number }).pr); } catch { /* malformed line is skipped */ }
72
+ }
73
+ const written: RepairShadowRow[] = [];
74
+ for (const row of rows) {
75
+ if ((row.category !== 'review-budget' && row.category !== 'review-oscillation') || seen.has(row.pr)) continue;
76
+ const view = JSON.parse(await deps.runGh(['pr', 'view', String(row.pr), '--json', 'body'])) as { body: string };
77
+ const goal = authorRepairGoal({ pr: row.pr, category: row.category, ...repairInputsFromBody(view.body) });
78
+ if (!goal) continue;
79
+ const entry: RepairShadowRow = { pr: row.pr, category: row.category, goalHash: createHash('sha256').update(goal).digest('hex').slice(0, 16), at: (deps.now ?? new Date()).toISOString() };
80
+ mkdirSync(dirname(ledger), { recursive: true });
81
+ appendFileSync(ledger, JSON.stringify({ ...entry, mode: 'shadow', goal }) + '\n');
82
+ debug.log('harness.helper', 'repair-authored', { pr: row.pr, category: row.category, mode: 'shadow' });
83
+ seen.add(row.pr);
84
+ written.push(entry);
85
+ }
86
+ return written;
87
+ }
@@ -0,0 +1,151 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { debug } from '../debug/log.js';
3
+
4
+ export type StoppedPrCategory = 'review-budget' | 'no-progress' | 'environment-gap' | 'memo-not-reflected' | 'review-oscillation' | 'goal-revision' | 'report-deficit' | 'unknown';
5
+ export type StoppedPrClassification = {
6
+ category: StoppedPrCategory;
7
+ signals: string[];
8
+ firstAction: string;
9
+ blockers: string[];
10
+ blockerCount: number;
11
+ hasPublicExportLeak: boolean;
12
+ };
13
+
14
+ function section(body: string, title: string): string {
15
+ const heading = `## ${title}`;
16
+ const lines = body.split(/\r?\n/);
17
+ const start = lines.findIndex((line) => line.trim() === heading);
18
+ if (start < 0) return '';
19
+ const end = lines.findIndex((line, i) => i > start && /^##\s+/.test(line));
20
+ return lines.slice(start + 1, end < 0 ? undefined : end).join('\n');
21
+ }
22
+
23
+ function field(text: string, key: string): string {
24
+ const line = text.split(/\r?\n/).find((entry) => new RegExp(`^\\s*(?:[-*]\\s*)?(?:\\*\\*)?${key}(?:\\*\\*)?\\s*[::]\\s*`, 'i').test(entry));
25
+ return line?.replace(new RegExp(`^\\s*(?:[-*]\\s*)?(?:\\*\\*)?${key}(?:\\*\\*)?\\s*[::]\\s*`, 'i'), '').trim().replace(/^`|`$/g, '') ?? '';
26
+ }
27
+
28
+ export function classifyStoppedPr({ body, comments }: { body: string; comments: readonly (string | { body: string })[] }): StoppedPrClassification {
29
+ const stop = section(body, '중단 사유');
30
+ const classification = section(body, '중단 원인 분류');
31
+ const gate = section(body, 'Gate');
32
+ const reason = field(stop, 'reason');
33
+ const verdict = field(stop, 'verdict');
34
+ const label = field(classification, 'classification');
35
+ const basis = field(classification, 'classificationBasis');
36
+ const commentText = comments.map((comment) => typeof comment === 'string' ? comment : comment.body);
37
+ const signals = [reason, verdict, label, basis].filter(Boolean);
38
+ const allBlockers = gate.split(/\r?\n/).filter((line) => /^\s*(?:✗\s+|\[test\]\s+FAIL\b)/i.test(line));
39
+ const blockers = allBlockers.slice(0, 5);
40
+ const publicExportLeak = allBlockers.find((line) => /^\s*✗\s+public-export-leak\b/i.test(line));
41
+ if (publicExportLeak && !blockers.includes(publicExportLeak)) signals.push(publicExportLeak);
42
+ const failedTests = allBlockers.filter((line) => /^\s*\[test\]\s+FAIL\b.*[—–-](?:\s*\d+\s+(?:pass|fail|skip|errors?)\b\s*[,|])*\s*[1-9]\d*\s+errors?\b/i.test(line));
43
+ const failedPwaTest = failedTests.find((line) => /\bapps\/pwa\/[^\s]*\.test\.tsx?\b/i.test(line));
44
+ if (failedPwaTest) signals.push(failedPwaTest);
45
+ const hostRetest = /(?:^|;\s*)host=[^;\n]+;\s*cause=(?:missing (?:node_modules|dependency|environment variable)|호스트 의존성 없음)[^;\n]*;\s*retest=PASS bun test (\S+\.test\.tsx?)(?:\s*(?:;|$))/i.exec(basis);
46
+ // A code error in the Gate outranks any written basis: a basis string alone never turns a code failure into an environment gap.
47
+ const codeErrorInGate = /\b(?:TypeError|ReferenceError|SyntaxError|RangeError)\b|expect\(received\)/.test(gate);
48
+ const hostFailureConfirmed = !codeErrorInGate && Boolean(hostRetest && failedTests.some((line) => {
49
+ const failedPath = /^\s*\[test\]\s+FAIL\s+bun test (\S+\.test\.tsx?)\s+[—–-]/i.exec(line)?.[1];
50
+ return failedPath === hostRetest?.[1];
51
+ }));
52
+ const reviews = commentText.flatMap((text) => {
53
+ const round = Number(/<!--\s*elanous-pr-comment v1 role=reviewer round=(\d+)\b/.exec(text)?.[1] ?? /\bRound\s+(\d+):\s*reviewer requested\b/i.exec(text)?.[1] ?? -1);
54
+ const mustFix = /\bRound\s+\d+:\s*reviewer requested\s+(\d+)\s+must-fix change\(s\)/i.exec(text);
55
+ return mustFix ? [{ round, count: Number(mustFix[1]), evidence: mustFix[0] }] : [];
56
+ }).sort((a, b) => b.round - a.round);
57
+ const latest = reviews[0];
58
+ if (latest) signals.push(latest.evidence);
59
+
60
+ const stopEvidence = `${reason}\n${verdict}\n${label}\n${basis}\n${body.match(/정지\((?:needs-human|no-progress)\)/g)?.join('\n') ?? ''}`;
61
+ let category: StoppedPrCategory = 'unknown';
62
+ let action = '하지 않음 — 정지 원인 근거 부족';
63
+ if (hostFailureConfirmed && failedTests.length > 0 && (/(?:자식\s*무관|environment(?:-gap|al)?|환경\s*결손)/i.test(`${label}\n${basis}`) || failedPwaTest)) {
64
+ category = 'environment-gap';
65
+ action = '그래프 인스턴스 성형 — 호스트에서 실패 시험만 재측; 원 Gate 보존';
66
+ } else if (/(?:메모\s*미반영|memo[- ]not[- ]reflected)/i.test(`${label}\n${basis}\n${reason}`)) {
67
+ category = 'memo-not-reflected';
68
+ action = '안내 메모 — 동일 칸의 살아 있는 런과 다음 산출 대조; 대상 불명이면 하지 않음';
69
+ } else if (/(?:리뷰\s*진동|설계\s*역행|review[- ]oscillation)/i.test(`${label}\n${basis}\n${reason}`)) {
70
+ category = 'review-oscillation';
71
+ action = latest?.count ? '헬퍼 자식 spawn — 기존 시험 설계 보존 후 한 라운드' : '하지 않음 — 구체적인 must-fix 없음';
72
+ } else if (label === 'goal-unconvergeable-candidate' || basis === 'supervisor-unconvergeable-goal-candidate') {
73
+ category = 'goal-revision';
74
+ signals.push(label === 'goal-unconvergeable-candidate'
75
+ ? `classification=${label} → goal-revision`
76
+ : `classificationBasis=${basis} → goal-revision`);
77
+ action = '골 개정 — 골 문면(판정 신호·경계)을 고쳐 재발사; 구현 자식 붙이지 않음';
78
+ } else if (label === 'report-deficit') {
79
+ category = 'report-deficit';
80
+ signals.push(`classification=${label} → report-deficit`);
81
+ action = '증거 재측 — 인용된 판정 명령을 호스트에서 다시 돌려 결과를 PR 에 남김; 코드 수정 아님';
82
+ } else if (/review-budget/i.test(stopEvidence) || basis === 'must-fix-reported') {
83
+ // Real supervisor bodies (UNCONVERGEABLE · implementation-deficit) carry `classificationBasis: must-fix-reported` instead of the RFC word.
84
+ if (basis === 'must-fix-reported') signals.push('classificationBasis=must-fix-reported → review-budget');
85
+ category = 'review-budget';
86
+ action = latest?.count ? '헬퍼 자식 spawn — must-fix 원문과 기존 시험 설계로 한 라운드' : '하지 않음 — must-fix 0 또는 미관측; 수리 자식 대상 아님';
87
+ } else if (/no-progress/i.test(stopEvidence) || basis === 'no-must-fix-without-clean-worktree-or-completed-without-changes') {
88
+ if (basis === 'no-must-fix-without-clean-worktree-or-completed-without-changes') signals.push(`classificationBasis=${basis} → no-progress`);
89
+ category = 'no-progress';
90
+ const green = /\[test\]\s+PASS\b/.test(gate) && !/\[test\]\s+FAIL\b/.test(gate);
91
+ const documentGoal = /(?:문서\s*(?:골|목표)|docs?\/|RFC)/i.test(`${reason}\n${basis}\n${body.split(/\r?\n/)[0] ?? ''}`);
92
+ action = green && latest?.count === 0 && documentGoal
93
+ ? '중간 착지 ⊕ 연결 골 — must-fix 0 · 시험 초록 · 문서 골이면 잔여 확인; 잔여 0이면 연결 골 하지 않음'
94
+ : '중간 착지 ⊕ 연결 골 — 녹색 조각만 확인; 불가분 골·실패 Gate 착지 하지 않음';
95
+ }
96
+ return {
97
+ category, signals,
98
+ firstAction: allBlockers.length
99
+ ? `Gate 차단 먼저${publicExportLeak ? ' (✗ public-export-leak 확인)' : category === 'environment-gap' ? ' (그래프 인스턴스 성형으로 호스트 실패 시험 재측)' : ''}`
100
+ : action,
101
+ blockers, blockerCount: allBlockers.length, hasPublicExportLeak: Boolean(publicExportLeak),
102
+ };
103
+ }
104
+
105
+ /** Stopped means the supervisor wrote a stop verdict or reason into the `## 중단 사유` section — a quoted heading or stop wording elsewhere in the body (goal text, templates) does not count. */
106
+ export function isStoppedPrBody(body: string): boolean {
107
+ const stop = section(body, '중단 사유');
108
+ return Boolean(field(stop, 'verdict') || field(stop, 'reason'));
109
+ }
110
+
111
+ export type ScannedStoppedPr = StoppedPrClassification & { pr: number; headRefName: string; isDraft: boolean };
112
+ export type RunGh = (args: readonly string[]) => string | Promise<string>;
113
+
114
+ export async function scanStoppedPrs({ runGh }: { runGh: RunGh }): Promise<ScannedStoppedPr[]> {
115
+ const prs = JSON.parse(await runGh(['pr', 'list', '--state', 'open', '--limit', '100000', '--json', 'number,headRefName,isDraft,body'])) as Array<{ number: number; headRefName: string; isDraft: boolean; body: string }>;
116
+ const results: ScannedStoppedPr[] = [];
117
+ for (const pr of prs) {
118
+ if (!pr.headRefName.startsWith('self-impl/') || !isStoppedPrBody(pr.body)) continue;
119
+ const data = JSON.parse(await runGh(['pr', 'view', String(pr.number), '--json', 'comments'])) as { comments: Array<{ body: string }> };
120
+ const result = classifyStoppedPr({ body: pr.body, comments: data.comments });
121
+ debug.log('harness.helper', 'classified', { pr: pr.number, category: result.category, firstAction: result.firstAction, blockers: result.blockers, blockerCount: result.blockerCount, hasPublicExportLeak: result.hasPublicExportLeak });
122
+ results.push({ pr: pr.number, headRefName: pr.headRefName, isDraft: pr.isDraft, ...result });
123
+ }
124
+ return results;
125
+ }
126
+
127
+ if (import.meta.main) {
128
+ const args = process.argv.slice(2);
129
+ const json = args.includes('--json');
130
+ const recordShadows = args.includes('--record-shadows');
131
+ if (args.some((arg) => arg !== '--json' && arg !== '--record-shadows') || (json && recordShadows) || args.length > 1) {
132
+ console.error('사용법: bun src/harness/helper-scan.ts [--json | --record-shadows]');
133
+ process.exitCode = 1;
134
+ } else {
135
+ // 열린 PR 본문 전체는 기본 버퍼(1MB)를 넘는다(10-05 운영 ENOBUFS) — 넉넉히 준다.
136
+ const runGh = (args: readonly string[]) => execFileSync('gh', [...args], { encoding: 'utf8', maxBuffer: 256 * 1024 * 1024 });
137
+ const rows = await scanStoppedPrs({ runGh });
138
+ if (recordShadows) {
139
+ // 명시적 그림자 기록에서만 추가 PR 조회와 헬퍼 원장 쓰기를 수행한다(발사 0).
140
+ const { recordRepairShadows } = await import('./helper-repair.js');
141
+ const { effectiveInstanceRoot } = await import('../instance/resolve.js');
142
+ const shadows = await recordRepairShadows(rows, { runGh, root: effectiveInstanceRoot() });
143
+ if (shadows.length) console.log(`수리 골 그림자 ${shadows.length}건 → helper/repairs.jsonl`);
144
+ }
145
+ if (json) console.log(JSON.stringify(rows, null, 2));
146
+ else for (const row of rows) {
147
+ const gate = row.blockerCount ? ` · Gate 차단 ${row.blockerCount}(${row.hasPublicExportLeak ? row.blockers.find((line) => /public-export-leak/i.test(line))?.trim() ?? '✗ public-export-leak (6번째 이후)' : row.blockers[0]!.trim()})` : '';
148
+ console.log(`#${row.pr} ${row.category}${gate} → ${row.firstAction}`);
149
+ }
150
+ }
151
+ }
@@ -0,0 +1,236 @@
1
+ /**
2
+ * ONEDOOR-1 — every harness run stamps which door it came through,
3
+ * who launched it, and whether it went through the queue.
4
+ *
5
+ * The stamp lives on the run ledger's first row (`run-origin`). A ledger
6
+ * written before this stamp, or a launch that named no door, reads as
7
+ * `unknown` — that is distinct from a count of zero.
8
+ */
9
+ import { readdirSync } from 'node:fs';
10
+ import { loadRunLedger, runLedgerDir, type RunLedgerEntry } from '../self-implement/run-ledger.js';
11
+
12
+ export const LAUNCH_DOORS = [
13
+ 'harness-say',
14
+ 'harness-ask',
15
+ 'dev-ask',
16
+ 'self-implement',
17
+ 'self-orchestrate',
18
+ 'queue-tick',
19
+ 'agent-mission',
20
+ 'daemon-tool',
21
+ 'telegram',
22
+ 'pwa',
23
+ 'unknown',
24
+ ] as const;
25
+
26
+ export type LaunchDoor = (typeof LAUNCH_DOORS)[number];
27
+
28
+ export const LAUNCH_SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
29
+ export type LaunchSeat = (typeof LAUNCH_SEATS)[number];
30
+
31
+ export interface LaunchStamp {
32
+ readonly entrance: LaunchDoor;
33
+ /** Seat, human session, or loop name — whichever the launch actually named. */
34
+ readonly actor: string;
35
+ readonly viaQueue: boolean;
36
+ readonly queueId?: string;
37
+ }
38
+
39
+ const ENTRANCE_TO_DOOR: Readonly<Record<string, LaunchDoor>> = {
40
+ 'cli-harness-say': 'harness-say',
41
+ 'cli-harness-ask': 'harness-ask',
42
+ 'cli-dev-ask': 'dev-ask',
43
+ 'cli-drive': 'dev-ask',
44
+ 'tui-slash-ask': 'dev-ask',
45
+ 'tui-slash-dev': 'dev-ask',
46
+ 'cli-self-implement': 'self-implement',
47
+ 'nl-self-implement': 'self-implement',
48
+ 'daemon-self-implement': 'daemon-tool',
49
+ 'cli-self-orchestrate': 'self-orchestrate',
50
+ 'nl-self-orchestrate': 'self-orchestrate',
51
+ 'cli-harness-orchestrate': 'self-orchestrate',
52
+ 'cli-harness-mission': 'agent-mission',
53
+ 'agent-mission': 'agent-mission',
54
+ 'daemon-harness-ask': 'daemon-tool',
55
+ 'daemon-tool': 'daemon-tool',
56
+ telegram: 'telegram',
57
+ pwa: 'pwa',
58
+ };
59
+
60
+ const DOOR_SET = new Set<string>(LAUNCH_DOORS);
61
+
62
+ export function isLaunchDoor(value: string): value is LaunchDoor {
63
+ return DOOR_SET.has(value);
64
+ }
65
+
66
+ /** Reuse an existing entrance tag. Anything unnamed or unrecognized is `unknown`, never a zero. */
67
+ export function doorFromEntrance(entrance: string | undefined | null): LaunchDoor {
68
+ if (!entrance) return 'unknown';
69
+ const trimmed = entrance.trim();
70
+ if (!trimmed) return 'unknown';
71
+ if (ENTRANCE_TO_DOOR[trimmed]) return ENTRANCE_TO_DOOR[trimmed];
72
+ if (isLaunchDoor(trimmed)) return trimmed;
73
+ return 'unknown';
74
+ }
75
+
76
+ export interface LaunchStampInput {
77
+ readonly entrance?: string | null;
78
+ readonly seat?: string | null;
79
+ readonly session?: string | null;
80
+ readonly loopName?: string | null;
81
+ readonly viaQueue?: boolean;
82
+ readonly queueId?: string | null;
83
+ readonly env?: NodeJS.ProcessEnv;
84
+ }
85
+
86
+ function named(value: string | null | undefined): string | undefined {
87
+ const trimmed = value?.trim();
88
+ return trimmed ? trimmed : undefined;
89
+ }
90
+
91
+ /** Seat wins, then the human session, then the loop name. A blank actor stays blank. */
92
+ export function resolveLaunchActor(input: LaunchStampInput, env: NodeJS.ProcessEnv = process.env): string {
93
+ const seat = named(input.seat) ?? named(env.ELANOUS_HARNESS_SEAT);
94
+ if (seat && (LAUNCH_SEATS as readonly string[]).includes(seat)) return seat;
95
+ return named(input.session)
96
+ ?? named(env.ELANOUS_ORIGIN_SESSION)
97
+ ?? named(input.loopName)
98
+ ?? named(env.ELANOUS_LOOP_NAME)
99
+ ?? '';
100
+ }
101
+
102
+ export function resolveLaunchStamp(input: LaunchStampInput = {}, env: NodeJS.ProcessEnv = input.env ?? process.env): LaunchStamp {
103
+ const queueId = named(input.queueId) ?? named(env.ELANOUS_HARNESS_QUEUE_LAUNCH);
104
+ const viaQueue = input.viaQueue === true || queueId !== undefined;
105
+ const door = viaQueue ? 'queue-tick' : doorFromEntrance(input.entrance ?? env.ELANOUS_HARNESS_ENTRANCE);
106
+ return {
107
+ entrance: door,
108
+ actor: resolveLaunchActor(input, env),
109
+ viaQueue,
110
+ ...(queueId ? { queueId } : {}),
111
+ };
112
+ }
113
+
114
+ /** Read the stamp off a ledger's first row. Missing or unreadable stamps are `unknown`. */
115
+ export function launchStampFromLedger(entries: readonly RunLedgerEntry[] | null | undefined): LaunchStamp {
116
+ const first = entries?.[0];
117
+ const raw = first?.data?.launch;
118
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
119
+ return { entrance: 'unknown', actor: '', viaQueue: false };
120
+ }
121
+ const launch = raw as Record<string, unknown>;
122
+ const entrance = typeof launch.entrance === 'string' && isLaunchDoor(launch.entrance) ? launch.entrance : 'unknown';
123
+ const queueId = typeof launch.queueId === 'string' && launch.queueId.trim() ? launch.queueId.trim() : undefined;
124
+ return {
125
+ entrance,
126
+ actor: typeof launch.actor === 'string' ? launch.actor : '',
127
+ viaQueue: launch.viaQueue === true || queueId !== undefined,
128
+ ...(queueId ? { queueId } : {}),
129
+ };
130
+ }
131
+
132
+ export interface DoorRow {
133
+ readonly entrance: LaunchDoor;
134
+ readonly runs: number;
135
+ readonly viaQueue: number;
136
+ readonly outsideQueue: number;
137
+ }
138
+
139
+ export interface DoorTable {
140
+ readonly sinceMs: number;
141
+ readonly runs: number;
142
+ readonly unknown: number;
143
+ readonly outsideQueue: number;
144
+ readonly viaQueue: number;
145
+ readonly rows: readonly DoorRow[];
146
+ }
147
+
148
+ export interface DoorTableOptions {
149
+ readonly dir?: string;
150
+ readonly sinceMs?: number;
151
+ readonly now?: number;
152
+ readonly list?: (dir: string) => string[];
153
+ readonly load?: (runId: string, dir: string) => RunLedgerEntry[] | null;
154
+ }
155
+
156
+ const DAY_MS = 24 * 60 * 60 * 1000;
157
+
158
+ function entryTime(entry: RunLedgerEntry | undefined): number | undefined {
159
+ if (!entry?.timestamp) return undefined;
160
+ const parsed = Date.parse(entry.timestamp);
161
+ return Number.isFinite(parsed) ? parsed : undefined;
162
+ }
163
+
164
+ /** One row per door that actually fired, plus the period totals. Untagged ledgers count as `unknown`. */
165
+ export function queryLaunchDoors(options: DoorTableOptions = {}): DoorTable {
166
+ const dir = options.dir ?? runLedgerDir();
167
+ const now = options.now ?? Date.now();
168
+ const sinceMs = options.sinceMs ?? DAY_MS;
169
+ const cutoff = now - sinceMs;
170
+ const list = options.list ?? ((path: string) => {
171
+ try { return readdirSync(path); } catch { return []; }
172
+ });
173
+ const load = options.load ?? ((runId: string, ledgerDir: string) => {
174
+ try { return loadRunLedger(runId, ledgerDir); } catch { return null; }
175
+ });
176
+ const counts = new Map<LaunchDoor, { runs: number; viaQueue: number }>();
177
+ let runs = 0;
178
+ let unknown = 0;
179
+ let outsideQueue = 0;
180
+ let viaQueue = 0;
181
+ for (const file of list(dir)) {
182
+ if (!file.endsWith('.jsonl')) continue;
183
+ const runId = file.slice(0, -'.jsonl'.length);
184
+ const entries = load(runId, dir);
185
+ if (!entries || entries.length === 0) continue;
186
+ const started = entryTime(entries[0]);
187
+ if (started !== undefined && started < cutoff) continue;
188
+ const stamp = launchStampFromLedger(entries);
189
+ runs += 1;
190
+ if (stamp.entrance === 'unknown') unknown += 1;
191
+ if (stamp.viaQueue) viaQueue += 1;
192
+ else outsideQueue += 1;
193
+ const row = counts.get(stamp.entrance) ?? { runs: 0, viaQueue: 0 };
194
+ row.runs += 1;
195
+ if (stamp.viaQueue) row.viaQueue += 1;
196
+ counts.set(stamp.entrance, row);
197
+ }
198
+ const rows = [...counts.entries()]
199
+ .map(([entrance, row]) => ({
200
+ entrance,
201
+ runs: row.runs,
202
+ viaQueue: row.viaQueue,
203
+ outsideQueue: row.runs - row.viaQueue,
204
+ }))
205
+ .sort((left, right) => right.runs - left.runs || left.entrance.localeCompare(right.entrance));
206
+ return { sinceMs, runs, unknown, outsideQueue, viaQueue, rows };
207
+ }
208
+
209
+ export function renderLaunchDoors(table: DoorTable): string {
210
+ const lines = [
211
+ `doors runs=${table.runs} outside-queue=${table.outsideQueue} via-queue=${table.viaQueue} unknown=${table.unknown}`,
212
+ 'entrance runs via-queue outside-queue',
213
+ ];
214
+ for (const row of table.rows) {
215
+ lines.push(`${row.entrance.padEnd(16)} ${String(row.runs).padStart(4)} ${String(row.viaQueue).padStart(9)} ${String(row.outsideQueue).padStart(13)}`);
216
+ }
217
+ if (table.rows.length === 0) lines.push('(no runs)');
218
+ const summed = table.rows.reduce((sum, row) => sum + row.runs, 0);
219
+ lines.push(`total ${String(summed).padStart(4)}`);
220
+ return lines.join('\n');
221
+ }
222
+
223
+ const SINCE_PATTERN = /^(\d+)(m|h|d)$/;
224
+
225
+ /** `--since 1d` (also `30m`, `12h`). Anything else is refused rather than guessed. */
226
+ export function parseDoorSince(value: string | undefined, now = Date.now()): { sinceMs: number } | { error: string } {
227
+ if (value === undefined || value === '') return { sinceMs: DAY_MS };
228
+ const match = SINCE_PATTERN.exec(value.trim());
229
+ if (!match) return { error: `--since 는 30m · 12h · 1d 형태여야 합니다: ${value}` };
230
+ const amount = Number(match[1]);
231
+ const unit = match[2] === 'm' ? 60_000 : match[2] === 'h' ? 3_600_000 : DAY_MS;
232
+ if (!Number.isSafeInteger(amount) || amount < 1) return { error: `--since 는 1 이상이어야 합니다: ${value}` };
233
+ const sinceMs = amount * unit;
234
+ if (sinceMs > now) return { sinceMs };
235
+ return { sinceMs };
236
+ }
@@ -0,0 +1,12 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
3
+ import { recordRunExit } from './harness-incidents.js';
4
+ import { appendRunLedgerEntry } from '../self-implement/run-ledger.js';
5
+
6
+ const root = prodInstanceRoot();
7
+ if (effectiveInstanceRoot() !== root) throw new Error('not the effective production root');
8
+ const before = existsSync(root) ? readdirSync(root) : null;
9
+ recordRunExit({ runId: 'run-test', reason: 'signal', status: 1, signal: null, at: new Date().toISOString() }, process.argv[2] ?? root);
10
+ appendRunLedgerEntry({ runId: 'run-test', event: 'start', data: {} }, process.argv[3] ?? `${root}/run-ledger`);
11
+ const after = existsSync(root) ? readdirSync(root) : null;
12
+ console.log(JSON.stringify({ env: { NODE_ENV: process.env.NODE_ENV, ELANOUS_TEST_HOME: process.env.ELANOUS_TEST_HOME, HOME: process.env.HOME, ELANOUS_STATE_DIR: process.env.ELANOUS_STATE_DIR }, root, effectiveInstanceRoot: effectiveInstanceRoot(), before, after }));
@@ -0,0 +1,35 @@
1
+ import { realpathSync } from 'node:fs';
2
+ import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
3
+ import { debug } from '../debug/log.js';
4
+ import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
5
+
6
+ function physicalPath(path: string): string {
7
+ const absolute = resolve(path);
8
+ try {
9
+ return realpathSync(absolute);
10
+ } catch (error) {
11
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
12
+ const parent = dirname(absolute);
13
+ if (parent === absolute) throw error;
14
+ return resolve(physicalPath(parent), relative(parent, absolute));
15
+ }
16
+ }
17
+
18
+ /** Refuse a test's write to the production instance, including an explicitly supplied ledger directory. */
19
+ export function refuseProductionLedgerWriteInTest(
20
+ root: string,
21
+ store: string,
22
+ env: NodeJS.ProcessEnv = process.env,
23
+ productionRoot = prodInstanceRoot(),
24
+ instanceRoot = effectiveInstanceRoot(),
25
+ ): boolean {
26
+ if (env.NODE_ENV !== 'test' && !env.ELANOUS_TEST_HOME) return false;
27
+ const target = physicalPath(root);
28
+ const production = physicalPath(productionRoot);
29
+ const withinProduction = relative(production, target);
30
+ if (withinProduction === '..' || withinProduction.startsWith(`..${sep}`) || isAbsolute(withinProduction)) return false;
31
+ try {
32
+ debug.log('harness.incidents', 'write-refused', { store, root: target, effectiveInstanceRoot: instanceRoot });
33
+ } catch { /* Observation cannot reopen a closed write boundary. */ }
34
+ return true;
35
+ }
@@ -0,0 +1,37 @@
1
+ import { debug } from '../debug/log.js';
2
+
3
+ /** One observation per harness node execution or loop tick, not per LLM request. */
4
+ export type ModelInputObservationTarget = {
5
+ scope: 'harness-node' | 'loop-tick';
6
+ nodeKind: string;
7
+ graphId?: string;
8
+ runId?: string;
9
+ nodeId?: string;
10
+ tickId?: string;
11
+ };
12
+
13
+ export type ModelInputObservation = ModelInputObservationTarget & (
14
+ | { status: 'measured'; inputTokens: number }
15
+ | { status: 'unmeasured'; inputTokens: null; reason: 'usage-unavailable' | 'invalid-usage' }
16
+ );
17
+
18
+ /**
19
+ * Publish a single structured record at the execution boundary. Only provider-reported,
20
+ * nonnegative integer counts are measurements; missing/invalid usage is never a zero.
21
+ * The caller supplies the total across model calls for this execution, if available.
22
+ */
23
+ export function observeModelInputTokens(
24
+ target: ModelInputObservationTarget,
25
+ inputTokens?: number | null,
26
+ ): ModelInputObservation {
27
+ const observation: ModelInputObservation = inputTokens === null || inputTokens === undefined
28
+ ? { ...target, status: 'unmeasured', inputTokens: null, reason: 'usage-unavailable' }
29
+ : Number.isSafeInteger(inputTokens) && inputTokens >= 0
30
+ ? { ...target, status: 'measured', inputTokens }
31
+ : { ...target, status: 'unmeasured', inputTokens: null, reason: 'invalid-usage' };
32
+
33
+ try {
34
+ debug.log('harness.model-input', 'recorded', observation);
35
+ } catch { /* Logging must not interrupt the node or tick. */ }
36
+ return observation;
37
+ }
@@ -0,0 +1,15 @@
1
+ import type { GoalType } from '../self-implement/goal-author.js';
2
+ import type { PodMemoryTier } from '../task-orchestrator/surfaces/self-implement-pod.js';
3
+
4
+ export type PodMemorySelectionReason = 'explicit-option' | 'goal-type-default' | 'existing-tier';
5
+
6
+ /** Select the harness Pod tier without discarding the existing implementation/operate decision. */
7
+ export function selectPodMemoryTier(
8
+ goalType: GoalType,
9
+ explicitPodMemory: PodMemoryTier | undefined,
10
+ existingTier: PodMemoryTier = 'standard',
11
+ ): { tier: PodMemoryTier; reason: PodMemorySelectionReason } {
12
+ if (explicitPodMemory !== undefined) return { tier: explicitPodMemory, reason: 'explicit-option' };
13
+ if (goalType === 'research' || goalType === 'document') return { tier: 'lite', reason: 'goal-type-default' };
14
+ return { tier: existingTier, reason: 'existing-tier' };
15
+ }
@@ -0,0 +1,55 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { getUserConfig, type UserConfig } from '../user-config.js';
3
+
4
+ const REPOSITORY_NAME = /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/;
5
+
6
+ export interface RepositoryNameOptions {
7
+ /** Explicit --repo value. */
8
+ repo?: string;
9
+ config?: Pick<UserConfig, 'harness'>;
10
+ env?: NodeJS.ProcessEnv;
11
+ cwd?: string;
12
+ executeGh?: (args: string[]) => string;
13
+ /** Test seam. Default asks git whether cwd is a work tree. */
14
+ insideGitWorkTree?: (cwd: string) => boolean;
15
+ }
16
+
17
+ /** Resolve the GitHub owner/name without asking gh outside a git checkout. */
18
+ export function resolveRepositoryName(options: RepositoryNameOptions = {}): string {
19
+ const env = options.env ?? process.env;
20
+ const cwd = options.cwd ?? process.cwd();
21
+ const valid = (source: string, candidate: string | undefined): string | undefined => {
22
+ if (!candidate?.trim()) return undefined;
23
+ const name = candidate.trim();
24
+ if (!REPOSITORY_NAME.test(name) || name.includes('..')) {
25
+ throw new Error(`invalid ${source} (expected owner/name): ${candidate}`);
26
+ }
27
+ return name;
28
+ };
29
+ const flag = valid('--repo', options.repo);
30
+ if (flag) return flag;
31
+ const configured = valid('harness.repo', (options.config ?? getUserConfig()).harness?.repo);
32
+ if (configured) return configured;
33
+ const inherited = valid('GH_REPO', env.GH_REPO);
34
+ if (inherited) return inherited;
35
+ const insideGit = options.insideGitWorkTree ?? ((dir) => {
36
+ try {
37
+ return execFileSync('git', ['rev-parse', '--is-inside-work-tree'], {
38
+ cwd: dir, encoding: 'utf8', timeout: 5_000, stdio: ['ignore', 'pipe', 'pipe'],
39
+ }).trim() === 'true';
40
+ } catch { return false; }
41
+ });
42
+ if (insideGit(cwd)) {
43
+ try {
44
+ const name = (options.executeGh ?? ((args) => execFileSync('gh', args, {
45
+ cwd, encoding: 'utf8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'], env,
46
+ })))(['repo', 'view', '--json', 'nameWithOwner', '--jq', '.nameWithOwner']).trim();
47
+ if (REPOSITORY_NAME.test(name) && !name.includes('..')) return name;
48
+ } catch (error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ const line = message.split(/\r?\n/).map((row) => row.trim()).find((row) => row && !/^at\s/.test(row));
51
+ throw new Error(line || '저장소 이름 없음 — --repo 또는 harness.repo 설정');
52
+ }
53
+ }
54
+ throw new Error('저장소 이름 없음 — --repo 또는 harness.repo 설정');
55
+ }