@open-mercato/cezar 0.9.2 → 0.10.0-pr892.1298

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 (269) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +169 -38
  3. package/dist/agent-config/account-identity.d.ts +44 -0
  4. package/dist/agent-config/account-identity.js +128 -0
  5. package/dist/agent-config/account-identity.js.map +1 -0
  6. package/dist/agent-config/catalog.d.ts +8 -0
  7. package/dist/agent-config/catalog.js +18 -0
  8. package/dist/agent-config/catalog.js.map +1 -1
  9. package/dist/agent-config/model-settings/claude.d.ts +2 -0
  10. package/dist/agent-config/model-settings/claude.js +11 -0
  11. package/dist/agent-config/model-settings/claude.js.map +1 -0
  12. package/dist/agent-config/model-settings/codex.d.ts +2 -0
  13. package/dist/agent-config/model-settings/codex.js +16 -0
  14. package/dist/agent-config/model-settings/codex.js.map +1 -0
  15. package/dist/agent-config/model-settings/opencode.d.ts +2 -0
  16. package/dist/agent-config/model-settings/opencode.js +8 -0
  17. package/dist/agent-config/model-settings/opencode.js.map +1 -0
  18. package/dist/agent-config/model-settings/pi.d.ts +12 -0
  19. package/dist/agent-config/model-settings/pi.js +18 -0
  20. package/dist/agent-config/model-settings/pi.js.map +1 -0
  21. package/dist/agent-config/model-settings/shared.d.ts +9 -0
  22. package/dist/agent-config/model-settings/shared.js +93 -0
  23. package/dist/agent-config/model-settings/shared.js.map +1 -0
  24. package/dist/agent-config/model-settings/types.d.ts +9 -0
  25. package/dist/agent-config/model-settings/types.js +2 -0
  26. package/dist/agent-config/model-settings/types.js.map +1 -0
  27. package/dist/agent-config/models.d.ts +9 -0
  28. package/dist/agent-config/models.js +32 -0
  29. package/dist/agent-config/models.js.map +1 -0
  30. package/dist/agent-config/service.js +4 -4
  31. package/dist/agent-config/service.js.map +1 -1
  32. package/dist/automations/types.d.ts +4 -0
  33. package/dist/automations/types.js +2 -1
  34. package/dist/automations/types.js.map +1 -1
  35. package/dist/config.d.ts +10 -1
  36. package/dist/config.js +48 -5
  37. package/dist/config.js.map +1 -1
  38. package/dist/contract/agent-config.d.ts +2 -0
  39. package/dist/contract/agent-profiles.d.ts +347 -0
  40. package/dist/contract/automations.d.ts +24 -0
  41. package/dist/contract/events.d.ts +59 -0
  42. package/dist/contract/github.d.ts +88 -0
  43. package/dist/contract/health.d.ts +7 -1
  44. package/dist/contract/index.d.ts +1 -0
  45. package/dist/contract/index.js +1005 -555
  46. package/dist/contract/projects.d.ts +33 -5
  47. package/dist/contract/runs.d.ts +247 -0
  48. package/dist/contract/skills.d.ts +7 -0
  49. package/dist/contract/workflows.d.ts +6 -0
  50. package/dist/contract/workspace.d.ts +137 -2
  51. package/dist/core/agent-env.js +41 -16
  52. package/dist/core/agent-env.js.map +1 -1
  53. package/dist/core/agent-model-policy.d.ts +10 -0
  54. package/dist/core/agent-model-policy.js +36 -0
  55. package/dist/core/agent-model-policy.js.map +1 -0
  56. package/dist/core/agent-profiles.d.ts +62 -0
  57. package/dist/core/agent-profiles.js +90 -0
  58. package/dist/core/agent-profiles.js.map +1 -0
  59. package/dist/core/agent-runner.d.ts +37 -6
  60. package/dist/core/agent-runner.js +35 -3
  61. package/dist/core/agent-runner.js.map +1 -1
  62. package/dist/core/backend-detect.d.ts +5 -5
  63. package/dist/core/backend-detect.js +44 -7
  64. package/dist/core/backend-detect.js.map +1 -1
  65. package/dist/core/claude-cli-runner.js +10 -5
  66. package/dist/core/claude-cli-runner.js.map +1 -1
  67. package/dist/core/codex-app-server-runner.js +13 -3
  68. package/dist/core/codex-app-server-runner.js.map +1 -1
  69. package/dist/core/codex-app-server-transport.js +7 -2
  70. package/dist/core/codex-app-server-transport.js.map +1 -1
  71. package/dist/core/model-identity.d.ts +23 -11
  72. package/dist/core/model-identity.js +41 -24
  73. package/dist/core/model-identity.js.map +1 -1
  74. package/dist/core/model-presets.d.ts +8 -2
  75. package/dist/core/model-presets.js +51 -8
  76. package/dist/core/model-presets.js.map +1 -1
  77. package/dist/core/opencode-model-catalog.d.ts +32 -0
  78. package/dist/core/opencode-model-catalog.js +153 -0
  79. package/dist/core/opencode-model-catalog.js.map +1 -0
  80. package/dist/core/opencode-server-runner.d.ts +2 -0
  81. package/dist/core/opencode-server-runner.js +42 -11
  82. package/dist/core/opencode-server-runner.js.map +1 -1
  83. package/dist/core/pi-runner.d.ts +24 -0
  84. package/dist/core/pi-runner.js +349 -0
  85. package/dist/core/pi-runner.js.map +1 -0
  86. package/dist/core/pi-ui-mapper.d.ts +34 -0
  87. package/dist/core/pi-ui-mapper.js +270 -0
  88. package/dist/core/pi-ui-mapper.js.map +1 -0
  89. package/dist/core/provider-auth.d.ts +95 -3
  90. package/dist/core/provider-auth.js +234 -15
  91. package/dist/core/provider-auth.js.map +1 -1
  92. package/dist/core/provider-availability.d.ts +1 -1
  93. package/dist/core/provider-availability.js +2 -1
  94. package/dist/core/provider-availability.js.map +1 -1
  95. package/dist/core/runner-factory.js +3 -0
  96. package/dist/core/runner-factory.js.map +1 -1
  97. package/dist/core/shell-env.d.ts +28 -0
  98. package/dist/core/shell-env.js +56 -0
  99. package/dist/core/shell-env.js.map +1 -0
  100. package/dist/core/ui-events.d.ts +1 -1
  101. package/dist/core/usage-limit.d.ts +42 -0
  102. package/dist/core/usage-limit.js +209 -0
  103. package/dist/core/usage-limit.js.map +1 -0
  104. package/dist/git-diff-base.d.ts +51 -26
  105. package/dist/git-diff-base.js +163 -37
  106. package/dist/git-diff-base.js.map +1 -1
  107. package/dist/git-worktree.d.ts +15 -6
  108. package/dist/git-worktree.js +10 -3
  109. package/dist/git-worktree.js.map +1 -1
  110. package/dist/index.js +3 -2
  111. package/dist/index.js.map +1 -1
  112. package/dist/paths.d.ts +53 -5
  113. package/dist/paths.js +63 -9
  114. package/dist/paths.js.map +1 -1
  115. package/dist/planner.js +6 -0
  116. package/dist/planner.js.map +1 -1
  117. package/dist/release/snapshot.d.ts +13 -2
  118. package/dist/release/snapshot.js +25 -1
  119. package/dist/release/snapshot.js.map +1 -1
  120. package/dist/runs/agent-tmpdir.d.ts +41 -0
  121. package/dist/runs/agent-tmpdir.js +183 -0
  122. package/dist/runs/agent-tmpdir.js.map +1 -0
  123. package/dist/runs/auto-name.js +6 -1
  124. package/dist/runs/auto-name.js.map +1 -1
  125. package/dist/runs/event-history.d.ts +50 -0
  126. package/dist/runs/event-history.js +613 -0
  127. package/dist/runs/event-history.js.map +1 -0
  128. package/dist/runs/run-index.d.ts +19 -0
  129. package/dist/runs/run-index.js +43 -0
  130. package/dist/runs/run-index.js.map +1 -0
  131. package/dist/runs/store.d.ts +78 -19
  132. package/dist/runs/store.js +175 -43
  133. package/dist/runs/store.js.map +1 -1
  134. package/dist/server/capabilities.d.ts +15 -1
  135. package/dist/server/capabilities.js +16 -1
  136. package/dist/server/capabilities.js.map +1 -1
  137. package/dist/server/forge/github.d.ts +169 -0
  138. package/dist/server/forge/github.js +511 -0
  139. package/dist/server/forge/github.js.map +1 -1
  140. package/dist/server/forge/index.d.ts +9 -0
  141. package/dist/server/forge/index.js +14 -0
  142. package/dist/server/forge/index.js.map +1 -1
  143. package/dist/server/git-changes.d.ts +7 -5
  144. package/dist/server/git-changes.js +15 -10
  145. package/dist/server/git-changes.js.map +1 -1
  146. package/dist/server/github.d.ts +2 -2
  147. package/dist/server/github.js +1 -1
  148. package/dist/server/github.js.map +1 -1
  149. package/dist/server/open-in-app.d.ts +2 -1
  150. package/dist/server/open-in-app.js +3 -1
  151. package/dist/server/open-in-app.js.map +1 -1
  152. package/dist/server/open-in-terminal.d.ts +46 -1
  153. package/dist/server/open-in-terminal.js +72 -13
  154. package/dist/server/open-in-terminal.js.map +1 -1
  155. package/dist/server/provider-action-gate.js +3 -1
  156. package/dist/server/provider-action-gate.js.map +1 -1
  157. package/dist/server/server.d.ts +2206 -434
  158. package/dist/server/server.js +1219 -136
  159. package/dist/server/server.js.map +1 -1
  160. package/dist/server-install/engine.js +5 -18
  161. package/dist/server-install/engine.js.map +1 -1
  162. package/dist/server-install/steps.js +6 -2
  163. package/dist/server-install/steps.js.map +1 -1
  164. package/dist/skills.js +4 -0
  165. package/dist/skills.js.map +1 -1
  166. package/dist/workflows/run.d.ts +229 -9
  167. package/dist/workflows/run.js +877 -147
  168. package/dist/workflows/run.js.map +1 -1
  169. package/dist/workflows/types.d.ts +3 -0
  170. package/dist/workflows/types.js +11 -2
  171. package/dist/workflows/types.js.map +1 -1
  172. package/dist/workspace/agent-accounts.d.ts +153 -0
  173. package/dist/workspace/agent-accounts.js +304 -0
  174. package/dist/workspace/agent-accounts.js.map +1 -0
  175. package/dist/workspace/agent-profiles.d.ts +78 -0
  176. package/dist/workspace/agent-profiles.js +115 -0
  177. package/dist/workspace/agent-profiles.js.map +1 -0
  178. package/dist/workspace/config.d.ts +27 -1
  179. package/dist/workspace/config.js +83 -2
  180. package/dist/workspace/config.js.map +1 -1
  181. package/dist/workspace/projects-cli.js +54 -4
  182. package/dist/workspace/projects-cli.js.map +1 -1
  183. package/dist/workspace/projects.d.ts +19 -1
  184. package/dist/workspace/projects.js +34 -1
  185. package/dist/workspace/projects.js.map +1 -1
  186. package/dist/workspace/semaphore.d.ts +46 -1
  187. package/dist/workspace/semaphore.js +41 -4
  188. package/dist/workspace/semaphore.js.map +1 -1
  189. package/dist/workspace/ui-state.d.ts +3 -2
  190. package/dist/workspace/ui-state.js +3 -2
  191. package/dist/workspace/ui-state.js.map +1 -1
  192. package/package.json +3 -3
  193. package/scripts/mock-claude.mjs +20 -0
  194. package/scripts/mock-pi-rpc.mjs +83 -0
  195. package/web/dist/assets/{alert-dialog-ghffK6g9.js → alert-dialog-Byp42_IG.js} +1 -1
  196. package/web/dist/assets/arrow-down-CGDbDYKD.js +1 -0
  197. package/web/dist/assets/arrow-left-ESCjr1Hq.js +1 -0
  198. package/web/dist/assets/centered-state-LfAW-igm.js +43 -0
  199. package/web/dist/assets/chevron-right-Du-P1IUd.js +1 -0
  200. package/web/dist/assets/{collapsible-B0JVzd0B.js → collapsible-CBiiqDJM.js} +1 -1
  201. package/web/dist/assets/{commit-list-wm6DPHNs.js → commit-list-6PPve7PE.js} +1 -1
  202. package/web/dist/assets/{compare-variants-DbdP4Y5T.js → compare-variants-BDnp6mXJ.js} +1 -1
  203. package/web/dist/assets/{diff-Bizc96qe.js → diff-_jK0OWpF.js} +2 -2
  204. package/web/dist/assets/{diff-stat-Dyfg3ltF.js → diff-stat-CIrTu4Ym.js} +1 -1
  205. package/web/dist/assets/{diff-view-CG8ek6tC.js → diff-view-COA_5IyY.js} +1 -1
  206. package/web/dist/assets/dropdown-menu-BofYpFFw.js +1 -0
  207. package/web/dist/assets/editable-title-_J0zFDl-.js +1 -0
  208. package/web/dist/assets/{ellipsis-vertical-DpGTRqC2.js → ellipsis-vertical-BeMR-l62.js} +1 -1
  209. package/web/dist/assets/{file-DlUGpUlb.js → file-DY996Pee.js} +1 -1
  210. package/web/dist/assets/{folder-BAY6Vddy.js → folder-NO3Ec-TY.js} +1 -1
  211. package/web/dist/assets/git-toolbar-BqJeUWcD.js +1 -0
  212. package/web/dist/assets/github-C96HPV9E.js +1 -0
  213. package/web/dist/assets/{image-preview-BWGk5fPX.js → image-preview-D9cCDfKQ.js} +1 -1
  214. package/web/dist/assets/index-Bx-grqa9.css +2 -0
  215. package/web/dist/assets/index-DoVvGDfW.js +7 -0
  216. package/web/dist/assets/{markdown-BGBOGCA_.js → markdown-DDzk11zj.js} +1 -1
  217. package/web/dist/assets/new-task-form-BNLLgC6P.js +1 -0
  218. package/web/dist/assets/pill-CVM1EmV4.js +1 -0
  219. package/web/dist/assets/{project-router-Cx8NPoqn.js → project-router-LRCtP48W.js} +1 -1
  220. package/web/dist/assets/prompt-templates-DFOvYgtW.js +15 -0
  221. package/web/dist/assets/{refresh-cw-BoGdAykg.js → refresh-cw-D1ys6QY2.js} +1 -1
  222. package/web/dist/assets/repo-git-CzUnsgWY.js +1 -0
  223. package/web/dist/assets/{run-diff-EcOkf3ti.js → run-diff-BlnqwfSS.js} +2 -2
  224. package/web/dist/assets/run-header-Bn7cN0Z1.js +1 -0
  225. package/web/dist/assets/{search-x-X3EpVhgs.js → search-x-D6FSpxId.js} +1 -1
  226. package/web/dist/assets/{skill-empty-hint-CZLALx6l.js → skill-empty-hint-iwpCcqgI.js} +1 -1
  227. package/web/dist/assets/skills-CezbOaol.js +1 -0
  228. package/web/dist/assets/{sparkles-Dt4q7pbG.js → sparkles-MrQrgCI9.js} +1 -1
  229. package/web/dist/assets/{square-terminal-hwBL8XsC.js → square-terminal-TTH64lO7.js} +1 -1
  230. package/web/dist/assets/tab-link-FYksz037.js +1 -0
  231. package/web/dist/assets/task-changes-ClWlnzBL.js +1 -0
  232. package/web/dist/assets/{task-commits-BTTA5k5T.js → task-commits-Crbrl86b.js} +1 -1
  233. package/web/dist/assets/{task-files-Da8_4C9g.js → task-files-uSCWyD0-.js} +2 -2
  234. package/web/dist/assets/task-thread-DPmamBA1.js +9 -0
  235. package/web/dist/assets/{textarea-CUdokhkv.js → textarea-B9Ahj4E4.js} +1 -1
  236. package/web/dist/assets/thread-loading-BH_ypQoy.js +1 -0
  237. package/web/dist/assets/{trash-2-DhVIdWv8.js → trash-2-D8S3V3fu.js} +1 -1
  238. package/web/dist/assets/{triangle-alert-XYT0DGCI.js → triangle-alert-bB7p9n5E.js} +1 -1
  239. package/web/dist/assets/{upload-BKjN59-1.js → upload-Bd5oRLPV.js} +1 -1
  240. package/web/dist/assets/{use-desktop-Bf5LS_P4.js → use-desktop-BNvbRBeZ.js} +1 -1
  241. package/web/dist/assets/{use-submit-shortcut-CzVyoAMd.js → use-submit-shortcut-B-xhu4Bf.js} +1 -1
  242. package/web/dist/assets/utils-BhpbVMwN.js +64 -0
  243. package/web/dist/assets/{workflows-o2QU_pvU.js → workflows-RmfbM9Vy.js} +3 -3
  244. package/web/dist/assets/{zoomable-image-7WyUiaVD.js → zoomable-image-BCW6ZBV0.js} +1 -1
  245. package/web/dist/index.html +24 -23
  246. package/web/dist/assets/arrow-down-Cz8qb71e.js +0 -1
  247. package/web/dist/assets/arrow-left-wcYKQXI_.js +0 -1
  248. package/web/dist/assets/centered-state-ZtnFHA-n.js +0 -43
  249. package/web/dist/assets/chevron-right-G4caF2QV.js +0 -1
  250. package/web/dist/assets/circle-check--KvBxXWp.js +0 -1
  251. package/web/dist/assets/circle-x-C3EFZ3xf.js +0 -1
  252. package/web/dist/assets/dropdown-menu-C1Dlf35P.js +0 -1
  253. package/web/dist/assets/editable-title-5rC_QRBZ.js +0 -1
  254. package/web/dist/assets/git-pull-request-agOG0iGM.js +0 -1
  255. package/web/dist/assets/git-toolbar-DGiNMmtY.js +0 -1
  256. package/web/dist/assets/github-Vp7lxYGQ.js +0 -1
  257. package/web/dist/assets/index-DSxyJpuK.css +0 -2
  258. package/web/dist/assets/index-DiawI6RX.js +0 -6
  259. package/web/dist/assets/new-task-form-BpAw_Dks.js +0 -1
  260. package/web/dist/assets/pill-Dvs-P0xy.js +0 -1
  261. package/web/dist/assets/prompt-templates-CSBYBR1I.js +0 -15
  262. package/web/dist/assets/repo-git-PmX-BNMo.js +0 -1
  263. package/web/dist/assets/run-header-BGiv6_qX.js +0 -1
  264. package/web/dist/assets/skills-CC91ouGH.js +0 -1
  265. package/web/dist/assets/tab-link-DIlR5T07.js +0 -1
  266. package/web/dist/assets/task-changes-XkHDxzQA.js +0 -1
  267. package/web/dist/assets/task-thread-DoNXUsHr.js +0 -9
  268. package/web/dist/assets/thread-loading-B8q8ukXJ.js +0 -1
  269. package/web/dist/assets/utils-DsLuRXwV.js +0 -64
@@ -0,0 +1,209 @@
1
+ /**
2
+ * Provider usage-limit detection (spec 2026-08-03-auto-resume-after-usage-limit).
3
+ *
4
+ * A subscription that runs out of its window does not fail like an agent bug does: the work is
5
+ * fine, the account is simply closed until a KNOWN instant. That instant is the whole feature —
6
+ * without it there is nothing to schedule, so this module answers exactly one question about a
7
+ * terminal error string: "is this a usage limit, and when does it lift?".
8
+ *
9
+ * The evidence differs per backend, so the shapes are recognized in order of how exact they are:
10
+ *
11
+ * 1. Claude Code's machine-readable envelope, `Claude AI usage limit reached|<epoch>` — the CLI
12
+ * puts it in an `is_error` result frame, which reaches cezar verbatim as the run's `error`.
13
+ * Exact, no locale, no parsing of prose. This is the one that matters in practice.
14
+ * 2. An explicit reset instant in the prose (`try again at 2026-08-03T18:00:00Z`) — how Codex and
15
+ * OpenCode phrase the same thing when they carry a timestamp at all.
16
+ * 3. A clock-only reset in prose (`resets 8:10pm (Europe/Warsaw)`) — how Claude Code phrases
17
+ * session windows in some interactive output.
18
+ * 4. A relative delay (`try again in 42 minutes`, `retry-after: 3600`).
19
+ *
20
+ * Nothing else counts. A limit message with no recoverable reset instant returns `null` on
21
+ * purpose: guessing a window would turn one interruption into a retry loop against a provider
22
+ * that is still refusing, and "we don't know when" is an honest answer the caller can surface.
23
+ */
24
+ /**
25
+ * The furthest ahead a reset may sit and still be believed. Claude's weekly window is the real
26
+ * ceiling; anything beyond a week is a corrupt or unit-confused number, and parking a task on it
27
+ * would be indistinguishable from losing the task.
28
+ */
29
+ export const MAX_USAGE_LIMIT_WAIT_MS = 7 * 24 * 60 * 60_000;
30
+ /** Claude Code's own envelope. The suffix is Unix EPOCH SECONDS (ms tolerated — see below). */
31
+ const CLAUDE_MARKER_RE = /claude(?:\s+ai)?\s+usage\s+limit\s+reached\s*\|\s*(\d{9,16})/i;
32
+ /**
33
+ * "This is a limit, not a crash." Deliberately narrow: it gates the two prose shapes below, and a
34
+ * false positive there would schedule a resume on a timestamp that means something else entirely.
35
+ */
36
+ const LIMIT_PHRASE_RE = /\b(?:usage|rate|session|weekly|hourly)[\s-]?limit\b|\brate[_-]?limit(?:_error|ed)?\b|\bquota\s+(?:exceeded|reached)\b|\bout\s+of\s+(?:credits|quota)\b/i;
37
+ /** `…try again at 2026-08-03T18:00:00Z`, `…resets at 2026-08-03 18:00`. */
38
+ const RESET_AT_RE = /(?:resets?|reset[s]?\s+at|try\s+again|retry|available\s+again|unlocks?)\b[^\n]{0,24}?\b(\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2})?(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})?)?)/i;
39
+ /** `...resets 8:10pm (Europe/Warsaw)`, `...try again at 20:10`. */
40
+ const RESET_CLOCK_RE = /(?:resets?|reset[s]?\s+at|try\s+again|retry|available\s+again|unlocks?)\b[^\n]{0,24}?\b(?:at\s*)?(\d{1,2})(?:(?::(\d{2}))\s*([ap]\.?m\.?)?|\s*([ap]\.?m\.?))(?:\s*\(([^)]+)\))?/i;
41
+ /** `…try again in 42 minutes`, `…retry after 3600 seconds`, `retry-after: 3600`. */
42
+ const RESET_IN_RE = /(?:try\s+again|retry|resets?|available\s+again)\b[^\n]{0,16}?\b(?:in|after)\s+(\d{1,7})\s*(seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)\b/i;
43
+ const RETRY_AFTER_HEADER_RE = /\bretry[-_\s]?after\b\s*[:=]?\s*(\d{1,7})\b/i;
44
+ const UNIT_MS = {
45
+ s: 1_000, sec: 1_000, secs: 1_000, second: 1_000, seconds: 1_000,
46
+ m: 60_000, min: 60_000, mins: 60_000, minute: 60_000, minutes: 60_000,
47
+ h: 3_600_000, hr: 3_600_000, hrs: 3_600_000, hour: 3_600_000, hours: 3_600_000,
48
+ };
49
+ /**
50
+ * Read a usage limit out of a terminal error message, or `null` when it is not one (or carries no
51
+ * usable reset instant). `now` is injected so callers and tests share one clock.
52
+ *
53
+ * Never throws: this runs on the failure path of every run, where a second failure helps nobody.
54
+ */
55
+ export function parseUsageLimit(message, now = Date.now()) {
56
+ if (!message)
57
+ return null;
58
+ const marker = CLAUDE_MARKER_RE.exec(message);
59
+ if (marker) {
60
+ const raw = Number(marker[1]);
61
+ // Epoch seconds is the documented unit; a value large enough to only make sense as
62
+ // milliseconds is accepted rather than parked ~50,000 years out.
63
+ const epochMs = raw >= 1e12 ? raw : raw * 1_000;
64
+ return settle(epochMs, now, 'claude-marker');
65
+ }
66
+ if (!LIMIT_PHRASE_RE.test(message))
67
+ return null;
68
+ const at = RESET_AT_RE.exec(message);
69
+ if (at) {
70
+ // A bare date, or `YYYY-MM-DD HH:MM` without a zone, is read in the host's local time — the
71
+ // provider is talking to the person at this machine, and `Date.parse` would otherwise read the
72
+ // space-separated form as UTC on some runtimes and local on others. A date with no time at all
73
+ // needs the midnight spelled out for the same reason: the ECMAScript date-ONLY form is defined
74
+ // as UTC, so `2026-08-03` alone would land up to a day away from the day the provider named.
75
+ const stamp = at[1].includes('T') ? at[1] : at[1].replace(' ', 'T');
76
+ const parsed = Date.parse(stamp.includes('T') ? stamp : `${stamp}T00:00`);
77
+ if (Number.isFinite(parsed))
78
+ return settle(parsed, now, 'timestamp');
79
+ }
80
+ const clock = RESET_CLOCK_RE.exec(message);
81
+ if (clock) {
82
+ const parsed = parseClockReset(clock, now);
83
+ if (parsed !== null)
84
+ return settle(parsed, now, 'clock');
85
+ }
86
+ const relative = RESET_IN_RE.exec(message);
87
+ if (relative) {
88
+ const unit = UNIT_MS[relative[2].toLowerCase()];
89
+ if (unit !== undefined)
90
+ return settle(now + Number(relative[1]) * unit, now, 'delay');
91
+ }
92
+ const header = RETRY_AFTER_HEADER_RE.exec(message);
93
+ if (header)
94
+ return settle(now + Number(header[1]) * 1_000, now, 'delay');
95
+ return null;
96
+ }
97
+ function parseClockReset(match, now) {
98
+ const hourRaw = Number(match[1]);
99
+ const minute = match[2] === undefined ? 0 : Number(match[2]);
100
+ const meridiem = (match[3] ?? match[4])?.toLowerCase().replaceAll('.', '');
101
+ if (!Number.isInteger(hourRaw) || !Number.isInteger(minute) || minute < 0 || minute > 59) {
102
+ return null;
103
+ }
104
+ let hour = hourRaw;
105
+ if (meridiem) {
106
+ if (hour < 1 || hour > 12)
107
+ return null;
108
+ if (meridiem === 'am')
109
+ hour = hour === 12 ? 0 : hour;
110
+ else if (meridiem === 'pm')
111
+ hour = hour === 12 ? 12 : hour + 12;
112
+ else
113
+ return null;
114
+ }
115
+ else if (hour < 0 || hour > 23) {
116
+ return null;
117
+ }
118
+ const timeZone = validTimeZone(match[5]?.trim());
119
+ if (timeZone)
120
+ return nextClockInTimeZone(hour, minute, timeZone, now);
121
+ return nextLocalClock(hour, minute, now);
122
+ }
123
+ function nextLocalClock(hour, minute, now) {
124
+ const candidate = new Date(now);
125
+ candidate.setHours(hour, minute, 0, 0);
126
+ if (candidate.getTime() < now)
127
+ candidate.setDate(candidate.getDate() + 1);
128
+ return candidate.getTime();
129
+ }
130
+ function nextClockInTimeZone(hour, minute, timeZone, now) {
131
+ const today = zonedParts(now, timeZone);
132
+ if (!today)
133
+ return null;
134
+ let candidate = zonedWallTimeToUtc(today.year, today.month, today.day, hour, minute, timeZone);
135
+ if (candidate === null)
136
+ return null;
137
+ if (candidate < now) {
138
+ const tomorrow = new Date(Date.UTC(today.year, today.month - 1, today.day + 1));
139
+ candidate = zonedWallTimeToUtc(tomorrow.getUTCFullYear(), tomorrow.getUTCMonth() + 1, tomorrow.getUTCDate(), hour, minute, timeZone);
140
+ }
141
+ return candidate;
142
+ }
143
+ function validTimeZone(candidate) {
144
+ if (!candidate)
145
+ return null;
146
+ try {
147
+ new Intl.DateTimeFormat('en-US', { timeZone: candidate }).format(new Date(0));
148
+ return candidate;
149
+ }
150
+ catch {
151
+ return null;
152
+ }
153
+ }
154
+ function zonedWallTimeToUtc(year, month, day, hour, minute, timeZone) {
155
+ const wallAsUtc = Date.UTC(year, month - 1, day, hour, minute, 0, 0);
156
+ let candidate = wallAsUtc;
157
+ for (let i = 0; i < 3; i += 1) {
158
+ const offset = timeZoneOffsetMs(timeZone, candidate);
159
+ if (offset === null)
160
+ return null;
161
+ const next = wallAsUtc - offset;
162
+ if (Math.abs(next - candidate) < 1_000)
163
+ return next;
164
+ candidate = next;
165
+ }
166
+ return candidate;
167
+ }
168
+ function timeZoneOffsetMs(timeZone, utcMs) {
169
+ const parts = zonedParts(utcMs, timeZone);
170
+ if (!parts)
171
+ return null;
172
+ const asUtc = Date.UTC(parts.year, parts.month - 1, parts.day, parts.hour, parts.minute, parts.second);
173
+ return asUtc - utcMs;
174
+ }
175
+ function zonedParts(utcMs, timeZone) {
176
+ try {
177
+ const parts = new Intl.DateTimeFormat('en-CA', {
178
+ timeZone,
179
+ year: 'numeric',
180
+ month: '2-digit',
181
+ day: '2-digit',
182
+ hour: '2-digit',
183
+ minute: '2-digit',
184
+ second: '2-digit',
185
+ hourCycle: 'h23',
186
+ }).formatToParts(new Date(utcMs));
187
+ const value = (type) => Number(parts.find((part) => part.type === type)?.value);
188
+ return {
189
+ year: value('year'),
190
+ month: value('month'),
191
+ day: value('day'),
192
+ hour: value('hour'),
193
+ minute: value('minute'),
194
+ second: value('second'),
195
+ };
196
+ }
197
+ catch {
198
+ return null;
199
+ }
200
+ }
201
+ /** Bound a candidate instant: past → now (the limit already lifted), absurd → not believed. */
202
+ function settle(epochMs, now, evidence) {
203
+ if (!Number.isFinite(epochMs))
204
+ return null;
205
+ if (epochMs - now > MAX_USAGE_LIMIT_WAIT_MS)
206
+ return null;
207
+ return { resetAt: new Date(Math.max(epochMs, now)), evidence };
208
+ }
209
+ //# sourceMappingURL=usage-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage-limit.js","sourceRoot":"","sources":["../../src/core/usage-limit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AASH;;;;GAIG;AACH,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,MAAM,CAAC;AAE5D,+FAA+F;AAC/F,MAAM,gBAAgB,GAAG,+DAA+D,CAAC;AAEzF;;;GAGG;AACH,MAAM,eAAe,GACnB,yJAAyJ,CAAC;AAE5J,2EAA2E;AAC3E,MAAM,WAAW,GACf,6KAA6K,CAAC;AAEhL,mEAAmE;AACnE,MAAM,cAAc,GAClB,kLAAkL,CAAC;AAErL,oFAAoF;AACpF,MAAM,WAAW,GACf,gJAAgJ,CAAC;AACnJ,MAAM,qBAAqB,GAAG,8CAA8C,CAAC;AAE7E,MAAM,OAAO,GAA2B;IACtC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK;IAChE,CAAC,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;IACrE,CAAC,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,SAAS;CAC/E,CAAC;AAEF;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,OAA2B,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC3E,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,MAAM,GAAG,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC9C,IAAI,MAAM,EAAE,CAAC;QACX,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9B,mFAAmF;QACnF,iEAAiE;QACjE,MAAM,OAAO,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,CAAC;QAChD,OAAO,MAAM,CAAC,OAAO,EAAE,GAAG,EAAE,eAAe,CAAC,CAAC;IAC/C,CAAC;IACD,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAEhD,MAAM,EAAE,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,EAAE,CAAC;QACP,4FAA4F;QAC5F,+FAA+F;QAC/F,+FAA+F;QAC/F,+FAA+F;QAC/F,6FAA6F;QAC7F,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC,CAAE,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACvE,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC;QAC1E,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,WAAW,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,KAAK,GAAG,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,CAAC,CAAC;QACjD,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,MAAM,GAAG,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,MAAM;QAAE,OAAO,MAAM,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,KAAK,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;IAEzE,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,eAAe,CAAC,KAAsB,EAAE,GAAW;IAC1D,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACjC,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IAC3E,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC,IAAI,MAAM,GAAG,EAAE,EAAE,CAAC;QACzF,OAAO,IAAI,CAAC;IACd,CAAC;IACD,IAAI,IAAI,GAAG,OAAO,CAAC;IACnB,IAAI,QAAQ,EAAE,CAAC;QACb,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE;YAAE,OAAO,IAAI,CAAC;QACvC,IAAI,QAAQ,KAAK,IAAI;YAAE,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;aAChD,IAAI,QAAQ,KAAK,IAAI;YAAE,IAAI,GAAG,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;;YAC3D,OAAO,IAAI,CAAC;IACnB,CAAC;SAAM,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,EAAE,EAAE,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,QAAQ;QAAE,OAAO,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,CAAC;IACtE,OAAO,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC;AAC3C,CAAC;AAED,SAAS,cAAc,CAAC,IAAY,EAAE,MAAc,EAAE,GAAW;IAC/D,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC;IAChC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACvC,IAAI,SAAS,CAAC,OAAO,EAAE,GAAG,GAAG;QAAE,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;IAC1E,OAAO,SAAS,CAAC,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED,SAAS,mBAAmB,CAAC,IAAY,EAAE,MAAc,EAAE,QAAgB,EAAE,GAAW;IACtF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACxC,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,SAAS,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC/F,IAAI,SAAS,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACpC,IAAI,SAAS,GAAG,GAAG,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAChF,SAAS,GAAG,kBAAkB,CAC5B,QAAQ,CAAC,cAAc,EAAE,EACzB,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC,EAC1B,QAAQ,CAAC,UAAU,EAAE,EACrB,IAAI,EACJ,MAAM,EACN,QAAQ,CACT,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,aAAa,CAAC,SAA6B;IAClD,IAAI,CAAC,SAAS;QAAE,OAAO,IAAI,CAAC;IAC5B,IAAI,CAAC;QACH,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9E,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,kBAAkB,CACzB,IAAY,EACZ,KAAa,EACb,GAAW,EACX,IAAY,EACZ,MAAc,EACd,QAAgB;IAEhB,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IACrE,IAAI,SAAS,GAAG,SAAS,CAAC;IAC1B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACrD,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QACjC,MAAM,IAAI,GAAG,SAAS,GAAG,MAAM,CAAC;QAChC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,SAAS,CAAC,GAAG,KAAK;YAAE,OAAO,IAAI,CAAC;QACpD,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,SAAS,gBAAgB,CAAC,QAAgB,EAAE,KAAa;IACvD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC1C,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvG,OAAO,KAAK,GAAG,KAAK,CAAC;AACvB,CAAC;AAED,SAAS,UAAU,CACjB,KAAa,EACb,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,cAAc,CAAC,OAAO,EAAE;YAC7C,QAAQ;YACR,IAAI,EAAE,SAAS;YACf,KAAK,EAAE,SAAS;YAChB,GAAG,EAAE,SAAS;YACd,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,SAAS;YACjB,MAAM,EAAE,SAAS;YACjB,SAAS,EAAE,KAAK;SACjB,CAAC,CAAC,aAAa,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QAClC,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;QACxF,OAAO;YACL,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC;YACrB,GAAG,EAAE,KAAK,CAAC,KAAK,CAAC;YACjB,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC;YACnB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;YACvB,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC;SACxB,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,+FAA+F;AAC/F,SAAS,MAAM,CAAC,OAAe,EAAE,GAAW,EAAE,QAAmC;IAC/E,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3C,IAAI,OAAO,GAAG,GAAG,GAAG,uBAAuB;QAAE,OAAO,IAAI,CAAC;IACzD,OAAO,EAAE,OAAO,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;AACjE,CAAC"}
@@ -6,22 +6,47 @@
6
6
  * to the task's own commits even after the base branch moves on, and even
7
7
  * after the task merges the base back in.
8
8
  *
9
- * The exception is a **repointed HEAD**. cezar hands the agent a worktree on
10
- * the task's own branch, but nothing stops the agent from checking out some
11
- * other branch in it — every `review/pr-NNN`, QA, and "continue someone
12
- * else's branch" run does exactly that. Anchoring at the merge-base then
13
- * silently redefines "this task's diff" as *the whole reviewed branch*, which
14
- * is how a task that committed nothing came to report `+22505 −2628`. When
15
- * HEAD is not on the task's branch the honest anchor is `HEAD` itself: the
16
- * uncommitted work in the tree is the only part this task can be said to have
17
- * produced.
18
- *
19
- * #591 established that rule for the Changes tab; #751 found the sidebar and
20
- * Tasks-table numbers still on the old anchor. It lives here so a third
21
- * surface cannot get it wrong again — callers hand in their own `git` runner
22
- * (`git-worktree.ts` and `server/git-changes.ts` each own a private one, and
23
- * `git-changes` needs its scratch-`GIT_INDEX_FILE` variant), so this module
24
- * stays a pure decision with no process-spawning of its own.
9
+ * Two things break that answer, and both are fixed here:
10
+ *
11
+ * **A stale local base ref.** `RunRecord.baseBranch` is a NAME (`main`),
12
+ * resolved to a ref once, when the worktree was created. Nothing ever
13
+ * fast-forwards the user's local `main` — cezar's agents only ever `git fetch`,
14
+ * which moves `origin/main` — so on a repo the user does not pull, the local
15
+ * ref drifts arbitrarily far behind. The merge-base then collapses onto that
16
+ * stale tip and every upstream commit the task forked from or merged in counts
17
+ * as the task's own work: an eight-line fix measured `+59514 −12160` against a
18
+ * `main` that was 98 commits and one monorepo restructure behind origin. So the
19
+ * base is re-resolved to the freshest ref it names at every call
20
+ * (`freshestBaseRef`) — the read-time twin of `resolveBaseRef`, which does the
21
+ * same thing at worktree-creation time and cannot know what happens later.
22
+ *
23
+ * **A repointed HEAD.** cezar hands the agent a worktree on the task's own
24
+ * branch, but nothing stops the agent from checking out another branch in it —
25
+ * every `review/pr-NNN` and QA run does exactly that, and so does every skill
26
+ * that opens its work on a named `feat/…` branch. The merge-base then silently
27
+ * redefines "this task's diff" as *the whole checked-out branch*, which is how
28
+ * a task that committed nothing came to report `+22505 −2628`. #591 and #751
29
+ * answered that with `HEAD` — uncommitted work only — which is right for a
30
+ * pure review and badly wrong for the far more common case of an agent that
31
+ * committed real work on a branch it created itself: those runs reported
32
+ * `+0 −0` for changes that were genuinely theirs.
33
+ *
34
+ * The honest anchor for a repointed HEAD is the branch as it stood **when this
35
+ * run first saw it** — its `<branch>@{<run start>}` reflog state. Diffing
36
+ * against that attributes exactly what the run did to the tree and nothing
37
+ * that was already there: a review run gets its `+0`, and a run that branched
38
+ * off the base and committed gets its real numbers. Where that baseline is
39
+ * itself stale — the run merged the base branch in afterwards, dragging the
40
+ * whole upstream delta behind it — the ordinary merge-base is the tighter
41
+ * answer, so the two candidates compete and the one that attributes FEWER
42
+ * CHANGED LINES to the task wins. The reported number is then never more than
43
+ * the least either measure can defend.
44
+ *
45
+ * It lives here so a third surface cannot get it wrong again — callers hand in
46
+ * their own `git` runner (`git-worktree.ts` and `server/git-changes.ts` each
47
+ * own a private one, and `git-changes` needs its scratch-`GIT_INDEX_FILE`
48
+ * variant), so this module stays a pure decision with no process-spawning of
49
+ * its own.
25
50
  */
26
51
  /** What a caller's `git` runner must answer with. Both existing runners are wider than this. */
27
52
  export interface GitRunResult {
@@ -38,23 +63,23 @@ export interface RepointedHead {
38
63
  export interface TaskDiffBase {
39
64
  /** The ref to diff against. */
40
65
  base: string;
41
- /** Present only when HEAD left the task's branch — the reason `base` is `HEAD`. */
66
+ /** Present only when HEAD left the task's branch — the reason `base` is what it is. */
42
67
  repointedHead?: RepointedHead;
43
68
  }
44
69
  /**
45
- * Resolve the diff anchor for a task worktree. Never throws: a failing git
46
- * call degrades to the base branch name, which is what the un-guarded callers
47
- * did before this helper existed.
70
+ * Resolve the diff anchor for a task worktree. Never throws: a failing git call
71
+ * degrades to the base branch name, which is what the un-guarded callers did
72
+ * before this helper existed.
48
73
  *
49
74
  * `taskBranch` is optional because not every caller has one (the main working
50
- * tree has no task branch at all). Without it there is nothing to compare
51
- * HEAD against, so the merge-base anchor is used unchanged — that is the
52
- * pre-#751 behavior, preserved exactly.
75
+ * tree has no task branch at all). Without it there is nothing to compare HEAD
76
+ * against, so the merge-base anchor is used unchanged.
53
77
  *
54
- * A detached HEAD (`rev-parse --abbrev-ref HEAD` → `HEAD`) counts as
55
- * repointed: it is by definition not the task's branch, so the same
56
- * uncommitted-only anchor is the honest one.
78
+ * `runStartedAt` is what makes the repointed-HEAD answer a measurement instead
79
+ * of a guess; without it the anchor stays `HEAD` (uncommitted work only), the
80
+ * conservative #751 answer that can never claim someone else's commits.
57
81
  */
58
82
  export declare function resolveTaskDiffBase(runGit: GitRunner, baseBranch: string, opts?: {
59
83
  taskBranch?: string;
84
+ runStartedAt?: string;
60
85
  }): Promise<TaskDiffBase>;
@@ -6,49 +6,175 @@
6
6
  * to the task's own commits even after the base branch moves on, and even
7
7
  * after the task merges the base back in.
8
8
  *
9
- * The exception is a **repointed HEAD**. cezar hands the agent a worktree on
10
- * the task's own branch, but nothing stops the agent from checking out some
11
- * other branch in it — every `review/pr-NNN`, QA, and "continue someone
12
- * else's branch" run does exactly that. Anchoring at the merge-base then
13
- * silently redefines "this task's diff" as *the whole reviewed branch*, which
14
- * is how a task that committed nothing came to report `+22505 −2628`. When
15
- * HEAD is not on the task's branch the honest anchor is `HEAD` itself: the
16
- * uncommitted work in the tree is the only part this task can be said to have
17
- * produced.
18
- *
19
- * #591 established that rule for the Changes tab; #751 found the sidebar and
20
- * Tasks-table numbers still on the old anchor. It lives here so a third
21
- * surface cannot get it wrong again — callers hand in their own `git` runner
22
- * (`git-worktree.ts` and `server/git-changes.ts` each own a private one, and
23
- * `git-changes` needs its scratch-`GIT_INDEX_FILE` variant), so this module
24
- * stays a pure decision with no process-spawning of its own.
9
+ * Two things break that answer, and both are fixed here:
10
+ *
11
+ * **A stale local base ref.** `RunRecord.baseBranch` is a NAME (`main`),
12
+ * resolved to a ref once, when the worktree was created. Nothing ever
13
+ * fast-forwards the user's local `main` — cezar's agents only ever `git fetch`,
14
+ * which moves `origin/main` — so on a repo the user does not pull, the local
15
+ * ref drifts arbitrarily far behind. The merge-base then collapses onto that
16
+ * stale tip and every upstream commit the task forked from or merged in counts
17
+ * as the task's own work: an eight-line fix measured `+59514 −12160` against a
18
+ * `main` that was 98 commits and one monorepo restructure behind origin. So the
19
+ * base is re-resolved to the freshest ref it names at every call
20
+ * (`freshestBaseRef`) — the read-time twin of `resolveBaseRef`, which does the
21
+ * same thing at worktree-creation time and cannot know what happens later.
22
+ *
23
+ * **A repointed HEAD.** cezar hands the agent a worktree on the task's own
24
+ * branch, but nothing stops the agent from checking out another branch in it —
25
+ * every `review/pr-NNN` and QA run does exactly that, and so does every skill
26
+ * that opens its work on a named `feat/…` branch. The merge-base then silently
27
+ * redefines "this task's diff" as *the whole checked-out branch*, which is how
28
+ * a task that committed nothing came to report `+22505 −2628`. #591 and #751
29
+ * answered that with `HEAD` — uncommitted work only — which is right for a
30
+ * pure review and badly wrong for the far more common case of an agent that
31
+ * committed real work on a branch it created itself: those runs reported
32
+ * `+0 −0` for changes that were genuinely theirs.
33
+ *
34
+ * The honest anchor for a repointed HEAD is the branch as it stood **when this
35
+ * run first saw it** — its `<branch>@{<run start>}` reflog state. Diffing
36
+ * against that attributes exactly what the run did to the tree and nothing
37
+ * that was already there: a review run gets its `+0`, and a run that branched
38
+ * off the base and committed gets its real numbers. Where that baseline is
39
+ * itself stale — the run merged the base branch in afterwards, dragging the
40
+ * whole upstream delta behind it — the ordinary merge-base is the tighter
41
+ * answer, so the two candidates compete and the one that attributes FEWER
42
+ * CHANGED LINES to the task wins. The reported number is then never more than
43
+ * the least either measure can defend.
44
+ *
45
+ * It lives here so a third surface cannot get it wrong again — callers hand in
46
+ * their own `git` runner (`git-worktree.ts` and `server/git-changes.ts` each
47
+ * own a private one, and `git-changes` needs its scratch-`GIT_INDEX_FILE`
48
+ * variant), so this module stays a pure decision with no process-spawning of
49
+ * its own.
50
+ */
51
+ import { isSafeGitRef } from './git-refs.js';
52
+ /**
53
+ * ISO-8601 instants only, for the `<branch>@{<date>}` revision below. The
54
+ * timestamp comes from a stored run record, and a git revision expression is
55
+ * the one place a stray value would be interpreted rather than compared —
56
+ * `@{-1}` is "the previously checked-out branch", not a date. Anything that is
57
+ * not plainly a timestamp simply disables the baseline anchor.
25
58
  */
59
+ const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
26
60
  /**
27
- * Resolve the diff anchor for a task worktree. Never throws: a failing git
28
- * call degrades to the base branch name, which is what the un-guarded callers
29
- * did before this helper existed.
61
+ * The freshest ref the configured base branch names: `origin/<base>` when the
62
+ * local branch is behind it (or missing entirely), the local branch otherwise.
63
+ *
64
+ * Same rule as `resolveBaseRef` (`git-worktree.ts`), which picks the fork point
65
+ * when the worktree is created; this one re-applies it every time a diff is
66
+ * measured, because that is when the local ref's staleness actually shows up.
67
+ * A base that is already remote-tracking, a commit sha pinned from a detached
68
+ * HEAD, or a repo with no `origin` all fall through unchanged.
69
+ */
70
+ async function freshestBaseRef(runGit, base) {
71
+ if (!isSafeGitRef(base) || base === 'HEAD' || base.startsWith('origin/'))
72
+ return base;
73
+ const remote = `origin/${base}`;
74
+ const hasRemote = await runGit(['rev-parse', '--verify', '--quiet', `${remote}^{commit}`]);
75
+ if (!hasRemote.ok)
76
+ return base;
77
+ // Exits 0 iff local is equal to or ahead of origin — the case where the local
78
+ // ref carries unpushed base commits and is the better answer.
79
+ const localCurrent = await runGit(['merge-base', '--is-ancestor', remote, base]);
80
+ return localCurrent.ok ? base : remote;
81
+ }
82
+ /**
83
+ * Where the repointed branch stood when this run started — `git rev-parse
84
+ * <branch>@{<runStartedAt>}`, i.e. its own reflog, read at a point in time.
85
+ *
86
+ * Git answers this generously by design: a timestamp older than the whole
87
+ * reflog resolves to the branch's OLDEST recorded state, which for a branch the
88
+ * agent created during the run is its creation commit — exactly the fork point
89
+ * we want. A branch that already existed resolves to the tip it had before the
90
+ * run touched it, so the previous author's commits stay theirs.
91
+ *
92
+ * Null (→ the caller keeps the conservative `HEAD` anchor) when there is no run
93
+ * start to read at, when HEAD is detached (no branch reflog to consult), or
94
+ * when reflogs are unavailable — a guess is worse than the narrow answer.
95
+ */
96
+ async function checkoutBaseline(runGit, headBranch, runStartedAt) {
97
+ if (!runStartedAt || !ISO_INSTANT.test(runStartedAt))
98
+ return null;
99
+ if (headBranch === 'HEAD' || !isSafeGitRef(headBranch))
100
+ return null;
101
+ const at = await runGit([
102
+ 'rev-parse',
103
+ '--verify',
104
+ '--quiet',
105
+ `${headBranch}@{${runStartedAt}}^{commit}`,
106
+ ]);
107
+ const sha = at.ok ? at.stdout.trim() : '';
108
+ return sha || null;
109
+ }
110
+ /**
111
+ * How many changed lines an anchor attributes to the task — the comparison that
112
+ * picks between two candidates, never a number anyone sees (the callers do their
113
+ * own parsing for that, `parseShortstat`). Both candidates cover the same
114
+ * uncommitted work, so the comparison is fair whatever the index holds.
115
+ *
116
+ * This is the one part of the decision that reads the index rather than just
117
+ * refs, and `git diff` rewrites the index it reads (a stat refresh). Callers
118
+ * that keep their diffs off an index they do not own — `collectChanges` and its
119
+ * scratch `GIT_INDEX_FILE` — must therefore hand this module a runner carrying
120
+ * that same environment, and the two probes below run one after the other so
121
+ * they never refresh one index file concurrently.
122
+ */
123
+ async function changedLines(runGit, ref) {
124
+ const res = await runGit(['diff', '--shortstat', ref]);
125
+ if (!res.ok)
126
+ return null;
127
+ let total = 0;
128
+ for (const m of res.stdout.matchAll(/(\d+) (?:insertions?\(\+\)|deletions?\(-\))/g)) {
129
+ total += Number(m[1]);
130
+ }
131
+ return total;
132
+ }
133
+ /**
134
+ * Resolve the diff anchor for a task worktree. Never throws: a failing git call
135
+ * degrades to the base branch name, which is what the un-guarded callers did
136
+ * before this helper existed.
30
137
  *
31
138
  * `taskBranch` is optional because not every caller has one (the main working
32
- * tree has no task branch at all). Without it there is nothing to compare
33
- * HEAD against, so the merge-base anchor is used unchanged — that is the
34
- * pre-#751 behavior, preserved exactly.
139
+ * tree has no task branch at all). Without it there is nothing to compare HEAD
140
+ * against, so the merge-base anchor is used unchanged.
35
141
  *
36
- * A detached HEAD (`rev-parse --abbrev-ref HEAD` → `HEAD`) counts as
37
- * repointed: it is by definition not the task's branch, so the same
38
- * uncommitted-only anchor is the honest one.
142
+ * `runStartedAt` is what makes the repointed-HEAD answer a measurement instead
143
+ * of a guess; without it the anchor stays `HEAD` (uncommitted work only), the
144
+ * conservative #751 answer that can never claim someone else's commits.
39
145
  */
40
146
  export async function resolveTaskDiffBase(runGit, baseBranch, opts = {}) {
41
- if (opts.taskBranch) {
42
- const headBranchResult = await runGit(['rev-parse', '--abbrev-ref', 'HEAD']);
43
- const headBranch = headBranchResult.ok ? headBranchResult.stdout.trim() : '';
44
- // An unreadable HEAD (no commits yet, broken worktree) is NOT evidence of a
45
- // repoint — fall through to the merge-base anchor rather than narrowing on a
46
- // guess.
47
- if (headBranch && headBranch !== opts.taskBranch) {
48
- return { base: 'HEAD', repointedHead: { headBranch, taskBranch: opts.taskBranch } };
49
- }
50
- }
51
- const mergeBase = await runGit(['merge-base', baseBranch, 'HEAD']);
52
- return { base: mergeBase.ok && mergeBase.stdout.trim() ? mergeBase.stdout.trim() : baseBranch };
147
+ const base = await freshestBaseRef(runGit, baseBranch);
148
+ const mergeBase = async () => {
149
+ const res = await runGit(['merge-base', base, 'HEAD']);
150
+ return res.ok && res.stdout.trim() ? res.stdout.trim() : base;
151
+ };
152
+ if (!opts.taskBranch)
153
+ return { base: await mergeBase() };
154
+ const headBranchResult = await runGit(['rev-parse', '--abbrev-ref', 'HEAD']);
155
+ const headBranch = headBranchResult.ok ? headBranchResult.stdout.trim() : '';
156
+ // An unreadable HEAD (no commits yet, broken worktree) is NOT evidence of a
157
+ // repoint — fall through to the merge-base anchor rather than narrowing on a
158
+ // guess. A detached HEAD, on the other hand, is by definition not the task's
159
+ // branch and takes the repointed path below.
160
+ if (!headBranch || headBranch === opts.taskBranch)
161
+ return { base: await mergeBase() };
162
+ const repointedHead = { headBranch, taskBranch: opts.taskBranch };
163
+ const baseline = await checkoutBaseline(runGit, headBranch, opts.runStartedAt);
164
+ if (!baseline)
165
+ return { base: 'HEAD', repointedHead };
166
+ // Two defensible anchors, so report the tighter one: the number must never
167
+ // claim more than the least either measure attributes to this task. The
168
+ // baseline is usually it — but not when the run merged the base branch in
169
+ // afterwards, which drags the whole upstream delta past a pre-merge baseline
170
+ // and leaves the merge-base as the only anchor that still measures the task.
171
+ const anchor = await mergeBase();
172
+ const viaBaseline = await changedLines(runGit, baseline);
173
+ if (viaBaseline === null)
174
+ return { base: anchor, repointedHead };
175
+ const viaMergeBase = await changedLines(runGit, anchor);
176
+ if (viaMergeBase === null || viaBaseline <= viaMergeBase)
177
+ return { base: baseline, repointedHead };
178
+ return { base: anchor, repointedHead };
53
179
  }
54
180
  //# sourceMappingURL=git-diff-base.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"git-diff-base.js","sourceRoot":"","sources":["../src/git-diff-base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAwBH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAiB,EACjB,UAAkB,EAClB,IAAI,GAA4B,EAAE;IAElC,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QACpB,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;QAC7E,MAAM,UAAU,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7E,4EAA4E;QAC5E,6EAA6E;QAC7E,SAAS;QACT,IAAI,UAAU,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE,CAAC;YACjD,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QACtF,CAAC;IACH,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,CAAC,YAAY,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;IACnE,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;AAClG,CAAC"}
1
+ {"version":3,"file":"git-diff-base.js","sourceRoot":"","sources":["../src/git-diff-base.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAwB7C;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,sEAAsE,CAAC;AAE3F;;;;;;;;;GASG;AACH,KAAK,UAAU,eAAe,CAAC,MAAiB,EAAE,IAAY;IAC5D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,IAAI,KAAK,MAAM,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IACtF,MAAM,MAAM,GAAG,UAAU,IAAI,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,MAAM,MAAM,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,WAAW,CAAC,CAAC,CAAC;IAC3F,IAAI,CAAC,SAAS,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IAC/B,8EAA8E;IAC9E,8DAA8D;IAC9D,MAAM,YAAY,GAAG,MAAM,MAAM,CAAC,CAAC,YAAY,EAAE,eAAe,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IACjF,OAAO,YAAY,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,KAAK,UAAU,gBAAgB,CAC7B,MAAiB,EACjB,UAAkB,EAClB,YAAgC;IAEhC,IAAI,CAAC,YAAY,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC;QAAE,OAAO,IAAI,CAAC;IAClE,IAAI,UAAU,KAAK,MAAM,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IACpE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC;QACtB,WAAW;QACX,UAAU;QACV,SAAS;QACT,GAAG,UAAU,KAAK,YAAY,YAAY;KAC3C,CAAC,CAAC;IACH,MAAM,GAAG,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC1C,OAAO,GAAG,IAAI,IAAI,CAAC;AACrB,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,KAAK,UAAU,YAAY,CAAC,MAAiB,EAAE,GAAW;IACxD,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC,CAAC;IACvD,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,8CAA8C,CAAC,EAAE,CAAC;QACpF,KAAK,IAAI,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,MAAiB,EACjB,UAAkB,EAClB,IAAI,GAAmD,EAAE;IAEzD,MAAM,IAAI,GAAG,MAAM,eAAe,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvD,MAAM,SAAS,GAAG,KAAK,IAAqB,EAAE;QAC5C,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,CAAC,YAAY,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QACvD,OAAO,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC,CAAC;IAEF,IAAI,CAAC,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,EAAE,EAAE,CAAC;IAEzD,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,CAAC,WAAW,EAAE,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7E,MAAM,UAAU,GAAG,gBAAgB,CAAC,EAAE,CAAC,CAAC,CAAC,gBAAgB,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC7E,4EAA4E;IAC5E,6EAA6E;IAC7E,6EAA6E;IAC7E,6CAA6C;IAC7C,IAAI,CAAC,UAAU,IAAI,UAAU,KAAK,IAAI,CAAC,UAAU;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,EAAE,EAAE,CAAC;IAEtF,MAAM,aAAa,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC;IAClE,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/E,IAAI,CAAC,QAAQ;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IAEtD,2EAA2E;IAC3E,wEAAwE;IACxE,0EAA0E;IAC1E,6EAA6E;IAC7E,6EAA6E;IAC7E,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;IACjC,MAAM,WAAW,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACzD,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;IACjE,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACxD,IAAI,YAAY,KAAK,IAAI,IAAI,WAAW,IAAI,YAAY;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC;IACnG,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,aAAa,EAAE,CAAC;AACzC,CAAC"}
@@ -22,6 +22,11 @@ export declare function branchFor(runId: string): string;
22
22
  * 142k-line diff. `origin/<base>` is the source of truth for a review base, so
23
23
  * only keep the local ref when it is equal to or ahead of origin (unpushed base
24
24
  * commits); otherwise use origin.
25
+ *
26
+ * This answers the question once, when the worktree is forked. The local ref
27
+ * goes stale AFTERWARDS too — agents fetch, they never pull — so every diff
28
+ * re-applies the same rule at read time through `freshestBaseRef`
29
+ * (`git-diff-base.ts`). Keep the two in agreement.
25
30
  */
26
31
  export declare function resolveBaseRef(repoRoot: string, base: string): Promise<string | null>;
27
32
  export declare function worktreePathFor(repoRoot: string, runId: string): string;
@@ -114,9 +119,10 @@ export interface DiffStat {
114
119
  adds: number;
115
120
  dels: number;
116
121
  files: number;
117
- /** Set only when the numbers were narrowed to uncommitted work because the worktree's
118
- * HEAD had been repointed off the task's branch (#751). Absent — never `false` — on a
119
- * normal run, so the persisted shape is unchanged for every task that behaved. */
122
+ /** Set only when the numbers were narrowed to what this run did on a branch it checked
123
+ * out into its worktree, because HEAD had been repointed off the task's branch (#751).
124
+ * Absent — never `false` — on a normal run, so the persisted shape is unchanged for
125
+ * every task that behaved. */
120
126
  repointed?: boolean;
121
127
  }
122
128
  /**
@@ -132,9 +138,11 @@ export declare function parseShortstat(s: string): DiffStat;
132
138
  * behind `RunRecord.diffStat`, which is what the sidebar quick list and the
133
139
  * Tasks table show. Same intent-to-add as `worktreeDiff`, but the anchor comes
134
140
  * from the shared `resolveTaskDiffBase` rule (`git-diff-base.ts`): pass the
135
- * run's own `taskBranch` and a worktree whose HEAD was repointed onto another
136
- * branch reports its uncommitted work only, instead of claiming that whole
137
- * branch's diff as this task's (#751 — the #591 guard, on this surface).
141
+ * run's own `taskBranch` and `runStartedAt`, and a worktree whose HEAD was
142
+ * repointed onto another branch reports what this run did to that branch
143
+ * instead of claiming the branch's whole diff as this task's (#751 — the #591
144
+ * guard, on this surface). The same rule re-resolves a stale local base ref,
145
+ * so the number never counts upstream history the task merely forked from.
138
146
  *
139
147
  * `repointed: true` rides along on the returned stat exactly when that
140
148
  * narrowing happened, so the UI can say why the number is what it is. Null on
@@ -143,6 +151,7 @@ export declare function parseShortstat(s: string): DiffStat;
143
151
  */
144
152
  export declare function worktreeShortstat(worktreePath: string, baseBranch: string, opts?: {
145
153
  taskBranch?: string;
154
+ runStartedAt?: string;
146
155
  }): Promise<DiffStat | null>;
147
156
  /**
148
157
  * Startup reconcile: `git worktree prune` + remove every directory under
@@ -37,6 +37,11 @@ export function branchFor(runId) {
37
37
  * 142k-line diff. `origin/<base>` is the source of truth for a review base, so
38
38
  * only keep the local ref when it is equal to or ahead of origin (unpushed base
39
39
  * commits); otherwise use origin.
40
+ *
41
+ * This answers the question once, when the worktree is forked. The local ref
42
+ * goes stale AFTERWARDS too — agents fetch, they never pull — so every diff
43
+ * re-applies the same rule at read time through `freshestBaseRef`
44
+ * (`git-diff-base.ts`). Keep the two in agreement.
40
45
  */
41
46
  export async function resolveBaseRef(repoRoot, base) {
42
47
  if (!isSafeGitRef(base))
@@ -453,9 +458,11 @@ export function parseShortstat(s) {
453
458
  * behind `RunRecord.diffStat`, which is what the sidebar quick list and the
454
459
  * Tasks table show. Same intent-to-add as `worktreeDiff`, but the anchor comes
455
460
  * from the shared `resolveTaskDiffBase` rule (`git-diff-base.ts`): pass the
456
- * run's own `taskBranch` and a worktree whose HEAD was repointed onto another
457
- * branch reports its uncommitted work only, instead of claiming that whole
458
- * branch's diff as this task's (#751 — the #591 guard, on this surface).
461
+ * run's own `taskBranch` and `runStartedAt`, and a worktree whose HEAD was
462
+ * repointed onto another branch reports what this run did to that branch
463
+ * instead of claiming the branch's whole diff as this task's (#751 — the #591
464
+ * guard, on this surface). The same rule re-resolves a stale local base ref,
465
+ * so the number never counts upstream history the task merely forked from.
459
466
  *
460
467
  * `repointed: true` rides along on the returned stat exactly when that
461
468
  * narrowing happened, so the UI can say why the number is what it is. Null on