elanous 0.2.23 → 0.2.24

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 (347) 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/{272-b23d2e92161d11fd.js → 272-0eed0fb0aab716b6.js} +1 -1
  4. package/apps/pwa/out/_next/static/chunks/{3961-fbef064c213b2b18.js → 3961-aaae04ed1d32622f.js} +1 -1
  5. package/apps/pwa/out/_next/static/chunks/app/approvals/{page-d5341ac96203a983.js → page-3b9fc39362473ae7.js} +1 -1
  6. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f2b62c19ca68fef8.js +1 -0
  7. package/apps/pwa/out/_next/static/chunks/app/board/{page-55c631c032dcc47e.js → page-d7602d489edf5f37.js} +1 -1
  8. package/apps/pwa/out/_next/static/chunks/app/botlab/{page-ad3bd8084048bee4.js → page-24282bf9f90031a5.js} +1 -1
  9. package/apps/pwa/out/_next/static/chunks/app/bots/page-8b7c5ab6b5bc8d64.js +1 -0
  10. package/apps/pwa/out/_next/static/chunks/app/ceo/{page-5c5d0e370f495ef6.js → page-4eb84d454f8c3415.js} +1 -1
  11. package/apps/pwa/out/_next/static/chunks/app/chat/{page-c325e21543cd48a8.js → page-b2e494bdad57fcb5.js} +1 -1
  12. package/apps/pwa/out/_next/static/chunks/app/consult/{page-6168a7c43e6ef8f5.js → page-c0ab229673a8b0eb.js} +1 -1
  13. package/apps/pwa/out/_next/static/chunks/app/control/page-3e2b45f3c36dbac4.js +1 -0
  14. package/apps/pwa/out/_next/static/chunks/app/decisions/page-c22aa511b28743ad.js +1 -0
  15. package/apps/pwa/out/_next/static/chunks/app/design-check/page-26345242e4049f55.js +1 -0
  16. package/apps/pwa/out/_next/static/chunks/app/editor/{page-25bb425afdb27541.js → page-bf88c0f558d8ef54.js} +1 -1
  17. package/apps/pwa/out/_next/static/chunks/app/exec/{page-32d72fdef1afe11a.js → page-52fec523e5b5d3ad.js} +1 -1
  18. package/apps/pwa/out/_next/static/chunks/app/field/page-67723dfdcde92b31.js +1 -0
  19. package/apps/pwa/out/_next/static/chunks/app/inside/{page-2bd9ce9c020d02d4.js → page-22725e2570f1126b.js} +1 -1
  20. package/apps/pwa/out/_next/static/chunks/app/intake/{page-a34096245e63b9f2.js → page-1891335950d68b70.js} +1 -1
  21. package/apps/pwa/out/_next/static/chunks/app/layout-3267af1ebdc73cf5.js +1 -0
  22. package/apps/pwa/out/_next/static/chunks/app/live/{page-4cc66d22ca4ec502.js → page-c314b75c11234998.js} +1 -1
  23. package/apps/pwa/out/_next/static/chunks/app/live-run/{page-83b14c729fcea837.js → page-9f9c85415f61782b.js} +1 -1
  24. package/apps/pwa/out/_next/static/chunks/app/loops/page-c1d4276059085587.js +1 -0
  25. package/apps/pwa/out/_next/static/chunks/app/market/page-833aab587f26ad4c.js +1 -0
  26. package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-ccdb83ed40c8d724.js → page-8526c17ae38e6a50.js} +1 -1
  27. package/apps/pwa/out/_next/static/chunks/app/morning/{page-7ad3de8fa06fdea9.js → page-f998637a8402115c.js} +1 -1
  28. package/apps/pwa/out/_next/static/chunks/app/not-found-b7bb53828b17b335.js +1 -0
  29. package/apps/pwa/out/_next/static/chunks/app/observatory/page-1b1850de0257e46d.js +1 -0
  30. package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-786111f30b6f0969.js → page-6f23159fedb41f67.js} +1 -1
  31. package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-48fb31810693c357.js → page-1d809a87139dd024.js} +1 -1
  32. package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-4e17cce5e2ed0fee.js → page-da9e570234d481fd.js} +1 -1
  33. package/apps/pwa/out/_next/static/chunks/app/outputs/{page-cb154acadb025414.js → page-b92caddb5c715f30.js} +1 -1
  34. package/apps/pwa/out/_next/static/chunks/app/page-8ce3a38e210b810a.js +1 -0
  35. package/apps/pwa/out/_next/static/chunks/app/reflection/{page-c2dd2b19c93f90ab.js → page-57ffeeb6019440fd.js} +1 -1
  36. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-abbb417bf514630b.js +1 -0
  37. package/apps/pwa/out/_next/static/chunks/app/sessions/{page-d3c007c8a0ddd7ee.js → page-2c2b3f29cec1efa4.js} +1 -1
  38. package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-7837ce2aacb97b04.js → page-9369279f72fc7b51.js} +1 -1
  39. package/apps/pwa/out/_next/static/chunks/app/settings/page-64861f14039499c3.js +1 -0
  40. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-ddc4c93efd8ab94b.js +1 -0
  41. package/apps/pwa/out/_next/static/chunks/app/setup/page-901a1391b81053bc.js +1 -0
  42. package/apps/pwa/out/_next/static/chunks/app/share/page-68d9bbaac9841a58.js +1 -0
  43. package/apps/pwa/out/_next/static/chunks/app/showroom/{page-119be9a0444d2482.js → page-b5c0c996f329371e.js} +1 -1
  44. package/apps/pwa/out/_next/static/chunks/app/tasks/{page-fa806c018833e332.js → page-89881ccbcf728978.js} +1 -1
  45. package/apps/pwa/out/_next/static/chunks/app/term/page-0e87a803b3d7165e.js +1 -0
  46. package/apps/pwa/out/_next/static/chunks/app/today/{page-d431fbdea6ad93dd.js → page-58d4e5cb30ef4871.js} +1 -1
  47. package/apps/pwa/out/_next/static/chunks/app/trace/page-02ce4c3b5f39e4d2.js +1 -0
  48. package/apps/pwa/out/_next/static/chunks/app/vault/page-e620d1bb8eadcec2.js +1 -0
  49. package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-847c3de6ca8a4416.js → page-acf868c64d77f950.js} +1 -1
  50. package/apps/pwa/out/_next/static/chunks/app/workflows/page-54fa605fa6dba457.js +1 -0
  51. package/apps/pwa/out/_next/static/chunks/app/workspace/{page-74e69ba2c8e970d4.js → page-bc79f0833533fa84.js} +1 -1
  52. package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-70038f41ae7cf931.js → page-d5c4398eea03d8f3.js} +1 -1
  53. package/apps/pwa/out/_next/static/css/72becfd42807b50d.css +1 -0
  54. package/apps/pwa/out/approvals/index.html +1 -1
  55. package/apps/pwa/out/approvals/index.txt +14 -14
  56. package/apps/pwa/out/autopilot/index.html +1 -1
  57. package/apps/pwa/out/autopilot/index.txt +14 -14
  58. package/apps/pwa/out/board/index.html +1 -1
  59. package/apps/pwa/out/board/index.txt +14 -14
  60. package/apps/pwa/out/botlab/index.html +1 -1
  61. package/apps/pwa/out/botlab/index.txt +14 -14
  62. package/apps/pwa/out/bots/index.html +1 -1
  63. package/apps/pwa/out/bots/index.txt +14 -14
  64. package/apps/pwa/out/ceo/index.html +1 -1
  65. package/apps/pwa/out/ceo/index.txt +14 -14
  66. package/apps/pwa/out/chat/index.html +1 -1
  67. package/apps/pwa/out/chat/index.txt +14 -14
  68. package/apps/pwa/out/consult/index.html +1 -1
  69. package/apps/pwa/out/consult/index.txt +14 -14
  70. package/apps/pwa/out/control/index.html +1 -1
  71. package/apps/pwa/out/control/index.txt +14 -14
  72. package/apps/pwa/out/decisions/index.html +1 -1
  73. package/apps/pwa/out/decisions/index.txt +14 -14
  74. package/apps/pwa/out/design-check/index.html +1 -1
  75. package/apps/pwa/out/design-check/index.txt +14 -14
  76. package/apps/pwa/out/editor/index.html +1 -1
  77. package/apps/pwa/out/editor/index.txt +14 -14
  78. package/apps/pwa/out/exec/index.html +1 -1
  79. package/apps/pwa/out/exec/index.txt +14 -14
  80. package/apps/pwa/out/field/index.html +1 -1
  81. package/apps/pwa/out/field/index.txt +14 -14
  82. package/apps/pwa/out/healthz +1 -1
  83. package/apps/pwa/out/index.html +1 -1
  84. package/apps/pwa/out/index.txt +14 -14
  85. package/apps/pwa/out/inside/index.html +1 -1
  86. package/apps/pwa/out/inside/index.txt +14 -14
  87. package/apps/pwa/out/intake/index.html +1 -1
  88. package/apps/pwa/out/intake/index.txt +14 -14
  89. package/apps/pwa/out/live/index.html +1 -1
  90. package/apps/pwa/out/live/index.txt +14 -14
  91. package/apps/pwa/out/live-run/index.html +1 -1
  92. package/apps/pwa/out/live-run/index.txt +14 -14
  93. package/apps/pwa/out/loops/index.html +1 -1
  94. package/apps/pwa/out/loops/index.txt +14 -14
  95. package/apps/pwa/out/market/index.html +1 -1
  96. package/apps/pwa/out/market/index.txt +14 -14
  97. package/apps/pwa/out/missions/placeholder/index.html +1 -1
  98. package/apps/pwa/out/missions/placeholder/index.txt +14 -14
  99. package/apps/pwa/out/morning/index.html +1 -1
  100. package/apps/pwa/out/morning/index.txt +14 -14
  101. package/apps/pwa/out/observatory/index.html +1 -1
  102. package/apps/pwa/out/observatory/index.txt +14 -14
  103. package/apps/pwa/out/ops/checklist/index.html +1 -1
  104. package/apps/pwa/out/ops/checklist/index.txt +14 -14
  105. package/apps/pwa/out/ops/release/index.html +1 -1
  106. package/apps/pwa/out/ops/release/index.txt +14 -14
  107. package/apps/pwa/out/ops/seats/index.html +1 -1
  108. package/apps/pwa/out/ops/seats/index.txt +14 -14
  109. package/apps/pwa/out/outputs/index.html +1 -1
  110. package/apps/pwa/out/outputs/index.txt +14 -14
  111. package/apps/pwa/out/reflection/index.html +1 -1
  112. package/apps/pwa/out/reflection/index.txt +15 -15
  113. package/apps/pwa/out/scheduler/index.html +1 -1
  114. package/apps/pwa/out/scheduler/index.txt +14 -14
  115. package/apps/pwa/out/sessions/index.html +1 -1
  116. package/apps/pwa/out/sessions/index.txt +14 -14
  117. package/apps/pwa/out/settings/devices/index.html +1 -1
  118. package/apps/pwa/out/settings/devices/index.txt +14 -14
  119. package/apps/pwa/out/settings/index.html +1 -1
  120. package/apps/pwa/out/settings/index.txt +14 -14
  121. package/apps/pwa/out/setup/done/index.html +1 -1
  122. package/apps/pwa/out/setup/done/index.txt +14 -14
  123. package/apps/pwa/out/setup/index.html +1 -1
  124. package/apps/pwa/out/setup/index.txt +14 -14
  125. package/apps/pwa/out/share/index.html +1 -1
  126. package/apps/pwa/out/share/index.txt +14 -14
  127. package/apps/pwa/out/showroom/index.html +1 -1
  128. package/apps/pwa/out/showroom/index.txt +14 -14
  129. package/apps/pwa/out/tasks/index.html +1 -1
  130. package/apps/pwa/out/tasks/index.txt +14 -14
  131. package/apps/pwa/out/term/index.html +1 -1
  132. package/apps/pwa/out/term/index.txt +14 -14
  133. package/apps/pwa/out/today/index.html +1 -1
  134. package/apps/pwa/out/today/index.txt +14 -14
  135. package/apps/pwa/out/trace/index.html +1 -1
  136. package/apps/pwa/out/trace/index.txt +14 -14
  137. package/apps/pwa/out/vault/index.html +1 -1
  138. package/apps/pwa/out/vault/index.txt +14 -14
  139. package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
  140. package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
  141. package/apps/pwa/out/workflows/index.html +1 -1
  142. package/apps/pwa/out/workflows/index.txt +14 -14
  143. package/apps/pwa/out/workspace/index.html +1 -1
  144. package/apps/pwa/out/workspace/index.txt +14 -14
  145. package/apps/pwa/out/worktrees/index.html +1 -1
  146. package/apps/pwa/out/worktrees/index.txt +14 -14
  147. package/catalog/models/openrouter/deepseek-v4-pro-0813.yaml +39 -0
  148. package/catalog/models/openrouter/deepseek-v4.1-flash.yaml +38 -0
  149. package/catalog/models/openrouter/glm-5.3-flash.yaml +35 -0
  150. package/catalog/providers/openrouter.yaml +2 -0
  151. package/graphs/implement-loop.yaml +31 -0
  152. package/graphs/ops/harness-gc.yaml +17 -0
  153. package/graphs/ops/recipes.yaml +3 -0
  154. package/graphs/overlays/README.md +1 -1
  155. package/graphs/overlays/stop-heal-ladder.yaml +105 -0
  156. package/package.json +1 -1
  157. package/scripts/add-api-key.sh +1 -1
  158. package/scripts/ci-typecheck-changed.ts +71 -14
  159. package/scripts/fleet/bedrock-pod-secret.ts +122 -0
  160. package/scripts/fleet/node-setup-linux.sh +97 -0
  161. package/scripts/fleet/node-setup.sh +11 -0
  162. package/scripts/fleet/zai-pod-secret.ts +86 -0
  163. package/scripts/land-global-checks.ts +3 -0
  164. package/scripts/measure-log-diet-2.ts +213 -0
  165. package/scripts/measure-tsc-affected-roots.ts +289 -0
  166. package/scripts/release-loop/gate-executor.ts +28 -0
  167. package/scripts/release-loop/gate-node.ts +262 -26
  168. package/scripts/release-loop/resume-release.ts +9 -3
  169. package/scripts/release-loop/resume.ts +41 -0
  170. package/scripts/release-loop/unattended-release.ts +22 -7
  171. package/scripts/tsc-baseline-cache.ts +147 -0
  172. package/scripts/usage-rollup.ts +58 -6
  173. package/src/agent/run-context.ts +10 -0
  174. package/src/agent-mission/driver.ts +18 -3
  175. package/src/agent-mission/resource-ladder.ts +8 -3
  176. package/src/agent-mission/review-loop.ts +8 -2
  177. package/src/agent-substrate/acp-implementer.ts +139 -0
  178. package/src/agent-substrate/pr-reviewer.ts +31 -4
  179. package/src/agent-substrate/self-review-cli.ts +48 -9
  180. package/src/autopilot/mission-critique.ts +18 -5
  181. package/src/autopilot/mission-feasibility-negotiate.ts +11 -10
  182. package/src/autopilot/mission-trace.ts +12 -1
  183. package/src/autopilot/system-lookback-run.ts +16 -10
  184. package/src/bench/bench.ts +183 -0
  185. package/src/budget/fetchers/grok.ts +15 -5
  186. package/src/budget/llm-cost.ts +37 -5
  187. package/src/budget/types.ts +8 -2
  188. package/src/budget/unified-usage.ts +88 -6
  189. package/src/cli/bench-cli.ts +34 -0
  190. package/src/cli/guardian-cli.ts +26 -1
  191. package/src/cli/image-cli.ts +101 -0
  192. package/src/cli/log-event-names.ts +4 -0
  193. package/src/cli/pr-cli.ts +62 -5
  194. package/src/cli/pr-land-overlap-evidence.ts +160 -12
  195. package/src/cli/release-cli.ts +18 -9
  196. package/src/cli/rfc-status.ts +52 -2
  197. package/src/cli/self-update.ts +104 -0
  198. package/src/cli/tasks-cli.ts +109 -4
  199. package/src/cli/usage-cli.ts +91 -8
  200. package/src/core-turn/run-goal-loop.ts +5 -4
  201. package/src/dashboard/ad-run-setup-bindings.ts +279 -0
  202. package/src/dashboard/index.ts +18 -276
  203. package/src/dashboard/stale-install-check.ts +11 -0
  204. package/src/debug/log-diet.ts +38 -7
  205. package/src/debug/log.ts +19 -1
  206. package/src/domains/release-tool.ts +39 -0
  207. package/src/error-report/report.ts +21 -4
  208. package/src/github/pr-labels.ts +1 -0
  209. package/src/graph-runner/graph-cli.ts +42 -1
  210. package/src/graph-runner/runner.ts +167 -6
  211. package/src/grok/usage.ts +23 -2
  212. package/src/grounding/ref-index.ts +253 -0
  213. package/src/harness/dispatch-backpressure.ts +10 -2
  214. package/src/harness/harness-clean.ts +199 -78
  215. package/src/harness/harness-cli-command.ts +122 -7
  216. package/src/harness/harness-gc.ts +331 -0
  217. package/src/harness/harness-stop.ts +4 -2
  218. package/src/harness/harness-substrate-default.ts +142 -7
  219. package/src/harness/launch-registry.ts +6 -0
  220. package/src/harness/worktree-branch-prefix.ts +9 -0
  221. package/src/hq/fence.ts +187 -0
  222. package/src/hq/seats.ts +38 -22
  223. package/src/images/image-ledger.ts +179 -0
  224. package/src/index.ts +93 -11
  225. package/src/instance/child-scope.ts +32 -1
  226. package/src/instance/dev-project.ts +133 -0
  227. package/src/instance/resolve.ts +24 -0
  228. package/src/instance/spawn-elanous.ts +45 -0
  229. package/src/intake-plane/ref-prefetch.ts +110 -0
  230. package/src/intelligence-map/model-catalog.ts +72 -0
  231. package/src/llm/bedrock-eventstream.ts +215 -0
  232. package/src/llm/bedrock-provider.ts +114 -41
  233. package/src/llm/bedrock.ts +174 -28
  234. package/src/llm/openrouter-byok-cost.ts +96 -0
  235. package/src/llm/openrouter-deepseek.ts +151 -0
  236. package/src/llm/openrouter-upstream-pins.config.ts +25 -0
  237. package/src/llm/zai-coding-plan.ts +700 -0
  238. package/src/llm.ts +87 -16
  239. package/src/loops/checker.ts +148 -9
  240. package/src/loops/orchestrator/resync-shadow.ts +34 -7
  241. package/src/maturity/feature-maturity.ts +2 -2
  242. package/src/model-tier/llm-tier-map.ts +9 -5
  243. package/src/models/prompts.ts +2 -1
  244. package/src/mss/logging/log-store.ts +58 -11
  245. package/src/nexus/api/missions.ts +73 -2
  246. package/src/nexus/api/ops-api.ts +18 -0
  247. package/src/nexus/index.ts +7 -75
  248. package/src/nexus/tools-kind.ts +78 -0
  249. package/src/oauth/codex-account-rotation.ts +39 -2
  250. package/src/oauth/codex-account-store.ts +323 -24
  251. package/src/oauth/fallback-chain.ts +53 -15
  252. package/src/onboarding/first-run.ts +37 -18
  253. package/src/onboarding/web-first.ts +20 -2
  254. package/src/ops/machine-heartbeat.ts +185 -0
  255. package/src/ops/overview-cache.ts +175 -0
  256. package/src/ops/overview.ts +8 -0
  257. package/src/prompt-cache/openai.ts +3 -0
  258. package/src/prompt-cache/types.ts +5 -0
  259. package/src/prompt-enhance/enhance.ts +23 -0
  260. package/src/release-loop/placement.ts +16 -1
  261. package/src/requirements/requirement-ledger.ts +536 -0
  262. package/src/retro/daily.ts +5 -5
  263. package/src/seat-loop/seat-loop.ts +32 -10
  264. package/src/self-dev/dev-cli.ts +44 -10
  265. package/src/self-dev/dev-pipeline.ts +7 -2
  266. package/src/self-dev/draft-residue.ts +14 -2
  267. package/src/self-dev/draft-sweep.ts +103 -0
  268. package/src/self-dev/graph-journey-route.ts +6 -0
  269. package/src/self-dev/orchestrate-cli.ts +25 -3
  270. package/src/self-dev/orchestrate.ts +41 -6
  271. package/src/self-dev/run-supervisor.ts +188 -15
  272. package/src/self-dev/run-types.ts +13 -0
  273. package/src/self-dev/supervisor-verdict-edges.ts +1 -0
  274. package/src/self-implement/budget-gate.ts +53 -13
  275. package/src/self-implement/child-context-composition.ts +139 -0
  276. package/src/self-implement/child-llm-preference.ts +13 -5
  277. package/src/self-implement/claude-acp-implement.ts +67 -0
  278. package/src/self-implement/flex-signals.ts +59 -0
  279. package/src/self-implement/frozen-merges.ts +8 -3
  280. package/src/self-implement/goal-author-decompose.ts +10 -2
  281. package/src/self-implement/goal-author.ts +24 -9
  282. package/src/self-implement/graph-overlay-condition.ts +28 -0
  283. package/src/self-implement/graph-overlay-yaml.ts +41 -7
  284. package/src/self-implement/graph-templates.ts +33 -1
  285. package/src/self-implement/graph-yaml.ts +56 -0
  286. package/src/self-implement/heal-intake.ts +5 -1
  287. package/src/self-implement/orchestrator.ts +144 -15
  288. package/src/self-implement/running-runs.ts +75 -5
  289. package/src/self-implement/seams.ts +47 -1
  290. package/src/self-implement/self-implement-cli.ts +9 -4
  291. package/src/self-implement/stop-autoheal.ts +69 -2
  292. package/src/session-runtime/retry-policy.ts +2 -1
  293. package/src/steward/launch.ts +5 -4
  294. package/src/steward/triage-budget.ts +62 -0
  295. package/src/steward/triage.ts +83 -22
  296. package/src/task-agent/actions.ts +62 -5
  297. package/src/task-agent/completion-evidence.ts +5 -0
  298. package/src/task-agent/cover.ts +101 -3
  299. package/src/task-agent/judge.ts +16 -0
  300. package/src/task-agent/land-order-ledger.ts +95 -0
  301. package/src/task-agent/live-move-history.ts +87 -1
  302. package/src/task-agent/live-moves.ts +350 -20
  303. package/src/task-agent/rejudge-on-head.ts +75 -20
  304. package/src/task-agent/review-prior-fix.ts +146 -0
  305. package/src/task-agent/run-adopt.ts +136 -0
  306. package/src/task-agent/shadow.ts +53 -9
  307. package/src/task-agent/task-hand.ts +54 -7
  308. package/src/task-orchestrator/surfaces/pod-account-broker.ts +38 -5
  309. package/src/task-orchestrator/surfaces/pod-bedrock-child.ts +74 -0
  310. package/src/task-orchestrator/surfaces/pod-child-default.ts +50 -0
  311. package/src/task-orchestrator/surfaces/pod-child-providers.ts +14 -1
  312. package/src/task-orchestrator/surfaces/pod-failure-reason.ts +79 -6
  313. package/src/task-orchestrator/surfaces/pod-lease.ts +17 -5
  314. package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
  315. package/src/task-orchestrator/surfaces/pod-pool.ts +20 -4
  316. package/src/task-orchestrator/surfaces/pod-zai-child.ts +101 -0
  317. package/src/task-orchestrator/surfaces/self-implement-pod.ts +213 -23
  318. package/src/task-orchestrator/surfaces/self-implement.ts +19 -1
  319. package/src/typecheck-ratchet.ts +1 -0
  320. package/src/user-config.ts +51 -11
  321. package/src/version/packed-revision.json +1 -1
  322. package/src/workflow-runtime/triggers/registry.ts +59 -0
  323. package/src/workflow-runtime/triggers/webhook-source.ts +35 -5
  324. package/apps/pwa/out/_next/static/chunks/app/autopilot/page-0160ab83c8685a12.js +0 -1
  325. package/apps/pwa/out/_next/static/chunks/app/bots/page-e6784759a2531031.js +0 -1
  326. package/apps/pwa/out/_next/static/chunks/app/control/page-31417d3604154a6d.js +0 -1
  327. package/apps/pwa/out/_next/static/chunks/app/decisions/page-e9606dc0bdec9305.js +0 -1
  328. package/apps/pwa/out/_next/static/chunks/app/design-check/page-a648bc464c9dc754.js +0 -1
  329. package/apps/pwa/out/_next/static/chunks/app/field/page-d27b794c8ba76bbb.js +0 -1
  330. package/apps/pwa/out/_next/static/chunks/app/layout-22170f4f00a15496.js +0 -1
  331. package/apps/pwa/out/_next/static/chunks/app/loops/page-6e2781c523dad761.js +0 -1
  332. package/apps/pwa/out/_next/static/chunks/app/market/page-26dd415c8f13f341.js +0 -1
  333. package/apps/pwa/out/_next/static/chunks/app/not-found-015c02c4fec4023e.js +0 -1
  334. package/apps/pwa/out/_next/static/chunks/app/observatory/page-6b0ae5b5c42f6d2f.js +0 -1
  335. package/apps/pwa/out/_next/static/chunks/app/page-16258e956ea93fc2.js +0 -1
  336. package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f35dd1dd96ae87.js +0 -1
  337. package/apps/pwa/out/_next/static/chunks/app/settings/page-7350ee93f4fe1033.js +0 -1
  338. package/apps/pwa/out/_next/static/chunks/app/setup/done/page-00723458575f2b19.js +0 -1
  339. package/apps/pwa/out/_next/static/chunks/app/setup/page-745d9b6c015076f8.js +0 -1
  340. package/apps/pwa/out/_next/static/chunks/app/share/page-736d33c7414a8a1a.js +0 -1
  341. package/apps/pwa/out/_next/static/chunks/app/term/page-c782041b5f5199ba.js +0 -1
  342. package/apps/pwa/out/_next/static/chunks/app/trace/page-0c056abff82b9891.js +0 -1
  343. package/apps/pwa/out/_next/static/chunks/app/vault/page-242d668e4fbf72c4.js +0 -1
  344. package/apps/pwa/out/_next/static/chunks/app/workflows/page-81dd0d8ae923ed72.js +0 -1
  345. package/apps/pwa/out/_next/static/css/5619d59e87eddf66.css +0 -1
  346. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_buildManifest.js +0 -0
  347. /package/apps/pwa/out/_next/static/{rfkMm4movqkqMebBrCXiR → Q-7iofoxOEv1WX18S35of}/_ssgManifest.js +0 -0
@@ -1,7 +1,7 @@
1
1
  import { copyFileSync, lstatSync, mkdirSync, realpathSync } from 'node:fs';
2
2
  import { join, relative, resolve, sep } from 'node:path';
3
3
  import { debug } from '../debug/log.js';
4
- import { judgeNextMove } from './judge.js';
4
+ import { judgeNextMove, retryMoveOf, SAME_MOVE_FAILURE_LIMIT } from './judge.js';
5
5
  import { checkCompletionEvidence } from './completion-evidence.js';
6
6
 
7
7
  export type DeliveryKind = 'research-report' | 'artifact' | 'content';
@@ -65,6 +65,48 @@ export interface ActionDeps {
65
65
  /** 실패 한 번을 잠금 안에서 디스크에 더한다(다른 프로세스의 실패를 덮지 않게) · 더한 뒤의 값을 돌려준다. */
66
66
  incrementFailure?(key: string): number;
67
67
  observe(event: 'task-agent.action', data: { taskId: string; kind: NextActionKind; result: string; reason: string }): void;
68
+ /** 사다리 R4 — 실제로 할 재발사 수의 허용 목록(`taskAgent.liveMoves` 어휘). 없으면 종전 그대로(재발사에 목록 검사 없음) · 목록에 없는 수는 shadow. */
69
+ liveMoves?: ReadonlySet<string>;
70
+ /** 한 판단 회차의 발사 칸 — 호출부가 회차마다 새로 준다. 없으면 회당 상한 없음(종전). 발사를 시도할 때마다 `count` 가 오른다. */
71
+ retryLaunches?: { count: number };
72
+ /** mbp 부하 관문(OP 신호 `loadMax`) — 지금 1분 부하가 `loadMax` 이상이거나 못 읽으면 발사하지 않고 shadow. 없으면 관문 없음(종전). */
73
+ loadGate?: { loadMax: number; load1(): number | null };
74
+ }
75
+
76
+ /** 재발사는 한 판단 회차에 한 건만 발사한다. */
77
+ export const RETRY_LAUNCHES_PER_CYCLE = 1;
78
+
79
+ /** live 인데도 shadow 로 내릴 사유 — 없으면 발사해도 된다(재발사 아닌 수는 늘 없다). 기다림(wait)은 발사가 아니라 부하·회당 상한을 보지 않는다. */
80
+ function retryShadowReason(action: NextAction, deps: ActionDeps): string | undefined {
81
+ if (action.kind !== 'retry') return undefined;
82
+ const move = retryMoveOf(action.variant);
83
+ if (deps.liveMoves && !deps.liveMoves.has(move)) return `${move} not in live allow-list`;
84
+ if (action.variant === 'wait') return undefined;
85
+ if (deps.loadGate) {
86
+ const load = deps.loadGate.load1();
87
+ if (load === null || !Number.isFinite(load)) return `load gate: load unreadable (loadMax ${deps.loadGate.loadMax})`;
88
+ if (load >= deps.loadGate.loadMax) return `load gate: load ${load} >= loadMax ${deps.loadGate.loadMax}`;
89
+ }
90
+ if (deps.retryLaunches && deps.retryLaunches.count >= RETRY_LAUNCHES_PER_CYCLE) return `one relaunch per cycle already used (${move})`;
91
+ return undefined;
92
+ }
93
+
94
+ /** 결정 카드가 «근거»로 쓰는 세 칸 — 막힌 과제의 서명 · 막힌 화면 · 리뷰 must-fix. */
95
+ export interface DecisionCardEvidence { signature?: string; screen?: string; mustFix?: readonly string[] }
96
+
97
+ /** 비지 않은 근거 칸 이름들 — 비어 있으면 카드를 올리지 않는다(근거 없는 카드 0). */
98
+ export function decisionCardGrounds(evidence: DecisionCardEvidence | undefined): Array<'signature' | 'screen' | 'must-fix'> {
99
+ const grounds: Array<'signature' | 'screen' | 'must-fix'> = [];
100
+ if (evidence?.signature?.trim()) grounds.push('signature');
101
+ if (evidence?.screen?.trim()) grounds.push('screen');
102
+ if (evidence?.mustFix?.some(fix => typeof fix === 'string' && fix.trim())) grounds.push('must-fix');
103
+ return grounds;
104
+ }
105
+
106
+ /** `decisions raise` 인자 — 종전 실행부(`executeNextAction`)와 live 수(`executeLiveDecisionCard`)가 같은 카드 모양을 쓴다. */
107
+ export function decisionRaiseArgs(input: { taskId: string; reason: string; risk?: NextAction['risk']; evidence: string }): string[] {
108
+ const category = input.risk === 'money' ? 'money' : input.risk === 'security' ? 'security' : input.risk === 'irreversible-publication' ? 'publish' : 'other';
109
+ return ['decisions', 'raise', '--title', `${input.taskId}: ${input.reason}`, '--category', category, '--s', `Task ${input.taskId} needs a decision.`, '--c', input.reason, '--ref', input.evidence, '--q', 'Which action should be taken?', '--option', 'h=Keep on hold:no automatic execution', '--option', 'p=Approve action:execute after approval', '--skip-recommend', 'requires human decision', '--no-xcheck', 'automated escalation'];
68
110
  }
69
111
 
70
112
  /** 실패 셈 키. 갈래가 있으면 갈래별로 센다(G2 — «다른 수»의 실패가 같은 칸에 쌓이지 않게). 없으면 종전 키 그대로. */
@@ -72,6 +114,19 @@ export function failureKey(action: Pick<NextAction, 'taskId' | 'kind' | 'variant
72
114
  return JSON.stringify(action.variant ? [action.taskId, action.kind, action.variant] : [action.taskId, action.kind]);
73
115
  }
74
116
 
117
+ /**
118
+ * 같은 «수»의 실패 수 — 상한 판정용. 셈 키(`failureKey`)는 종전 그대로 두되, 갈래 없는 재발사와 `narrow` 갈래는
119
+ * 같은 수(`narrow-relaunch` · `retryMoveOf`)라 두 키를 합쳐 본다(TA-LIVE-RETRY: 같은 과제·같은 수 3회 반복 0).
120
+ */
121
+ export function sameMoveFailures(action: Pick<NextAction, 'taskId' | 'kind' | 'variant'>, deps: Pick<ActionDeps, 'failureCounts' | 'failures'>): number {
122
+ if (!deps.failureCounts) return deps.failures ?? 0;
123
+ if (action.kind !== 'retry' || retryMoveOf(action.variant) !== 'narrow-relaunch') return deps.failureCounts[failureKey(action)] ?? deps.failures ?? 0;
124
+ const bare = deps.failureCounts[failureKey({ taskId: action.taskId, kind: 'retry' })];
125
+ const narrow = deps.failureCounts[failureKey({ taskId: action.taskId, kind: 'retry', variant: 'narrow' })];
126
+ if (bare === undefined && narrow === undefined) return deps.failures ?? 0;
127
+ return (bare ?? 0) + (narrow ?? 0);
128
+ }
129
+
75
130
  const RETRY_HINT: Record<Exclude<RetryVariant, 'wait'>, (action: NextAction) => string> = {
76
131
  narrow: action => `Narrow relaunch: previous run ${action.runId} made no progress. Halve the scope; keep the same acceptance line.`,
77
132
  salvage: action => `Salvage relaunch: continue from the salvage branch ${action.salvageBranch ?? `of run ${action.runId}`} — take its diff, do not restart from scratch.`,
@@ -89,18 +144,19 @@ export async function executeNextAction(action: NextAction, deps: ActionDeps): P
89
144
  deps.observe('task-agent.action', { taskId: action.taskId, kind: action.kind, result, reason });
90
145
  return result;
91
146
  };
92
- if (deps.mode !== 'live') {
147
+ const shadowReason = deps.mode === 'live' ? retryShadowReason(action, deps) : undefined;
148
+ if (deps.mode !== 'live' || shadowReason) {
93
149
  const destination = action.delivery && action.kind === 'deliver'
94
150
  ? ` → ${join(action.delivery.projectTarget, 'elanous-out', action.taskId)}/${action.delivery.files.join(', ')}` : '';
95
- return record('shadow', `would ${action.kind}${action.variant ? `:${action.variant}` : ''}: ${action.rationale}${destination}`);
151
+ return record('shadow', `would ${action.kind}${action.variant ? `:${action.variant}` : ''}: ${action.rationale}${destination}${shadowReason ? ` · shadow: ${shadowReason}` : ''}`);
96
152
  }
97
153
  const card = async (reason: string) => {
98
154
  const evidence = JSON.stringify({ runId: action.runId, pr: action.pr, mustFix: action.mustFix ?? [], history: action.history ?? [], selected: action.kind, reason });
99
- const result = await deps.command(['decisions', 'raise', '--title', `${action.taskId}: ${reason}`, '--category', action.risk === 'money' ? 'money' : action.risk === 'security' ? 'security' : action.risk === 'irreversible-publication' ? 'publish' : 'other', '--s', `Task ${action.taskId} needs a decision.`, '--c', reason, '--ref', evidence, '--q', 'Which action should be taken?', '--option', 'h=Keep on hold:no automatic execution', '--option', 'p=Approve action:execute after approval', '--skip-recommend', 'requires human decision', '--no-xcheck', 'automated escalation']);
155
+ const result = await deps.command(decisionRaiseArgs({ taskId: action.taskId, reason, ...(action.risk ? { risk: action.risk } : {}), evidence }));
100
156
  return record(result.status === 0 ? 'decision' : 'failed', reason);
101
157
  };
102
158
  if (action.risk) return card(`risk: ${action.risk}`);
103
- if ((deps.failureCounts?.[failureKey(action)] ?? deps.failures ?? 0) >= 2) return card('same task/action failed twice');
159
+ if (sameMoveFailures(action, deps) >= SAME_MOVE_FAILURE_LIMIT) return card('same task/action failed twice');
104
160
  if (action.kind === 'land') {
105
161
  if (!action.cwd) return card('landing worktree unknown');
106
162
  const view = await deps.command(['gh', 'pr', 'view', String(action.pr), '--json', 'labels']);
@@ -175,6 +231,7 @@ export async function executeNextAction(action: NextAction, deps: ActionDeps): P
175
231
  // 여기 닿는 종류는 land · retry 둘뿐이다(green·decision·review 는 위에서 끝난다).
176
232
  const args = action.kind === 'land' ? ['pr', 'land', '--cwd', action.cwd!] : ['harness', 'say', retryText(action)];
177
233
  const day = deps.landingDay ?? new Date().toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
234
+ if (action.kind === 'retry' && deps.retryLaunches) deps.retryLaunches.count++;
178
235
  const outcome = await deps.command(args);
179
236
  if (outcome.status !== 0) {
180
237
  if (action.kind === 'land') deps.releaseLanding?.(day);
@@ -5,6 +5,11 @@ import type { DecisionLedger } from '../decisions/decision-ledger.js';
5
5
  import { getLastStatus } from '../domains/schedule-registry.js';
6
6
 
7
7
  export type CompletionEvidenceKind = 'research-report' | 'artifact' | 'content' | 'ops-action' | 'watch-brief' | 'decision-support' | 'deliverable';
8
+ /** Card evidence stays a string in the existing ledger; retain both the checked output reference and the confirmed receipt. */
9
+ export function deliverableReceiptEvidence(ref: string): string {
10
+ return JSON.stringify({ ref, receipt: true });
11
+ }
12
+
8
13
  export interface CompletionEvidenceDeps {
9
14
  ledger?: Pick<DecisionLedger, 'list'>;
10
15
  schedulesDb?: Database;
@@ -1,3 +1,4 @@
1
+ import { debug } from '../debug/log.js';
1
2
  import type { NextActionKind } from './actions.js';
2
3
 
3
4
  type CoverVerb = 'land' | 'review' | 'retry' | 'green';
@@ -21,16 +22,91 @@ export interface CoverRow {
21
22
 
22
23
  /** null means the source could not be read; [] means it was read and had no matching events. */
23
24
  export interface TaskAgentCoverInput {
24
- taskAgentActions: readonly { kind: NextActionKind; result: string }[] | null;
25
+ taskAgentActions: readonly { kind: NextActionKind; result: string; reason?: string; taskId?: string }[] | null;
25
26
  allLands: readonly { step: string; ok: boolean }[] | null;
27
+ /** Card-ledger denominator for review·retry·green. undefined = not requested; null = ledger unreadable; [] = no cards. */
28
+ taskCards?: readonly { id: string }[] | null;
29
+ /** Daily launch and PR counts come from their independent event sources; null means unreadable. */
30
+ launched?: number | null;
31
+ cardPrs?: number | null;
32
+ /** null means the pending-landing age source could not be read; [] means no pending landings. */
33
+ pendingLandingAgeSeconds?: readonly number[] | null;
26
34
  /** A source reached the query safety limit; its aggregate is not known to be complete. */
27
35
  truncated?: { taskAgentActions: boolean; allLands: boolean };
28
36
  }
29
37
 
30
- /** Only land has an independent denominator. The other three verbs have no hand-work source yet. */
38
+ export type LandingFailureReason = 'load' | 'oom' | 'time-limit' | 'conflict' | 'review' | 'other';
39
+ export interface LandingThroughput {
40
+ launched: number | null;
41
+ cardPrs: number | null;
42
+ taskAgentLandings: number | null;
43
+ launchToPrRatio: number | null;
44
+ prToLandingRatio: number | null;
45
+ failureReasons: Record<LandingFailureReason, number> | null;
46
+ pendingLandings: number | null;
47
+ oldestPendingAgeSeconds: number | null;
48
+ }
49
+
50
+ const FAILURE_REASONS: readonly LandingFailureReason[] = ['load', 'oom', 'time-limit', 'conflict', 'review', 'other'];
51
+
52
+ function landingFailureReason(action: { kind: NextActionKind; reason?: string }): LandingFailureReason {
53
+ const reason = action.reason ?? '';
54
+ if (/\boom\b|out[- ]of[- ]memory|oomkilled|메모리.{0,12}(초과|부족|킬)/i.test(reason)) return 'oom';
55
+ if (/timeout|timed out|time[- ]limit|deadline|시간.{0,12}(초과|한도)/i.test(reason)) return 'time-limit';
56
+ if (/conflict|merge conflict|충돌/i.test(reason)) return 'conflict';
57
+ if (action.kind === 'review' || /review|리뷰/i.test(reason)) return 'review';
58
+ if (/load|resource.{0,12}(pressure|exhaust|limit)|cpu.{0,8}load|부하|포화/i.test(reason)) return 'load';
59
+ return 'other';
60
+ }
61
+
62
+ /** Daily funnel, failure-reason distribution, and oldest pending-landing age. */
63
+ export function measureLandingThroughput(input: TaskAgentCoverInput): LandingThroughput {
64
+ const actionsReadable = input.taskAgentActions !== null && input.truncated?.taskAgentActions !== true;
65
+ const taskAgentLandings = actionsReadable
66
+ ? input.taskAgentActions!.filter((action) => action.kind === 'land' && action.result === 'done').length
67
+ : null;
68
+ const failureReasons = actionsReadable
69
+ ? Object.fromEntries(FAILURE_REASONS.map((reason) => [reason, 0])) as Record<LandingFailureReason, number>
70
+ : null;
71
+ if (failureReasons && input.taskAgentActions) {
72
+ for (const action of input.taskAgentActions) {
73
+ if (action.result !== 'failed') continue;
74
+ const reason = landingFailureReason(action);
75
+ failureReasons[reason]++;
76
+ }
77
+ }
78
+ const launched = input.launched ?? null;
79
+ const cardPrs = input.cardPrs ?? null;
80
+ const pendingAges = input.pendingLandingAgeSeconds ?? null;
81
+ const launchToPrRatio = launched !== null && launched > 0 && cardPrs !== null ? cardPrs / launched : null;
82
+ const prToLandingRatio = cardPrs !== null && cardPrs > 0 && taskAgentLandings !== null ? taskAgentLandings / cardPrs : null;
83
+ return {
84
+ launched, cardPrs, taskAgentLandings, launchToPrRatio, prToLandingRatio, failureReasons,
85
+ pendingLandings: pendingAges === null ? null : pendingAges.length,
86
+ oldestPendingAgeSeconds: pendingAges === null || pendingAges.length === 0 ? null : Math.max(...pendingAges),
87
+ };
88
+ }
89
+
90
+ /** Unknown values stay unknown; empty pending queues are reported distinctly from unreadable ages. */
91
+ export function formatLandingThroughput(row: LandingThroughput, period = '1d'): string {
92
+ const count = (value: number | null): string => value === null ? '-' : String(value);
93
+ const ratio = (value: number | null): string => value === null ? '-' : `${(value * 100).toFixed(1)}%`;
94
+ const failures = row.failureReasons === null ? '못 잼' :
95
+ `부하 ${row.failureReasons.load} · OOM ${row.failureReasons.oom} · 시간 한도 ${row.failureReasons['time-limit']} · 충돌 ${row.failureReasons.conflict} · 리뷰 ${row.failureReasons.review} · 기타 ${row.failureReasons.other}`;
96
+ const pendingAge = row.pendingLandings === null ? '못 잼'
97
+ : row.pendingLandings === 0 ? '없음'
98
+ : row.oldestPendingAgeSeconds === null ? '못 잼'
99
+ : row.oldestPendingAgeSeconds < 3600
100
+ ? `${Math.floor(row.oldestPendingAgeSeconds / 60)}분`
101
+ : `${(row.oldestPendingAgeSeconds / 3600).toFixed(1)}h`;
102
+ return `처리량(${period}) 발사 ${count(row.launched)} → 카드 PR ${count(row.cardPrs)} → TA 착지 ${count(row.taskAgentLandings)} (발사→PR ${ratio(row.launchToPrRatio)} · PR→착지 ${ratio(row.prToLandingRatio)}) · 착지 실패 ${failures} · 착지 대기 ${count(row.pendingLandings)}건, 최장 ${pendingAge}`;
103
+ }
104
+
105
+ /** Land uses the merge-step denominator; review·retry·green use the card ledger only when `taskCards` is given. */
31
106
  export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
32
107
  const verbs: CoverVerb[] = ['land', 'review', 'retry', 'green'];
33
- return verbs.map((verb) => {
108
+ const cardIds = input.taskCards ? new Set(input.taskCards.map((card) => card.id)) : null;
109
+ const rows = verbs.map((verb): CoverRow => {
34
110
  const actions = input.taskAgentActions?.filter((action) => action.kind === verb);
35
111
  const actionsUnreadable = actions === undefined || input.truncated?.taskAgentActions === true;
36
112
  const byTaskAgent = actionsUnreadable ? null : actions.filter((action) => action.result === 'done').length;
@@ -38,6 +114,19 @@ export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
38
114
  const liveActions = actionsUnreadable ? null : actions.filter((action) => action.result !== 'shadow').length;
39
115
  const shadowActions = actionsUnreadable ? null : actions.filter((action) => action.result === 'shadow').length;
40
116
  const counts = { liveActions, shadowActions };
117
+ if (verb !== 'land' && input.taskCards !== undefined) {
118
+ if (actionsUnreadable || cardIds === null) return {
119
+ verb, byTaskAgent: null, total: null, observedActions, ratio: null, state: 'unreadable' as const,
120
+ ...counts, stewardTransition: 'unmeasured' as const,
121
+ };
122
+ const doneCards = new Set(actions.filter((action) => action.result === 'done' && action.taskId !== undefined && cardIds.has(action.taskId)).map((action) => action.taskId));
123
+ const total = cardIds.size;
124
+ const ratio = total === 0 ? null : doneCards.size / total;
125
+ return {
126
+ verb, byTaskAgent: doneCards.size, total, observedActions, ratio, state: total === 0 ? 'no-denominator' as const : 'measured' as const,
127
+ ...counts, stewardTransition: ratio === null ? 'unmeasured' as const : doneCards.size > 0 ? 'observed' as const : 'not-observed' as const,
128
+ };
129
+ }
41
130
  if (verb !== 'land') return {
42
131
  verb, byTaskAgent, total: null, observedActions, ratio: null,
43
132
  state: actionsUnreadable ? 'unreadable' : 'no-denominator',
@@ -52,6 +141,15 @@ export function measureTaskAgentCover(input: TaskAgentCoverInput): CoverRow[] {
52
141
  stewardTransition: ratio === null ? 'unmeasured' : byTaskAgent !== null && byTaskAgent > 0 ? 'observed' : 'not-observed',
53
142
  };
54
143
  });
144
+ if (input.taskCards !== undefined) {
145
+ try {
146
+ debug.log('task-agent.cover', 'by-card', {
147
+ cards: input.taskCards === null ? null : cardIds?.size,
148
+ verbs: rows.filter((row) => row.verb !== 'land').map(({ verb, byTaskAgent, total, state }) => ({ verb, byTaskAgent, total, state })),
149
+ });
150
+ } catch { /* fail-soft */ }
151
+ }
152
+ return rows;
55
153
  }
56
154
 
57
155
  /** A single human-readable line per verb; unknown numbers are never rendered as zero. */
@@ -72,6 +72,22 @@ const EXECUTOR_VARIANT: Partial<Record<TaskMove, RetryVariant>> = {
72
72
  alternative: 'alternative',
73
73
  };
74
74
 
75
+ /** 사다리 R4 — 실행부 허용 목록에 올릴 수 있는 재발사 계열 수(설정 `taskAgent.liveMoves` 어휘 그대로). */
76
+ export const LIVE_RETRY_MOVES = ['narrow-relaunch', 'wait-retry', 'salvage-relaunch', 'alternative'] as const satisfies readonly TaskMove[];
77
+ export type LiveRetryMove = typeof LIVE_RETRY_MOVES[number];
78
+
79
+ const RETRY_MOVE_OF_VARIANT: Record<RetryVariant, LiveRetryMove> = {
80
+ narrow: 'narrow-relaunch',
81
+ wait: 'wait-retry',
82
+ salvage: 'salvage-relaunch',
83
+ alternative: 'alternative',
84
+ };
85
+
86
+ /** 실행부 `retry` 가 어느 수인가 — 갈래 없는 재발사(리뷰 fail 의 must-fix 재발사)는 «좁혀 재발사»다. */
87
+ export function retryMoveOf(variant?: RetryVariant): LiveRetryMove {
88
+ return RETRY_MOVE_OF_VARIANT[variant ?? 'narrow'];
89
+ }
90
+
75
91
  function base(input: TaskJudgeInput): Omit<TaskJudgement, 'executorKind'> {
76
92
  if (input.deliveryEvidence) return input.deliveryEvidence.ok
77
93
  ? { move: 'propose-green', stage: 'closing', reason: `전달 근거 확인: ${input.deliveryEvidence.files.join(', ')}` }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * TA-LAND-ORDER-LEDGER — 겹침 쌍·착지 순서 원장(첫 조각 · MK 0.2.24 칸 LAND-ORDER-LEDGER).
3
+ *
4
+ * - 왜: 재판정 틱이 같은 PR 의 여러 머리를 겹쳐 재게이트·land 하면서 «무엇이 먼저 착지했고 그때 리뷰·게이트가
5
+ * 무엇을 말했나»가 카드 이력 흐름에만 남는다. 착지 순서를 정하는 다음 소비자(L8 머지 큐 · OVERLAP-METRIC 집계 —
6
+ * 다음 조각)는 한 줄씩인 «원장» 모양이 필요하다 — 이 모듈은 그 행을 만들고 붙인다(순서 결정은 여기서 하지 않는다).
7
+ * - 행 = {pr, head, cardId, gateBase, reviewVerdict, mustFix, landOk, landClass, at} — `joinLandOrderRow` 가 순수하게
8
+ * 만들고(파일을 읽지 않는다) `appendLandOrderRow` 가 카드 상태 파일 옆 `land-order-ledger.jsonl`(JSONL) 에 붙인다.
9
+ * - reviewVerdict: review-results 파일(`review-result-capture.ts` 의 reviewResultPath 꼴)을 못 읽으면 'missing' —
10
+ * 0 으로 쓰지 않는다. mustFix 도 읽었을 때만 수로, 모르면 null(0 이 아니다).
11
+ * - 같은 pr+head 는 한 줄만 — 두 번 불러도 첫 줄이 그대로다(덮어쓰지 않는다).
12
+ * - 호출자: `rejudge-on-head.ts` ③ land 단계 — executeLiveLand 직후 한 번. 쓰기 실패는 호출자가 삼킨다(land 결과 무변).
13
+ * - 관측: `task-agent.land-order` · 'appended' | 'append-failed' {pr, head} — 호출자가 남긴다.
14
+ */
15
+ import { appendFileSync, readFileSync } from 'node:fs';
16
+ import { dirname, join } from 'node:path';
17
+ import type { LiveMoveResult } from './live-moves.js';
18
+ import type { ParsedReviewResult } from './review-result-capture.js';
19
+
20
+ /** 원장 행 — 같은 pr+head 는 한 줄만. */
21
+ export interface LandOrderRow {
22
+ pr: number;
23
+ head: string;
24
+ cardId: string;
25
+ /** 재게이트 base — 겹침 증거(#26023 꼴)의 `gate.baseCommit`. */
26
+ gateBase: string;
27
+ /** review-results 파일을 못 읽으면 'missing' — 0 으로 쓰지 않는다. */
28
+ reviewVerdict: string;
29
+ /** must-fix 수 — 읽었을 때만. 모르면 null(0 이 아니다). */
30
+ mustFix: number | null;
31
+ landOk: boolean;
32
+ /** 실패 갈래(infra|judgement|base-stale) — 갈래가 없으면(성공) null. */
33
+ landClass: string | null;
34
+ at: string;
35
+ }
36
+
37
+ /**
38
+ * 겹침 증거(#26023 꼴 `{pr, head, gate:{head, baseCommit, passed}, reviewResult}`) ⊕ 카드 식별.
39
+ * `cardId` 는 증거 «파일»에는 없는 칸이라 호출자가 메모리에서 싣는다(파일 형식은 무변).
40
+ */
41
+ export interface LandOrderOverlapEvidence {
42
+ pr: number;
43
+ head: string;
44
+ cardId: string;
45
+ gate: { head: string; baseCommit: string; passed: boolean };
46
+ reviewResult?: string;
47
+ }
48
+
49
+ /** 원장 파일 — 카드 상태 파일 옆(reviewResultPath 와 같은 «옆» 규칙). */
50
+ export function landOrderLedgerPath(statePath: string): string {
51
+ return join(dirname(statePath), 'land-order-ledger.jsonl');
52
+ }
53
+
54
+ /** 순수 함수 — 겹침 증거 ⊕ 회수한 리뷰 결과 ⊕ land 결과를 원장 한 행으로 합친다(파일을 읽지 않는다). */
55
+ export function joinLandOrderRow(input: {
56
+ overlapEvidence: LandOrderOverlapEvidence;
57
+ /** review-results 파일을 읽어 해석한 결과 — 못 읽으면 null(→ reviewVerdict 'missing'). */
58
+ reviewResult: ParsedReviewResult | null;
59
+ landed: Pick<LiveMoveResult, 'ok' | 'landAttempt'>;
60
+ at: string;
61
+ }): LandOrderRow {
62
+ return {
63
+ pr: input.overlapEvidence.pr,
64
+ head: input.overlapEvidence.head,
65
+ cardId: input.overlapEvidence.cardId,
66
+ gateBase: input.overlapEvidence.gate.baseCommit,
67
+ reviewVerdict: input.reviewResult?.verdict ?? 'missing',
68
+ mustFix: input.reviewResult?.mustFix ?? null,
69
+ landOk: input.landed.ok === true,
70
+ landClass: input.landed.landAttempt?.class ?? null,
71
+ at: input.at,
72
+ };
73
+ }
74
+
75
+ /** 원장에 한 줄 붙인다 — 같은 pr+head 가 이미 있으면 그대로(두 번 불러도 1행). 못 붙이면 던진다(호출자가 삼킨다). */
76
+ export function appendLandOrderRow(statePath: string, row: LandOrderRow): void {
77
+ const ledgerPath = landOrderLedgerPath(statePath);
78
+ let existing: string;
79
+ try {
80
+ existing = readFileSync(ledgerPath, 'utf8');
81
+ } catch (error) {
82
+ if ((error as NodeJS.ErrnoException)?.code !== 'ENOENT') throw error;
83
+ existing = '';
84
+ }
85
+ const samePair = (value: unknown): boolean => {
86
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return false;
87
+ const entry = value as Partial<LandOrderRow>;
88
+ return entry.pr === row.pr && typeof entry.head === 'string' && entry.head.toLowerCase() === row.head.toLowerCase();
89
+ };
90
+ const seen = existing.split('\n').filter(Boolean).some((line) => {
91
+ try { return samePair(JSON.parse(line)); } catch { return false; }
92
+ });
93
+ if (seen) return;
94
+ appendFileSync(ledgerPath, `${JSON.stringify(row)}\n`);
95
+ }
@@ -10,7 +10,7 @@
10
10
  */
11
11
  import { debug } from '../debug/log.js';
12
12
  import type { LiveMoveResult, TaskAgentLiveMove } from './live-moves.js';
13
- import { LIVE_MOVE_HISTORY_EVENT, updateTaskCard, type TaskCard, type TaskCardEvent } from './task-hand.js';
13
+ import { HAND_MOVE_HISTORY_EVENT, LIVE_MOVE_HISTORY_EVENT, updateTaskCard, type TaskCard, type TaskCardEvent } from './task-hand.js';
14
14
 
15
15
  export const LIVE_MOVE_EFFECT_UNKNOWN = 'unknown';
16
16
  /** 리뷰는 떼어 띄운다(`defaultRequestReview` · stdio ignore) — 판정·must-fix·reviewRoute 는 그 리뷰 자기 관측에만 있다. */
@@ -131,3 +131,89 @@ export function recordLiveMoveHistory(
131
131
  }
132
132
  return outcome;
133
133
  }
134
+
135
+ /**
136
+ * HAND-MOVE-LEDGER — 자리가 손으로 마무리한 수(기존 실패 면제 · diff 재적용 · 범위 자르기 · 쪼개기 · 다른 모델 의견)도
137
+ * 같은 원장 칸(카드 history)에 한 줄로 남긴다.
138
+ * - 어휘는 다섯뿐(`HAND_MOVE_KINDS`) — 모르는 kind 는 거부한다(지어낸 줄을 원장에 남기지 않는다).
139
+ * - 쓰기 길은 live-move 와 같은 `updateTaskCard` — 기존 history 항목은 건드리지 않고 덧붙이기만.
140
+ */
141
+ /** 카드 history 의 hand-move 줄 이벤트 이름(정의는 task-hand.ts — 상태 사유 판정이 같은 상수로 뺀다). */
142
+ export { HAND_MOVE_HISTORY_EVENT };
143
+
144
+ /** 자리가 손으로 쓸 수 있는 수의 어휘(칸 문면 그대로 다섯) — 이 밖은 거부된다. */
145
+ export const HAND_MOVE_KINDS = ['exempt-failure', 'reapply-diff', 'trim-scope', 'split', 'second-opinion'] as const;
146
+ export type HandMoveKind = typeof HAND_MOVE_KINDS[number];
147
+
148
+ export function isHandMoveKind(kind: string): kind is HandMoveKind {
149
+ return (HAND_MOVE_KINDS as readonly string[]).includes(kind);
150
+ }
151
+
152
+ export interface HandMoveHistoryInput {
153
+ /** 손으로 쓴 수의 종류 — `HAND_MOVE_KINDS` 밖이면 거부된다. */
154
+ kind: string;
155
+ /** 남긴 자리(예: OP·MK·TC·UX) — 누가 손으로 마무리했나. */
156
+ by: string;
157
+ /** 무엇을 했나 — 한 줄. */
158
+ detail: string;
159
+ /** 관련 PR(있으면). */
160
+ pr?: number;
161
+ /** 줄 시각(시험 주입 · 기본 지금). */
162
+ at?: string;
163
+ }
164
+
165
+ /** hand-move 줄의 history 칸 — `by` 는 `TaskCardEvent` 밖 칸이라 확장 인터페이스로 달린다. */
166
+ export interface HandMoveHistoryEntry extends TaskCardEvent {
167
+ event: typeof HAND_MOVE_HISTORY_EVENT;
168
+ kind: HandMoveKind;
169
+ by: string;
170
+ detail: string;
171
+ }
172
+
173
+ export function handMoveHistoryEntry(input: HandMoveHistoryInput & { at: string; kind: HandMoveKind }): HandMoveHistoryEntry {
174
+ return {
175
+ at: input.at,
176
+ event: HAND_MOVE_HISTORY_EVENT,
177
+ kind: input.kind,
178
+ by: input.by,
179
+ detail: input.detail,
180
+ ...(input.pr !== undefined ? { pr: input.pr } : {}),
181
+ };
182
+ }
183
+
184
+ export type HandMoveHistoryOutcome = { written: true; entry: HandMoveHistoryEntry } | { written: false; reason: string };
185
+
186
+ /**
187
+ * 자리가 손으로 마무리한 수 한 줄을 카드 history 에 덧붙인다(`recordLiveMoveHistory` 와 같은 길 · 같은 계약).
188
+ * 던지지 않는다 — 모르는 kind · 카드 없음 · 쓰기 실패는 관측으로 남기고 결과로 돌려준다.
189
+ */
190
+ export function recordHandMove(
191
+ statePath: string, cardId: string, input: HandMoveHistoryInput,
192
+ log: (category: string, event: string, data: Record<string, unknown>) => void = (c, e, d) => debug.log(c, e, d, { level: 'warn' }),
193
+ ): HandMoveHistoryOutcome {
194
+ if (!isHandMoveKind(input.kind)) {
195
+ const reason = `unknown hand-move kind: ${input.kind} (known: ${HAND_MOVE_KINDS.join('|')})`;
196
+ try {
197
+ log('task-agent', 'hand-move-history-failed', { card: cardId, kind: input.kind, by: input.by, reason });
198
+ } catch { /* fail-soft */ }
199
+ return { written: false, reason };
200
+ }
201
+ const entry = handMoveHistoryEntry({ ...input, kind: input.kind, at: input.at ?? new Date().toISOString() });
202
+ let missing = false;
203
+ let outcome: HandMoveHistoryOutcome;
204
+ try {
205
+ updateTaskCard(statePath, cardId, (current) => {
206
+ if (!current) { missing = true; return undefined; }
207
+ return { ...current, history: [...(current.history ?? []), entry] };
208
+ });
209
+ outcome = missing ? { written: false, reason: 'task card not in state file' } : { written: true, entry };
210
+ } catch (error) {
211
+ outcome = { written: false, reason: (error instanceof Error ? error.message : String(error)).slice(-300) };
212
+ }
213
+ if (!outcome.written) {
214
+ try {
215
+ log('task-agent', 'hand-move-history-failed', { card: cardId, kind: input.kind, by: input.by, reason: outcome.reason });
216
+ } catch { /* fail-soft */ }
217
+ }
218
+ return outcome;
219
+ }