@open-mercato/cezar 0.9.2-pr782.1076 → 0.9.2-pr786.1090

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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/contract/index.js +50 -11
  4. package/dist/contract/runs.d.ts +86 -0
  5. package/dist/git-diff-base.d.ts +26 -51
  6. package/dist/git-diff-base.js +37 -163
  7. package/dist/git-diff-base.js.map +1 -1
  8. package/dist/git-worktree.d.ts +6 -15
  9. package/dist/git-worktree.js +3 -10
  10. package/dist/git-worktree.js.map +1 -1
  11. package/dist/runs/run-index.d.ts +19 -0
  12. package/dist/runs/run-index.js +43 -0
  13. package/dist/runs/run-index.js.map +1 -0
  14. package/dist/runs/store.d.ts +20 -7
  15. package/dist/runs/store.js +48 -37
  16. package/dist/runs/store.js.map +1 -1
  17. package/dist/server/git-changes.d.ts +5 -7
  18. package/dist/server/git-changes.js +10 -15
  19. package/dist/server/git-changes.js.map +1 -1
  20. package/dist/server/server.d.ts +84 -58
  21. package/dist/server/server.js +88 -2
  22. package/dist/server/server.js.map +1 -1
  23. package/dist/workflows/run.js +3 -5
  24. package/dist/workflows/run.js.map +1 -1
  25. package/dist/workspace/ui-state.d.ts +3 -2
  26. package/dist/workspace/ui-state.js +3 -2
  27. package/dist/workspace/ui-state.js.map +1 -1
  28. package/package.json +3 -3
  29. package/web/dist/assets/{alert-dialog-DDkrFSNo.js → alert-dialog-DgGXiX0-.js} +1 -1
  30. package/web/dist/assets/arrow-down-CVWn39DB.js +1 -0
  31. package/web/dist/assets/arrow-left-CytehkvJ.js +1 -0
  32. package/web/dist/assets/centered-state-BWu9EZeH.js +43 -0
  33. package/web/dist/assets/chevron-right-Bn2cprQo.js +1 -0
  34. package/web/dist/assets/circle-check-C9dg0Lwg.js +1 -0
  35. package/web/dist/assets/{circle-x-BswQ4rx0.js → circle-x-HJMlr00g.js} +1 -1
  36. package/web/dist/assets/{collapsible-Cv8ki6G4.js → collapsible-CW__roLv.js} +1 -1
  37. package/web/dist/assets/{commit-list-Bwor0g70.js → commit-list-D-ZJT7xL.js} +1 -1
  38. package/web/dist/assets/{compare-variants-BDQZcNEr.js → compare-variants-D1ECFWOe.js} +1 -1
  39. package/web/dist/assets/{diff-DwpwUMyz.js → diff-CutJnvjv.js} +2 -2
  40. package/web/dist/assets/{diff-stat-Bc-3XwTY.js → diff-stat-DFXP90Vc.js} +1 -1
  41. package/web/dist/assets/{diff-view-BBp2DSEa.js → diff-view-BWuBSVFT.js} +1 -1
  42. package/web/dist/assets/{dropdown-menu-Bnk-hg8y.js → dropdown-menu-D2A2pGjJ.js} +1 -1
  43. package/web/dist/assets/{editable-title-veprAEpy.js → editable-title-C6a64Vwp.js} +1 -1
  44. package/web/dist/assets/{ellipsis-vertical-C8LP-M9x.js → ellipsis-vertical-vx7lL0mm.js} +1 -1
  45. package/web/dist/assets/{file-DnCwXidV.js → file-DAIQGWQp.js} +1 -1
  46. package/web/dist/assets/{folder-BBBuunLT.js → folder-BvhNOUoH.js} +1 -1
  47. package/web/dist/assets/{git-pull-request-BuHs3KJ6.js → git-pull-request-CymF90c4.js} +1 -1
  48. package/web/dist/assets/{git-toolbar-Cqce5laj.js → git-toolbar-D72TdLai.js} +1 -1
  49. package/web/dist/assets/github-DFQJ9345.js +1 -0
  50. package/web/dist/assets/{image-preview-B-UiFJKd.js → image-preview-DsqvjJhH.js} +1 -1
  51. package/web/dist/assets/index-CV1rs06s.js +6 -0
  52. package/web/dist/assets/index-cLI08SdE.css +2 -0
  53. package/web/dist/assets/{markdown-B0APBJCK.js → markdown-v5x5LE3s.js} +1 -1
  54. package/web/dist/assets/{new-task-form-BjNLkW4w.js → new-task-form-DAqGlabw.js} +1 -1
  55. package/web/dist/assets/{pill-DF3ZRiuk.js → pill-AwEgoqpU.js} +1 -1
  56. package/web/dist/assets/{project-router-DMinES4q.js → project-router-CLFUa8om.js} +1 -1
  57. package/web/dist/assets/{prompt-templates-CKnyW-ae.js → prompt-templates-DhV3gn9A.js} +1 -1
  58. package/web/dist/assets/{refresh-cw-BTiJYD2N.js → refresh-cw-D3EzHdHY.js} +1 -1
  59. package/web/dist/assets/{repo-git-BXiF7cAO.js → repo-git-BPzfBF_2.js} +1 -1
  60. package/web/dist/assets/{run-diff-BVWjG5af.js → run-diff-D7nsnTW9.js} +2 -2
  61. package/web/dist/assets/{run-header-CPlEDuu4.js → run-header-CTsQn0Xg.js} +1 -1
  62. package/web/dist/assets/{search-x--hctwQW4.js → search-x-B5IWjGTy.js} +1 -1
  63. package/web/dist/assets/{skill-empty-hint-DXlK1qF5.js → skill-empty-hint-Cc3J-Vw9.js} +1 -1
  64. package/web/dist/assets/skills-CpNKVpuR.js +1 -0
  65. package/web/dist/assets/{sparkles-BSB94GWf.js → sparkles-CfUE1-C7.js} +1 -1
  66. package/web/dist/assets/{square-terminal-DI9vN2oU.js → square-terminal-D2wOSwur.js} +1 -1
  67. package/web/dist/assets/{tab-link-xE17QWo3.js → tab-link-gfQ-aL-o.js} +1 -1
  68. package/web/dist/assets/{task-changes-Cdf47v_x.js → task-changes-DbC7okdh.js} +1 -1
  69. package/web/dist/assets/{task-commits-C1-KIKs5.js → task-commits-B9D_741o.js} +1 -1
  70. package/web/dist/assets/{task-files-Ci2mpBPX.js → task-files-DDQorUtr.js} +2 -2
  71. package/web/dist/assets/{task-thread-b84hFEfs.js → task-thread-DPZQ_MH3.js} +7 -7
  72. package/web/dist/assets/{textarea-CxwpSdn4.js → textarea-DsfeVQQ3.js} +1 -1
  73. package/web/dist/assets/thread-loading-u9UysgWm.js +1 -0
  74. package/web/dist/assets/{trash-2-COvilYIt.js → trash-2-DgRtM_8B.js} +1 -1
  75. package/web/dist/assets/{triangle-alert-BVqzlcpl.js → triangle-alert-rNmHpkue.js} +1 -1
  76. package/web/dist/assets/{upload-Ctrt-DXu.js → upload-3tpzQ7VY.js} +1 -1
  77. package/web/dist/assets/{use-desktop-RO7MYDxA.js → use-desktop-BebfYYE1.js} +1 -1
  78. package/web/dist/assets/{use-submit-shortcut-C-M46aas.js → use-submit-shortcut-CNJod3_0.js} +1 -1
  79. package/web/dist/assets/{utils-Dfe0KwV2.js → utils-CNU1Tppw.js} +1 -1
  80. package/web/dist/assets/{workflows-vXDYE1Od.js → workflows-gDZ8Bdk8.js} +3 -3
  81. package/web/dist/assets/{zoomable-image-D0J9qb-_.js → zoomable-image-DJH8H5T5.js} +1 -1
  82. package/web/dist/index.html +23 -23
  83. package/web/dist/assets/arrow-down-6yITIPsm.js +0 -1
  84. package/web/dist/assets/arrow-left-ClFtONFn.js +0 -1
  85. package/web/dist/assets/centered-state-B6eNyY9K.js +0 -43
  86. package/web/dist/assets/chevron-right-DU1ld4lK.js +0 -1
  87. package/web/dist/assets/circle-check-CrpGUTuT.js +0 -1
  88. package/web/dist/assets/github-C8kC-K7K.js +0 -1
  89. package/web/dist/assets/index-B84F80Q8.css +0 -2
  90. package/web/dist/assets/index-BbRfsA5Z.js +0 -6
  91. package/web/dist/assets/skills-C7WoVqg8.js +0 -1
  92. package/web/dist/assets/thread-loading-lFlqLRN5.js +0 -1
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
- [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](#license)
22
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
23
23
  ![Node 20+](https://img.shields.io/badge/Node-20%2B-339933)
24
24
  ![TypeScript 7.x](https://img.shields.io/badge/TypeScript-7.x-3178c6)
25
25
  ![Zero config](https://img.shields.io/badge/config-zero-success)
@@ -712,4 +712,4 @@ Start here; graduate to cezar when a team needs shared visibility.
712
712
 
713
713
  ## License
714
714
 
715
- **MIT** © Patryk Lewczuk
715
+ **MIT** © Patryk Lewczuk — full text in [LICENSE](LICENSE).
@@ -204,12 +204,11 @@ var diffStatSchema = z4.object({
204
204
  adds: z4.number(),
205
205
  dels: z4.number(),
206
206
  files: z4.number(),
207
- /** Additive since #751, and present ONLY when true: the numbers were measured against a
208
- * branch the agent checked out into the task's worktree, as the run found it, because the
209
- * worktree's HEAD had been repointed off the task's own branch (every review/QA run does
210
- * this) and the merge-base anchor would otherwise have reported that branch's entire diff
211
- * as this task's. Absent on every normal run and on every record written before #751 — a
212
- * consumer that ignores it sees exactly the old shape. */
207
+ /** Additive since #751, and present ONLY when true: the numbers cover uncommitted work
208
+ * alone, because the worktree's HEAD had been repointed off the task's own branch (every
209
+ * review/QA run does this) and the merge-base anchor would otherwise have reported the
210
+ * checked-out branch's entire diff as this task's. Absent on every normal run and on every
211
+ * record written before #751 — a consumer that ignores it sees exactly the old shape. */
213
212
  repointed: z4.boolean().optional()
214
213
  });
215
214
  var queuedMessageSchema = z4.object({
@@ -356,6 +355,40 @@ var runRecordSchema = z4.object({
356
355
  var apiRunSchema = runRecordSchema.extend({
357
356
  usage: processUsageSchema.optional()
358
357
  });
358
+ var runIndexEntrySchema = z4.object({
359
+ /** The registered project this run belongs to. Joins against `GET /projects`. */
360
+ projectId: z4.string(),
361
+ id: z4.string(),
362
+ title: z4.string(),
363
+ titleSummary: z4.string().optional(),
364
+ titleOrigin: z4.enum(["user", "auto", "marker"]).optional(),
365
+ status: runStatusSchema,
366
+ activity: runActivitySchema.optional(),
367
+ createdAt: z4.string(),
368
+ finishedAt: z4.string().optional(),
369
+ /** With `status`/`finishedAt`/`archived`, the four inputs `isUnread` reads — what lets the
370
+ * palette lead with "finished while you weren't looking" across every project, not just the
371
+ * one you happen to be standing in. */
372
+ seenAt: z4.string().optional(),
373
+ /** Always present, like `RunRecord.archived`: absent would read as "not archived", and the
374
+ * unread rule treats archiving as a stronger "done with this" than reading. */
375
+ archived: z4.boolean(),
376
+ /** A run parked by a provider usage limit is `failed` on the record with a resume booked
377
+ * (spec 2026-08-03-auto-resume-after-usage-limit). Both `deriveAttention` and `isUnread` read
378
+ * it, so without it here a cross-project row would show a red "failed" dot and land in
379
+ * Recently finished for work that is simply waiting for its appointment. */
380
+ autoResumeAt: z4.string().optional()
381
+ });
382
+ var runsIndexResponseSchema = z4.object({
383
+ /** Newest first, across every registered project. Archived runs are included — `GET /runs`
384
+ * carries them for the project you are standing in, and a finder that dropped them elsewhere
385
+ * would make a task vanish the moment you left its project. */
386
+ runs: z4.array(runIndexEntrySchema),
387
+ /** The per-project cap that produced this list. */
388
+ perProjectLimit: z4.number(),
389
+ /** Ids of the projects that had more runs than the cap allowed. */
390
+ truncated: z4.array(z4.string())
391
+ });
359
392
  var createRunResponseSchema = z4.union([
360
393
  runRecordSchema,
361
394
  z4.object({ runs: z4.array(runRecordSchema) })
@@ -929,9 +962,11 @@ var workspaceLastLocationSchema = z8.strictObject({
929
962
  hash: z8.string().max(2048).startsWith("#").optional()
930
963
  });
931
964
  var workspaceUiStateSchema = z8.looseObject({
932
- /** The sidebar's per-project collapse map (step 3.3) — `true` collapses a group, `false` pins
933
- * it open, absent means the default (the active project expands, the rest collapse). Loose for
934
- * the same round-trip reason as its parent. */
965
+ /** LEGACY — the sidebar's per-project collapse map (step 3.3). Still accepted and still
966
+ * round-tripped so an older cockpit sharing this home keeps working, but the current cockpit
967
+ * neither reads nor writes it: which groups are shut describes the WINDOW, not the workspace,
968
+ * so it lives in that browser's localStorage (`packages/web/src/lib/sidebar-collapse.ts`).
969
+ * One shared answer meant a phone collapsing a group collapsed it on the desktop too. */
935
970
  sidebar: z8.looseObject({ collapsed: z8.record(z8.string(), z8.boolean()).optional() }).optional(),
936
971
  /** Dismissed runtime-auth incident IDs, keyed by provider. An ID is only dismissed until the
937
972
  * provider reports a different incident, so this stays workspace-global with the browser
@@ -947,8 +982,10 @@ var workspaceUiStateSchema = z8.looseObject({
947
982
  /** Settings → Notifications, GLOBAL since step 3.5 — one answer for the whole workspace, since
948
983
  * the delivering browser is one browser whichever project you are looking at. */
949
984
  notifications: z8.looseObject({ enabled: z8.boolean().optional() }).optional(),
950
- /** Last settled project-scoped page. Used only when entering at the exact bare root;
951
- * explicit deep links always win. */
985
+ /** LEGACY, exactly like `sidebar` above — the last settled project-scoped page, restored when
986
+ * entering at the exact bare root. The shape is unchanged and still accepted, but the current
987
+ * cockpit keeps it in localStorage (`packages/web/src/lib/last-location.ts`): stored here, the
988
+ * last client to navigate decided where every OTHER client's next launch landed. */
952
989
  lastLocation: workspaceLastLocationSchema.optional(),
953
990
  /** The user's curated selection of default (vendor) skills. Tri-state: ABSENT means "not
954
991
  * curated", so every default skill shows; a PRESENT array (even `[]`) means only those names
@@ -1563,12 +1600,14 @@ export {
1563
1600
  runCommitSchema,
1564
1601
  runCommitsResponseSchema,
1565
1602
  runEventSchema,
1603
+ runIndexEntrySchema,
1566
1604
  runRecordSchema,
1567
1605
  runStatusSchema,
1568
1606
  runnerModelCatalogResponseSchema,
1569
1607
  runnerModelOptionSchema,
1570
1608
  runnerModelsSchema,
1571
1609
  runnerSchema,
1610
+ runsIndexResponseSchema,
1572
1611
  saveWorkflowInputSchema,
1573
1612
  saveWorkflowResponseSchema,
1574
1613
  selectAgentProfileInputSchema,
@@ -445,6 +445,92 @@ export declare const apiRunSchema: z.ZodObject<{
445
445
  }, z.core.$strip>>;
446
446
  }, z.core.$strip>;
447
447
  export type ApiRun = z.infer<typeof apiRunSchema>;
448
+ /**
449
+ * One run in the WORKSPACE-level index (`GET /api/v1/workspace/runs-index`) — the ⌘K palette's
450
+ * "find a task in any project" list.
451
+ *
452
+ * Deliberately a separate, slim shape rather than `ApiRun`. The index answers for every
453
+ * registered project at once, and `runRecordSchema` carries `steps[]` and `workflowDef` — a fat
454
+ * record whose cost is fine per project and absurd multiplied by the registry. These are exactly
455
+ * the fields a palette row renders: `runTitle`'s three (`title`, `titleSummary`, `titleOrigin`),
456
+ * `deriveAttention`'s `AttentionInput`, `isUnread`'s `ReadStateInput`, and the timestamps
457
+ * `shortAge` reads. Adding a field here is cheap; adding the whole record is what this exists to
458
+ * avoid — but note that widening either of those two `Pick`s means widening this too, or the
459
+ * palette's cross-project rows silently answer differently from every other surface.
460
+ *
461
+ * `projectId` is the join key, NOT the project name: the registry is already on the client and is
462
+ * authoritative for display names, and duplicating one here would let a renamed project show two
463
+ * different labels in one palette.
464
+ */
465
+ export declare const runIndexEntrySchema: z.ZodObject<{
466
+ projectId: z.ZodString;
467
+ id: z.ZodString;
468
+ title: z.ZodString;
469
+ titleSummary: z.ZodOptional<z.ZodString>;
470
+ titleOrigin: z.ZodOptional<z.ZodEnum<{
471
+ auto: "auto";
472
+ marker: "marker";
473
+ user: "user";
474
+ }>>;
475
+ status: z.ZodEnum<{
476
+ cancelled: "cancelled";
477
+ done: "done";
478
+ failed: "failed";
479
+ queued: "queued";
480
+ review: "review";
481
+ running: "running";
482
+ waiting: "waiting";
483
+ }>;
484
+ activity: z.ZodOptional<z.ZodEnum<{
485
+ monitoring: "monitoring";
486
+ }>>;
487
+ createdAt: z.ZodString;
488
+ finishedAt: z.ZodOptional<z.ZodString>;
489
+ seenAt: z.ZodOptional<z.ZodString>;
490
+ archived: z.ZodBoolean;
491
+ autoResumeAt: z.ZodOptional<z.ZodString>;
492
+ }, z.core.$strip>;
493
+ export type RunIndexEntry = z.infer<typeof runIndexEntrySchema>;
494
+ /**
495
+ * `GET /workspace/runs-index`.
496
+ *
497
+ * `truncated` is not decoration: the index caps each project's contribution, and a capped list
498
+ * that says nothing reads as "your task is not here" when the honest answer is "not in the
499
+ * newest N". Naming the projects that hit the cap is what lets a consumer say so.
500
+ */
501
+ export declare const runsIndexResponseSchema: z.ZodObject<{
502
+ runs: z.ZodArray<z.ZodObject<{
503
+ projectId: z.ZodString;
504
+ id: z.ZodString;
505
+ title: z.ZodString;
506
+ titleSummary: z.ZodOptional<z.ZodString>;
507
+ titleOrigin: z.ZodOptional<z.ZodEnum<{
508
+ auto: "auto";
509
+ marker: "marker";
510
+ user: "user";
511
+ }>>;
512
+ status: z.ZodEnum<{
513
+ cancelled: "cancelled";
514
+ done: "done";
515
+ failed: "failed";
516
+ queued: "queued";
517
+ review: "review";
518
+ running: "running";
519
+ waiting: "waiting";
520
+ }>;
521
+ activity: z.ZodOptional<z.ZodEnum<{
522
+ monitoring: "monitoring";
523
+ }>>;
524
+ createdAt: z.ZodString;
525
+ finishedAt: z.ZodOptional<z.ZodString>;
526
+ seenAt: z.ZodOptional<z.ZodString>;
527
+ archived: z.ZodBoolean;
528
+ autoResumeAt: z.ZodOptional<z.ZodString>;
529
+ }, z.core.$strip>>;
530
+ perProjectLimit: z.ZodNumber;
531
+ truncated: z.ZodArray<z.ZodString>;
532
+ }, z.core.$strip>;
533
+ export type RunsIndexResponse = z.infer<typeof runsIndexResponseSchema>;
448
534
  /**
449
535
  * `POST /runs` (201) — one record for ×1, a group for ×2/×3.
450
536
  *
@@ -6,47 +6,22 @@
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
- * 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.
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.
50
25
  */
51
26
  /** What a caller's `git` runner must answer with. Both existing runners are wider than this. */
52
27
  export interface GitRunResult {
@@ -63,23 +38,23 @@ export interface RepointedHead {
63
38
  export interface TaskDiffBase {
64
39
  /** The ref to diff against. */
65
40
  base: string;
66
- /** Present only when HEAD left the task's branch — the reason `base` is what it is. */
41
+ /** Present only when HEAD left the task's branch — the reason `base` is `HEAD`. */
67
42
  repointedHead?: RepointedHead;
68
43
  }
69
44
  /**
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.
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.
73
48
  *
74
49
  * `taskBranch` is optional because not every caller has one (the main working
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.
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.
77
53
  *
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.
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.
81
57
  */
82
58
  export declare function resolveTaskDiffBase(runGit: GitRunner, baseBranch: string, opts?: {
83
59
  taskBranch?: string;
84
- runStartedAt?: string;
85
60
  }): Promise<TaskDiffBase>;
@@ -6,175 +6,49 @@
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
- * 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.
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.
58
25
  */
59
- const ISO_INSTANT = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
60
26
  /**
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.
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.
137
30
  *
138
31
  * `taskBranch` is optional because not every caller has one (the main working
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.
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.
141
35
  *
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.
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.
145
39
  */
146
40
  export async function resolveTaskDiffBase(runGit, baseBranch, opts = {}) {
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 };
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 };
179
53
  }
180
54
  //# 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":"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"}
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"}
@@ -22,11 +22,6 @@ 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.
30
25
  */
31
26
  export declare function resolveBaseRef(repoRoot: string, base: string): Promise<string | null>;
32
27
  export declare function worktreePathFor(repoRoot: string, runId: string): string;
@@ -119,10 +114,9 @@ export interface DiffStat {
119
114
  adds: number;
120
115
  dels: number;
121
116
  files: number;
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. */
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. */
126
120
  repointed?: boolean;
127
121
  }
128
122
  /**
@@ -138,11 +132,9 @@ export declare function parseShortstat(s: string): DiffStat;
138
132
  * behind `RunRecord.diffStat`, which is what the sidebar quick list and the
139
133
  * Tasks table show. Same intent-to-add as `worktreeDiff`, but the anchor comes
140
134
  * from the shared `resolveTaskDiffBase` rule (`git-diff-base.ts`): pass the
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.
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).
146
138
  *
147
139
  * `repointed: true` rides along on the returned stat exactly when that
148
140
  * narrowing happened, so the UI can say why the number is what it is. Null on
@@ -151,7 +143,6 @@ export declare function parseShortstat(s: string): DiffStat;
151
143
  */
152
144
  export declare function worktreeShortstat(worktreePath: string, baseBranch: string, opts?: {
153
145
  taskBranch?: string;
154
- runStartedAt?: string;
155
146
  }): Promise<DiffStat | null>;
156
147
  /**
157
148
  * Startup reconcile: `git worktree prune` + remove every directory under