elanous 0.2.14 → 0.2.16

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 (350) hide show
  1. package/apps/pwa/out/404/index.html +1 -1
  2. package/apps/pwa/out/404.html +1 -1
  3. package/apps/pwa/out/_next/static/chunks/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
  7. package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
  14. package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
  16. package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
  21. package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
  24. package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
  25. package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
  39. package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
  46. package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
  53. package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
  54. package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
  55. package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
  56. package/apps/pwa/out/approvals/index.html +1 -1
  57. package/apps/pwa/out/approvals/index.txt +15 -15
  58. package/apps/pwa/out/autopilot/index.html +1 -1
  59. package/apps/pwa/out/autopilot/index.txt +15 -15
  60. package/apps/pwa/out/board/index.html +1 -1
  61. package/apps/pwa/out/board/index.txt +15 -15
  62. package/apps/pwa/out/botlab/index.html +1 -1
  63. package/apps/pwa/out/botlab/index.txt +15 -15
  64. package/apps/pwa/out/bots/index.html +1 -1
  65. package/apps/pwa/out/bots/index.txt +15 -15
  66. package/apps/pwa/out/ceo/index.html +1 -0
  67. package/apps/pwa/out/ceo/index.txt +33 -0
  68. package/apps/pwa/out/chat/index.html +1 -1
  69. package/apps/pwa/out/chat/index.txt +15 -15
  70. package/apps/pwa/out/consult/index.html +1 -1
  71. package/apps/pwa/out/consult/index.txt +15 -15
  72. package/apps/pwa/out/control/index.html +1 -1
  73. package/apps/pwa/out/control/index.txt +15 -15
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +15 -15
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +15 -15
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +15 -15
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +15 -15
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +15 -15
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +15 -15
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +15 -15
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +15 -15
  91. package/apps/pwa/out/loops/index.html +1 -1
  92. package/apps/pwa/out/loops/index.txt +15 -15
  93. package/apps/pwa/out/market/index.html +1 -1
  94. package/apps/pwa/out/market/index.txt +15 -15
  95. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  96. package/apps/pwa/out/missions/placeholder/index.txt +15 -15
  97. package/apps/pwa/out/morning/index.html +1 -1
  98. package/apps/pwa/out/morning/index.txt +15 -15
  99. package/apps/pwa/out/observatory/index.html +1 -1
  100. package/apps/pwa/out/observatory/index.txt +15 -15
  101. package/apps/pwa/out/ops/checklist/index.html +1 -1
  102. package/apps/pwa/out/ops/checklist/index.txt +15 -15
  103. package/apps/pwa/out/ops/release/index.html +1 -1
  104. package/apps/pwa/out/ops/release/index.txt +15 -15
  105. package/apps/pwa/out/ops/seats/index.html +1 -1
  106. package/apps/pwa/out/ops/seats/index.txt +15 -15
  107. package/apps/pwa/out/outputs/index.html +1 -1
  108. package/apps/pwa/out/outputs/index.txt +15 -15
  109. package/apps/pwa/out/reflection/index.html +1 -1
  110. package/apps/pwa/out/reflection/index.txt +16 -16
  111. package/apps/pwa/out/scheduler/index.html +1 -1
  112. package/apps/pwa/out/scheduler/index.txt +15 -15
  113. package/apps/pwa/out/sessions/index.html +1 -1
  114. package/apps/pwa/out/sessions/index.txt +15 -15
  115. package/apps/pwa/out/settings/devices/index.html +1 -1
  116. package/apps/pwa/out/settings/devices/index.txt +15 -15
  117. package/apps/pwa/out/settings/index.html +1 -1
  118. package/apps/pwa/out/settings/index.txt +15 -15
  119. package/apps/pwa/out/setup/done/index.html +1 -1
  120. package/apps/pwa/out/setup/done/index.txt +15 -15
  121. package/apps/pwa/out/setup/index.html +1 -1
  122. package/apps/pwa/out/setup/index.txt +15 -15
  123. package/apps/pwa/out/share/index.html +1 -1
  124. package/apps/pwa/out/share/index.txt +15 -15
  125. package/apps/pwa/out/showroom/index.html +1 -1
  126. package/apps/pwa/out/showroom/index.txt +15 -15
  127. package/apps/pwa/out/tasks/index.html +1 -1
  128. package/apps/pwa/out/tasks/index.txt +15 -15
  129. package/apps/pwa/out/term/index.html +1 -1
  130. package/apps/pwa/out/term/index.txt +15 -15
  131. package/apps/pwa/out/today/index.html +1 -1
  132. package/apps/pwa/out/today/index.txt +15 -15
  133. package/apps/pwa/out/trace/index.html +1 -1
  134. package/apps/pwa/out/trace/index.txt +15 -15
  135. package/apps/pwa/out/vault/index.html +1 -1
  136. package/apps/pwa/out/vault/index.txt +15 -15
  137. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  138. package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
  139. package/apps/pwa/out/workflows/index.html +1 -1
  140. package/apps/pwa/out/workflows/index.txt +15 -15
  141. package/apps/pwa/out/workspace/index.html +1 -1
  142. package/apps/pwa/out/workspace/index.txt +15 -15
  143. package/apps/pwa/out/worktrees/index.html +1 -1
  144. package/apps/pwa/out/worktrees/index.txt +15 -15
  145. package/graphs/doc-gen/doc-gen.yaml +24 -0
  146. package/graphs/doc-gen/recipes.yaml +3 -0
  147. package/graphs/heal/heal-loop.yaml +1 -1
  148. package/graphs/implement-loop.yaml +1 -1
  149. package/graphs/orchestrator/orchestrator.yaml +7 -0
  150. package/graphs/orchestrator/recipes.yaml +3 -0
  151. package/graphs/release/recipes.yaml +3 -0
  152. package/graphs/release/release-loop.yaml +4 -2
  153. package/graphs/steward/recipes.yaml +2 -1
  154. package/package.json +1 -1
  155. package/scripts/brand/check.ts +7 -5
  156. package/scripts/ci-public-export-import-gate.ts +154 -0
  157. package/scripts/ci-test-interference-gate.ts +5 -1
  158. package/scripts/ci-typecheck-changed.ts +82 -31
  159. package/scripts/docs/doc-rot.ts +164 -0
  160. package/scripts/docs/generate-graph-anatomy.ts +437 -0
  161. package/scripts/docs-cli-check.ts +4 -4
  162. package/scripts/expose-rubric-suggest.ts +18 -12
  163. package/scripts/goals/goal-census.ts +343 -0
  164. package/scripts/lecture-note/node.ts +31 -11
  165. package/scripts/lessons/handbook-page.ts +30 -3
  166. package/scripts/marketing/generate-ax-brief.ts +140 -0
  167. package/scripts/marketing/generate-feature-map.ts +118 -0
  168. package/scripts/measure/author-section-contribution.ts +84 -0
  169. package/scripts/release-loop/checklist-gate-node.ts +53 -14
  170. package/scripts/release-loop/cut-branch.ts +43 -13
  171. package/scripts/release-loop/gate-node.ts +82 -14
  172. package/scripts/release-loop/vault-note-node.ts +257 -8
  173. package/scripts/release-story/draft.ts +57 -9
  174. package/scripts/rhythm/daily.ts +263 -76
  175. package/scripts/rhythm/monthly.ts +97 -0
  176. package/scripts/rhythm/weekly.ts +30 -9
  177. package/scripts/smart-merge/resync-shadow.ts +147 -0
  178. package/src/acp/client.ts +17 -2
  179. package/src/agent-env/profile.ts +161 -0
  180. package/src/agent-mission/driver.ts +51 -1
  181. package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
  182. package/src/agent-mission/supervisor-metrics.ts +218 -0
  183. package/src/agent-substrate/pr-reviewer.ts +2 -0
  184. package/src/ask-user-question/pending-questions.ts +5 -0
  185. package/src/auth/github-app-token.ts +112 -13
  186. package/src/autopilot/build/llm-conflict-merge.ts +32 -6
  187. package/src/autopilot/track-agent-shadow.ts +2 -1
  188. package/src/briefing/brief-items.ts +276 -0
  189. package/src/budget/usage-outcomes.ts +204 -0
  190. package/src/claims/claims-render.ts +14 -5
  191. package/src/cli/agent-env-cli.ts +21 -0
  192. package/src/cli/brief-cli.ts +73 -0
  193. package/src/cli/decisions-cli.ts +9 -0
  194. package/src/cli/doctor-cli.ts +23 -1
  195. package/src/cli/doctor-node-modules.ts +147 -0
  196. package/src/cli/intake-cli.ts +46 -17
  197. package/src/cli/know-cli.ts +20 -0
  198. package/src/cli/logs-cli.ts +43 -1
  199. package/src/cli/pod-cli.ts +2 -0
  200. package/src/cli/pr-cli.ts +80 -12
  201. package/src/cli/release-cli.ts +66 -29
  202. package/src/cli/seat-requests-cli.ts +52 -0
  203. package/src/cli/self-update.ts +200 -1
  204. package/src/cli/usage-cli.ts +26 -0
  205. package/src/context-bus/context-now-public.ts +16 -6
  206. package/src/context-bus/context-now.ts +3 -1
  207. package/src/context-bus/coord-events.ts +6 -4
  208. package/src/decisions/decision-cards.ts +33 -7
  209. package/src/decisions/proact-meter.ts +286 -0
  210. package/src/decisions/proact1-lite.ts +86 -0
  211. package/src/decisions/web-push-decision-cards.ts +1 -1
  212. package/src/discord-self-message.ts +9 -0
  213. package/src/domains/alert-audit.ts +271 -0
  214. package/src/domains/core-tools.ts +4 -3
  215. package/src/domains/ops-facts-tool.ts +31 -1
  216. package/src/flow-loop/split.ts +24 -0
  217. package/src/harness/harness-cli-command.ts +147 -17
  218. package/src/harness/harness-incidents.ts +2 -0
  219. package/src/harness/harness-pod-dispatch.ts +67 -8
  220. package/src/harness/harness-queue.ts +380 -50
  221. package/src/harness/harness-seams.ts +13 -3
  222. package/src/harness/helper-repair.ts +218 -0
  223. package/src/harness/helper-scan.ts +27 -4
  224. package/src/harness/launch-stamp.ts +236 -0
  225. package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
  226. package/src/harness/ledger-write-guard.ts +35 -0
  227. package/src/harness/nested-elanous-policy.ts +114 -0
  228. package/src/harness/pod-memory-policy.ts +15 -0
  229. package/src/harness/repository-name.ts +55 -0
  230. package/src/hq/fence-audit.ts +90 -0
  231. package/src/hq/hq.ts +70 -6
  232. package/src/hq/lease.ts +9 -7
  233. package/src/hq/move-plan.ts +126 -0
  234. package/src/hq/promote.ts +31 -0
  235. package/src/hq/transfer.ts +185 -0
  236. package/src/index.ts +189 -13
  237. package/src/intake-plane/check.ts +63 -0
  238. package/src/intake-plane/digest.ts +65 -21
  239. package/src/intake-plane/front-classifier.ts +6 -0
  240. package/src/intake-plane/items.ts +3 -2
  241. package/src/intake-plane/lens.ts +177 -0
  242. package/src/intake-plane/route.ts +145 -1
  243. package/src/knowledge/know-find.ts +105 -0
  244. package/src/llm.ts +75 -19
  245. package/src/loops/budget.ts +36 -5
  246. package/src/loops/checker.ts +2 -2
  247. package/src/loops/orchestrator/author-depth.ts +65 -9
  248. package/src/loops/orchestrator/author-input-check.ts +8 -3
  249. package/src/loops/orchestrator/author-ledger.ts +3 -1
  250. package/src/loops/orchestrator/finish-rate.ts +14 -3
  251. package/src/loops/orchestrator/seat-share.ts +56 -0
  252. package/src/loops/orchestrator/tick.ts +107 -29
  253. package/src/loops/orchestrator/traffic.ts +117 -11
  254. package/src/loops/registry.ts +142 -61
  255. package/src/maturity/bot-slash-catalog.ts +11 -2
  256. package/src/maturity/feature-maturity.ts +4 -3
  257. package/src/maturity/tui-slash-availability.ts +40 -0
  258. package/src/mss/logging/log-store.ts +35 -4
  259. package/src/nexus/api/http-server.ts +28 -14
  260. package/src/nexus/api/loop-edges.ts +173 -0
  261. package/src/nexus/api/schedules-read.ts +13 -0
  262. package/src/nexus/api/seat-requests.ts +79 -56
  263. package/src/oauth/overload-failover-launch.ts +75 -0
  264. package/src/oauth/overload-failover.ts +137 -0
  265. package/src/prompt-enhance/enhance.ts +6 -1
  266. package/src/release-loop/auto-start.ts +6 -3
  267. package/src/release-loop/checklist.ts +19 -5
  268. package/src/release-loop/feature-store.ts +59 -31
  269. package/src/release-loop/merged-pr-checklist.ts +125 -0
  270. package/src/release-loop/placement.ts +98 -29
  271. package/src/seat-dispatch/seat-ask.ts +33 -6
  272. package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
  273. package/src/seat-dispatch/seat-request-ledger.ts +133 -0
  274. package/src/seat-loop/seat-loop.ts +215 -12
  275. package/src/self-dev/ask-launch-flow.ts +22 -1
  276. package/src/self-dev/authored-goal-trace.ts +64 -0
  277. package/src/self-dev/dev-cli.ts +19 -1
  278. package/src/self-dev/dev-pipeline.ts +14 -9
  279. package/src/self-dev/draft-sweep.ts +90 -10
  280. package/src/self-dev/draft-triage-rules.ts +25 -8
  281. package/src/self-dev/entrance-registry.ts +10 -6
  282. package/src/self-dev/old-door.ts +73 -0
  283. package/src/self-dev/orchestrate.ts +15 -1
  284. package/src/self-dev/review-provider-fallback.ts +6 -1
  285. package/src/self-dev/run-supervisor.ts +49 -2
  286. package/src/self-implement/app-api-budget.ts +166 -0
  287. package/src/self-implement/budget-gate.ts +7 -5
  288. package/src/self-implement/follow-up-goals.ts +289 -0
  289. package/src/self-implement/frozen-merges.ts +3 -2
  290. package/src/self-implement/goal-author.ts +50 -6
  291. package/src/self-implement/goal-clarification-escalation.ts +5 -0
  292. package/src/self-implement/heal-intake.ts +120 -2
  293. package/src/self-implement/heal-triage.ts +44 -0
  294. package/src/self-implement/host-regate.ts +9 -2
  295. package/src/self-implement/l8-merge-queue.ts +132 -0
  296. package/src/self-implement/orchestrator.ts +325 -47
  297. package/src/self-implement/pipeline-shape.ts +1 -1
  298. package/src/self-implement/revert-guard.ts +42 -7
  299. package/src/self-implement/run-ledger.ts +6 -0
  300. package/src/self-implement/run-status-mapping.ts +2 -0
  301. package/src/self-implement/seams.ts +45 -16
  302. package/src/self-implement/self-implement-cli.ts +1 -0
  303. package/src/self-implement/speed-claims.ts +338 -0
  304. package/src/session-runtime/index.ts +2 -0
  305. package/src/session-runtime/retry-api.ts +87 -0
  306. package/src/session-runtime/retry-policy.ts +107 -6
  307. package/src/steward/triage.ts +74 -3
  308. package/src/steward/working-backwards.ts +20 -2
  309. package/src/task-cards/card-cli.ts +25 -0
  310. package/src/task-cards/card-store.ts +5 -3
  311. package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
  312. package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
  313. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
  315. package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
  316. package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
  317. package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
  318. package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
  319. package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
  320. package/src/telegram-commands.ts +5 -0
  321. package/src/telegram.ts +72 -4
  322. package/src/user-config.ts +64 -7
  323. package/src/version/packed-revision.json +1 -1
  324. package/src/workflow-runtime/nodes/bash.ts +9 -1
  325. package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
  349. package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
  350. /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
@@ -1,6 +1,6 @@
1
1
  import { CLAIM_IDLE_HOURS, PR_LABELS, STALLED_DRAFT_HOURS } from '../github/pr-labels.js';
2
2
  import { debug } from '../debug/log.js';
3
- import { decideDraft, type DraftTriagePr } from './draft-triage-rules.js';
3
+ import { decideDraft, sameGoalPr, type DraftTriagePr } from './draft-triage-rules.js';
4
4
 
5
5
  export interface SweepDraft extends DraftTriagePr {
6
6
  readonly title: string;
@@ -33,6 +33,18 @@ export interface DraftSweepAdapters {
33
33
  getLatestFileChanges?(draft: SweepDraft, repository: string): Promise<Readonly<Record<string, string>> | undefined>;
34
34
  /** Optional merge-time file inventory for the existing all-files-landed decision. */
35
35
  getPrFiles?(repository: string, number: number): Promise<readonly string[] | undefined>;
36
+ /**
37
+ * Review and gate posture for the daily count. Omitted means unknown — not harvestable and not blocked.
38
+ * `review: 'pass'` is a review PASS; `mustFix` means must-fix remains; `gate: 'pass'` is a green gate.
39
+ */
40
+ getReviewGate?(draft: SweepDraft, repository: string): Promise<SweepReviewGate | undefined>;
41
+ }
42
+
43
+ /** Review PASS and gate posture used only by the daily old-draft count. */
44
+ export interface SweepReviewGate {
45
+ readonly review?: 'pass' | 'fail';
46
+ readonly mustFix?: boolean;
47
+ readonly gate?: 'pass' | 'fail';
36
48
  }
37
49
 
38
50
  export interface DraftSweepOptions {
@@ -67,9 +79,23 @@ export interface DraftSweepResult {
67
79
  claimed?: number;
68
80
  /** Running claims older than the idle window, including those kept by liveness. */
69
81
  claimExpired?: number;
82
+ /**
83
+ * One line per sweep: harness drafts older than 24h, split into closable (a same-goal landing exists),
84
+ * harvestable (review PASS and gate pass), and blocked (must-fix remains).
85
+ */
86
+ daily?: DraftSweepDaily;
70
87
  error?: string;
71
88
  }
72
89
 
90
+ /** Daily old-draft census. `date` is the sweep clock's UTC calendar day. */
91
+ export interface DraftSweepDaily {
92
+ readonly date: string;
93
+ readonly over24h: number;
94
+ readonly closable: number;
95
+ readonly harvestable: number;
96
+ readonly blocked: number;
97
+ }
98
+
73
99
  const stateLabels = PR_LABELS.filter((label) => label.axis === 'state');
74
100
  const label = (action: 'mark-stalled' | 'close', name?: string): string => {
75
101
  const match = PR_LABELS.find((entry) => entry.axis === 'state' && entry.sweep.action === action && (name === undefined || entry.name.endsWith(name)));
@@ -115,7 +141,7 @@ export async function supersedeDraftsOnMerge(input: {
115
141
  const mergedFiles = await adapters.getPrFiles?.(repository, merged.number);
116
142
  const landed = { ...merged, changedFiles: mergedFiles };
117
143
  for (const draft of drafts) {
118
- if (draft.number !== merged.number && !liveBranches.has(draft.branch)
144
+ if (draft.number !== merged.number && draft.branch.startsWith('self-impl/') && !liveBranches.has(draft.branch)
119
145
  && Date.parse(draft.createdAt) < Date.parse(merged.mergedAt)) {
120
146
  statuses.set(draft.number, await adapters.getRunStatus(draft, repository));
121
147
  if (draft.changedFiles?.length && adapters.getLatestFileChanges) {
@@ -125,7 +151,7 @@ export async function supersedeDraftsOnMerge(input: {
125
151
  }
126
152
  for (const listedDraft of drafts) {
127
153
  const draft = { ...listedDraft, latestFileChanges: latestChanges.get(listedDraft.number) };
128
- if (draft.number === merged.number || liveBranches.has(draft.branch)
154
+ if (draft.number === merged.number || !draft.branch.startsWith('self-impl/') || liveBranches.has(draft.branch)
129
155
  || Date.parse(draft.createdAt) >= Date.parse(merged.mergedAt)) {
130
156
  kept.push(draft.number);
131
157
  continue;
@@ -134,7 +160,6 @@ export async function supersedeDraftsOnMerge(input: {
134
160
  mergedTwins: [landed], liveBranches, ageHours: NaN });
135
161
  if (decision.action !== 'close' || !decision.reason.startsWith(`superseded-by #${merged.number}`)
136
162
  || PR_LABELS.filter((entry) => entry.axis === 'state' && draft.labels.includes(entry.name)).length > 1
137
- || (draft.labels.length === 0 && !draft.branch.startsWith('self-impl/'))
138
163
  || closeAttempts >= DRAFT_SWEEP_CLOSE_CAP) {
139
164
  kept.push(draft.number);
140
165
  continue;
@@ -144,7 +169,8 @@ export async function supersedeDraftsOnMerge(input: {
144
169
  try {
145
170
  // Label first: a failed label write must never strand a closed, unlabelled PR outside open-draft sweeps.
146
171
  if (!draft.labels.includes(supersededLabel)) await adapters.setLabels(repository, draft.number, { add: supersededLabel, remove });
147
- await adapters.closeDraft(repository, draft.number, `Draft sweep: superseded-by #${merged.number}. Branch preserved.`);
172
+ await adapters.closeDraft(repository, draft.number,
173
+ `Draft sweep: superseded-by #${merged.number} (https://github.com/${repository}/pull/${merged.number}). Branch preserved.`);
148
174
  closed.push(draft.number);
149
175
  } catch (failure) {
150
176
  kept.push(draft.number);
@@ -171,6 +197,46 @@ async function collectPages<T>(fetch: (page: number, perPage: number) => Promise
171
197
  throw new Error('PR listing exceeded pagination limit');
172
198
  }
173
199
 
200
+ /** First line of a failure, with no stack — the draft-cleanup loop log shows this line as-is. */
201
+ export function sweepFailureReason(error: unknown): string {
202
+ const message = error instanceof Error ? error.message : String(error);
203
+ const line = message.split(/\r?\n/).map((part) => part.trim()).find((part) => part.length > 0 && !/^\s*at\s/.test(part));
204
+ return line && line.length > 0 ? line : 'sweep failed';
205
+ }
206
+
207
+ function harnessDraft(draft: SweepDraft): boolean {
208
+ return draft.branch.startsWith('self-impl/') || draft.labels.some((name) => name.startsWith('elanous:'));
209
+ }
210
+
211
+ /**
212
+ * Census of harness drafts older than 24h. Same-goal landing uses `sameGoalPr` unchanged.
213
+ * A draft counts as closable when a same-goal merged PR exists, harvestable when review PASS and the gate passed,
214
+ * and blocked when must-fix remains. Unknown review/gate is neither harvestable nor blocked.
215
+ */
216
+ export function draftSweepDaily(
217
+ drafts: readonly SweepDraft[],
218
+ merged: readonly SweepMergedPr[],
219
+ now: Date,
220
+ reviewGate: ReadonlyMap<number, SweepReviewGate | undefined>,
221
+ ): DraftSweepDaily {
222
+ const cutoff = now.getTime() - STALLED_DRAFT_HOURS * 3_600_000;
223
+ let over24h = 0;
224
+ let closable = 0;
225
+ let harvestable = 0;
226
+ let blocked = 0;
227
+ for (const draft of drafts) {
228
+ if (!harnessDraft(draft)) continue;
229
+ const created = Date.parse(draft.createdAt);
230
+ if (!Number.isFinite(created) || created > cutoff) continue;
231
+ over24h += 1;
232
+ if (merged.some((pr) => pr.number !== draft.number && sameGoalPr(draft, pr))) closable += 1;
233
+ const posture = reviewGate.get(draft.number);
234
+ if (posture?.mustFix === true) blocked += 1;
235
+ else if (posture?.review === 'pass' && posture.gate === 'pass') harvestable += 1;
236
+ }
237
+ return { date: now.toISOString().slice(0, 10), over24h, closable, harvestable, blocked };
238
+ }
239
+
174
240
  /** A failed inventory or liveness lookup is never treated as proof that a draft can be closed. */
175
241
  export async function runDraftSweep({ repository, adapters, apply = false, now = new Date() }: DraftSweepOptions): Promise<DraftSweepResult> {
176
242
  const result: DraftSweepResult = { repository, apply, complete: false, entries: [], counts: {} };
@@ -181,10 +247,12 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
181
247
  };
182
248
  const finish = (): DraftSweepResult => {
183
249
  debug.log('drafts.cleanup', 'summary', result.counts);
250
+ if (result.daily) debug.log('harness.drafts', 'daily', result.daily);
251
+ if (!result.complete && result.error) debug.log('drafts.cleanup', 'failed', { reason: sweepFailureReason(result.error) });
184
252
  return result;
185
253
  };
186
- let drafts: SweepDraft[];
187
- let merged: SweepMergedPr[];
254
+ let drafts: SweepDraft[] = [];
255
+ let merged: SweepMergedPr[] = [];
188
256
  let liveBranches: ReadonlySet<string> | undefined;
189
257
  try {
190
258
  [drafts, merged, liveBranches] = await Promise.all([
@@ -196,7 +264,8 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
196
264
  if (drafts.some((pr) => !Number.isInteger(pr.number) || !pr.branch || !Array.isArray(pr.labels) || !Number.isFinite(Date.parse(pr.createdAt)))
197
265
  || merged.some((pr) => !Number.isInteger(pr.number) || !pr.branch)) throw new Error('Invalid PR inventory');
198
266
  } catch (error) {
199
- result.error = String(error);
267
+ result.error = sweepFailureReason(error);
268
+ if (drafts.length > 0) result.daily = draftSweepDaily(drafts, merged, now, new Map());
200
269
  return finish();
201
270
  }
202
271
  let statuses: Map<number, string | undefined>;
@@ -218,9 +287,19 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
218
287
  }
219
288
  }
220
289
  } catch (error) {
221
- result.error = String(error);
290
+ result.error = sweepFailureReason(error);
222
291
  return finish();
223
292
  }
293
+ let reviewGate = new Map<number, SweepReviewGate | undefined>();
294
+ if (adapters.getReviewGate) {
295
+ try {
296
+ for (const draft of drafts) reviewGate.set(draft.number, await adapters.getReviewGate(draft, repository));
297
+ } catch (error) {
298
+ result.error = sweepFailureReason(error);
299
+ return finish();
300
+ }
301
+ }
302
+ result.daily = draftSweepDaily(drafts, merged, now, reviewGate);
224
303
  result.complete = true;
225
304
  let closes = 0;
226
305
  let unobserved = 0;
@@ -330,7 +409,8 @@ export async function runDraftSweep({ repository, adapters, apply = false, now =
330
409
  if (action === 'close') {
331
410
  await adapters.closeDraft(repository, draft.number, decision.reason === 'stale-unobserved'
332
411
  ? `Draft sweep: run unobserved — no run record reachable here, no host worktree for this branch, idle ≥${STALLED_DRAFT_HOURS}h. Closed as stalled. Branch preserved; reopen to restore.`
333
- : `Draft sweep: ${decision.reason}. Branch preserved.`);
412
+ : `Draft sweep: ${decision.reason}${decision.reason.startsWith('superseded-by #')
413
+ ? ` (https://github.com/${repository}/pull/${decision.reason.slice('superseded-by #'.length).match(/^\d+/)![0]})` : ''}. Branch preserved.`);
334
414
  }
335
415
  entry.applied = labelsChanged || action === 'close';
336
416
  } catch (error) {
@@ -38,6 +38,21 @@ const PROTECTED_LABELS: ReadonlySet<string> = new Set<string>([...PR_LABELS
38
38
  const goalId = (branch: string): string | undefined => /(?:^|[-/])goalid-([a-f0-9]+)(?=-|\/|$)/i.exec(branch)?.[1]?.toLowerCase();
39
39
  export const branchStem = (branch: string): string | undefined => /^self-impl\/(.+)-[a-f0-9]{8}-r[a-f0-9]+$/i.exec(branch)?.[1];
40
40
  const slot = (body?: string): string | undefined => /^칸:\s*(.+?)\s*$/m.exec(body ?? '')?.[1];
41
+ const goalText = (body?: string): string | undefined => /^골:\s*(\S.*?)\s*$/m.exec(body ?? '')?.[1];
42
+ // Auto titles name only files («src/harness: harness-queue.ts») — different goals share them, so they never identify a goal.
43
+ export const isAutoTitle = (title: string): boolean =>
44
+ /^[\w./-]+:\s*[^\s,]+\.[a-z0-9]+(?:\s*,\s*[^\s,]+\.[a-z0-9]+)*$/i.test(title.trim());
45
+
46
+ /** Same goal (TC 10-05): ① both carry a goal identifier → compare it ② otherwise exact title ⊕ both harness drafts ⊕ same branch stem, never an auto title ③ else false. */
47
+ export function sameGoalPr(a: DraftTriagePr, b: DraftTriagePr): boolean {
48
+ const idA = goalId(a.branch), idB = goalId(b.branch);
49
+ if (idA && idB) return idA === idB;
50
+ const textA = goalText(a.body), textB = goalText(b.body);
51
+ if (textA && textB) return textA === textB;
52
+ if (!a.title || a.title !== b.title || isAutoTitle(a.title)) return false;
53
+ const stemA = branchStem(a.branch);
54
+ return Boolean(stemA && a.branch.startsWith('self-impl/') && b.branch.startsWith('self-impl/') && stemA === branchStem(b.branch));
55
+ }
41
56
  const referencesDraft = (text: string, number: number): boolean =>
42
57
  new RegExp(`(?:\\(수확\\s*#${number}\\s*\\)|\\bsuperseded\\s*#${number}(?!\\d))`, 'i').test(text);
43
58
  const landingCommentReferencesDraft = (text: string, number: number): boolean =>
@@ -55,28 +70,30 @@ export function decideDraft({ draft, runStatus, mergedTwins, openDrafts, ageHour
55
70
  return { action: 'keep', reason: 'live' };
56
71
  }
57
72
  const lineage = goalId(draft.branch);
58
- const twin = mergedTwins.find((pr) => pr.number !== draft.number && (
59
- (draft.title && pr.title === draft.title) || (lineage && goalId(pr.branch) === lineage)
60
- ));
61
- if (twin) return { action: 'close', reason: `superseded-by #${twin.number}` };
73
+ const draftSlot = slot(draft.body);
62
74
  const stem = branchStem(draft.branch);
75
+ const compatible = (pr: DraftTriagePr): boolean =>
76
+ !(lineage && goalId(pr.branch) && goalId(pr.branch) !== lineage)
77
+ && !(draftSlot && slot(pr.body) && slot(pr.body) !== draftSlot);
78
+ const twin = mergedTwins.find((pr) => pr.number !== draft.number && compatible(pr) && sameGoalPr(draft, pr));
79
+ if (twin) return { action: 'close', reason: `superseded-by #${twin.number}` };
63
80
  const createdAt = Date.parse(draft.createdAt ?? '');
64
81
  const newer = stem && draft.title && Number.isFinite(createdAt) ? openDrafts
65
- ?.filter((pr) => pr.number !== draft.number && pr.title === draft.title && branchStem(pr.branch) === stem
82
+ ?.filter((pr) => pr.number !== draft.number && pr.title === draft.title && branchStem(pr.branch) === stem && sameGoalPr(draft, pr)
66
83
  && Number.isFinite(Date.parse(pr.createdAt ?? '')) && Date.parse(pr.createdAt!) > createdAt)
67
84
  .sort((a, b) => Date.parse(b.createdAt!) - Date.parse(a.createdAt!) || b.number - a.number)[0] : undefined;
68
85
  if (newer) return { action: 'close', reason: `duplicate-of-open #${newer.number}` };
69
- const harvest = mergedTwins.find((pr) => pr.number !== draft.number && (
86
+ const harvest = mergedTwins.find((pr) => pr.number !== draft.number && compatible(pr) && (
70
87
  [pr.mergeCommitMessage, pr.body].some((text) => text && referencesDraft(text, draft.number))
71
88
  || pr.landingVerifiedComments?.some((text) => landingCommentReferencesDraft(text, draft.number))
72
89
  ));
73
90
  if (harvest) return { action: 'close', reason: `superseded-by #${harvest.number} (harvest #${draft.number})` };
74
91
  const created = Date.parse(draft.createdAt ?? '');
75
92
  const files = draft.changedFiles;
76
- const draftSlot = slot(draft.body);
77
93
  const later = mergedTwins.filter((pr) => pr.number !== draft.number && Number.isFinite(created)
78
94
  && Number.isFinite(Date.parse(pr.mergedAt ?? '')) && Date.parse(pr.mergedAt!) > created
79
- && ((lineage && goalId(pr.branch) === lineage) || (draftSlot && slot(pr.body) === draftSlot)));
95
+ && compatible(pr) && ((lineage && goalId(pr.branch) === lineage)
96
+ || (draftSlot && slot(pr.body) === draftSlot) || (stem && branchStem(pr.branch) === stem)));
80
97
  if (files?.length && files.every((file) => {
81
98
  const changed = Date.parse(draft.latestFileChanges?.[file] ?? '');
82
99
  return Number.isFinite(changed) && changed >= created && later.some((pr) =>
@@ -1,6 +1,6 @@
1
1
  import { findNativeTool, isNativeToolModelExposed } from '../native-tool-catalog.js';
2
2
 
3
- export type EntranceStatus = 'live' | 'retired';
3
+ export type EntranceStatus = 'live' | 'retired' | 'closed';
4
4
 
5
5
  export type EntranceStampability = 'stampable' | 'structurally-unstampable' | 'unknown';
6
6
 
@@ -181,12 +181,14 @@ const ENTRANCE_DECLARATIONS = [
181
181
  // `dev --ask` 와 ***같은*** `runAskLaunchFlow` 를 탄다. 두 문의 차이는 `entrance` 태그뿐이다.
182
182
  // ⇒ 판정값이 `'verified' | 'unknown'` 둘뿐이라 「구조적 부재」를 적을 칸이 없다. `unknown` 으로 둔다.
183
183
  // ⛔ 그러니 이 `unknown` 을 「미완」으로 읽지 마라 — 그 구분은 이 주석이 canonical 이다.
184
- { id: 'cli-dev-ask', surface: 'cli', status: 'live', verification: { actualMission: 'verified', legacyParity: 'unknown' } },
184
+ // OLD-DOOR-CLOSE — a direct `dev --ask` from a terminal or seat session refuses.
185
+ // Harness-internal calls still pass, marked with the ONEDOOR-1 entrance stamp.
186
+ { id: 'cli-dev-ask', surface: 'cli', status: 'closed', verification: { actualMission: 'verified', legacyParity: 'unknown' } },
185
187
  // `elanous drive` 는 `elanous dev` 의 Commander alias 로 살아 있고, buildDriveAliasDevSpec 이 entrance 를
186
188
  // `cli-drive` 로 넘기며 completion 을 `worktree-only` 로 못 박는다. 그래서 별도 무인 권한 축이 아니다.
187
189
  { id: 'cli-drive', surface: 'cli', status: 'live' },
188
- { id: 'cli-self-implement', surface: 'cli', status: 'live' },
189
- { id: 'cli-self-orchestrate', surface: 'cli', status: 'live' },
190
+ { id: 'cli-self-implement', surface: 'cli', status: 'closed' },
191
+ { id: 'cli-self-orchestrate', surface: 'cli', status: 'closed' },
190
192
  // ⭐ 2026-08-22 · 🅣 — ***사람이 «돌려 보고» 적은 판정***이다(⛔ 도구가 추측한 값이 아니다).
191
193
  // actualMission: 그 입구로 실제 골을 «끝까지» 돌렸다 — 17발 · 자동 병합 5 · 회수 착지 7.
192
194
  // 📏 원장 각인이 그것을 말한다: `"entrance":"cli-harness-ask"` (아침엔 0건이었다).
@@ -331,6 +333,7 @@ export function summarizeEntrances(registry: readonly EntranceDeclaration[] = EN
331
333
  readonly total: number;
332
334
  readonly live: number;
333
335
  readonly retired: number;
336
+ readonly closed: number;
334
337
  readonly bySurface: ReadonlyArray<{ surface: EntranceSurface; total: number; retired: number }>;
335
338
  } {
336
339
  const surfaces = [...new Set(registry.map((e) => e.surface))];
@@ -338,6 +341,7 @@ export function summarizeEntrances(registry: readonly EntranceDeclaration[] = EN
338
341
  total: registry.length,
339
342
  live: registry.filter((e) => e.status === 'live').length,
340
343
  retired: registry.filter((e) => e.status === 'retired').length,
344
+ closed: registry.filter((e) => e.status === 'closed').length,
341
345
  bySurface: surfaces.map((surface) => ({
342
346
  surface,
343
347
  total: registry.filter((e) => e.surface === surface).length,
@@ -350,12 +354,12 @@ export function summarizeEntrances(registry: readonly EntranceDeclaration[] = EN
350
354
  export function renderLaunchEntrances(registry: readonly EntranceDeclaration[] = ENTRANCE_REGISTRY): string {
351
355
  const summary = summarizeEntrances(registry);
352
356
  const rows = listEntrancesWithModelExposure(registry)
353
- .map((e) => ` ${e.status === 'retired' ? '🪦' : '✅'} ${e.surface.padEnd(7)} ${e.id} (${e.stampability}) verification=actualMission:${e.verification.actualMission},legacyParity:${e.verification.legacyParity} imprintEvidence=${e.imprintEvidence} evidenceSource=${e.stampabilityEvidenceSource} evidenceSourceFilePath=${e.evidenceSourceFilePath ?? 'missing'} modelExposed=${e.modelExposed ?? 'missing'}`);
357
+ .map((e) => ` ${e.status === 'retired' ? '🪦' : e.status === 'closed' ? '닫힘' : '✅'} ${e.surface.padEnd(7)} ${e.id} (${e.stampability}) verification=actualMission:${e.verification.actualMission},legacyParity:${e.verification.legacyParity} imprintEvidence=${e.imprintEvidence} evidenceSource=${e.stampabilityEvidenceSource} evidenceSourceFilePath=${e.evidenceSourceFilePath ?? 'missing'} modelExposed=${e.modelExposed ?? 'missing'}`);
354
358
  const surfaces = summary.bySurface
355
359
  .map((s) => `${s.surface}=${s.total}${s.retired > 0 ? `(은퇴 ${s.retired})` : ''}`)
356
360
  .join(' · ');
357
361
  return [
358
- `launch entrances: ${summary.total} (live ${summary.live} · retired ${summary.retired})`,
362
+ `launch entrances: ${summary.total} (live ${summary.live} · retired ${summary.retired} · closed ${summary.closed})`,
359
363
  ` 표면별: ${surfaces}`,
360
364
  ...rows,
361
365
  ].join('\n');
@@ -0,0 +1,73 @@
1
+ /**
2
+ * OLD-DOOR-CLOSE — the three outside doors refuse a direct call.
3
+ *
4
+ * `elanous dev --ask`, `elanous self implement`, and `elanous self orchestrate`
5
+ * stay callable from inside the harness. The harness marks those calls with
6
+ * the same stamp ONEDOOR-1 uses for a door (`ELANOUS_HARNESS_ENTRANCE`) or with
7
+ * the explicit internal flag `--harness-internal`. A terminal or seat session
8
+ * carries neither, so it exits non-zero and is told where to go.
9
+ */
10
+ import type { LaunchDoor } from '../harness/launch-stamp.js';
11
+
12
+ export const OLD_DOORS = ['dev-ask', 'self-implement', 'self-orchestrate'] as const;
13
+ export type OldDoor = (typeof OLD_DOORS)[number];
14
+
15
+ /** Same env ONEDOOR-1 already stamps onto a harness launch. */
16
+ export const OLD_DOOR_STAMP_ENV = 'ELANOUS_HARNESS_ENTRANCE';
17
+ /** Same meaning as the env, for a caller that can pass an argument but not an env. */
18
+ export const OLD_DOOR_STAMP_FLAG = '--harness-internal';
19
+
20
+ const STAMP_BY_DOOR: Readonly<Record<OldDoor, readonly string[]>> = {
21
+ 'dev-ask': ['dev-ask', 'cli-dev-ask', 'cli-drive', 'harness-say', 'cli-harness-say', 'harness-ask', 'cli-harness-ask', 'queue-tick', 'agent-mission', 'daemon-tool'],
22
+ 'self-implement': ['self-implement', 'cli-self-implement', 'nl-self-implement', 'daemon-self-implement', 'daemon-tool', 'harness-say', 'cli-harness-say', 'harness-ask', 'cli-harness-ask', 'queue-tick', 'agent-mission'],
23
+ 'self-orchestrate': ['self-orchestrate', 'cli-self-orchestrate', 'nl-self-orchestrate', 'cli-harness-orchestrate', 'harness-say', 'cli-harness-say', 'harness-ask', 'cli-harness-ask', 'queue-tick', 'agent-mission', 'daemon-tool'],
24
+ };
25
+
26
+ export interface OldDoorRefusal {
27
+ readonly door: OldDoor;
28
+ readonly caller: 'outside';
29
+ readonly exitCode: 1;
30
+ readonly message: string;
31
+ }
32
+
33
+ export function oldDoorStampAccepted(door: OldDoor, stamp: string | undefined | null): boolean {
34
+ const value = stamp?.trim();
35
+ if (!value) return false;
36
+ return (STAMP_BY_DOOR[door] as readonly string[]).includes(value);
37
+ }
38
+
39
+ /** A harness-internal call carries the stamp in the environment or on the argv. Anything else is outside. */
40
+ export function isOldDoorInternalCall(
41
+ door: OldDoor,
42
+ env: NodeJS.ProcessEnv = process.env,
43
+ argv: readonly string[] = process.argv,
44
+ ): boolean {
45
+ if (argv.includes(OLD_DOOR_STAMP_FLAG)) return true;
46
+ return oldDoorStampAccepted(door, env[OLD_DOOR_STAMP_ENV]);
47
+ }
48
+
49
+ /** Env a harness parent sets so the child door stays open. */
50
+ export function oldDoorInternalEnv(door: LaunchDoor | OldDoor): Record<string, string> {
51
+ return { [OLD_DOOR_STAMP_ENV]: door };
52
+ }
53
+
54
+ const REPLACEMENT: Readonly<Record<OldDoor, string>> = {
55
+ 'dev-ask': 'elanous harness ask <goal.md>',
56
+ 'self-implement': 'elanous harness say "<sentence>"',
57
+ 'self-orchestrate': 'elanous harness say "<sentence>"',
58
+ };
59
+
60
+ const DOOR_LABEL: Readonly<Record<OldDoor, string>> = {
61
+ 'dev-ask': 'elanous dev --ask',
62
+ 'self-implement': 'elanous self implement',
63
+ 'self-orchestrate': 'elanous self orchestrate',
64
+ };
65
+
66
+ /** One line: the closed door, the harness door to use, and a command of the same meaning. */
67
+ export function oldDoorRefusalMessage(door: OldDoor): string {
68
+ return `${DOOR_LABEL[door]} 는 닫혔습니다 — harness say / harness ask 로. 예: ${REPLACEMENT[door]}`;
69
+ }
70
+
71
+ export function refuseOldDoor(door: OldDoor): OldDoorRefusal {
72
+ return { door, caller: 'outside', exitCode: 1, message: oldDoorRefusalMessage(door) };
73
+ }
@@ -260,12 +260,23 @@ export interface SelfDevJobResult {
260
260
  * (merged / pr-opened / gate-failed / review-blocked / pr-declined). */
261
261
  stage?: string;
262
262
  branch?: string;
263
+ /** Child confirmed the finished implementation was pushed for manual harvest. */
264
+ harvestable?: true;
263
265
  worktreePath?: string;
264
266
  prUrl?: string;
265
267
  prNumber?: number;
266
268
  merged?: boolean;
267
269
  /** Child commit SHA, when observed by the Pod/host. */
268
270
  checkedHeadCommit?: string;
271
+ /**
272
+ * This run's own `--base` (not main). A non-main completion mode
273
+ * (worktree-only, draft, `--base` other than main) lands commits here.
274
+ */
275
+ base?: string;
276
+ /** Child finished with GOAL-COMPLETE (`ok=true`). Distinct from a main merge. */
277
+ ok?: boolean;
278
+ /** Commits this child added against its own `--base`. 0 is not completion progress. */
279
+ commitsAheadOfBase?: number;
269
280
  /** A dependency is done but its result is not on main; no child was launched. */
270
281
  blockReason?: 'parent-unlanded';
271
282
  parentPrNumber?: number;
@@ -405,6 +416,7 @@ export type ResumeDisposition = 'skip' | 'rerun' | 'rerun-duplicate-risk';
405
416
  */
406
417
  export function classifyResumeDisposition(result: SelfDevJobResult): ResumeDisposition {
407
418
  if (hasDelivered(result)) return 'skip';
419
+ if (result.harvestable === true && result.branch) return 'skip';
408
420
  if (result.stage === 'pr-opened') return 'rerun-duplicate-risk';
409
421
  if (result.merged === false) return 'rerun';
410
422
  if (result.status !== 'done') return 'rerun';
@@ -621,6 +633,7 @@ export function classifyFailure(
621
633
  ) {
622
634
  return 'awaiting-human';
623
635
  }
636
+ if (result.harvestable === true && result.branch) return 'main-sync-blocked';
624
637
  if (result.status === 'done' && !hasDelivered(result)) return convergedFailureKind();
625
638
  // ⭐ 상류가 죽어 «취소»된 조각 — 종전엔 status 가 failed 도 done 도 아니라 null 로 사라졌다.
626
639
  // ⛔ 이것을 못 보면 「무엇이 상류를 기다리는가」를 슈퍼바이저가 영영 모른다.
@@ -1479,6 +1492,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
1479
1492
  ...(resumeDisposition ? { resumeDisposition } : {}),
1480
1493
  ...(disp?.stage ? { stage: disp.stage } : {}),
1481
1494
  ...(disp?.branch ? { branch: disp.branch } : {}),
1495
+ ...(disp?.harvestable === true && disp.branch ? { harvestable: true } : {}),
1482
1496
  ...(disp?.worktreePath ? { worktreePath: disp.worktreePath } : {}),
1483
1497
  ...(disp?.prUrl ? { prUrl: disp.prUrl } : {}),
1484
1498
  ...(disp?.prNumber !== undefined ? { prNumber: disp.prNumber } : {}),
@@ -1526,7 +1540,7 @@ export function orchestrateSelfDev(opts: OrchestrateSelfDevOptions): Promise<Sel
1526
1540
  const rm = opts.removeWorktree ?? defaultRemoveWorktree;
1527
1541
  let removed = 0;
1528
1542
  for (const r of out) {
1529
- if (!r.worktreePath || r.prUrl) continue;
1543
+ if (!r.worktreePath || r.prUrl || r.harvestable) continue;
1530
1544
  try { rm(r.worktreePath); removed++; } catch { /* fail-soft */ }
1531
1545
  }
1532
1546
  if (removed > 0) debug.log('self-dev.orchestrate', 'teardown', { removed });
@@ -73,13 +73,18 @@ export function buildReviewProviderAttempts(
73
73
  const out: ReviewProviderAttempt[] = [primary];
74
74
  const seen = new Set([primary.model]);
75
75
  for (const f of fallbacks) {
76
- if (!f.model.trim() || seen.has(f.model)) continue;
76
+ if (!f.model.trim() || seen.has(f.model) || (primary.provider && f.provider?.name === primary.provider.name)) continue;
77
77
  seen.add(f.model);
78
78
  out.push(f);
79
79
  }
80
80
  return out;
81
81
  }
82
82
 
83
+ /** A distinct provider is available even when the optional config list is empty. */
84
+ export function defaultReviewFallbackModel(primaryProvider?: string): string {
85
+ return resolveModelAlias(primaryProvider === 'grok' ? 'claude' : 'grok')!;
86
+ }
87
+
83
88
  export interface ReviewFallbackObservation {
84
89
  readonly attempt: number;
85
90
  readonly total: number;
@@ -10,6 +10,14 @@ import { sweepFrozenMerges } from '../self-implement/frozen-merges.js';
10
10
  export interface SupervisorRound {
11
11
  round: number;
12
12
  landed: number;
13
+ /**
14
+ * Pieces that finished (`ok=true`) with at least one new commit against
15
+ * this run's own `--base` — not a main merge. Omitted on rounds that
16
+ * never measured it, so older history stays comparable.
17
+ */
18
+ baseCompleted?: number;
19
+ /** The run's own `--base` those completions were counted against. */
20
+ base?: string;
13
21
  actionable: number;
14
22
  reviewMustFixTrend?: SupervisorDecision['reviewMustFixTrend'];
15
23
  }
@@ -22,6 +30,7 @@ export type SupervisorStopReason =
22
30
  | 'parent-signals-red'
23
31
  | 'needs-human'
24
32
  | 'no-actionable-work'
33
+ | 'harvestable-awaiting-human'
25
34
  | 'handed-off-to-salvage'
26
35
  | 'max-rounds'
27
36
  | 'no-progress'
@@ -39,6 +48,7 @@ export const SUPERVISOR_STOP_REASONS = [
39
48
  'parent-signals-red',
40
49
  'needs-human',
41
50
  'no-actionable-work',
51
+ 'harvestable-awaiting-human',
42
52
  'handed-off-to-salvage',
43
53
  'max-rounds',
44
54
  'no-progress',
@@ -105,9 +115,25 @@ export function countLanded(results: readonly SelfDevJobResult[]): number {
105
115
  return results.filter(hasDelivered).length;
106
116
  }
107
117
 
118
+ /** One base label for the round. Mixed or missing bases stay unnamed. */
119
+ export function roundBase(results: readonly SelfDevJobResult[]): string | undefined {
120
+ const bases = [...new Set(results.flatMap((result) => result.base === undefined ? [] : [result.base]))];
121
+ return bases.length === 1 ? bases[0] : undefined;
122
+ }
123
+
124
+ /**
125
+ * Finished pieces (`ok=true`) that added at least one commit against this
126
+ * run's own `--base`. A main merge is not required, and a commit without
127
+ * `ok=true` is not completion.
128
+ */
129
+ export function countBaseCompleted(results: readonly SelfDevJobResult[]): number {
130
+ return results.filter((result) => result.ok === true && (result.commitsAheadOfBase ?? 0) >= 1).length;
131
+ }
132
+
108
133
  export function madeProgress(prev: SupervisorRound | undefined, next: SupervisorRound): boolean {
109
134
  if (!prev) return true;
110
135
  if (next.landed > 0) return true;
136
+ if ((next.baseCompleted ?? 0) > (prev.baseCompleted ?? 0)) return true;
111
137
  if (next.actionable < prev.actionable) return true;
112
138
  return next.reviewMustFixTrend === 'improving';
113
139
  }
@@ -191,8 +217,16 @@ export function decideNextRun(input: {
191
217
  const t = triageRun(activeResults, deployFindings, observation === 'failed');
192
218
  const round = history.length;
193
219
  const landed = countLanded(results);
220
+ const baseCompleted = countBaseCompleted(results);
221
+ const completionBase = roundBase(results);
194
222
  const actionable = countActionable(t);
195
- const thisRound: SupervisorRound = { round, landed, actionable, reviewMustFixTrend };
223
+ const thisRound: SupervisorRound = {
224
+ round,
225
+ landed,
226
+ ...(baseCompleted > 0 || completionBase !== undefined ? { baseCompleted, ...(completionBase === undefined ? {} : { base: completionBase }) } : {}),
227
+ actionable,
228
+ reviewMustFixTrend,
229
+ };
196
230
  const goalPlanRevisionObservation = formatGoalPlanRevisionObservation(results);
197
231
  const withGoalPlanRevisionObservation = (why: string): string => goalPlanRevisionObservation
198
232
  && goalPlanRevisionObservation !== GOAL_PLAN_REVISION_OBSERVATION_UNAVAILABLE
@@ -226,6 +260,16 @@ export function decideNextRun(input: {
226
260
  };
227
261
  }
228
262
 
263
+ const harvestable = results.filter((result) => result.harvestable === true && !!result.branch);
264
+ if (harvestable.length > 0) {
265
+ return {
266
+ ...base,
267
+ action: 'stop',
268
+ stopReason: 'harvestable-awaiting-human',
269
+ why: withGoalPlanRevisionObservation(`구현 완료 · 수확 대기 — ${harvestable.map((result) => result.branch).join(' · ')}`),
270
+ };
271
+ }
272
+
229
273
  if (deliverableMergeCompleteness === 'complete') {
230
274
  return {
231
275
  ...base,
@@ -364,7 +408,7 @@ export function decideNextRun(input: {
364
408
  ...base,
365
409
  action: 'stop',
366
410
  stopReason: 'no-progress',
367
- why: withNoProgressGoalPlanRevisionObservation(`${stallRounds}라운드 연속 제자리 — 착지도 안 늘고 남은 조각도 안 줄었다(${actionable}). 골·불변식을 의심할 자리`),
411
+ why: withNoProgressGoalPlanRevisionObservation(`${stallRounds}라운드 연속 제자리 — base=${completionBase ?? 'main'} 기준 완료 ${baseCompleted} · 착지 ${landed} · 남은 조각도 안 줄었다(${actionable}). 골·불변식을 의심할 자리`),
368
412
  };
369
413
  }
370
414
  }
@@ -390,9 +434,12 @@ export function appendRound(
390
434
  reviewMustFixTrend?: SupervisorDecision['reviewMustFixTrend'],
391
435
  ): SupervisorRound[] {
392
436
  const t = triageRun(results.filter((result) => result.salvage !== 'launched'));
437
+ const baseCompleted = countBaseCompleted(results);
438
+ const base = roundBase(results);
393
439
  return [...history, {
394
440
  round: history.length,
395
441
  landed: countLanded(results),
442
+ ...(baseCompleted > 0 || base !== undefined ? { baseCompleted, ...(base === undefined ? {} : { base }) } : {}),
396
443
  actionable: countActionable(t),
397
444
  ...(reviewMustFixTrend === undefined ? {} : { reviewMustFixTrend }),
398
445
  }];