elanous 0.2.18 → 0.2.20

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-30dff22c9e1f3073.js → 1307-77fad80cf54477c6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/1612-f60c0c93ec773f81.js +1 -0
  5. package/apps/pwa/out/_next/static/chunks/2116-6429c188eb846ab8.js +1 -0
  6. package/apps/pwa/out/_next/static/chunks/224-e7be8f762fe74986.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/2489-ff55a35b202c0f94.js +1 -0
  8. package/apps/pwa/out/_next/static/chunks/2549-89c518a4c8ed4d00.js +1 -0
  9. package/apps/pwa/out/_next/static/chunks/3857-995ea9f5ef33d71e.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/4144-fad095b48a1862ee.js +1 -0
  11. package/apps/pwa/out/_next/static/chunks/5755-ea7ed88eb956f47b.js +1 -0
  12. package/apps/pwa/out/_next/static/chunks/5977-94ed6f78e9814b2b.js +1 -0
  13. package/apps/pwa/out/_next/static/chunks/7748-6e51bfe927019f5c.js +25 -0
  14. package/apps/pwa/out/_next/static/chunks/8401-35e94e9091f5eb8d.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/9247-c1753a059e7bac08.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/a9f2b54d-33c0689b70ed8cf3.js +1 -0
  17. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d92f7ee3994e84c7.js → page-c855c24de7db74fe.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-4733c967b56a4277.js → page-85b316bb4235980b.js} +1 -1
  19. package/apps/pwa/out/_next/static/chunks/app/board/{page-d22fd849cc22f104.js → page-9b2b26a33cb2cd42.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-bb742f995c6769e7.js → page-c9955fde67b1bcb5.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/bots/{page-02b073a2ac793b83.js → page-7a852956c74015a3.js} +1 -1
  22. package/apps/pwa/out/_next/static/chunks/app/ceo/page-c5a5c621be0e04ae.js +1 -0
  23. package/apps/pwa/out/_next/static/chunks/app/chat/{page-8a9b3594bc23289b.js → page-4ba4abe096b6c2a8.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/consult/page-575d4a8b4f7becb2.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/control/{page-308111abadf35670.js → page-f5d56ff5f45ad164.js} +1 -1
  26. package/apps/pwa/out/_next/static/chunks/app/decisions/page-2260e9eef0357421.js +1 -0
  27. package/apps/pwa/out/_next/static/chunks/app/design-check/{page-5d97b2901e0d1ea6.js → page-7c058069c5e7fa63.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/editor/page-dc5da0b8a2edb8a4.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/exec/{page-8c3d8e63ae2e1c98.js → page-466bf0ee16ad1432.js} +1 -1
  30. package/apps/pwa/out/_next/static/chunks/app/field/{page-9b72a07d424701c6.js → page-a23bc71713bcc08f.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/inside/page-ed4a6f799f005d09.js +1 -0
  32. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a572f58cd3ca5864.js → page-9cd565005b93aca6.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/layout-55f0f40c6db95f15.js +1 -0
  34. package/apps/pwa/out/_next/static/chunks/app/live/{page-be5ea965a07fbc41.js → page-474cb5888ea0e8f3.js} +1 -1
  35. package/apps/pwa/out/_next/static/chunks/app/loops/page-35bfec2e631d3eae.js +1 -0
  36. package/apps/pwa/out/_next/static/chunks/app/market/{page-fa244bd146a00ae8.js → page-9ca21f9ad3208a61.js} +1 -1
  37. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-44d56d6391965a6a.js → page-f9ccb1b1adbea14d.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/morning/{page-83a700d4c754d802.js → page-ec102c9b8b691e37.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/{not-found-7b3170f374f77b9d.js → not-found-a9c2cfa8dda922a5.js} +1 -1
  40. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6da35144878feb6d.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-f6055495e9ed7ea8.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-25204694fa9984a5.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a60e035f151d84d3.js +1 -0
  44. package/apps/pwa/out/_next/static/chunks/app/outputs/page-189c587f36874538.js +1 -0
  45. package/apps/pwa/out/_next/static/chunks/app/page-c56634bde705a65d.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1467047104141297.js → page-db03ae3589a81c9e.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4a6463b97f0ad94f.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-102310930791197a.js → page-75bffecc9aa1004e.js} +1 -1
  49. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a8d2fe0c669e0de4.js +1 -0
  50. package/apps/pwa/out/_next/static/chunks/app/settings/{page-6ed0b73bae8351d8.js → page-31d5b875760bb62d.js} +1 -1
  51. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-c26fa10aac6c21c4.js +1 -0
  52. package/apps/pwa/out/_next/static/chunks/app/setup/{page-43336055451f6e37.js → page-68793c6e10331d66.js} +1 -1
  53. package/apps/pwa/out/_next/static/chunks/app/share/{page-3cc47dca0922ab9a.js → page-46d5201d2207a511.js} +1 -1
  54. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-a77ee355a2304ff0.js → page-d81c07a7b4f618ae.js} +1 -1
  55. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-b17cd46cf0e9b8cf.js → page-be1c4e8697f22360.js} +1 -1
  56. package/apps/pwa/out/_next/static/chunks/app/term/page-cf0a4b4abead71ea.js +1 -0
  57. package/apps/pwa/out/_next/static/chunks/app/today/page-a4717e2c4bf7a28f.js +1 -0
  58. package/apps/pwa/out/_next/static/chunks/app/trace/{page-7bf54c9bb9274af8.js → page-0fd895fab9b62c1b.js} +1 -1
  59. package/apps/pwa/out/_next/static/chunks/app/vault/page-8b6c1454a8e8f306.js +1 -0
  60. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-c6e4364d9de8f717.js +1 -0
  61. package/apps/pwa/out/_next/static/chunks/app/workflows/page-0acc8e198f7b0dfb.js +1 -0
  62. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-134da468538baf48.js → page-34882c84f84021c5.js} +1 -1
  63. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-0506a1e0e4bdb2f5.js +1 -0
  64. package/apps/pwa/out/_next/static/chunks/{webpack-c2782b330447ffb6.js → webpack-c260571513bf43c2.js} +1 -1
  65. package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
  66. package/apps/pwa/out/_next/static/css/c23d959d3ee15e57.css +1 -0
  67. package/apps/pwa/out/_next/static/css/d3427996da11e480.css +1 -0
  68. package/apps/pwa/out/approvals/index.html +1 -1
  69. package/apps/pwa/out/approvals/index.txt +14 -14
  70. package/apps/pwa/out/autopilot/index.html +1 -1
  71. package/apps/pwa/out/autopilot/index.txt +14 -14
  72. package/apps/pwa/out/board/index.html +1 -1
  73. package/apps/pwa/out/board/index.txt +14 -14
  74. package/apps/pwa/out/botlab/index.html +1 -1
  75. package/apps/pwa/out/botlab/index.txt +14 -14
  76. package/apps/pwa/out/bots/index.html +1 -1
  77. package/apps/pwa/out/bots/index.txt +14 -14
  78. package/apps/pwa/out/ceo/index.html +1 -1
  79. package/apps/pwa/out/ceo/index.txt +14 -14
  80. package/apps/pwa/out/chat/index.html +1 -1
  81. package/apps/pwa/out/chat/index.txt +14 -14
  82. package/apps/pwa/out/consult/index.html +1 -1
  83. package/apps/pwa/out/consult/index.txt +14 -14
  84. package/apps/pwa/out/control/index.html +1 -1
  85. package/apps/pwa/out/control/index.txt +14 -14
  86. package/apps/pwa/out/decisions/index.html +1 -1
  87. package/apps/pwa/out/decisions/index.txt +14 -14
  88. package/apps/pwa/out/design-check/index.html +1 -1
  89. package/apps/pwa/out/design-check/index.txt +14 -14
  90. package/apps/pwa/out/editor/index.html +1 -1
  91. package/apps/pwa/out/editor/index.txt +16 -15
  92. package/apps/pwa/out/exec/index.html +1 -1
  93. package/apps/pwa/out/exec/index.txt +14 -14
  94. package/apps/pwa/out/field/index.html +1 -1
  95. package/apps/pwa/out/field/index.txt +14 -14
  96. package/apps/pwa/out/healthz +1 -1
  97. package/apps/pwa/out/index.html +1 -1
  98. package/apps/pwa/out/index.txt +14 -14
  99. package/apps/pwa/out/inside/index.html +1 -1
  100. package/apps/pwa/out/inside/index.txt +16 -15
  101. package/apps/pwa/out/intake/index.html +1 -1
  102. package/apps/pwa/out/intake/index.txt +14 -14
  103. package/apps/pwa/out/live/index.html +1 -1
  104. package/apps/pwa/out/live/index.txt +14 -14
  105. package/apps/pwa/out/loops/index.html +1 -1
  106. package/apps/pwa/out/loops/index.txt +24 -23
  107. package/apps/pwa/out/market/index.html +1 -1
  108. package/apps/pwa/out/market/index.txt +14 -14
  109. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  110. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  111. package/apps/pwa/out/morning/index.html +1 -1
  112. package/apps/pwa/out/morning/index.txt +14 -14
  113. package/apps/pwa/out/observatory/index.html +1 -1
  114. package/apps/pwa/out/observatory/index.txt +14 -14
  115. package/apps/pwa/out/ops/checklist/index.html +1 -1
  116. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  117. package/apps/pwa/out/ops/release/index.html +1 -1
  118. package/apps/pwa/out/ops/release/index.txt +14 -14
  119. package/apps/pwa/out/ops/seats/index.html +1 -1
  120. package/apps/pwa/out/ops/seats/index.txt +14 -14
  121. package/apps/pwa/out/outputs/index.html +1 -1
  122. package/apps/pwa/out/outputs/index.txt +14 -14
  123. package/apps/pwa/out/reflection/index.html +1 -1
  124. package/apps/pwa/out/reflection/index.txt +15 -15
  125. package/apps/pwa/out/scheduler/index.html +1 -1
  126. package/apps/pwa/out/scheduler/index.txt +14 -14
  127. package/apps/pwa/out/sessions/index.html +1 -1
  128. package/apps/pwa/out/sessions/index.txt +14 -14
  129. package/apps/pwa/out/settings/devices/index.html +1 -1
  130. package/apps/pwa/out/settings/devices/index.txt +14 -14
  131. package/apps/pwa/out/settings/index.html +1 -1
  132. package/apps/pwa/out/settings/index.txt +14 -14
  133. package/apps/pwa/out/setup/done/index.html +1 -1
  134. package/apps/pwa/out/setup/done/index.txt +14 -14
  135. package/apps/pwa/out/setup/index.html +1 -1
  136. package/apps/pwa/out/setup/index.txt +14 -14
  137. package/apps/pwa/out/share/index.html +1 -1
  138. package/apps/pwa/out/share/index.txt +14 -14
  139. package/apps/pwa/out/showroom/index.html +1 -1
  140. package/apps/pwa/out/showroom/index.txt +14 -14
  141. package/apps/pwa/out/tasks/index.html +1 -1
  142. package/apps/pwa/out/tasks/index.txt +14 -14
  143. package/apps/pwa/out/term/index.html +1 -1
  144. package/apps/pwa/out/term/index.txt +14 -14
  145. package/apps/pwa/out/today/index.html +1 -1
  146. package/apps/pwa/out/today/index.txt +14 -14
  147. package/apps/pwa/out/trace/index.html +1 -1
  148. package/apps/pwa/out/trace/index.txt +14 -14
  149. package/apps/pwa/out/vault/index.html +1 -1
  150. package/apps/pwa/out/vault/index.txt +14 -14
  151. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  152. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  153. package/apps/pwa/out/workflows/index.html +1 -1
  154. package/apps/pwa/out/workflows/index.txt +14 -14
  155. package/apps/pwa/out/workspace/index.html +1 -1
  156. package/apps/pwa/out/workspace/index.txt +14 -14
  157. package/apps/pwa/out/worktrees/index.html +1 -1
  158. package/apps/pwa/out/worktrees/index.txt +14 -14
  159. package/graphs/demo/editor-recipes.yaml +22 -0
  160. package/graphs/ops/guardian.yaml +26 -0
  161. package/graphs/ops/recipes.yaml +3 -0
  162. package/graphs/retro/daily.yaml +25 -0
  163. package/graphs/retro/recipes.yaml +24 -0
  164. package/package.json +1 -1
  165. package/scripts/ask-marker-check.ts +3 -1
  166. package/scripts/coord-channel-watch.sh +2 -2
  167. package/scripts/discord-seat-probe.ts +3 -1
  168. package/scripts/land-global-checks.ts +89 -0
  169. package/scripts/lib/pch-regress-map.ts +24 -0
  170. package/scripts/morning-report.ts +18 -4
  171. package/scripts/pch-regress.ts +88 -0
  172. package/scripts/release-loop/gate-diff.ts +18 -0
  173. package/scripts/release-loop/gate-node.ts +628 -65
  174. package/scripts/release-loop/gate-partial.ts +106 -0
  175. package/scripts/release-loop/gate-timing.ts +229 -0
  176. package/scripts/release-loop/preflight.ts +6 -4
  177. package/scripts/release-loop/publish-window.ts +141 -0
  178. package/scripts/release-loop/release-docs.ts +1 -4
  179. package/scripts/release-loop/release-universe.ts +38 -0
  180. package/scripts/release-loop/resume-release.ts +67 -5
  181. package/scripts/release-loop/shard-plan.ts +65 -0
  182. package/scripts/release-loop/unattended-release.ts +12 -2
  183. package/scripts/release-story/draft.ts +5 -1
  184. package/scripts/retro/shadow-compare.ts +71 -0
  185. package/scripts/rhythm/daily.ts +6 -1
  186. package/scripts/video/scene-table.ts +159 -0
  187. package/src/acp/core-turn-bridge.ts +39 -2
  188. package/src/agent-mission/driver.ts +34 -6
  189. package/src/autopilot/build/llm-conflict-merge.ts +132 -7
  190. package/src/autopilot/build/merge-intent.ts +135 -7
  191. package/src/away/presence.ts +40 -0
  192. package/src/away/release-watch.ts +272 -0
  193. package/src/chat/index.ts +2 -1
  194. package/src/chat/short-question.ts +3 -40
  195. package/src/cli/away-cli.ts +68 -0
  196. package/src/cli/config-drift.ts +513 -0
  197. package/src/cli/guardian-cli.ts +49 -0
  198. package/src/cli/intake-cli.ts +13 -7
  199. package/src/cli/log-event-names.ts +5 -0
  200. package/src/cli/pod-cli.ts +5 -1
  201. package/src/cli/pr-cli.ts +20 -0
  202. package/src/cli/pty-drive-cli.ts +14 -3
  203. package/src/cli/release-cli.ts +52 -12
  204. package/src/cli/retro-cli.ts +78 -0
  205. package/src/cli/setup-cli.ts +31 -1
  206. package/src/cli/tasks-cli.ts +193 -14
  207. package/src/context-bus/context-now-public.ts +1 -1
  208. package/src/context-bus/context-now-surfaces.ts +16 -6
  209. package/src/context-bus/context-now.ts +97 -3
  210. package/src/core-turn/short-question.ts +100 -0
  211. package/src/dashboard/dashboard-status-lines.ts +24 -4
  212. package/src/dashboard/index.ts +50 -5
  213. package/src/dashboard/input/slash-executor.ts +6 -2
  214. package/src/dashboard/slash-runtime/dashboard-handlers.ts +68 -40
  215. package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
  216. package/src/dashboard/slash-runtime/help-from-registry.ts +62 -23
  217. package/src/dashboard/slash-runtime/help-groups.ts +44 -0
  218. package/src/dashboard/slash-runtime/model-choices.ts +125 -0
  219. package/src/domains/guardian-tick.ts +138 -0
  220. package/src/domains/outbound-alert.ts +44 -13
  221. package/src/domains/outbound-log-probe.ts +6 -1
  222. package/src/domains/schedule-registry.ts +23 -0
  223. package/src/domains/telegram-kind-route.ts +39 -0
  224. package/src/domains/watch-default-scheduler.ts +95 -0
  225. package/src/esc-abort-gate.ts +44 -0
  226. package/src/graph-runner/graph-run-api.ts +205 -0
  227. package/src/graph-runner/runner.ts +36 -6
  228. package/src/harness/harness-cli-command.ts +420 -51
  229. package/src/harness/harness-queue.ts +315 -25
  230. package/src/harness/harness-salvage-cli.ts +140 -0
  231. package/src/harness/harness-worktree-add.ts +68 -0
  232. package/src/harness/ledger-write-guard.ts +6 -16
  233. package/src/hq/fence-outcomes.ts +63 -0
  234. package/src/hq/fence-wrapper.ts +33 -0
  235. package/src/hq/hq.ts +17 -2
  236. package/src/index.ts +118 -6
  237. package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
  238. package/src/instance/test-write-guard.ts +200 -0
  239. package/src/intake-plane/digest.ts +39 -15
  240. package/src/intake-plane/harness-target.ts +4 -3
  241. package/src/intake-plane/intake-sources.ts +1 -0
  242. package/src/intake-plane/items.ts +1 -0
  243. package/src/loops/orchestrator/degradation.ts +57 -0
  244. package/src/loops/orchestrator/finish-rate.ts +33 -3
  245. package/src/loops/orchestrator/tick.ts +166 -45
  246. package/src/maturity/feature-maturity.ts +6 -4
  247. package/src/maturity/tui-slash-maturity.ts +1 -0
  248. package/src/mss/logging/log-store.ts +3 -0
  249. package/src/nexus/api/graph-kinds.ts +10 -4
  250. package/src/nexus/api/graphs-api.ts +178 -6
  251. package/src/nexus/api/http-server.ts +54 -8
  252. package/src/nexus/api/loop-edges.ts +292 -17
  253. package/src/nexus/api/ops-api.ts +112 -29
  254. package/src/nexus/api/ops-board-route.ts +48 -0
  255. package/src/nexus/index.ts +7 -0
  256. package/src/onboarding/entry-hints.ts +5 -0
  257. package/src/onboarding.ts +24 -6
  258. package/src/release-loop/checklist.ts +2 -0
  259. package/src/release-loop/feature-store.ts +68 -18
  260. package/src/release-loop/gate-shards.ts +158 -0
  261. package/src/release-loop/light-rc.ts +173 -0
  262. package/src/release-loop/placement.ts +51 -17
  263. package/src/release-loop/release-schedule.ts +6 -2
  264. package/src/release-loop/rubric.ts +57 -1
  265. package/src/retro/daily.ts +372 -0
  266. package/src/seat-loop/op-decide.ts +173 -0
  267. package/src/seat-loop/seat-loop.ts +60 -3
  268. package/src/seat-loop/seat-report.ts +58 -3
  269. package/src/self-dev/ask-launch-flow.ts +5 -4
  270. package/src/self-dev/ask-launch-io.ts +49 -1
  271. package/src/self-dev/ask-path-candidates.ts +40 -2
  272. package/src/self-dev/dev-cli.ts +36 -0
  273. package/src/self-dev/draft-sweep.ts +371 -16
  274. package/src/self-dev/draft-triage-rules.ts +19 -4
  275. package/src/self-dev/orchestrate.ts +1 -0
  276. package/src/self-dev/pr-preview.ts +247 -0
  277. package/src/self-dev/shard-run.ts +23 -7
  278. package/src/self-implement/blocked-draft-policy.ts +101 -0
  279. package/src/self-implement/execution-clarification.ts +11 -0
  280. package/src/self-implement/gate-cli.ts +29 -8
  281. package/src/self-implement/gate-env-retry.ts +83 -0
  282. package/src/self-implement/goal-author-clarification.ts +88 -13
  283. package/src/self-implement/goal-clarification-escalation.ts +2 -0
  284. package/src/self-implement/orchestrator.ts +220 -6
  285. package/src/self-implement/run-stop.ts +123 -0
  286. package/src/self-implement/run-ttl.ts +258 -0
  287. package/src/self-implement/salvage-branches.ts +254 -0
  288. package/src/self-implement/salvage-retention.ts +123 -0
  289. package/src/self-implement/seams.ts +4 -0
  290. package/src/self-implement/self-implement-cli.ts +2 -0
  291. package/src/self-implement/sibling-resync.ts +223 -0
  292. package/src/self-implement/stop-autoheal.ts +204 -0
  293. package/src/task-agent/actions.ts +63 -2
  294. package/src/task-agent/board.ts +132 -0
  295. package/src/task-agent/card-evidence.ts +292 -0
  296. package/src/task-agent/completion-evidence.ts +126 -0
  297. package/src/task-agent/judge.ts +25 -0
  298. package/src/task-agent/live-moves.ts +276 -0
  299. package/src/task-agent/mission.ts +33 -7
  300. package/src/task-agent/shadow.ts +192 -15
  301. package/src/task-agent/task-hand.ts +124 -17
  302. package/src/task-orchestrator/surfaces/pod-command-job.ts +82 -8
  303. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +4 -0
  304. package/src/task-orchestrator/surfaces/pod-install-slots.ts +89 -0
  305. package/src/task-orchestrator/surfaces/pod-lease.ts +233 -21
  306. package/src/task-orchestrator/surfaces/pod-pool.ts +155 -34
  307. package/src/task-orchestrator/surfaces/self-implement-pod.ts +21 -5
  308. package/src/telegram-away-command.ts +38 -0
  309. package/src/telegram-commands.ts +52 -0
  310. package/src/telegram-report-log-probe.ts +21 -0
  311. package/src/telegram-report.ts +27 -3
  312. package/src/testing/real-cli-spawn.ts +106 -0
  313. package/src/tui-client/dashboard-session.ts +1 -0
  314. package/src/user-config.ts +190 -5
  315. package/src/version/packed-revision.json +1 -1
  316. package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
  317. package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +0 -1
  318. package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
  319. package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +0 -1
  320. package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +0 -1
  321. package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
  322. package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +0 -1
  323. package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
  324. package/apps/pwa/out/_next/static/chunks/9247-269e4464cc4b491c.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/consult/page-a0eb02638b1e6f31.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/editor/page-c39554c321576982.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/loops/page-c130b63862437814.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-a3c58f5c72a5f3f9.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/outputs/page-e8e1ecaea7cd71d2.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-44d5f767491692bf.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +0 -1
  345. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +0 -1
  346. package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +0 -1
  347. package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +0 -1
  348. package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +0 -1
  349. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_buildManifest.js +0 -0
  350. /package/apps/pwa/out/_next/static/{P6YTVgzuZz5oTZhY4Din_ → Bam1YziK2iH9bN4K0utGk}/_ssgManifest.js +0 -0
@@ -0,0 +1,247 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { mkdtempSync, rmSync, mkdirSync, writeFileSync, readFileSync } from 'node:fs';
3
+ import { join, resolve } from 'node:path';
4
+
5
+ export interface PreviewOptions {
6
+ target: string;
7
+ repoRoot: string;
8
+ scenario?: string;
9
+ /** Main-based combined preview: only existing config paths may be enabled. */
10
+ flags?: readonly string[];
11
+ /** PR heads to preview together on main when no feature flags exist. */
12
+ bundlePrs?: readonly number[];
13
+ commentPr?: number;
14
+ }
15
+ export interface PreviewResult {
16
+ target: string;
17
+ head: string;
18
+ scenario: string;
19
+ status: 'passed' | 'friction';
20
+ report: string;
21
+ prNumber?: number;
22
+ commented: boolean;
23
+ }
24
+ export function readPreviewResult(directory: string): PreviewResult | null {
25
+ try {
26
+ const value = JSON.parse(readFileSync(join(directory, 'preview-result.json'), 'utf8')) as PreviewResult;
27
+ return value && (value.status === 'passed' || value.status === 'friction') && typeof value.head === 'string' ? value : null;
28
+ } catch { return null; }
29
+ }
30
+
31
+ export interface PreviewDeps {
32
+ run?: (command: string, args: readonly string[], cwd: string, timeoutMs?: number) => { status: number | null; stdout: string; stderr: string };
33
+ makeDir?: () => string;
34
+ dispose?: (directory: string) => void;
35
+ wait?: (ms: number) => Promise<void>;
36
+ /** Probe a test-mode daemon without injecting a global HTTP client into the command runner. */
37
+ probePwa?: (url: string) => Promise<boolean>;
38
+ }
39
+
40
+ const SCENARIOS: Record<string, { input: string; expected: string; matches: (snapshot: string) => boolean }> = {
41
+ // GOODHART (#24844 review): a group head (`▸ 시작`) or the echoed `/help` is not the list. The /help modal
42
+ // (help-from-registry.ts) prints one `/name description` row per command; the «시작» group alone has six.
43
+ // Pass only when at least three other start-group commands are listed as rows.
44
+ S2: { input: '/help', expected: 'help command list (/status, /model, /clear ... rows)', matches: (s) =>
45
+ ['status', 'model', 'clear', 'new', 'resume'].filter((name) => new RegExp(`(?:^|\\s)/${name}(?: \\([^)]*\\))?\\s{2,}\\S`, 'mu').test(s)).length >= 3 },
46
+ S3: { input: '/status', expected: 'model and connection', matches: (s) => /모델|model/iu.test(s) && /연결|connection/iu.test(s) },
47
+ S6: { input: '/now', expected: 'current runs or schedules', matches: (s) => /지금|도는 런|스케줄|running|schedule/iu.test(s) },
48
+ S7: { input: '/term list', expected: 'terminal/PTY list', matches: (s) => /terminal|pty|터미널/iu.test(s) },
49
+ S8: { input: '/model', expected: 'model choices', matches: (s) => /빠름|보통|깊음|terra|sol|luna|선택/iu.test(s) },
50
+ S9: { input: '/harness', expected: 'harness usage or stages', matches: (s) => /usage:.*harness|저작|구현|리뷰|착지/iu.test(s) },
51
+ };
52
+
53
+ /**
54
+ * Lines of `after` that were not already on `before`'s screen (multiset by line, trailing spaces ignored).
55
+ * A prefix check is not enough: the PTY re-renders and scrolls, so the previous scenario's answer can
56
+ * reappear anywhere on the next screen (#24844 review). A repeated line is conservatively treated as old.
57
+ */
58
+ export function previewFreshLines(before: string, after: string): string {
59
+ const seen = new Map<string, number>();
60
+ for (const line of before.split('\n')) {
61
+ const key = line.trimEnd();
62
+ if (key) seen.set(key, (seen.get(key) ?? 0) + 1);
63
+ }
64
+ return after.split('\n').filter((line) => {
65
+ const key = line.trimEnd();
66
+ if (!key) return false;
67
+ const left = seen.get(key) ?? 0;
68
+ if (left > 0) { seen.set(key, left - 1); return false; }
69
+ return true;
70
+ }).join('\n');
71
+ }
72
+
73
+ /** Whether a fresh screen region shows the scenario's expected answer (exported for the preview tests). */
74
+ export function previewScenarioMatches(scenario: string, fresh: string): boolean {
75
+ const entry = Object.hasOwn(SCENARIOS, scenario) ? SCENARIOS[scenario] : undefined;
76
+ if (!entry) throw new Error(`TUI-COMFORT §4: unsupported scenario ${scenario}`);
77
+ return entry.matches(fresh);
78
+ }
79
+
80
+ export function previewScenarioInput(scenario: string): string {
81
+ const text = Object.hasOwn(SCENARIOS, scenario) ? SCENARIOS[scenario] : undefined;
82
+ if (!text) throw new Error(`TUI-COMFORT §4: unsupported scenario ${scenario}; available: ${Object.keys(SCENARIOS).join(', ')}`);
83
+ return text.input;
84
+ }
85
+
86
+ const defaultRun: NonNullable<PreviewDeps['run']> = (command, args, cwd, timeoutMs = 120_000) => {
87
+ const r = spawnSync(command, [...args], { cwd, encoding: 'utf8', timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 });
88
+ return { status: r.status, stdout: r.stdout ?? '', stderr: r.stderr ?? String(r.error ?? '') };
89
+ };
90
+
91
+ /** Build and exercise a detached head, never the caller's checkout. The report is posted only after measurement. */
92
+ export async function runPrPreview(options: PreviewOptions, deps: PreviewDeps = {}): Promise<PreviewResult> {
93
+ const run = deps.run ?? defaultRun;
94
+ const root = resolve(options.repoRoot);
95
+ const target = options.target.trim();
96
+ if (!target || target.startsWith('-') || /[\s\x00-\x1f]/u.test(target)) throw new Error('preview: invalid PR number or branch');
97
+ const isPr = /^[1-9]\d*$/u.test(target);
98
+ if (isPr && !Number.isSafeInteger(Number(target))) throw new Error('preview: invalid PR number');
99
+ if (/^\d+$/u.test(target) && !isPr) throw new Error('preview: invalid PR number');
100
+ if (options.commentPr !== undefined && (!Number.isSafeInteger(options.commentPr) || options.commentPr <= 0)) throw new Error('preview: invalid --comment-pr');
101
+ const bundlePrs = options.bundlePrs ?? [];
102
+ if (bundlePrs.length && (target !== 'main' || (options.flags?.length ?? 0) > 0)) throw new Error('preview: --bundle-pr requires main without feature flags');
103
+ if (bundlePrs.length && (new Set(bundlePrs).size !== bundlePrs.length || bundlePrs.some((pr) => !Number.isSafeInteger(pr) || pr <= 0))) throw new Error('preview: invalid or duplicate --bundle-pr');
104
+ if (target === 'main' && options.commentPr === undefined) throw new Error('preview: main ⊕ flags requires --comment-pr <PR number> to publish the result');
105
+ if (isPr && options.commentPr !== undefined && options.commentPr !== Number(target)) throw new Error('preview: --comment-pr must match the target PR');
106
+ const flags = options.flags ?? [];
107
+ if (flags.length && target !== 'main') throw new Error('preview: --flag requires main (main ⊕ feature flags)');
108
+ if (flags.some((flag) => !/^[a-zA-Z][\w-]*(?:\.[a-zA-Z][\w-]*)+$/u.test(flag))) throw new Error('preview: --flag expects an existing dotted config path');
109
+ const scenarios = (options.scenario ?? 'S2').split(',').map((id) => id.trim());
110
+ if (scenarios.length === 0 || new Set(scenarios).size !== scenarios.length) throw new Error('preview: duplicate or empty scenario');
111
+ for (const id of scenarios) previewScenarioInput(id);
112
+ const scenario = scenarios.join(',');
113
+ const check = (command: string, args: readonly string[], cwd: string, timeout?: number): string => {
114
+ const r = run(command, args, cwd, timeout);
115
+ if (r.status !== 0) throw new Error(`preview: ${command} ${args.join(' ')} failed: ${(r.stdout + '\n' + r.stderr).slice(-600)}`);
116
+ return r.stdout.trim();
117
+ };
118
+ let prNumber = isPr ? Number(target) : options.commentPr;
119
+ let branchPrHead: string | undefined;
120
+ let ref = target;
121
+ if (isPr) {
122
+ const pr = JSON.parse(check('gh', ['pr', 'view', target, '--json', 'number,headRefOid'], root)) as { number?: number; headRefOid?: string };
123
+ if (pr.number !== Number(target) || !/^[0-9a-f]{40}$/iu.test(pr.headRefOid ?? '')) throw new Error('preview: PR head is unavailable');
124
+ ref = pr.headRefOid!;
125
+ } else if (target !== 'main') {
126
+ check('git', ['check-ref-format', '--branch', target], root);
127
+ if (options.commentPr === undefined) {
128
+ const matching = JSON.parse(check('gh', ['pr', 'list', '--head', target, '--state', 'open', '--json', 'number,headRefOid'], root)) as Array<{ number?: number; headRefOid?: string }>;
129
+ if (matching.length !== 1 || !Number.isSafeInteger(matching[0]?.number)) throw new Error('preview: branch has no unique open PR; use --comment-pr <PR number>');
130
+ prNumber = matching[0]!.number;
131
+ branchPrHead = matching[0]!.headRefOid;
132
+ }
133
+ }
134
+ if (isPr && run('git', ['cat-file', '-e', `${ref}^{commit}`], root).status !== 0) {
135
+ check('git', ['fetch', '--no-tags', 'origin', `pull/${target}/head`], root, 120_000);
136
+ const fetched = check('git', ['rev-parse', '--verify', 'FETCH_HEAD^{commit}'], root);
137
+ if (fetched !== ref) throw new Error('preview: PR head changed while fetching; retry');
138
+ }
139
+ const head = check('git', ['rev-parse', '--verify', `${ref}^{commit}`], root);
140
+ if (!/^[0-9a-f]{40}$/iu.test(head)) throw new Error('preview: unresolved commit');
141
+ if (target !== 'main' && !isPr) {
142
+ const pr = JSON.parse(check('gh', ['pr', 'view', String(prNumber), '--json', 'headRefOid,headRefName'], root)) as { headRefOid?: string; headRefName?: string };
143
+ if (pr.headRefName !== target || pr.headRefOid !== head || (branchPrHead !== undefined && branchPrHead !== head)) throw new Error('preview: branch does not match the target PR head; use the PR number');
144
+ }
145
+ const bundleHeads = bundlePrs.map((number) => {
146
+ const pr = JSON.parse(check('gh', ['pr', 'view', String(number), '--json', 'number,headRefOid'], root)) as { number?: number; headRefOid?: string };
147
+ if (pr.number !== number || !/^[0-9a-f]{40}$/iu.test(pr.headRefOid ?? '')) throw new Error(`preview: PR #${number} head is unavailable`);
148
+ const oid = pr.headRefOid!;
149
+ if (run('git', ['cat-file', '-e', `${oid}^{commit}`], root).status !== 0) {
150
+ check('git', ['fetch', '--no-tags', 'origin', `pull/${number}/head`], root, 120_000);
151
+ if (check('git', ['rev-parse', '--verify', 'FETCH_HEAD^{commit}'], root) !== oid) throw new Error(`preview: PR #${number} head changed while fetching; retry`);
152
+ }
153
+ return { number, oid };
154
+ });
155
+ const directory = (deps.makeDir ?? (() => {
156
+ const previews = join(root, '.elanous-test', 'previews');
157
+ mkdirSync(previews, { recursive: true });
158
+ return mkdtempSync(join(previews, 'preview-'));
159
+ }))();
160
+ const worktree = join(directory, 'checkout');
161
+ let added = false;
162
+ let heldPty: string | undefined;
163
+ let report = '';
164
+ const wait = deps.wait ?? ((ms: number) => new Promise<void>((done) => setTimeout(done, ms)));
165
+ try {
166
+ check('git', ['worktree', 'add', '--detach', worktree, head], root);
167
+ added = true;
168
+ for (const { number, oid } of bundleHeads) {
169
+ const current = JSON.parse(check('gh', ['pr', 'view', String(number), '--json', 'number,headRefOid'], root)) as { number?: number; headRefOid?: string };
170
+ if (current.number !== number || current.headRefOid !== oid) throw new Error(`preview: PR #${number} head changed while preparing bundle; retry`);
171
+ check('git', ['-c', 'user.name=elanous-preview', '-c', 'user.email=preview@localhost', 'merge', '--no-ff', '--no-edit', oid], worktree, 120_000);
172
+ }
173
+ check('bun', ['install', '--frozen-lockfile'], worktree, 300_000);
174
+ check('bun', ['install', '--frozen-lockfile'], join(worktree, 'apps/pwa'), 300_000);
175
+ for (const flag of flags) {
176
+ const existing = check('bun', ['bin/elanous.mjs', '--test', 'config', 'get', flag], worktree);
177
+ if (existing !== 'true' && existing !== 'false') throw new Error(`preview: ${flag} is not a boolean feature flag`);
178
+ check('bun', ['bin/elanous.mjs', '--test', 'config', 'set', flag, 'true'], worktree);
179
+ }
180
+ check('bun', ['bin/elanous.mjs', '--test', 'nexus', 'build', '--cwd', join(worktree, 'apps/pwa')], worktree, 300_000);
181
+ const pwa = check('bun', ['bin/elanous.mjs', '--test', 'nexus', 'run', '--test', '--tool-cwd', worktree], worktree, 120_000);
182
+ const port = /\bnexus\s+:(314[5-9]\d)\b/u.exec(pwa)?.[1];
183
+ const endpoint = port ? `http://127.0.0.1:${port}` : undefined;
184
+ if (!endpoint) throw new Error('preview: P-1 has no local test endpoint');
185
+ const probeOnce = deps.probePwa ?? (async (url: string) => {
186
+ try {
187
+ const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
188
+ return response.ok && (await response.text()).includes('<');
189
+ } catch { return false; }
190
+ });
191
+ // `nexus run` detaches and returns before the daemon listens — its exit is not readiness. Poll.
192
+ let probe = false;
193
+ for (let attempt = 0; attempt < 60 && !probe; attempt++) {
194
+ probe = await probeOnce(`${endpoint}/app/`);
195
+ if (!probe) await wait(1000);
196
+ }
197
+ if (!probe) throw new Error('preview: P-1 did not serve the built PWA');
198
+ const version = check('bun', ['bin/elanous.mjs', '--version'], worktree);
199
+ if (!version) throw new Error('preview: B-1 version probe returned no output');
200
+ const held = check('bun', ['bin/elanous.mjs', '--test', 'dev', '--elanous', '--hold', '--cwd', worktree, '--isolated-root', join(directory, 'tui'), '--json'], worktree, 120_000);
201
+ const last = held.split('\n').reverse().find((line: string) => line.trim().startsWith('{'));
202
+ const pty = last ? JSON.parse(last) as { held?: boolean; ptyId?: string } : {};
203
+ if (pty.held !== true || !/^pty_[\w-]+$/u.test(pty.ptyId ?? '')) throw new Error('preview: B-1 held TUI did not return a PTY reference');
204
+ heldPty = pty.ptyId;
205
+ const snapshotArgs = ['bin/elanous.mjs', '--test', 'pty', 'snapshot', pty.ptyId!] as const;
206
+ const rows: string[] = [];
207
+ const snapshots: string[] = [];
208
+ for (const id of scenarios) {
209
+ const { input, expected, matches } = SCENARIOS[id]!;
210
+ const before = check('bun', snapshotArgs, worktree);
211
+ check('bun', ['bin/elanous.mjs', '--test', 'pty', 'text', pty.ptyId!, input, '--enter'], worktree);
212
+ // A constant sleep misses slow frames under load — poll until this scenario's expected response (≤ 20s).
213
+ let snapshot = '';
214
+ let matched = false;
215
+ for (let attempt = 0; attempt < 20; attempt++) {
216
+ await wait(1000);
217
+ snapshot = check('bun', snapshotArgs, worktree);
218
+ const fresh = previewFreshLines(before, snapshot);
219
+ matched = snapshot !== 'ok' && snapshot !== before && matches(fresh);
220
+ if (matched) break;
221
+ }
222
+ const observed = snapshot && snapshot !== 'ok' && snapshot !== before;
223
+ rows.push(`| TUI-COMFORT §4 ${id} | \`${input}\` → ${matched ? `expected ${expected} matched` : `friction: expected ${expected} not found${observed ? '' : ' (no response within 20s)'}`} |`);
224
+ snapshots.push(`<details><summary>Inner PTY snapshot ${id}</summary>\n\n\`\`\`text\n${snapshot.slice(-3000).replaceAll('```', "'''")}\n\`\`\`\n</details>`);
225
+ }
226
+ report = `| Check | Observation |\n|---|---|\n| head | \`${head}\` |${bundleHeads.length ? `\n| bundled PR heads | ${bundleHeads.map(({ number, oid }) => `#${number} \`${oid}\``).join(' ⊕ ')} |` : ''}\n| B-1 version | ${version.slice(0, 120)} |\n| P-1 PWA | \`${endpoint}/app/\` served the built PWA |\n${rows.join('\n')}\n\n${snapshots.join('\n\n')}`;
227
+ } catch (error) {
228
+ report = `| Check | Friction |\n|---|---|\n| head | \`${head}\` |\n| TUI-COMFORT §4 ${scenario} | ${String(error).replaceAll('|', '\\|').slice(0, 700)} |`;
229
+ } finally {
230
+ if (added) {
231
+ if (heldPty) run('bun', ['bin/elanous.mjs', '--test', 'pty', 'key', heldPty, 'ctrl+c'], worktree, 30_000);
232
+ run('bun', ['bin/elanous.mjs', '--test', 'nexus', 'run', '--test', '--stop'], worktree, 30_000);
233
+ const removed = run('git', ['worktree', 'remove', '--force', worktree], root, 30_000);
234
+ if (removed.status !== 0) throw new Error(`preview: failed to remove isolated worktree: ${removed.stderr}`);
235
+ }
236
+ (deps.dispose ?? ((dir) => rmSync(dir, { recursive: true, force: true })))(directory);
237
+ }
238
+ const status = report.includes('| Friction |') || report.includes('→ friction:') ? 'friction' : 'passed';
239
+ const body = `## PR preview — ${target} (${status})\n\n${report}`;
240
+ if (prNumber === undefined) throw new Error('preview: cannot comment without a PR number');
241
+ check('gh', ['pr', 'comment', String(prNumber), '--body', body], root);
242
+ const result: PreviewResult = { target, head, scenario, status, report: body, prNumber, commented: true };
243
+ const resultDir = join(root, '.elanous-test');
244
+ mkdirSync(resultDir, { recursive: true });
245
+ writeFileSync(join(resultDir, 'preview-result.json'), JSON.stringify(result), { mode: 0o600 });
246
+ return result;
247
+ }
@@ -11,6 +11,7 @@ export interface ShardRunResult {
11
11
  shards: GateTestShard[];
12
12
  attempts: GateShardAttempt[];
13
13
  reason?: string;
14
+ measurementFailures?: number;
14
15
  }
15
16
 
16
17
  export type ShardProcess = (cwd: string, files: readonly string[]) => {
@@ -34,24 +35,39 @@ export function runShardedGateTests(
34
35
  if (!Number.isSafeInteger(count) || count < 1) throw new Error('invalid --shards count');
35
36
  const rss = new Map<string, number>();
36
37
  const seconds = new Map<string, number>();
38
+ const failedMeasurements: string[] = [];
37
39
  for (const file of [...new Set(files)]) {
38
40
  let sample: ReturnType<ShardProcess>;
39
41
  try {
40
42
  sample = run(cwd, [file]);
41
- } catch (error) {
42
- return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: `measurement failed: ${file}: ${String(error)}` };
43
+ } catch {
44
+ failedMeasurements.push(file);
45
+ continue;
43
46
  }
44
- if (![0, 1].includes(sample.exitCode ?? -1) || sample.signal || !sample.junit || !Number.isFinite(sample.rssMb) || !Number.isFinite(sample.seconds)) {
45
- return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: `measurement unavailable: ${file}` };
47
+ if (![0, 1].includes(sample.exitCode ?? -1) || sample.signal || !sample.junit
48
+ || !Number.isFinite(sample.rssMb) || sample.rssMb! <= 0
49
+ || !Number.isFinite(sample.seconds) || sample.seconds! < 0) {
50
+ failedMeasurements.push(file);
51
+ continue;
46
52
  }
47
53
  rss.set(file, sample.rssMb!);
48
54
  seconds.set(file, sample.seconds!);
49
55
  }
56
+ if (failedMeasurements.length) {
57
+ if (!rss.size) return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: 'no valid file measurements', measurementFailures: failedMeasurements.length };
58
+ const averageRss = [...rss.values()].reduce((sum, value) => sum + value, 0) / rss.size;
59
+ const averageSeconds = [...seconds.values()].reduce((sum, value) => sum + value, 0) / seconds.size;
60
+ for (const file of failedMeasurements) {
61
+ rss.set(file, averageRss);
62
+ seconds.set(file, averageSeconds);
63
+ }
64
+ }
65
+ const measurementFailures = failedMeasurements.length || undefined;
50
66
  let shards: GateTestShard[];
51
67
  try {
52
68
  shards = planGateTestShards(files, rss, seconds, budgetGiB, count);
53
69
  } catch (error) {
54
- return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: String(error) };
70
+ return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: String(error), ...(measurementFailures ? { measurementFailures } : {}) };
55
71
  }
56
72
  const attempts: GateShardAttempt[] = [];
57
73
  const baseline = baselineWorktree(cwd, baseRef, (dir) => {
@@ -76,8 +92,8 @@ export function runShardedGateTests(
76
92
  }
77
93
  return aggregateGateTestShards(shards, attempts);
78
94
  });
79
- if ('retryShardIds' in baseline) return { aggregate: baseline, shards, attempts };
80
- return { aggregate: { status: 'unmeasured', retryShardIds: shards.map((shard) => shard.id) }, shards, attempts, reason: baseline.log };
95
+ if ('retryShardIds' in baseline) return { aggregate: baseline, shards, attempts, ...(measurementFailures ? { measurementFailures } : {}) };
96
+ return { aggregate: { status: 'unmeasured', retryShardIds: shards.map((shard) => shard.id) }, shards, attempts, reason: baseline.log, ...(measurementFailures ? { measurementFailures } : {}) };
81
97
  }
82
98
 
83
99
  export function runBunShard(cwd: string, files: readonly string[]): ReturnType<ShardProcess> {
@@ -1,6 +1,107 @@
1
+ import type { AbandonedClassification } from './abandoned-classification.js';
2
+
1
3
  /** Environment interruptions have a resumable branch, not a human draft decision. */
2
4
  export function blockedDraftDisposition(classification: string | undefined): 'open-draft' | 'preserve-branch' {
3
5
  return classification === 'provider-error' || classification === 'quota-exhausted' || classification === 'credential-failure'
4
6
  ? 'preserve-branch'
5
7
  : 'open-draft';
6
8
  }
9
+
10
+ /**
11
+ * DRAFT-NOT-ARCHIVE (RFC-draft-pr-accumulation-root-fix R1 · 첫 조각)
12
+ *
13
+ * S: 멈춘 런은 산출을 draft PR 로 남겼다 — PR 이 «실패 보관소»가 되어 열린 PR 이 200 을 넘고 겹침 검사가 눈이 멀었다.
14
+ * C: 닫힌 147건 중 사람이 실제로 봐야 했던 것은 «주인 자리» 47건뿐이었다.
15
+ * Q: 어느 멈춤이 PR 을 열 자격이 있나?
16
+ * A: 사람 판단이 필요한 멈춤(needs-owner)만. 나머지(harvestable)는 수확 가지 `salvage/<run>/<leaf>` ⊕ 런 원장의 «사유 · 다음 수»로 남긴다.
17
+ * `tools.selfImplement.draftOnStop` = 'always'(기본 · 종전) | 'needs-owner-only'.
18
+ */
19
+ export type DraftOnStopMode = 'needs-owner-only' | 'always';
20
+ export const DRAFT_ON_STOP_MODES = ['needs-owner-only', 'always'] as const satisfies readonly DraftOnStopMode[];
21
+ export const DEFAULT_DRAFT_ON_STOP: DraftOnStopMode = 'always';
22
+
23
+ /** landable = 승인된 산출(merge-approved-abandoned) — 다음 수가 `pr land` 이므로 수확 가지로 숨기지 않고 PR 로 남긴다(⛔ needs-owner 라벨은 안 단다). */
24
+ export type StopClass = 'needs-owner' | 'harvestable' | 'landable';
25
+
26
+ const LANDABLE: ReadonlySet<string> = new Set<AbandonedClassification>(['merge-approved-abandoned']);
27
+
28
+ /** 사람(주인 자리)이 골·계약·처분을 정해야 풀리는 멈춤. 모르는 분류는 «needs-owner» 쪽으로 기운다(⛔ 산출을 PR 밖으로 숨기지 않는다). */
29
+ const NEEDS_OWNER: ReadonlySet<string> = new Set<AbandonedClassification>([
30
+ 'contract-conflict',
31
+ 'goal-unconvergeable-candidate',
32
+ 'pr-declined',
33
+ ]);
34
+
35
+ const HARVESTABLE: ReadonlySet<string> = new Set<AbandonedClassification>([
36
+ 'implementation-deficit',
37
+ 'report-deficit',
38
+ 'artifact-deficit',
39
+ 'run-deadline-exceeded',
40
+ 'already-satisfied',
41
+ 'quota-exhausted',
42
+ 'provider-error',
43
+ 'credential-failure',
44
+ ]);
45
+
46
+ export function stopClassFor(classification: string | undefined): StopClass {
47
+ if (classification && NEEDS_OWNER.has(classification)) return 'needs-owner';
48
+ if (classification && LANDABLE.has(classification)) return 'landable';
49
+ if (classification && HARVESTABLE.has(classification)) return 'harvestable';
50
+ return 'needs-owner';
51
+ }
52
+
53
+ const NEXT_MOVE: Record<string, string> = {
54
+ 'implementation-deficit': '수확 가지에서 같은 골을 이어 돌린다(resume 1회) — 그래도 멈추면 needs-owner',
55
+ 'report-deficit': '수확 가지에서 완료 증거·보고를 보강해 다시 게이트에 올린다',
56
+ 'artifact-deficit': '수확 가지에서 요구 산출물을 채워 다시 돌린다',
57
+ 'run-deadline-exceeded': '수확 가지에서 이어 돌리거나, 산출이 작으면 골을 쪼개 재발사한다',
58
+ 'merge-approved-abandoned': '승인된 산출이다 — 이 PR 을 착지 경로(pr land)로 올린다',
59
+ 'already-satisfied': '고칠 것이 없다 — 재발사하지 말고 카드를 닫는다',
60
+ 'quota-exhausted': '용량 회복 뒤 수확 가지에서 이어 돌린다',
61
+ 'provider-error': 'provider 복구 뒤 수확 가지에서 이어 돌린다',
62
+ 'credential-failure': '인증 복구 뒤 수확 가지에서 이어 돌린다',
63
+ 'contract-conflict': '주인 자리가 골의 상충 기준을 정정한다',
64
+ 'goal-unconvergeable-candidate': '주인 자리가 되풀이된 must-fix 와 골 계약을 대조해 골을 다시 쓰거나 쪼갠다',
65
+ 'pr-declined': '주인 자리가 거절 사유를 반영할지 골을 다시 정할지 정한다',
66
+ };
67
+
68
+ export function nextMoveFor(classification: string | undefined): string {
69
+ return (classification && NEXT_MOVE[classification]) ?? '주인 자리가 중단 사유를 읽고 처분을 정한다(분류 못 함)';
70
+ }
71
+
72
+ export type DraftOnStopDecision =
73
+ | { action: 'open-draft'; stopClass: StopClass; mode: DraftOnStopMode; ownerLabel?: string }
74
+ | { action: 'salvage-branch'; stopClass: 'harvestable'; mode: 'needs-owner-only' };
75
+
76
+ /** 'always' 는 종전 그대로(라벨도 그대로). 'needs-owner-only' 는 harvestable → 수확 가지 · needs-owner·landable → draft ⊕ 주인 자리 라벨.
77
+ * ⛔ 자리를 모르면 자리 라벨을 «안 단다»(OP 로 추측해 달면 «안다»고 말하는 셈이다) — `elanous:needs-owner` 만 남는다. */
78
+ export function decideDraftOnStop(input: { mode: DraftOnStopMode; classification: string | undefined; seat?: string }): DraftOnStopDecision {
79
+ const stopClass = stopClassFor(input.classification);
80
+ if (input.mode !== 'needs-owner-only') return { action: 'open-draft', stopClass, mode: 'always' };
81
+ if (stopClass === 'harvestable') return { action: 'salvage-branch', stopClass, mode: 'needs-owner-only' };
82
+ const ownerLabel = ownerSeatLabel(input.seat);
83
+ return { action: 'open-draft', stopClass, mode: 'needs-owner-only', ...(ownerLabel ? { ownerLabel } : {}) };
84
+ }
85
+
86
+ const SEATS = ['OP', 'TC', 'MK', 'UX'] as const;
87
+
88
+ /** 주인 자리 라벨 — `helper-repair` 가 읽는 `elanous:seat-<자리>` 꼴. 아는 자리일 때만 낸다(모르면 undefined). */
89
+ export function ownerSeatLabel(seat: string | undefined): string | undefined {
90
+ const trimmed = seat?.trim().toUpperCase();
91
+ return trimmed && (SEATS as readonly string[]).includes(trimmed) ? `elanous:seat-${trimmed}` : undefined;
92
+ }
93
+
94
+ /** 런 하나의 수확 가지 — `salvage/run-<6hex>/<가지 잎>`. 잎이 `-r<6hex>` 로 끝나면 `selectRunSalvageRefs` 가 그대로 찾는다. */
95
+ export function salvageBranchForRun(runId: string, branch: string): string {
96
+ // `salvage-branches.ts` `runSuffixKey` 와 같은 규칙 — 그 모듈은 실행 러너를 끌고 와 user-config 가 부를 수 없다.
97
+ const key = /^(?:run-)?([0-9a-f]{6})/i.exec(runId.trim())?.[1]?.toLowerCase();
98
+ const runShort = key ? `run-${key}` : (runId.replace(/[^A-Za-z0-9_-]+/g, '-').replace(/^-+|-+$/g, '').slice(0, 24) || 'run-unknown');
99
+ const leaf = branch.replace(/[^A-Za-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '').replace(/\.\.+/g, '.').replace(/\.lock$/i, '-lock') || 'branch';
100
+ return `salvage/${runShort}/${leaf}`;
101
+ }
102
+
103
+ export function parseDraftOnStopMode(raw: unknown): { mode: DraftOnStopMode; invalid: boolean } {
104
+ if (raw === undefined) return { mode: DEFAULT_DRAFT_ON_STOP, invalid: false };
105
+ if (typeof raw === 'string' && (DRAFT_ON_STOP_MODES as readonly string[]).includes(raw)) return { mode: raw as DraftOnStopMode, invalid: false };
106
+ return { mode: DEFAULT_DRAFT_ON_STOP, invalid: true };
107
+ }
@@ -15,6 +15,10 @@ import { appendRunLedgerEntry, type RunLedgerWriter } from './run-ledger.js';
15
15
 
16
16
  export interface ExecutionClarificationContext {
17
17
  runId: string;
18
+ outsideFiles?: readonly string[];
19
+ /** Full outside count — when larger than `outsideFiles`, the card says how many names it does not show. */
20
+ outsideCount?: number;
21
+ declaredPaths?: readonly string[];
18
22
  budget?: ClarificationContext['budget'];
19
23
  pendingQuestionCount?: number;
20
24
  config?: { hitl?: { executionDeadlineMinutes?: number } };
@@ -71,6 +75,13 @@ export async function askAtExecution(
71
75
 
72
76
  const request = toAskUserQuestionRequest(decision);
73
77
  if (!request) throw new Error('Execution clarification policy approved a question without a valid wire request.');
78
+ if (candidate.id === 'execution_scope' && ctx.outsideFiles) {
79
+ const sibling = (file: string) => ctx.declaredPaths === undefined ? 'unknown'
80
+ : file.endsWith('.test.ts') && ctx.declaredPaths.includes(file.replace(/\.test\.ts$/, '.ts')) ? 'yes' : 'no';
81
+ const hidden = Math.max(0, (ctx.outsideCount ?? ctx.outsideFiles.length) - ctx.outsideFiles.length);
82
+ const details = `Run ID: ${ctx.runId}\nOutside files:\n${ctx.outsideFiles.map(file => `- ${file} (declared-path sibling test: ${sibling(file)})`).join('\n')}${hidden > 0 ? `\n- … ${hidden} more outside file(s) not shown — inspect the worktree diff before expanding scope` : ''}`;
83
+ for (const question of request.questions) question.question += `\n\n${details}`;
84
+ }
74
85
  const configured = ctx.config === undefined
75
86
  ? getUserConfig().hitl?.executionDeadlineMinutes
76
87
  : ctx.config.hitl?.executionDeadlineMinutes;
@@ -26,10 +26,13 @@ import {
26
26
  import { listWorktrees } from '../git-fs/worktree.js';
27
27
  import { runShardedGateTests } from '../self-dev/shard-run.js';
28
28
 
29
- interface SelfGateCliOptions {
29
+ export interface SelfGateCliOptions {
30
30
  base?: string;
31
31
  pr?: string;
32
32
  shards?: number;
33
+ /** LIGHT-RC (0.2.19) — repository-relative test paths run on every call, whatever changed. Existing ones join the
34
+ * run (deduplicated, same introduced/preexisting baseline as the rest); missing ones go to `alwaysIncludeMissing`. */
35
+ alwaysInclude?: readonly string[];
33
36
  }
34
37
 
35
38
  interface ProcessResult {
@@ -102,7 +105,7 @@ interface GateOutput {
102
105
  error: (message: string) => void;
103
106
  }
104
107
 
105
- interface SelfGateCliResult {
108
+ export interface SelfGateCliResult {
106
109
  exitCode: number;
107
110
  lines: string[];
108
111
  changedFiles: string[];
@@ -110,6 +113,11 @@ interface SelfGateCliResult {
110
113
  unverified: readonly string[];
111
114
  documentPaths: readonly string[];
112
115
  documentsWithoutDerivedTests: readonly string[];
116
+ /** `alwaysInclude` paths that do not exist in the tree (empty when none were given). */
117
+ alwaysIncludeMissing: readonly string[];
118
+ /** New vs. already-present failures (unknown/preconditionUnmet = failures it could not classify). Absent = the test
119
+ * step did not produce a measurement (e.g. shards unmeasured). */
120
+ baseline?: { introduced: number; preexisting: number; unknown: number; preconditionUnmet: number };
113
121
  }
114
122
 
115
123
  /** 시험 단계와 독립인 정책 관문 넷. skipTestStep 이든 시험 통과든 같은 함수를 부른다. */
@@ -590,7 +598,12 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
590
598
  const expose = publicExposureFiles(selection.files).length > 0
591
599
  ? runExposeGate(cwd, selection.files, getUserConfig().harness?.exposeGate === 'strict' ? 'strict' : 'warn', deps.runCommand)
592
600
  : { passed: true, log: '' };
593
- const testFiles = [...(scope.testArgs ?? [])];
601
+ const alwaysInclude = [...new Set((options.alwaysInclude ?? []).map(normalizeGatePath).filter(Boolean))];
602
+ const alwaysIncludeMissing = alwaysInclude.filter((path) => !exists(path));
603
+ const scopedTests = scope.testArgs ?? [];
604
+ const alwaysIncludeExtra = alwaysInclude.filter((path) => exists(path) && !scopedTests.includes(path));
605
+ const testFiles = [...scopedTests, ...alwaysIncludeExtra];
606
+ const skipTestStep = scope.skipTestStep && alwaysIncludeExtra.length === 0;
594
607
  const unrunImporterTotal = lookupFailed ? null : (scope.importerTestsNotRun?.total ?? 0);
595
608
  const importerLookupFailed = Boolean(lookupFailed);
596
609
  const lines = [
@@ -619,6 +632,10 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
619
632
  ...(routeIndex?.lookupFailures.length ? [`⚠️ caller route base lookup failed — unmeasured: ${routeIndex.lookupFailures.map(({ source, reason }) => `${source} (${reason})`).join(', ')}`] : []),
620
633
  ...(scope.callerTestsOverflow.length ? [`⚠️ caller test cap exceeded — not run: ${scope.callerTestsOverflow.map(({ file, reasons }) => `${file} (${reasons.join('+')})`).join(', ')}`] : []),
621
634
  changedTestCountNote(cwd, selection.files, selection.baseRef, deps.runCommand, deps.readFile),
635
+ // Only printed when the caller asked for always-include paths — the plain `self gate` output is unchanged.
636
+ ...(options.alwaysInclude !== undefined
637
+ ? [`always include: ${alwaysInclude.length - alwaysIncludeMissing.length}/${alwaysInclude.length} (added ${alwaysIncludeExtra.length}${alwaysIncludeMissing.length ? ` · missing: ${alwaysIncludeMissing.join(', ')}` : ''})`]
638
+ : []),
622
639
  ];
623
640
  if (expose.log) lines.push(expose.log);
624
641
  if (options.pr) {
@@ -641,17 +658,18 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
641
658
  const iosPassed = runAdditionalGate('ios-gate', deps.runIosGate ?? runIosUnitTestGate, selection.files, cwd, lines);
642
659
  const pwaPassed = runAdditionalGate('pwa-gate', deps.runPwaGate ?? runPwaBuildGate, selection.files, cwd, lines);
643
660
 
644
- if (scope.skipTestStep) {
661
+ if (skipTestStep) {
645
662
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
646
663
  logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
647
- return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed && expose.passed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
664
+ return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed && expose.passed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests, alwaysIncludeMissing, baseline: { introduced: 0, preexisting: 0, unknown: 0, preconditionUnmet: 0 } };
648
665
  }
649
666
 
650
667
  if (options.shards !== undefined) {
651
668
  const sharded = (deps.runShards ?? runShardedGateTests)(cwd, testFiles, selection.baseRef, options.shards);
652
- lines.push(`shards: ${sharded.shards.length} (${sharded.shards.map((shard) => `${shard.id}=${shard.files.join(',')}`).join('; ')})`);
669
+ lines.push(`shards: ${sharded.shards.length} (${sharded.shards.map((shard) => `${shard.id}=${shard.files.join(',')}`).join('; ')})${sharded.measurementFailures ? ` · 측정 실패 ${sharded.measurementFailures}` : ''}`);
653
670
  for (const attempt of sharded.attempts) lines.push(`shard attempt: ${attempt.shardId} #${attempt.attempt}`);
654
671
  const { aggregate } = sharded;
672
+ let shardBaseline: SelfGateCliResult['baseline'];
655
673
  if (aggregate.status === 'unmeasured') lines.push(`shards: unmeasured (${aggregate.retryShardIds.join(', ') || sharded.reason || 'no complete JUnit'})`);
656
674
  else if (aggregate.report) {
657
675
  const report = aggregate.report;
@@ -662,15 +680,18 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
662
680
  baselineFiles: report.files, baselineStatus: report.baselineStatus, unrunImporterTotal, lookupFailed: importerLookupFailed,
663
681
  });
664
682
  lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, formatGateBaselineNote(report, unrunImporterTotal ?? 0)));
683
+ shardBaseline = { introduced: report.introduced, preexisting: report.preexisting, unknown: report.unknown, preconditionUnmet: report.preconditionUnmet };
665
684
  } else {
666
685
  lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, `tests: pass (${testFiles.length} files)`));
667
686
  logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
687
+ shardBaseline = { introduced: 0, preexisting: 0, unknown: 0, preconditionUnmet: 0 };
668
688
  }
669
689
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
670
690
  return {
671
691
  exitCode: aggregate.status !== 'passed' || !policyPassed || !androidPassed || !iosPassed || !pwaPassed || !expose.passed ? 1 : 0,
672
692
  lines, changedFiles: selection.files, testFiles, unverified: scope.unverified,
673
693
  documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests,
694
+ alwaysIncludeMissing, ...(shardBaseline ? { baseline: shardBaseline } : {}),
674
695
  };
675
696
  }
676
697
 
@@ -680,7 +701,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
680
701
  lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, `tests: pass (${testFiles.length} files)`));
681
702
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
682
703
  logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
683
- return { exitCode: policyPassed && androidPassed && iosPassed && pwaPassed && expose.passed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
704
+ return { exitCode: policyPassed && androidPassed && iosPassed && pwaPassed && expose.passed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests, alwaysIncludeMissing, baseline: { introduced: 0, preexisting: 0, unknown: 0, preconditionUnmet: 0 } };
684
705
  }
685
706
 
686
707
  if (worktreeLog.includes('deterministic environment setup failed')) lines.push(worktreeLog);
@@ -702,5 +723,5 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
702
723
  lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, formatGateBaselineNote(report, unrunImporterTotal ?? 0)));
703
724
  const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
704
725
  const exitCode = !androidPassed || !iosPassed || !pwaPassed || !policyPassed || !expose.passed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
705
- return { exitCode, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
726
+ return { exitCode, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests, alwaysIncludeMissing, baseline: { introduced: report.introduced, preexisting: report.preexisting, unknown: report.unknown, preconditionUnmet: report.preconditionUnmet } };
706
727
  }