@open-mercato/cezar 0.9.2 → 0.9.3
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.
- package/LICENSE +21 -0
- package/README.md +4 -2
- package/dist/contract/health.d.ts +2 -0
- package/dist/contract/index.js +23 -10
- package/dist/git-diff-base.d.ts +51 -26
- package/dist/git-diff-base.js +163 -37
- package/dist/git-diff-base.js.map +1 -1
- package/dist/git-worktree.d.ts +15 -6
- package/dist/git-worktree.js +10 -3
- package/dist/git-worktree.js.map +1 -1
- package/dist/server/capabilities.d.ts +15 -1
- package/dist/server/capabilities.js +16 -1
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/git-changes.d.ts +7 -5
- package/dist/server/git-changes.js +15 -10
- package/dist/server/git-changes.js.map +1 -1
- package/dist/server/server.d.ts +59 -58
- package/dist/server/server.js +54 -3
- package/dist/server/server.js.map +1 -1
- package/dist/workflows/run.d.ts +27 -7
- package/dist/workflows/run.js +106 -43
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/config.d.ts +8 -0
- package/dist/workspace/config.js +29 -2
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +7 -1
- package/dist/workspace/semaphore.js +10 -4
- package/dist/workspace/semaphore.js.map +1 -1
- package/dist/workspace/ui-state.d.ts +3 -2
- package/dist/workspace/ui-state.js +3 -2
- package/dist/workspace/ui-state.js.map +1 -1
- package/package.json +3 -3
- package/web/dist/assets/{alert-dialog-ghffK6g9.js → alert-dialog-V9cigyAO.js} +1 -1
- package/web/dist/assets/arrow-down-DflHdNlN.js +1 -0
- package/web/dist/assets/arrow-left-IIL4uR-C.js +1 -0
- package/web/dist/assets/centered-state-BkAJVLJE.js +43 -0
- package/web/dist/assets/chevron-right-CxAoD1eD.js +1 -0
- package/web/dist/assets/circle-check-CdwcIbkx.js +1 -0
- package/web/dist/assets/{circle-x-C3EFZ3xf.js → circle-x-Cnc43xJ2.js} +1 -1
- package/web/dist/assets/{collapsible-B0JVzd0B.js → collapsible-BlF-7CeC.js} +1 -1
- package/web/dist/assets/{commit-list-wm6DPHNs.js → commit-list-D2mKElUx.js} +1 -1
- package/web/dist/assets/{compare-variants-DbdP4Y5T.js → compare-variants-CFBY16Rc.js} +1 -1
- package/web/dist/assets/{diff-Bizc96qe.js → diff-DE6u5dTe.js} +2 -2
- package/web/dist/assets/{diff-stat-Dyfg3ltF.js → diff-stat-B1j6L7C6.js} +1 -1
- package/web/dist/assets/{diff-view-CG8ek6tC.js → diff-view-Gb2kk3Le.js} +1 -1
- package/web/dist/assets/{dropdown-menu-C1Dlf35P.js → dropdown-menu-Ns-rY6sm.js} +1 -1
- package/web/dist/assets/editable-title-BGuiDxFT.js +1 -0
- package/web/dist/assets/{ellipsis-vertical-DpGTRqC2.js → ellipsis-vertical-BbxtXb6s.js} +1 -1
- package/web/dist/assets/{file-DlUGpUlb.js → file-j5O4NryU.js} +1 -1
- package/web/dist/assets/{folder-BAY6Vddy.js → folder-w9Lwj9x9.js} +1 -1
- package/web/dist/assets/{git-pull-request-agOG0iGM.js → git-pull-request-DlSop3Ch.js} +1 -1
- package/web/dist/assets/{git-toolbar-DGiNMmtY.js → git-toolbar-BdJ8XSxI.js} +1 -1
- package/web/dist/assets/github-CCLn2OAU.js +1 -0
- package/web/dist/assets/{image-preview-BWGk5fPX.js → image-preview-CcFdkZXW.js} +1 -1
- package/web/dist/assets/index-BmHHLbwc.js +6 -0
- package/web/dist/assets/index-u8OAnq0r.css +2 -0
- package/web/dist/assets/{markdown-BGBOGCA_.js → markdown-q9lm7kQ2.js} +1 -1
- package/web/dist/assets/{new-task-form-BpAw_Dks.js → new-task-form-BuYxAWN7.js} +1 -1
- package/web/dist/assets/{pill-Dvs-P0xy.js → pill-t97MvPW_.js} +1 -1
- package/web/dist/assets/{project-router-Cx8NPoqn.js → project-router-DudOmkHf.js} +1 -1
- package/web/dist/assets/{prompt-templates-CSBYBR1I.js → prompt-templates-C5iO3Lda.js} +1 -1
- package/web/dist/assets/{refresh-cw-BoGdAykg.js → refresh-cw-_C2EGJK5.js} +1 -1
- package/web/dist/assets/{repo-git-PmX-BNMo.js → repo-git-DqQzj33d.js} +1 -1
- package/web/dist/assets/{run-diff-EcOkf3ti.js → run-diff-BLoKEnTx.js} +1 -1
- package/web/dist/assets/run-header-Dho4eurS.js +1 -0
- package/web/dist/assets/{search-x-X3EpVhgs.js → search-x-BgxaJgro.js} +1 -1
- package/web/dist/assets/{skill-empty-hint-CZLALx6l.js → skill-empty-hint-D5MOmycv.js} +1 -1
- package/web/dist/assets/skills-B5g064CC.js +1 -0
- package/web/dist/assets/{sparkles-Dt4q7pbG.js → sparkles-ANSka5bH.js} +1 -1
- package/web/dist/assets/{square-terminal-hwBL8XsC.js → square-terminal-qojq20cH.js} +1 -1
- package/web/dist/assets/{tab-link-DIlR5T07.js → tab-link-CIcwFJtC.js} +1 -1
- package/web/dist/assets/task-changes-yRfsEsnh.js +1 -0
- package/web/dist/assets/{task-commits-BTTA5k5T.js → task-commits-yXmDrCoE.js} +1 -1
- package/web/dist/assets/{task-files-Da8_4C9g.js → task-files-BDRmCCPN.js} +2 -2
- package/web/dist/assets/task-thread-C9AnIz4Z.js +9 -0
- package/web/dist/assets/{textarea-CUdokhkv.js → textarea-DVO7pg8Y.js} +1 -1
- package/web/dist/assets/{thread-loading-B8q8ukXJ.js → thread-loading-DJDl701Z.js} +1 -1
- package/web/dist/assets/{trash-2-DhVIdWv8.js → trash-2-DKcnmg6e.js} +1 -1
- package/web/dist/assets/{triangle-alert-XYT0DGCI.js → triangle-alert-CPUEVSZ3.js} +1 -1
- package/web/dist/assets/{upload-BKjN59-1.js → upload-D5Jrq7QK.js} +1 -1
- package/web/dist/assets/{use-desktop-Bf5LS_P4.js → use-desktop-CXae6uyk.js} +1 -1
- package/web/dist/assets/{use-submit-shortcut-CzVyoAMd.js → use-submit-shortcut-RKJLKdsW.js} +1 -1
- package/web/dist/assets/{utils-DsLuRXwV.js → utils-Wtkd5E6j.js} +1 -1
- package/web/dist/assets/{workflows-o2QU_pvU.js → workflows-CGyTdyds.js} +3 -3
- package/web/dist/assets/{zoomable-image-7WyUiaVD.js → zoomable-image-hcqwUIlQ.js} +1 -1
- package/web/dist/index.html +22 -22
- package/web/dist/assets/arrow-down-Cz8qb71e.js +0 -1
- package/web/dist/assets/arrow-left-wcYKQXI_.js +0 -1
- package/web/dist/assets/centered-state-ZtnFHA-n.js +0 -43
- package/web/dist/assets/chevron-right-G4caF2QV.js +0 -1
- package/web/dist/assets/circle-check--KvBxXWp.js +0 -1
- package/web/dist/assets/editable-title-5rC_QRBZ.js +0 -1
- package/web/dist/assets/github-Vp7lxYGQ.js +0 -1
- package/web/dist/assets/index-DSxyJpuK.css +0 -2
- package/web/dist/assets/index-DiawI6RX.js +0 -6
- package/web/dist/assets/run-header-BGiv6_qX.js +0 -1
- package/web/dist/assets/skills-CC91ouGH.js +0 -1
- package/web/dist/assets/task-changes-XkHDxzQA.js +0 -1
- package/web/dist/assets/task-thread-DoNXUsHr.js +0 -9
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patryk Lewczuk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -19,7 +19,7 @@ your phone, working your backlog while you're away.
|
|
|
19
19
|
|
|
20
20
|
[A look inside](#a-look-inside) · [What cezar does best](#what-cezar-does-best) · [What it solves](#what-it-solves) · [Who it's for](#who-its-for) · [Quick start](#quick-start) · [How it works](#how-it-works) · [Core concepts](#core-concepts) · [Cockpit tour](#cockpit-tour) · [Agent backends](#coding-agent-backends) · [Remote access](#remote-access-host-cezar-on-a-server)
|
|
21
21
|
|
|
22
|
-
[](
|
|
22
|
+
[](LICENSE)
|
|
23
23
|

|
|
24
24
|

|
|
25
25
|

|
|
@@ -429,6 +429,7 @@ Useful environment variables:
|
|
|
429
429
|
| `CEZ_DRY_RUN=1` | Use the bundled mock instead of the real `claude` CLI — the entire cockpit works offline, for demos and development. |
|
|
430
430
|
| `CEZ_APPROVAL_GATE=1` | Opt into Claude's interactive approval UI; by default, unapproved tools are denied without interrupting the run. |
|
|
431
431
|
| `CEZ_FOLLOWUPS=1` | Turn on the global follow-up **Inbox**: agents are asked to leave follow-ups in `todos.json` when they finish, and the Inbox view appears. Off by default — each task's own **Notes** handoff journal runs either way. |
|
|
432
|
+
| `CEZ_AUTOMATIONS=1` | Turn on **GitHub automations**: the Automations view appears and cezar polls GitHub on each enabled automation's interval, launching tasks from what it finds. Off by default, and only the exact value `1` enables it — without it nothing polls GitHub, the automations endpoints answer `409`, and the nav item is absent. Read at boot, so restart after changing it; definitions, receipts and high-watermarks are retained, so unsetting it and restarting restores the feature without migration or data loss. |
|
|
432
433
|
| `CEZ_AUTOSAVE=1` | Re-enable the periodic (90 s) autosave commit in task worktrees. Off by default (#471) — turn-end and pre-PR flushes always run, so branches still end complete. Every autosave names its trigger in the commit subject (`cezar autosave (periodic)` vs `(turn end)` / `(run finalize)` / `(pre-PR)`), so the flushes you keep are distinguishable from the timer you disabled. |
|
|
433
434
|
| `CEZ_CLAUDE_BIN=/path/to/claude` | Override which `claude` binary is used. |
|
|
434
435
|
| `CEZ_CODEX_BIN=/path/to/codex` | Override which `codex` binary is used. |
|
|
@@ -438,6 +439,7 @@ Useful environment variables:
|
|
|
438
439
|
| `CEZ_SKILLS_AUTO_UPDATE=0` | Disable automatic checks and updates for upstream-CLI-tracked Open Mercato skill installations. On by default; a saved global Skills setting overrides this environment default. Checks are delayed, bounded, cached, and non-blocking. |
|
|
439
440
|
| `CEZ_AUTONOMOUS_DEFAULT=0` | Seed the New Task Autonomous default (`0` or `1`). Without a seed, skills default on and workflows off; a saved global Resources setting overrides it. |
|
|
440
441
|
| `CEZ_WORKTREE_DEFAULT=1` | Seed the New Task Worktree default (`0` or `1`). Without a seed, eligible runs default on; a saved global Resources setting overrides it. |
|
|
442
|
+
| `CEZ_DISABLE_REPO_LOCK=1` | **Dangerous escape hatch:** allow any run executing in the repository root — an explicit `worktree=false` run, non-Git degradation, or a continuation whose worktree cannot be restored — to proceed without Cezar’s repository-root lease. Agents can overwrite each other’s files or Git state; isolated worktree runs are unaffected. Off by default; only the exact value `1` enables it. |
|
|
441
443
|
| `CEZ_SINGLE_PROJECT=1` | Opt into a launch-project-only cockpit: only the exact value `1` enables it. Project add, edit, checkout, folder browsing, and removal are refused and only the launch project is shown. Off by default; stored registry rows are retained, so unsetting it and restarting restores the full multi-project workspace without migration or data loss. |
|
|
442
444
|
| `CEZ_HIDE_TOKEN_USAGE=1` | Hide raw input/output token counts throughout the browser cockpit while leaving backend-reported cost visible. Only the exact value `1` enables it; telemetry and API payloads are unchanged, and a restart is required after changing it. |
|
|
443
445
|
| `CEZ_HIDE_COST=1` | Hide backend-reported monetary cost throughout the browser cockpit while leaving raw input/output token counts visible. Only the exact value `1` enables it; telemetry and API payloads are unchanged, and a restart is required after changing it. |
|
|
@@ -702,4 +704,4 @@ Start here; graduate to cezar when a team needs shared visibility.
|
|
|
702
704
|
|
|
703
705
|
## License
|
|
704
706
|
|
|
705
|
-
**MIT** © Patryk Lewczuk
|
|
707
|
+
**MIT** © Patryk Lewczuk — full text in [LICENSE](LICENSE).
|
|
@@ -38,6 +38,7 @@ export declare const capabilitiesSchema: z.ZodObject<{
|
|
|
38
38
|
localHandoff: z.ZodBoolean;
|
|
39
39
|
followups: z.ZodBoolean;
|
|
40
40
|
singleProject: z.ZodBoolean;
|
|
41
|
+
automations: z.ZodBoolean;
|
|
41
42
|
tokenMetrics: z.ZodBoolean;
|
|
42
43
|
tokenUsageMetrics: z.ZodBoolean;
|
|
43
44
|
costMetrics: z.ZodBoolean;
|
|
@@ -83,6 +84,7 @@ export declare const healthResponseSchema: z.ZodObject<{
|
|
|
83
84
|
localHandoff: z.ZodBoolean;
|
|
84
85
|
followups: z.ZodBoolean;
|
|
85
86
|
singleProject: z.ZodBoolean;
|
|
87
|
+
automations: z.ZodBoolean;
|
|
86
88
|
tokenMetrics: z.ZodBoolean;
|
|
87
89
|
tokenUsageMetrics: z.ZodBoolean;
|
|
88
90
|
costMetrics: z.ZodBoolean;
|
package/dist/contract/index.js
CHANGED
|
@@ -44,6 +44,14 @@ var capabilitiesSchema = z2.object({
|
|
|
44
44
|
localHandoff: z2.boolean(),
|
|
45
45
|
followups: z2.boolean(),
|
|
46
46
|
singleProject: z2.boolean(),
|
|
47
|
+
/**
|
|
48
|
+
* `true` means `CEZ_AUTOMATIONS=1` opted this server into GitHub automations (#801). Off — the
|
|
49
|
+
* default — the whole feature is absent: no `Automations` nav item anywhere it is rendered, the
|
|
50
|
+
* `/api/v1/…/automations*` family answers `409`, and the workspace scheduler never polls GitHub.
|
|
51
|
+
*
|
|
52
|
+
* REQUIRED for the same reason as `tokenMetrics` below: this server always sends it.
|
|
53
|
+
*/
|
|
54
|
+
automations: z2.boolean(),
|
|
47
55
|
/**
|
|
48
56
|
* `false` means `CEZ_HIDE_TOKEN_METRICS=1` asks the browser to omit token counts and monetary
|
|
49
57
|
* cost (#481). The telemetry itself still rides in run/event payloads — this is presentation
|
|
@@ -199,11 +207,12 @@ var diffStatSchema = z4.object({
|
|
|
199
207
|
adds: z4.number(),
|
|
200
208
|
dels: z4.number(),
|
|
201
209
|
files: z4.number(),
|
|
202
|
-
/** Additive since #751, and present ONLY when true: the numbers
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
210
|
+
/** Additive since #751, and present ONLY when true: the numbers were measured against a
|
|
211
|
+
* branch the agent checked out into the task's worktree, as the run found it, because the
|
|
212
|
+
* worktree's HEAD had been repointed off the task's own branch (every review/QA run does
|
|
213
|
+
* this) and the merge-base anchor would otherwise have reported that branch's entire diff
|
|
214
|
+
* as this task's. Absent on every normal run and on every record written before #751 — a
|
|
215
|
+
* consumer that ignores it sees exactly the old shape. */
|
|
207
216
|
repointed: z4.boolean().optional()
|
|
208
217
|
});
|
|
209
218
|
var queuedMessageSchema = z4.object({
|
|
@@ -881,9 +890,11 @@ var workspaceLastLocationSchema = z8.strictObject({
|
|
|
881
890
|
hash: z8.string().max(2048).startsWith("#").optional()
|
|
882
891
|
});
|
|
883
892
|
var workspaceUiStateSchema = z8.looseObject({
|
|
884
|
-
/**
|
|
885
|
-
*
|
|
886
|
-
*
|
|
893
|
+
/** LEGACY — the sidebar's per-project collapse map (step 3.3). Still accepted and still
|
|
894
|
+
* round-tripped so an older cockpit sharing this home keeps working, but the current cockpit
|
|
895
|
+
* neither reads nor writes it: which groups are shut describes the WINDOW, not the workspace,
|
|
896
|
+
* so it lives in that browser's localStorage (`packages/web/src/lib/sidebar-collapse.ts`).
|
|
897
|
+
* One shared answer meant a phone collapsing a group collapsed it on the desktop too. */
|
|
887
898
|
sidebar: z8.looseObject({ collapsed: z8.record(z8.string(), z8.boolean()).optional() }).optional(),
|
|
888
899
|
/** Dismissed runtime-auth incident IDs, keyed by provider. An ID is only dismissed until the
|
|
889
900
|
* provider reports a different incident, so this stays workspace-global with the browser
|
|
@@ -899,8 +910,10 @@ var workspaceUiStateSchema = z8.looseObject({
|
|
|
899
910
|
/** Settings → Notifications, GLOBAL since step 3.5 — one answer for the whole workspace, since
|
|
900
911
|
* the delivering browser is one browser whichever project you are looking at. */
|
|
901
912
|
notifications: z8.looseObject({ enabled: z8.boolean().optional() }).optional(),
|
|
902
|
-
/**
|
|
903
|
-
*
|
|
913
|
+
/** LEGACY, exactly like `sidebar` above — the last settled project-scoped page, restored when
|
|
914
|
+
* entering at the exact bare root. The shape is unchanged and still accepted, but the current
|
|
915
|
+
* cockpit keeps it in localStorage (`packages/web/src/lib/last-location.ts`): stored here, the
|
|
916
|
+
* last client to navigate decided where every OTHER client's next launch landed. */
|
|
904
917
|
lastLocation: workspaceLastLocationSchema.optional(),
|
|
905
918
|
/** The user's curated selection of default (vendor) skills. Tri-state: ABSENT means "not
|
|
906
919
|
* curated", so every default skill shows; a PRESENT array (even `[]`) means only those names
|
package/dist/git-diff-base.d.ts
CHANGED
|
@@ -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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
|
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
|
-
*
|
|
47
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
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>;
|
package/dist/git-diff-base.js
CHANGED
|
@@ -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
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
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
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
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
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
1
|
+
{"version":3,"file":"git-diff-base.js","sourceRoot":"","sources":["../src/git-diff-base.ts"],"names":[],"mappings":"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"}
|
package/dist/git-worktree.d.ts
CHANGED
|
@@ -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
|
|
118
|
-
* HEAD had been repointed off the task's branch (#751).
|
|
119
|
-
* normal run, so the persisted shape is unchanged for
|
|
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
|
|
136
|
-
* branch reports
|
|
137
|
-
* branch's diff as this task's (#751 — the #591
|
|
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
|
package/dist/git-worktree.js
CHANGED
|
@@ -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
|
|
457
|
-
* branch reports
|
|
458
|
-
* branch's diff as this task's (#751 — the #591
|
|
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
|