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
@@ -24,12 +24,16 @@ export interface GraphNodeSpec {
24
24
  readonly kind: GraphNodeKind | (string & {});
25
25
  readonly recipe: string;
26
26
  readonly maxVisits: number;
27
+ /** L0 journey-stage declaration (optional for other graph templates). */
28
+ readonly journeyStage?: string;
27
29
  readonly progress?: readonly string[];
28
30
  readonly phases?: readonly string[];
29
31
  readonly terminalStages?: readonly string[];
30
32
  /** ⭐ 「아크마다 한 번」처럼 «한 노드가 여럿 돈다」를 값으로. 없으면 한 번이다. */
31
33
  readonly fanOut?: string;
32
34
  readonly contract?: GraphNodeContract;
35
+ readonly call?: string;
36
+ readonly budget?: { readonly innerSteps: number };
33
37
  }
34
38
 
35
39
  export interface GraphEdgeSpec {
@@ -50,11 +54,13 @@ export interface GraphTemplateSpec {
50
54
  readonly version: number;
51
55
  readonly entryNode: string;
52
56
  readonly terminalNodes: readonly string[];
57
+ readonly journeyStages?: readonly string[];
53
58
  /** 문서 전용 변경이면 gate 노드를 건너뛸 수 있다는 템플릿 정책. */
54
59
  readonly docsOnlyGateSkip?: boolean;
55
60
  readonly state?: readonly string[];
56
61
  /** ⭐ 런 계약(선택) — 이 그래프가 «기본으로» 요구하는 실행 칸 등. 발사 인자가 덮는다(graph-run-contract.ts). */
57
62
  readonly runContract?: GraphRunContractSpec;
63
+ readonly unit?: { readonly inputs: readonly string[]; readonly outputs: readonly string[]; readonly exits: readonly string[] };
58
64
  readonly nodes: readonly GraphNodeSpec[];
59
65
  readonly edges: readonly GraphEdgeSpec[];
60
66
  }
@@ -102,6 +108,12 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
102
108
  const terminalNodes = asArray(doc.terminal_nodes).filter((v): v is string => typeof v === 'string');
103
109
  if (terminalNodes.length === 0) errors.push({ path: `${label}/terminal_nodes`, message: 'terminal_nodes 가 비었다 — 끝날 수 없는 그래프다' });
104
110
 
111
+ const journeyStages = doc.journey_stages;
112
+ if (journeyStages !== undefined && (!Array.isArray(journeyStages) || journeyStages.length === 0
113
+ || !journeyStages.every((stage) => typeof stage === 'string' && stage.trim())
114
+ || new Set(journeyStages).size !== journeyStages.length)) {
115
+ errors.push({ path: `${label}/journey_stages`, message: 'journey_stages 는 중복 없는 비어 있지 않은 문자열 목록이어야 한다' });
116
+ }
105
117
  const nodes: GraphNodeSpec[] = [];
106
118
  for (const [index, entry] of asArray(doc.nodes).entries()) {
107
119
  const at = `${label}/nodes/${index}`;
@@ -126,6 +138,13 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
126
138
  errors.push({ path: `${at}/max_visits`, message: 'max_visits 는 1 이상의 정수여야 한다' });
127
139
  continue;
128
140
  }
141
+ if (n.journey_stage !== undefined && (typeof n.journey_stage !== 'string' || !n.journey_stage.trim()
142
+ || (Array.isArray(journeyStages) && !journeyStages.includes(n.journey_stage)))) {
143
+ errors.push({ path: `${at}/journey_stage`, message: typeof n.journey_stage === 'string' && n.journey_stage.trim()
144
+ ? `journey_stage '${n.journey_stage}' 가 journey_stages 목록에 없다`
145
+ : 'journey_stage 는 비어 있지 않은 문자열이어야 한다' });
146
+ continue;
147
+ }
129
148
  const contractRaw = n.contract as Record<string, unknown> | undefined;
130
149
  const contract: GraphNodeContract | undefined = contractRaw && typeof contractRaw === 'object'
131
150
  ? {
@@ -134,17 +153,38 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
134
153
  outputs: asArray(contractRaw.outputs).filter((v): v is string => typeof v === 'string'),
135
154
  }
136
155
  : undefined;
156
+ // A subgraph node becomes a unit call only when it names `call:`; without it the node keeps the
157
+ // pre-unit meaning (catalog role / recipe node), so existing declarations stay valid.
158
+ if (kind === 'subgraph' && (n.call !== undefined || n.budget !== undefined)) {
159
+ if (typeof n.call !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*@(?:\^?[1-9]\d*)$/.test(n.call))
160
+ errors.push({ path: `${at}/call`, message: 'call 은 id@정수판 또는 id@^주판이어야 한다' });
161
+ const budget = n.budget as Record<string, unknown> | undefined;
162
+ if (!budget || typeof budget !== 'object' || Array.isArray(budget) ||
163
+ !Number.isSafeInteger(budget.inner_steps) || (budget.inner_steps as number) < 1 || Object.keys(budget).some(key => key !== 'inner_steps'))
164
+ errors.push({ path: `${at}/budget/inner_steps`, message: 'inner_steps 는 1 이상의 안전한 정수여야 한다' });
165
+ if (!contractRaw || !Array.isArray(contractRaw.inputs) || !contractRaw.inputs.every(v => typeof v === 'string') ||
166
+ !Array.isArray(contractRaw.outputs) || !contractRaw.outputs.every(v => typeof v === 'string'))
167
+ errors.push({ path: `${at}/contract`, message: 'subgraph 에는 inputs·outputs 계약이 필요하다' });
168
+ if (recipe !== 'none') errors.push({ path: `${at}/recipe`, message: 'subgraph 는 recipe 를 실행하지 않는다' });
169
+ } else if (kind !== 'subgraph' && (n.call !== undefined || n.budget !== undefined)) {
170
+ errors.push({ path: at, message: 'call·budget 은 subgraph 전용이다' });
171
+ }
137
172
  // ⛔ 계약이 없으면 「이 노드가 무엇을 할 수 있나」를 답할 수 없다 — 경고로 남긴다(거절은 오버레이 쪽).
138
173
  if (contract === undefined) warnings.push({ path: `${at}/contract`, message: '계약(inputs·tools·outputs)이 없다 — 권한을 코드에서만 알 수 있다' });
139
174
  else if (contract.tools === '') warnings.push({ path: `${at}/contract/tools`, message: 'tools 가 비었다' });
140
175
 
141
176
  nodes.push({
142
177
  nodeId, kind, recipe, maxVisits,
178
+ ...(typeof n.journey_stage === 'string' ? { journeyStage: n.journey_stage } : {}),
143
179
  ...(Array.isArray(n.progress) ? { progress: n.progress.filter((v): v is string => typeof v === 'string') } : {}),
144
180
  ...(Array.isArray(n.phases) ? { phases: n.phases.filter((v): v is string => typeof v === 'string') } : {}),
145
181
  ...(Array.isArray(n.terminal_stages) ? { terminalStages: n.terminal_stages.filter((v): v is string => typeof v === 'string') } : {}),
146
182
  ...(typeof n.fan_out === 'string' ? { fanOut: n.fan_out } : {}),
147
183
  ...(contract ? { contract } : {}),
184
+ ...(kind === 'subgraph' && typeof n.call === 'string' ? { call: n.call } : {}),
185
+ ...(kind === 'subgraph' && n.budget && typeof n.budget === 'object' && !Array.isArray(n.budget) &&
186
+ typeof (n.budget as Record<string, unknown>).inner_steps === 'number'
187
+ ? { budget: { innerSteps: (n.budget as { inner_steps: number }).inner_steps } } : {}),
148
188
  });
149
189
  }
150
190
  if (nodes.length === 0) errors.push({ path: `${label}/nodes`, message: '노드가 하나도 없다' });
@@ -198,6 +238,20 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
198
238
  if (entryNode !== undefined && !known.has(entryNode)) errors.push({ path: `${label}/entry_node`, message: `'${entryNode}' 는 선언된 노드가 아니다` });
199
239
  for (const t of terminalNodes) if (!known.has(t)) errors.push({ path: `${label}/terminal_nodes`, message: `'${t}' 는 선언된 노드가 아니다` });
200
240
 
241
+ let unit: GraphTemplateSpec['unit'];
242
+ if (doc.unit !== undefined) {
243
+ const rawUnit = doc.unit as Record<string, unknown>;
244
+ if (!rawUnit || typeof rawUnit !== 'object' || Array.isArray(rawUnit) ||
245
+ Object.keys(rawUnit).some(key => !['inputs', 'outputs', 'exits'].includes(key)) ||
246
+ !['inputs', 'outputs', 'exits'].every(key => Array.isArray(rawUnit[key]) &&
247
+ (rawUnit[key] as unknown[]).every(v => typeof v === 'string' && v.length > 0) &&
248
+ new Set(rawUnit[key] as string[]).size === (rawUnit[key] as string[]).length) ||
249
+ !(rawUnit.exits as string[]).length ||
250
+ (rawUnit.exits as string[]).some(exit => !terminalNodes.includes(exit)) ||
251
+ terminalNodes.some(exit => !(rawUnit.exits as string[]).includes(exit))) {
252
+ errors.push({ path: `${label}/unit`, message: 'unit 의 inputs·outputs·exits 및 terminal_nodes 가 일치해야 한다' });
253
+ } else unit = { inputs: rawUnit.inputs as string[], outputs: rawUnit.outputs as string[], exits: rawUnit.exits as string[] };
254
+ }
201
255
  const runContract = parseGraphRunContract(doc.run_contract);
202
256
  if (runContract.error) errors.push({ path: `${label}/run_contract`, message: runContract.error });
203
257
  let loop: GraphTemplateSpec['loop'];
@@ -225,6 +279,8 @@ export function parseGraphTemplateYaml(source: string, label = '<inline>'): Grap
225
279
  return {
226
280
  template: {
227
281
  graphId: graphId!, version: version!, entryNode: entryNode!, terminalNodes, nodes, edges,
282
+ ...(unit ? { unit } : {}),
283
+ ...(Array.isArray(journeyStages) ? { journeyStages: journeyStages as string[] } : {}),
228
284
  ...(loop ? { loop } : {}),
229
285
  ...(typeof doc.docs_only_gate_skip === 'boolean' ? { docsOnlyGateSkip: doc.docs_only_gate_skip } : {}),
230
286
  ...(Array.isArray(doc.state) ? { state: doc.state.filter((v): v is string => typeof v === 'string') } : {}),
@@ -170,7 +170,11 @@ function withInboxLock<T>(file: string, work: () => T): T {
170
170
  finally { rmdirSync(lock); }
171
171
  }
172
172
 
173
- /** One durable JSON line per failure; repeat source+ref within an hour is folded. */
173
+ /** One durable JSON line per failure; repeat source+ref within an hour is folded.
174
+ * POD-HEAL-HOST — the loop start itself is refused inside a Pod (`startHealLoop` → `skipped-in-pod`):
175
+ * a loop started there dies with the Pod. The line stays in the inbox, and the host re-records the
176
+ * recovered Pod ledger failure into the HOST universe (`pod-ledger-collect.ts` → `intakeCollectedFailure`),
177
+ * so the host heal loop triages it (harvest + narrowed relaunch). */
174
178
  export function recordFailureEvent(event: FailureEvent, root = effectiveInstanceRoot(), startLoop: StartHealLoop = startHealLoop): { folded: boolean } {
175
179
  if (!['release-run', 'harness-run', 'loop-tick', 'cron'].includes(event.source) ||
176
180
  !event.kind || !event.ref || !event.summary || !Number.isFinite(Date.parse(event.at))) {
@@ -31,7 +31,8 @@ export {
31
31
  type PrEvidenceInput,
32
32
  } from './pr-evidence-artifact.js';
33
33
  import { judgePredictionAccuracy } from './judge-prediction-accuracy.js';
34
- import { classifyPodReviewBudgetResidue, measureReviewFindingRecurrence, type ReviewFindingRecurrence } from './review-finding-recurrence.js';
34
+ import { classifyPodReviewBudgetResidue, measureReviewFindingRecurrence, type ReviewFindingRecurrence, type RecurrenceFinding } from './review-finding-recurrence.js';
35
+ import type { ReviewRework } from '../self-dev/run-types.js';
35
36
  import { createHash } from 'node:crypto';
36
37
  import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, statSync } from 'node:fs';
37
38
  import { writeGoalDocumentAtomic } from './goal-document-write.js';
@@ -80,7 +81,7 @@ import { releasePathHold, releasePathHoldComment, RELEASE_PATH_LABEL } from '../
80
81
  import { emitDecision } from '../live/detail-switch.js';
81
82
  import { reworkDecision } from './decision-events.js';
82
83
  import type { LogSink } from '../mss/logging/sink.js';
83
- import type { LogQuery, LogStoreRow } from '../mss/logging/log-store.js';
84
+ import { LogStore, logsDbPath, type LogQuery, type LogStoreRow } from '../mss/logging/log-store.js';
84
85
  import { refreshCodexQuotaSignals } from '../budget/codex-quota-refresh.js';
85
86
  import { inspectActiveProvider, type ActiveProviderInfo } from '../provider-summary.js';
86
87
  import { recordHitlEvent } from '../self-dev/hitl-log.js';
@@ -151,6 +152,7 @@ export { slugifyFeature } from '../harness/worktree-branch-prefix.js';
151
152
  import { AUTO_REVIEW_LABEL, resolveAutoReviewLabels } from './context-capsule.js';
152
153
  import { GOAL_TYPES, extractVerbatimOriginalAsk, leadingGoalMetadata, parseAskFile, parseGoalType, type GoalType } from './goal-author.js';
153
154
  import { renderRoundSummaryCard, type RoundSummaryEntry } from './round-summary-card.js';
155
+ import { computeFlexSignals } from './flex-signals.js';
154
156
  import { resolveAdaptiveMaxReworkDecision, resolveEscalateTier, resolveEscalateTarget, buildReworkFeature, failIndicator, isRepeatedUnverifiedOnlyGateFailure, unverifiedRepeatKey, appendReworkHistory, truncateReworkHistoryByItem, applyReworkBudgetDecision, parseContractConflictRelaxation, parseReworkBudgetDecision, resolveReworkBudgetCarry, stripReworkBudgetHeaders, countConsecutiveMustFixIds, mergeReworkNotes, resolveReworkKind, type ReworkPlanRevision, type ReworkNotePart } from './rework-policy.js';
155
157
  import type { SupervisionReworkSource } from './supervision-vocabulary.js';
156
158
  import { classifyReworkBudgetShadow } from './classify-shadow.js';
@@ -246,11 +248,12 @@ import { persistReviewArtifact, type ReviewArtifactWriter } from '../agent-subst
246
248
  import { createArtifactStore } from '../artifact/index.js';
247
249
  import { mapStageToRunStatus, type SelfImplementStage } from './run-status-mapping.js';
248
250
  import type { LifecycleScreenComparison } from './lifecycle-screen-scoreboard.js';
251
+ import { buildRuntimeOverlayState } from './graph-overlay-condition.js';
249
252
  import { activeTemplate, decideTemplate, graphAuthorityFields, nodeNameForStage, overlayDecisionObservation, resolveGraphAuthorityForUserConfig, routeGate, visitBudgetOf } from './graph-authority.js';
250
253
  import type { GraphTemplate } from './graph-templates.js';
251
254
  import { defaultOverlaysDir, loadGraphOverlays, type GraphOverlaySpec } from './graph-overlay-yaml.js';
252
- import { graphIdentityOf, pipelineNodeEntryPayload } from './graph-templates.js';
253
- import { AMBIGUOUS_STAGES, checkPipelineTraversal, nodesForStage, pipelineGraphIdentity, type PipelineNodeId, type PipelineTraversalCheck } from './pipeline-shape.js';
255
+ import { graphIdentityOf, measureL0Coverage, pipelineNodeEntryPayload } from './graph-templates.js';
256
+ import { AMBIGUOUS_STAGES, checkPipelineTraversal, nodesForStage, pipelineGraphIdentity, TERMINAL_STAGES_BY_NODE, type PipelineNodeId, type PipelineTraversalCheck } from './pipeline-shape.js';
254
257
  import { appendGateWorktreeFreshnessEvidence, gateEvidenceNote } from '../harness/review-adapter.js';
255
258
  import { diffHeaderPaths } from '../agent-substrate/self-review-cli.js';
256
259
  import { format as formatPrComment, type PrCommentRole } from '../agent-substrate/pr-comment-meta.js';
@@ -786,7 +789,9 @@ function changedFileIsInsideDeclaredTarget(changedFile: string, declaredPath: st
786
789
  const target = normalizeDeclaredScopePath(declaredPath);
787
790
  if (!file || !target) return false;
788
791
  return file === target || file.startsWith(`${target}/`)
789
- || (target.endsWith('.ts') && !target.endsWith('.test.ts') && file === `${target.slice(0, -3)}.test.ts`);
792
+ || (target.endsWith('.ts') && !target.endsWith('.test.ts') && file === `${target.slice(0, -3)}.test.ts`)
793
+ // SCOPE-LOCKFILE-PAIR — 선언한 package.json 을 고치면 같은 폴더의 bun.lock 이 따라 바뀐다(10-11 TSGO-TRIAL 이 bun.lock 하나로 보류).
794
+ || ((target === 'package.json' || target.endsWith('/package.json')) && file === `${target.slice(0, -'package.json'.length)}bun.lock`);
790
795
  }
791
796
 
792
797
  function parseDeclaredTargetPathsFromAsk(askText: string): { readable: boolean; paths: readonly string[] } {
@@ -803,10 +808,20 @@ function parseDeclaredTargetPathsFromAsk(askText: string): { readable: boolean;
803
808
  return { readable: true, paths: [...new Set(paths.filter(Boolean))] };
804
809
  }
805
810
 
811
+ // SCOPE-NEW-FILES-LINE — 칸 문면은 새로 만들 파일을 «신설:» 줄로 따로 적는다(OP D칸 규칙). 그 줄도 선언 범위다.
812
+ // 10-11 FLEX-SIGNALS run-95799887: 칸이 적은 신설 파일 둘을 «범위 밖»으로 세어 구현을 보류했다.
813
+ const DECLARED_NEW_FILES_LINE = /^\s*(?:신설|new\s*files?)\s*[::]\s*(.*)$/i;
814
+
806
815
  function declaredPathsFromGoalDocumentText(document: string): { readable: boolean; paths: readonly string[] } {
807
816
  const originalAsk = extractVerbatimOriginalAsk(document)?.ask ?? null;
808
817
  if (originalAsk === null) return { readable: false, paths: [] };
809
- return parseDeclaredTargetPathsFromAsk(originalAsk);
818
+ const declared = parseDeclaredTargetPathsFromAsk(originalAsk);
819
+ if (!declared.readable) return declared;
820
+ const newFiles = originalAsk.split(/\r?\n/).flatMap((line) => {
821
+ const match = DECLARED_NEW_FILES_LINE.exec(line);
822
+ return match ? parseDeclaredTargetPathsFromAsk(`대상 경로: ${match[1]}`).paths : [];
823
+ });
824
+ return newFiles.length > 0 ? { readable: true, paths: [...new Set([...declared.paths, ...newFiles])] } : declared;
810
825
  }
811
826
 
812
827
  /** Only paths explicitly designated as targets in the ask or piece text protect an intentional revert. */
@@ -2280,6 +2295,7 @@ export interface SelfImplementResult {
2280
2295
  branch?: string;
2281
2296
  gate?: SelfImplementGateResult;
2282
2297
  review?: SelfImplementReview;
2298
+ rework?: ReviewRework;
2283
2299
  /** Worktree commit observed while this review ran; not inferred from the later PR head. */
2284
2300
  reviewedHeadCommit?: string;
2285
2301
  completionDisposition?: SelfImplementCompletionDisposition;
@@ -3569,6 +3585,29 @@ async function autohealRecordedStop(stopEntry: RunLedgerEntry, s: SelfImplementS
3569
3585
  }
3570
3586
  }
3571
3587
 
3588
+ export function measureReviewRework(counts: readonly number[], findings: readonly (readonly RecurrenceFinding[])[]): ReviewRework {
3589
+ const deltas = counts.slice(1).map((count, index) => Math.sign(count - counts[index]!));
3590
+ const directions = deltas.filter((delta) => delta !== 0);
3591
+ const seen = new Set<string>();
3592
+ let resurfaced = false;
3593
+ for (let index = 0; index < findings.length; index++) {
3594
+ const ids = new Set(findings[index]!.map((finding) => finding.id));
3595
+ if (index > 0 && [...ids].some((id) => seen.has(id) && !findings[index - 1]!.some((finding) => finding.id === id))) {
3596
+ const prior = findings.slice(0, index - 1).reverse().find((round) => round.some((finding) => ids.has(finding.id)));
3597
+ if (prior && measureReviewFindingRecurrence(findings[index], prior)?.normalizedRepeatedReviewFindingCount) resurfaced = true;
3598
+ }
3599
+ for (const id of ids) seen.add(id);
3600
+ }
3601
+ const trend: ReviewRework['trend'] = resurfaced || directions.some((delta) => delta !== directions[0])
3602
+ ? 'oscillating'
3603
+ : counts.length < 2 ? 'unmeasured'
3604
+ : deltas.every((delta) => delta < 0) ? 'improving'
3605
+ : deltas.every((delta) => delta === 0) ? 'stable'
3606
+ : counts.at(-1)! > counts[0]! && deltas.every((delta) => delta >= 0) ? 'worsening'
3607
+ : 'stable';
3608
+ return { trend, signature: [...new Set((findings.at(-1) ?? []).map((finding) => finding.id))].sort().join(','), rounds: counts.length };
3609
+ }
3610
+
3572
3611
  export async function runSelfImplement(opts: SelfImplementOptions): Promise<SelfImplementResult> {
3573
3612
  if (opts.childLlm && (typeof opts.childLlm.provider !== 'string' || !opts.childLlm.provider.trim() || typeof opts.childLlm.model !== 'string' || !opts.childLlm.model.trim())) {
3574
3613
  throw new Error('childLlm.provider and childLlm.model must both be non-empty');
@@ -3633,6 +3672,13 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3633
3672
  let roundCount = 0;
3634
3673
  let lastTier: EscalateTier | undefined;
3635
3674
  let lastReviewFindings: string[] | undefined;
3675
+ let measuredRework: ReviewRework | undefined;
3676
+ const withMeasuredRework = (result: SelfImplementResult): SelfImplementResult => {
3677
+ if (!measuredRework) return result;
3678
+ const classification = result.abandonedClassification?.classification;
3679
+ // An empty must-fix set stays an empty signature: a bare classification prefix would make two clean reviews «match».
3680
+ return { ...result, rework: { ...measuredRework, signature: `${classification && measuredRework.signature ? `${classification}:` : ''}${measuredRework.signature}` } };
3681
+ };
3636
3682
  let reviewFindingRoundCount = 0;
3637
3683
  const verbatimReviewFindingOccurrences = new Map<string, number>();
3638
3684
  const normalizedReviewFindingFirstSeenRounds = new Map<string, number>();
@@ -3982,6 +4028,62 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
3982
4028
  const finalizeTraversal = (): void => {
3983
4029
  if (traversalFinalized) return;
3984
4030
  traversalFinalized = true;
4031
+ if (terminalResult?.ok && runGraphTemplate?.graphId === 'self-implement') {
4032
+ const entered = new Set<string>(traversedNodes);
4033
+ let journeyRowsRead = false;
4034
+ const path = logsDbPath();
4035
+ if (existsSync(path)) {
4036
+ let store: LogStore | undefined;
4037
+ try {
4038
+ store = LogStore.openReadOnly(path);
4039
+ const join = journeyJoinFields();
4040
+ const rows = [
4041
+ ...store.queryByDataKeys({ exactCategories: ['self-implement'], runIds: [runId] }),
4042
+ ...(join.journeyKey ? store.queryAll({ exactCategories: ['self-implement'], events: ['pipeline-node-entry'], grep: join.journeyKey }) : []),
4043
+ ...(join.queueLaunchId ? store.queryAll({ exactCategories: ['self-implement'], events: ['pipeline-node-entry'], grep: join.queueLaunchId }) : []),
4044
+ ];
4045
+ for (const row of rows) {
4046
+ if (row.event !== 'pipeline-node-entry' || !row.data) continue;
4047
+ let data: { runId?: string; journeyKey?: string; queueLaunchId?: string; graphId?: string; graphVersion?: string; node?: string; phase?: string };
4048
+ try { data = JSON.parse(row.data); } catch { continue; }
4049
+ const sameRun = data.runId === runId || (data.runId === undefined && (
4050
+ (join.journeyKey !== undefined && data.journeyKey === join.journeyKey)
4051
+ || (join.queueLaunchId !== undefined && data.queueLaunchId === join.queueLaunchId)
4052
+ ));
4053
+ if (sameRun && data.phase === 'journey' && data.graphId === runGraphTemplate.graphId && data.graphVersion === runGraphTemplate.version && typeof data.node === 'string') {
4054
+ entered.add(data.node);
4055
+ journeyRowsRead = true;
4056
+ }
4057
+ }
4058
+ } catch { /* observation failure must not fail the run */ }
4059
+ finally { store?.close(); }
4060
+ }
4061
+ // Only nodes this process runs (TERMINAL_STAGES_BY_NODE) can be «skipped» with certainty:
4062
+ // their non-entry here means the condition was false. Nodes run by other processes
4063
+ // (author · intake · dispatch · pod …) that were not joined stay «unjoined» — not
4064
+ // skipped and not accounted — so the walk ratio can fall below 1.0.
4065
+ const skipped = new Set<string>();
4066
+ const unjoined: string[] = [];
4067
+ for (const node of runGraphTemplate.nodes) {
4068
+ if (entered.has(node.nodeId)) continue;
4069
+ if (!Object.hasOwn(TERMINAL_STAGES_BY_NODE, node.nodeId)) { unjoined.push(node.nodeId); continue; }
4070
+ const condition = node.nodeId === 'rework' ? 'gate/review passed'
4071
+ : node.nodeId === 'main-sync' || node.nodeId === 'regate' ? 'mergeMain && commitWork'
4072
+ : 'completion disposition';
4073
+ observeOuter('node-skipped', {
4074
+ ...graphIdentityOf(runGraphTemplate), ...journeyJoinFields(),
4075
+ node: node.nodeId, reason: 'condition', condition,
4076
+ }, { category: 'graph.path' });
4077
+ skipped.add(node.nodeId);
4078
+ }
4079
+ const coverage = measureL0Coverage(runGraphTemplate, entered, skipped);
4080
+ observeOuter('l0-coverage', {
4081
+ ...graphIdentityOf(runGraphTemplate), ...journeyJoinFields(),
4082
+ ...coverage,
4083
+ journeyRowsRead,
4084
+ unjoinedNodes: unjoined,
4085
+ }, { category: 'graph.path' });
4086
+ }
3985
4087
  try {
3986
4088
  const checked = terminalNode
3987
4089
  ? (opts.seams.checkPipelineTraversal ?? checkPipelineTraversal)(traversedNodes, terminalNode)
@@ -4147,6 +4249,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4147
4249
  },
4148
4250
  observeRoundExecution,
4149
4251
  observeReviewFindings,
4252
+ (rework) => { measuredRework = rework; },
4150
4253
  setRunGraphIdentity,
4151
4254
  (template) => { runGraphTemplate = template; },
4152
4255
  // ⭐ 「요구 증거 충족」을 받아 결과에 실어 원장까지 나른다(`JDG-T60`)
@@ -4200,7 +4303,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4200
4303
  if (lastCredentialFailure) {
4201
4304
  observeOuter('credential-failure-observed', { count: credentialFailureCount, ...lastCredentialFailure });
4202
4305
  }
4203
- terminalResult = withEvidenceCoverage(addressedResult);
4306
+ terminalResult = withEvidenceCoverage(withMeasuredRework(addressedResult));
4204
4307
  // runSelfImplement is the terminal caller: retain every existing PR/ledger/log observation and append one stop fact.
4205
4308
  if ((addressedResult.stage === 'aborted' || (addressedResult.mergeReason && addressedResult.mergeReason !== 'auto')) && !runStopRecorded(runId)) {
4206
4309
  const reason = addressedResult.mergeReason;
@@ -4263,7 +4366,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4263
4366
  });
4264
4367
  observeRunRollup(observeOuter, addressedResult, roundClassifications, roundCount, capturedPrNumber, openPrInvoked);
4265
4368
  observeProgressDelivery();
4266
- return addressedResult;
4369
+ return terminalResult;
4267
4370
  } catch (e) {
4268
4371
  if (e instanceof StepTimeoutError) {
4269
4372
  observeOuter('step-timeout', { step: e.step, ms: e.ms, branch, feature: opts.feature.slice(0, 80) }, { level: 'warn' });
@@ -4296,7 +4399,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4296
4399
  if (lastCredentialFailure) {
4297
4400
  observeOuter('credential-failure-observed', { count: credentialFailureCount, ...lastCredentialFailure });
4298
4401
  }
4299
- terminalResult = addressedResult; // ⛔ 이 분기(StepTimeoutError)는 스코프가 달라 부착 헬퍼가 «안 보인다» — 타임아웃 런은 달성 근사값이 «없다»(0 으로 채우지 않는다)
4402
+ terminalResult = withMeasuredRework(addressedResult); // ⛔ 이 분기(StepTimeoutError)는 스코프가 달라 달성 근사값은 «없다»(0 으로 채우지 않는다)
4300
4403
  observeDeclaredScopeDiff(observeOuter, {
4301
4404
  goalFile: opts.goalFile,
4302
4405
  changedFiles: opts.goalFile && addressedResult.worktreePath
@@ -4338,7 +4441,7 @@ export async function runSelfImplement(opts: SelfImplementOptions): Promise<Self
4338
4441
  });
4339
4442
  observeRunRollup(observeOuter, addressedResult, roundClassifications, roundCount, capturedPrNumber, openPrInvoked);
4340
4443
  observeProgressDelivery();
4341
- return addressedResult;
4444
+ return terminalResult;
4342
4445
  }
4343
4446
  recordHitlEvent({
4344
4447
  kind: 'escalation',
@@ -4767,6 +4870,7 @@ async function runSelfImplementInner(
4767
4870
  onWorktreeCreated: (path: string) => void = () => {},
4768
4871
  onRoundExecution: (tier: EscalateTier) => void = () => {},
4769
4872
  onReviewFindings: (round: number, mustFix: readonly string[]) => void = () => {},
4873
+ onReworkMeasured: (rework: ReviewRework) => void = () => {},
4770
4874
  /** ⭐ 이 런이 «따르는» 그래프 신원 — 맥락이 정해진 «직후» 한 번 불린다.
4771
4875
  * ⛔ 이것이 없으면 걸음은 YAML 을 따르고 «신원은 옛 TS 상수»를 따라 둘이 갈린다. */
4772
4876
  onGraphResolved: (identity: { graphId: string; graphVersion: string }) => void = () => {},
@@ -4858,9 +4962,11 @@ async function runSelfImplementInner(
4858
4962
  writeFileSync(graphFile, stringify({
4859
4963
  graph_id: variantSpec.graphId, version: variantSpec.version,
4860
4964
  entry_node: variantSpec.entryNode, terminal_nodes: variantSpec.terminalNodes,
4965
+ ...(variantSpec.journeyStages ? { journey_stages: variantSpec.journeyStages } : {}),
4861
4966
  ...(variantSpec.runContract ? { run_contract: variantSpec.runContract } : {}),
4862
4967
  nodes: variantSpec.nodes.map(n => ({
4863
4968
  node_id: n.nodeId, kind: n.kind, recipe: n.recipe, max_visits: n.maxVisits,
4969
+ ...(n.journeyStage ? { journey_stage: n.journeyStage } : {}),
4864
4970
  ...(n.progress ? { progress: n.progress } : {}), ...(n.phases ? { phases: n.phases } : {}),
4865
4971
  ...(n.terminalStages ? { terminal_stages: n.terminalStages } : {}),
4866
4972
  ...(n.fanOut ? { fan_out: n.fanOut } : {}), ...(n.contract ? { contract: n.contract } : {}),
@@ -4907,12 +5013,12 @@ async function runSelfImplementInner(
4907
5013
  const lastStop = entries?.slice().reverse().find((entry) => entry.event === 'stop');
4908
5014
  if (lastStop && isRunStopClass(lastStop.data.class)) stopClass = readRunStopRecord(lastStop)?.class;
4909
5015
  } catch { /* Unreadable STOP-RECORD is unknown, not an invented class. */ }
4910
- const runtimeOverlayState = {
5016
+ const runtimeOverlayState = buildRuntimeOverlayState({
4911
5017
  attempts: round,
4912
- ...(opts.goalId === undefined ? {} : { goal_id: opts.goalId }),
4913
- ...(stopClass === undefined ? {} : { stop_class: stopClass }),
4914
- ...(reviewRepeat === undefined ? {} : { review_repeat: reviewRepeat }),
4915
- };
5018
+ ...(opts.goalId === undefined ? {} : { goalId: opts.goalId }),
5019
+ ...(stopClass === undefined ? {} : { stopClass }),
5020
+ ...(reviewRepeat === undefined ? {} : { reviewRepeat }),
5021
+ });
4916
5022
  const decision = decideTemplate({
4917
5023
  goalType: graphGoalType,
4918
5024
  authority: graphAuthority,
@@ -5266,6 +5372,8 @@ async function runSelfImplementInner(
5266
5372
  /** Review-budget leftovers and the body that carried them, so a later merge drafts from the same text. */
5267
5373
  let preparedReviewBudgetPrBody: string | undefined;
5268
5374
  const reviewMustFixCountHistory: number[] = [];
5375
+ const reviewedFindings: RecurrenceFinding[][] = [];
5376
+ const structuredRework = (): ReviewRework => measureReviewRework(reviewMustFixCountHistory, reviewedFindings);
5269
5377
  const reviewMustFixTrend = (): string => {
5270
5378
  if (reviewMustFixCountHistory.length < 2) return '아직 못 잰다';
5271
5379
  return reviewMustFixCountHistory.every((count, index) => index === 0 || count < reviewMustFixCountHistory[index - 1]!)
@@ -6777,6 +6885,25 @@ async function runSelfImplementInner(
6777
6885
  ...(repeatedWithRound === undefined ? {} : { repeatedWithRound }), omittedRounds,
6778
6886
  }, { category: 'self-dev.rework' });
6779
6887
  }
6888
+ if (round > 0) {
6889
+ // ⛔ 관측 전용 — 계산은 순수 함수가, 기록은 `observe`(= 런 원장)가 한다. 판정·분기·오버레이 상태는 건드리지 않는다.
6890
+ try {
6891
+ const latestEntry = roundSummaryEntries[roundSummaryEntries.length - 1];
6892
+ const gateRoundIds = latestEntry?.kind === 'gate'
6893
+ ? roundSummaryEntries.filter((entry) => entry.kind === 'gate').map((entry) => entry.failedIds)
6894
+ : [];
6895
+ observe('flex-signals', {
6896
+ round,
6897
+ ...computeFlexSignals({
6898
+ gateFailedIdsByRound: gateRoundIds,
6899
+ failureAttributions: reworkKind === 'gate' ? gate.baselineFailures?.map(({ attribution }) => attribution) : undefined,
6900
+ reviewRepeat,
6901
+ round,
6902
+ plannedRounds: effectiveMax,
6903
+ }),
6904
+ });
6905
+ } catch { /* observation must not interrupt rework */ }
6906
+ }
6780
6907
  const refutationGuidanceEligible = round > 0 && refutableFindings.length > 0;
6781
6908
  const dispatchedRefutationGuidance = refutationGuidancePresented(feature, refutationGuidanceEligible);
6782
6909
  pendingPlanRevision = undefined;
@@ -7518,6 +7645,8 @@ async function runSelfImplementInner(
7518
7645
  });
7519
7646
  if (review.reviewed) {
7520
7647
  reviewMustFixCountHistory.push(review.mustFix.length);
7648
+ reviewedFindings.push(review.mustFix.map((item) => ({ id: stableMustFixId(item), item })));
7649
+ onReworkMeasured(structuredRework());
7521
7650
  // The next runtime overlay sees completed review rounds, not consecutive repeats of one finding.
7522
7651
  reviewRepeat = reviewMustFixCountHistory.length - 1;
7523
7652
  }
@@ -566,13 +566,83 @@ export function queryRunningRuns(options: { includeTest?: boolean; runIds?: read
566
566
  }
567
567
  }
568
568
 
569
- export function renderRunningRuns(result: RunningRunsResult): string {
570
- const lingeringLaunchParents = result.lingeringLaunchParents ?? { observation: 'indeterminate' } as const;
571
- const partial = (result.count !== undefined && typeof result.count !== 'number' && result.count.count === null)
569
+ function isPartialRunningRuns(result: RunningRunsResult): boolean {
570
+ return (result.count !== undefined && typeof result.count !== 'number' && result.count.count === null)
572
571
  || result.completeness === 'partial' || hasUnreadableLedgerObservation(result.ledger) || (result.unreadable?.length ?? 0) > 0;
573
- const lowerBound = result.count !== undefined && typeof result.count !== 'number'
572
+ }
573
+
574
+ function runningRunsLowerBound(result: RunningRunsResult): number {
575
+ return result.count !== undefined && typeof result.count !== 'number'
574
576
  ? result.count.lowerBound : result.counts.running + result.counts['probable-running'];
575
- const unreadable = result.unreadable ?? (result.count !== undefined && typeof result.count !== 'number' ? result.count.unreadable : []);
577
+ }
578
+
579
+ function runningRunsUnreadable(result: RunningRunsResult): readonly UnreadableRunningRunsLedger[] {
580
+ return result.unreadable ?? (result.count !== undefined && typeof result.count !== 'number' ? result.count.unreadable : []);
581
+ }
582
+
583
+ export interface RunProgressRow {
584
+ readonly runId: string;
585
+ readonly status: RunningRunStatus;
586
+ /** null = no phase observed; never rendered as «none». */
587
+ readonly lastPhase: string | null;
588
+ readonly lastPhaseObservedAt: string | null;
589
+ readonly lastActivityTimestamp: string | null;
590
+ /** Milliseconds since lastActivityTimestamp; null when the timestamp is absent or unparseable. */
591
+ readonly elapsedMs: number | null;
592
+ readonly ptyRefs: readonly string[];
593
+ readonly phaseObservation: RunningRunPhaseObservation;
594
+ }
595
+
596
+ export function buildRunProgressRows(result: RunningRunsResult, nowMs: number = Date.now()): RunProgressRow[] {
597
+ return result.entries.map((entry) => {
598
+ const activityMs = entry.lastActivityTimestamp === null ? Number.NaN : Date.parse(entry.lastActivityTimestamp);
599
+ return {
600
+ runId: entry.runId,
601
+ status: entry.status,
602
+ lastPhase: entry.lastPhase ?? null,
603
+ lastPhaseObservedAt: entry.lastPhaseObservedAt ?? null,
604
+ lastActivityTimestamp: entry.lastActivityTimestamp,
605
+ elapsedMs: Number.isFinite(activityMs) ? Math.max(0, nowMs - activityMs) : null,
606
+ ptyRefs: entry.ptyRefs.map((ref) => `${ref.instance}:${ref.id}`),
607
+ phaseObservation: entry.phaseObservation ?? 'not-observed',
608
+ };
609
+ });
610
+ }
611
+
612
+ function formatElapsed(ms: number): string {
613
+ const totalSeconds = Math.floor(ms / 1000);
614
+ const days = Math.floor(totalSeconds / 86_400);
615
+ const hours = Math.floor((totalSeconds % 86_400) / 3_600);
616
+ const minutes = Math.floor((totalSeconds % 3_600) / 60);
617
+ const seconds = totalSeconds % 60;
618
+ if (days > 0) return `${days}d${hours}h`;
619
+ if (hours > 0) return `${hours}h${minutes}m`;
620
+ if (minutes > 0) return `${minutes}m${seconds}s`;
621
+ return `${seconds}s`;
622
+ }
623
+
624
+ export function renderRunProgressTable(result: RunningRunsResult, nowMs: number = Date.now()): string {
625
+ const partial = isPartialRunningRuns(result);
626
+ const header = partial
627
+ ? `부분 관측 — 최소 ${runningRunsLowerBound(result)} (읽지 못한 원장: ${runningRunsUnreadable(result).map((item) => `${item.dir}:${item.reason}`).join(',') || '없음'})`
628
+ : `도는 런 ${result.counts.running + result.counts['probable-running']} (확인 ${result.counts.running} · 추정 ${result.counts['probable-running']})`;
629
+ const rows = buildRunProgressRows(result, nowMs).map((row) => {
630
+ const phaseUnreadable = row.phaseObservation === 'unreadable' || row.phaseObservation === 'partially-unreadable';
631
+ const phase = row.lastPhase ?? (phaseUnreadable ? '미관측 (단계 저장소 읽기 실패)' : '미관측');
632
+ const phaseAt = row.lastPhaseObservedAt ?? '미관측';
633
+ const progress = row.lastActivityTimestamp === null
634
+ ? '미관측'
635
+ : `${row.lastActivityTimestamp} (경과 ${row.elapsedMs === null ? '알 수 없음' : formatElapsed(row.elapsedMs)})`;
636
+ return `${row.runId} · ${row.status} · 단계 ${phase} · 진입 ${phaseAt} · 마지막 진전 ${progress} · pty ${row.ptyRefs.join(',') || '없음'}`;
637
+ });
638
+ return [header, ...rows].join('\n');
639
+ }
640
+
641
+ export function renderRunningRuns(result: RunningRunsResult): string {
642
+ const lingeringLaunchParents = result.lingeringLaunchParents ?? { observation: 'indeterminate' } as const;
643
+ const partial = isPartialRunningRuns(result);
644
+ const lowerBound = runningRunsLowerBound(result);
645
+ const unreadable = runningRunsUnreadable(result);
576
646
  return [
577
647
  partial
578
648
  ? `running runs: unknown (at least ${lowerBound}) unreadable: ${unreadable.map((item) => `${item.dir}:${item.reason}`).join(',')}`