my-durable 0.0.24 → 0.0.26

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.
@@ -53,8 +53,13 @@ export interface GitStatus {
53
53
  subject?: string;
54
54
  worktreeOf?: string;
55
55
  worktrees?: { path: string; branch: string | null; head: string }[];
56
- files?: { path: string; x: string; y: string }[];
56
+ /** ±n line counts from numstat; -1 marks a binary file. */
57
+ files?: { path: string; x: string; y: string; a?: number; d?: number }[];
57
58
  branches?: { name: string; current?: boolean; upstream?: string }[];
59
+ merge?: boolean;
60
+ conflicts?: number;
61
+ identity?: { name?: string; email?: string };
62
+ remoteUrl?: string;
58
63
  }
59
64
  export type GitOpName =
60
65
  | "diff"
@@ -67,7 +72,31 @@ export type GitOpName =
67
72
  | "commit"
68
73
  | "worktree-list"
69
74
  | "worktree-add"
70
- | "worktree-remove";
75
+ | "worktree-remove"
76
+ | "push"
77
+ | "stage"
78
+ | "discard"
79
+ | "diff-file"
80
+ | "stash-push"
81
+ | "stash-pop"
82
+ | "unstage"
83
+ | "compare"
84
+ | "merge-abort"
85
+ | "show-files"
86
+ | "ignore"
87
+ | "checkpoint"
88
+ | "checkpoints"
89
+ | "rewind";
90
+
91
+ /** Everything a git op may return; most carry only text. */
92
+ export interface GitOpResult {
93
+ text: string;
94
+ commits?: { hash: string; refs: string; author: string; date: string; subject: string; parents: string[] }[];
95
+ ahead?: number;
96
+ behind?: number;
97
+ changed?: { status: string; path: string }[];
98
+ checkpoints?: { name: string; hash: string; date: string; subject: string }[];
99
+ }
71
100
 
72
101
  export class DpiClient {
73
102
  readonly baseUrl: string;
@@ -181,13 +210,25 @@ export class DpiClient {
181
210
  gitOp(
182
211
  path: string,
183
212
  op: GitOpName,
184
- opts: { ref?: string; branch?: string; message?: string; target?: string; files?: string[] } = {},
213
+ opts: {
214
+ ref?: string;
215
+ branch?: string;
216
+ message?: string;
217
+ target?: string;
218
+ files?: string[];
219
+ create?: boolean;
220
+ staged?: boolean;
221
+ rebase?: boolean;
222
+ setUpstream?: boolean;
223
+ name?: string;
224
+ lines?: string[];
225
+ } = {},
185
226
  ) {
186
- return this.request<{ text: string; commits?: { hash: string; refs: string; author: string; date: string; subject: string }[] }>(
187
- "POST",
188
- "/v1/git",
189
- { path, op, ...opts },
190
- );
227
+ return this.request<GitOpResult>("POST", "/v1/git", { path, op, ...opts });
228
+ }
229
+ /** Paths this session's agents wrote/edited — the session-changelist. */
230
+ sessionGitFiles(sessionId: string) {
231
+ return this.request<{ files: string[] }>("GET", `/v1/sessions/${enc(sessionId)}/git-files`);
191
232
  }
192
233
  /** Many paths, one round-trip — session lists want branch-per-cwd. */
193
234
  gitStatusBatch(paths: string[], options: { timeoutMs?: number } = {}) {
@@ -28,6 +28,7 @@ const ROUTES: readonly (readonly [method: string, pattern: string])[] = [
28
28
  ["GET", "/sessions/:s/tasks"],
29
29
  ["POST", "/sessions/:s/tasks/:taskId/abort"],
30
30
  ["GET", "/sessions/:s/inspect"],
31
+ ["GET", "/sessions/:s/git-files"],
31
32
  ["GET", "/sessions/:s/submissions/:id"],
32
33
 
33
34
  ["GET", "/sessions/:s/agents"],
@@ -0,0 +1,194 @@
1
+ # Git UI Feature Checklist — orca / paseo / zcode → durable-pi
2
+
3
+ Run: `team_20261024073535_a2a56c3410bfe1d9` · Steps: 01_explore (research) → 02_analyze (verify/correct) → 03_write (this file)
4
+ Status: **verified against source** — every coverage claim below was re-read in the actual files before persisting (see [Verification log](#verification-log)).
5
+
6
+ ## Why this doc exists
7
+
8
+ Our git UI (`GitPanel.vue` + device `git.ts`) is a working IntelliJ-style panel, but three local
9
+ reference repos have shipped years of git-UX refinement we have not. This checklist inventories
10
+ every git feature their UIs expose, marks what we already cover, and sequences the remainder into
11
+ adoption waves sized for PR-sized work. It is a planning input, not a commitment: the leader picks
12
+ the sequencing, especially for Wave 3.
13
+
14
+ **How to read it**
15
+
16
+ - Coverage vs our baseline: ✅ covered · 🟡 partial (name the delta) · ❌ missing.
17
+ - Tier: **MUST-LEARN** = orca main flows (primary reference — desktop app, richest git UX) ·
18
+ **SHOULD** = paseo/zcode niceties · **SKIP** = infra-specific, do not adopt.
19
+ - Waves sequence by effort/dependency: 1a FE-only → 1b tiny BE ops → 2 medium → 3 differentiators.
20
+
21
+ ## Our baseline (verified today)
22
+
23
+ All line numbers verified by direct read in this run.
24
+
25
+ **BE — `durable-pi/apps/dpi-backend/src/git.ts` (~285 lines):**
26
+
27
+ - `gitOp` — 17 whitelisted ops (`GitOp` union, ~:190-207): `diff, log, log-list, fetch, pull,
28
+ switch, branch, commit, worktree-list, worktree-add, worktree-remove, push, stage, discard,
29
+ diff-file, stash-push, stash-pop`. Every call is `execFile("git", ["-C", cwd, ...args])` with
30
+ hand-built argv and `safeRef` validation — no shell, no interpolated paths.
31
+ - `gitStatus(path)` (~:70-160) — one call returns branch, upstream, **ahead/behind** (parsed from
32
+ `## branch...upstream [ahead N, behind M]`, ~:126-131), staged/unstaged/untracked counts,
33
+ per-file porcelain `x`/`y` codes **capped at 500 files** (`body.slice(0, 500)`, ~:118), local
34
+ branches (for pickers), and the worktree list.
35
+ - `pull` is `--ff-only` (unchanged by this checklist; any rebase fallback must be explicit opt-in).
36
+
37
+ **FE — `durable-pi/apps/dpi-playground/apps/dpi-playground-fe/src/components/GitPanel.vue` (~415 lines):**
38
+
39
+ - Branch menu + new-branch input; fetch/pull/push/stash/unstash buttons; per-file view-diff /
40
+ stage / discard (discard guarded by `confirm()`, `fileAction()` ~:95); commit-selected with
41
+ changelist semantics; structured log list; worktree add / per-worktree **session** launch
42
+ (`sessionOn()` ~:167) / remove (confirm-guarded, ~:193).
43
+ - States: loading / refreshing / error+retry / not-a-repo hint; busy disables; `↑N ↓N` ahead/behind
44
+ tags (~:247-248) and `+~?` count chip (~:249); file-diff popup truncated at 8000 chars (~:303),
45
+ op output at 6000 (~:373).
46
+
47
+ **Known defect found during review:** `loadLog()` swallows errors (`catch { /* decorative */ }`,
48
+ ~:212) — a failed log fetch renders as "no commits", which is misleading. Fix is Wave 1a.
49
+
50
+ ## Corrections vs the first-draft table
51
+
52
+ The explorer's original 45-row table had 5 wrong coverage cells. The corrected values below are
53
+ verified; do not resurrect the originals.
54
+
55
+ | # | Feature | Was | Now | Evidence |
56
+ |---|---|---|---|---|
57
+ | 1 | Upstream ahead/behind | ❌ | ✅ | `git.ts` `gitStatus()` parses `[ahead N, behind M]`; GitPanel renders `↑N ↓N`. Only *arbitrary-base* compare (`rev-list --left-right --count base...HEAD`) is missing. |
58
+ | 2 | Discard confirmation | ❌ | ✅ | Native `confirm()` in `GitPanel.vue` `fileAction()`. Orca's multi-file dialog is a refinement, not a gap. |
59
+ | 3 | Worktree-remove warning | ❌ | 🟡 | Basic `confirm()` exists in `removeWorktree()`. Delta: orca's "uncommitted changes will be lost" wording (orca `i18n/en.json:10533`). |
60
+ | 4 | Staged/unstaged split | ❌ | 🟡 | Per-file `x`/`y` codes and aggregate counts already flow from BE; the sectioned list is **FE-only** work. |
61
+ | 5 | Too-many-changes cap | ❌ | narrower | BE already caps at 500 files (`git.ts` ~:118). Missing: a "showing N of M" banner when the cap bites. |
62
+ | 6* | Diff truncation | ❌ | 🟡 | *Write-time note:* GitPanel already slices the file-diff popup at 8000 chars and op output at 6000. Still missing: binary/large-file labels and diff-viewer options (orca `DiffViewer.tsx`). |
63
+
64
+ \* Row 6 was corrected by the write step during final verification, not by the analyst.
65
+
66
+ ## Wave 1a — FE-only, no BE change (start here)
67
+
68
+ | Tier | Feature | Source (path:line) | Ours | Adoption suggestion |
69
+ |---|---|---|---|---|
70
+ | MUST | Staged/Unstaged split sections | orca `listing/uncommitted-sections.tsx:99-155` | 🟡 (x/y codes exist) | Split file list on `f.x !== ' '` vs rest |
71
+ | MUST | Stage-all button | orca `uncommitted-sections.tsx:99` | ❌ | `stage` op already takes `files[]` |
72
+ | SHOULD | ⌘/Ctrl+Enter to commit | zcode `GitActionMenu.tsx:369` | ❌ | keydown on commit textarea |
73
+ | MUST | Commit draft persistence | orca `commit/commit-drafts.ts` | ❌ | localStorage keyed by cwd |
74
+ | MUST | Copy-path / reveal actions | orca `listing/entry-context-menu.tsx:110-200` | ❌ | icon buttons per file row |
75
+ | MUST | Log error surfacing | our gap (`GitPanel.vue` `loadLog` ~:212) | ❌ | replace `catch {}` with notify |
76
+ | MUST | "Showing N of 500" banner | orca `listing/too-many-changes-banner.tsx:101` | ❌ | note when `files.length === 500` |
77
+ | MUST | Folder/tree grouping | orca `listing/tree-directory-rows.tsx:106-135` | ❌ | group by dirname, collapsible |
78
+
79
+ ## Wave 1b — three tiny BE ops (whitelisted, same execFile pattern)
80
+
81
+ | Tier | Feature | Source (path:line) | Ours | Adoption suggestion |
82
+ |---|---|---|---|---|
83
+ | MUST | Unstage (`git reset HEAD --`) | orca `git-bridge.ts:170` | ❌ | new `unstage` op beside stage icon |
84
+ | MUST | ±n line counts | orca `listing/diff-line-counts.tsx` | ❌ | `diff --numstat` op → row chip |
85
+ | MUST | Base-compare ahead/behind | orca `git-bridge.ts:60-63` | 🟡 (upstream ✅) | `rev-list --left-right --count` |
86
+ | MUST | Commit&push | orca primary-action; zcode `GitActionMenu.tsx:243` | ❌ | FE chain commit→push (**no BE op needed**) |
87
+ | MUST | Conflict banner + abort | orca `git-bridge.ts:49-52` | ❌ | detect MERGE_HEAD in `gitStatus` + `abort` op |
88
+ | SHOULD | Identity check pre-commit | zcode `switchAssist.ts` | ❌ | `rev-parse user.name` → warn if unset |
89
+ | MUST | Worktree-remove loss warning | orca `i18n/en.json:10533` | 🟡 | extend existing confirm text |
90
+
91
+ ## Wave 2 — medium
92
+
93
+ | Tier | Feature | Source (path:line) | Ours | Adoption suggestion |
94
+ |---|---|---|---|---|
95
+ | SHOULD | AI commit message | orca `git-bridge.ts:125`; zcode `GitActionMenu.tsx:992` | ❌ | agent summarizes `diff --cached` into textarea |
96
+ | MUST | Expand files per commit | orca `sync/git-history-commit-files.tsx:121` | ❌ | `show --name-status <hash>` op |
97
+ | MUST | Log graph swimlanes | orca `sync/git-history-graph-svg.tsx`; zcode `git-graph/layoutAlgorithm.ts:50-279` | 🟡 | start 2-lane SVG; zcode algo is portable TS |
98
+ | MUST | Diff binary/large labels | orca `editor/DiffViewer.tsx` options | 🟡 (truncation exists) | label binaries; cap oversized hunks |
99
+ | MUST | Push-failure recovery hints | orca `sync/push-recovery.ts` | ❌ | parse error → suggest pull |
100
+ | SHOULD | Auto-stash on switch | paseo `branch-switcher-operations.ts:6-16`; `use-branch-switcher.ts:93-99` | 🟡 | stash→switch→pop on conflict |
101
+ | MUST | Pull policy fallback | orca `commit/pull-policy-error-notice.ts` | 🟡 | offer `--rebase` when ff-only fails (explicit opt-in) |
102
+ | MUST | .gitignore helper | orca `git-bridge.ts:35-43` | ❌ | "ignore" → append to .gitignore |
103
+ | MUST | Open in remote | orca `git-bridge.ts:190-195` | ❌ | parse `remote get-url` → link |
104
+
105
+ ## Wave 3 — differentiators / later
106
+
107
+ | Tier | Feature | Source (path:line) | Ours | Adoption suggestion |
108
+ |---|---|---|---|---|
109
+ | SHOULD (flagship) | **Session-scoped commit** (tag session-touched files) | zcode `session/taskChangeSummary.ts:21-129`; `GitActionMenu.tsx:992` | ❌ | track session-touched paths → "commit session changes" |
110
+ | SHOULD | Per-turn change summaries | zcode `taskChangeSummary.ts` | ❌ | natural extension of the above |
111
+ | SHOULD | Git checkpoints (rewind) | zcode `git/repo/gitCheckpointStore.ts:23-45` | ❌ | hidden-ref per session; fits durable model |
112
+ | SHOULD | Diff modes (working/staged/base) | paseo `diff-pane.tsx:136` | 🟡 | toggle on diff-file (staged opt exists, unused) |
113
+ | MUST | Worktree palette quick-switch | orca `lib/worktree-palette-create-action.ts` | ❌ | dropdown reusing worktree-add |
114
+ | — (defer) | PR create/merge, CI checks, diff comments, submodule UI, FS auto-refresh | paseo/zcode (e.g. paseo `use-actions.tsx:705-795`) | ❌ | defer — needs forge APIs / line-diff / fs events |
115
+
116
+ **Why session-scoped commit is the flagship:** zcode builds per-turn file-change summaries from
117
+ persisted snapshots (`taskChangeSummary.ts` aggregates before→after per path per turn) and scopes
118
+ the commit dialog to the current session's files. That maps directly onto durable-pi's
119
+ session-on-worktree model (`GitPanel.vue` `sessionOn()`) — "commit what this session touched" is
120
+ the git primitive an agent runtime wants and none of the other references have end-to-end.
121
+
122
+ ## SKIP — infra-specific, do not adopt
123
+
124
+ - orca command-runner / admission / WSL-SSH routing (`src/main/git/*`)
125
+ - orca syncFork + Gitea/GitLab hosted review (`git-bridge.ts:75`; `src/main/gitea/`)
126
+ - orca sparse worktree dir sets + remote worktree sleep (`fr.json:8834`; `remote-worktree-sleep.ts`)
127
+ - paseo forge checkout ref resolution (`forge-service.ts:445-466`)
128
+ - paseo desktop per-worktree isolation (`desktop/src/main.ts:300-325`)
129
+ - i18n auto-key infra (orca)
130
+
131
+ ## UX shape by repo
132
+
133
+ Synthesized from the explorer pass; items marked ✓n were re-verified in this run.
134
+
135
+ - **orca** — IntelliJ/VSCode hybrid right-sidebar: sectioned changes with per-section bulk actions
136
+ (Stage all / Unstage all / Discard all — ✓n `uncommitted-sections.tsx:99-155`), context menus,
137
+ adaptive split button, cancellable ops, per-state rendering (idle/loading/ready/error).
138
+ - **paseo** — mobile/agent-first: toasts, policy gating with human-readable "unavailable" reasons,
139
+ a11y/testIDs, workspaces == worktrees, PR-centric actions (`use-actions.tsx`).
140
+ - **zcode** — VSCode-ish pane with explicit states; segmented commit dialog where ⏎ submits and
141
+ ↑/↓ move between actions (✓n `GitActionMenu.tsx:355-375`); graph dialog; agent-turn grouping —
142
+ the closest cousin to "git UI for an agent runtime".
143
+ - **durable-pi today** — flat single panel, icon rows, busy/error strings, native `confirm()`.
144
+
145
+ ## Sessions / agents ↔ worktrees
146
+
147
+ - **orca**: worktree IS the workspace unit — tabs/terminals/agents keyed by worktreeId
148
+ (`store/slices/worktrees.ts:44-57`, explorer-verified).
149
+ - **paseo**: worktree per workspace; PR checkouts become workspaces (`checkout/status-projection.test.ts:264`).
150
+ - **zcode**: no worktree fan-out; checkpoints + per-turn summaries tie agents to git instead.
151
+ - **durable-pi**: `worktree-list/add/remove` + "session on worktree" (`GitPanel.vue:345-362`) —
152
+ direction matches orca; lacks per-worktree panel context and loss warnings.
153
+
154
+ ## Open questions (for the leader / planner)
155
+
156
+ 1. **Sequencing**: land Wave 3 session-scoped commit before Wave 2 polish (graph, AI message), or
157
+ after? Strategic call — the analyst deferred it to the leader.
158
+ 2. **Wave 3 design**: where do "session-touched paths" come from — device fs events or the
159
+ session's tool-call log? Needed before any Wave 3 PR.
160
+ 3. **AI commit message latency**: an agent round-trip per commit; no budget specified. Cap or async?
161
+ 4. **Testability**: native `confirm()` guards cannot be exercised in headless UI runs (bench
162
+ evidence needs an app modal). Swap to a modal when adopting dialog refinements.
163
+ 5. **Unverified claims** (adopt conservatively): orca's full keyboard-shortcut registry is
164
+ i18n-keyed and only the commit screen-submit was verified (analyst, via `CommitArea.test.tsx:11`);
165
+ zcode graph search/filter interactions are unverified — adopt the layout algorithm only.
166
+
167
+ ## Constraints for adopters
168
+
169
+ - New BE ops must follow the existing `execFile` whitelist pattern in `git.ts` (`safeRef`/argv, no shell).
170
+ - `pull` stays `--ff-only`; any rebase fallback is explicit opt-in.
171
+ - No forge/GitHub-API features (PR merge, CI checks) on the device side.
172
+ - Waves 1-2 touch exactly two files: `git.ts` and `GitPanel.vue` — single-file ownership per PR.
173
+
174
+ ## Verification log
175
+
176
+ Evidence gathered by the write step (03_write) before persisting this file; direct `read`
177
+ inspections, no shell:
178
+
179
+ | Read | Confirmed |
180
+ |---|---|
181
+ | `durable-pi/apps/dpi-backend/src/git.ts` (full) | 17-op union; ahead/behind parsing; 500-file cap; x/y codes; `pull --ff-only`; `safeRef`/execFile pattern; `isNotARepoError` |
182
+ | `durable-pi/apps/dpi-playground/apps/dpi-playground-fe/src/components/GitPanel.vue` (full) | discard/worktree confirm guards; ↑/↓ tags; +~? chip; per-file x/y render; `sessionOn()`; log-error swallow; 8000/6000-char caps; error+retry/busy/loading states |
183
+ | orca `src/preload/api/git-bridge.ts:155-195` | stage/bulkStage/unstage/bulkUnstage/discard/bulkDiscard, remoteFileUrl |
184
+ | orca `listing/uncommitted-sections.tsx:95-155` | Stage all / Unstage all / Discard-all section actions; conflicts section |
185
+ | zcode `GitActionMenu.tsx:355-385` | dialog keydown: Enter submits, ↑/↓ select action |
186
+ | zcode `session/taskChangeSummary.ts:1-60` | per-turn before→after file summaries with line stats |
187
+ | paseo `branch-switcher-operations.ts:1-16` | stash list/save/pop + switch bound to one cwd |
188
+
189
+ Upstream verification (02_analyze): 7 additional spot-checks across the three repos, all matching;
190
+ 5 coverage corrections to the explorer table (register above). Original research: 01_explore, 45
191
+ rows, all cited path:line.
192
+
193
+ Explorer/analyst handoffs: `.crew/artifacts/team_20261024073535_a2a56c3410bfe1d9/results/01_explore.txt`,
194
+ `.crew/artifacts/team_20261024073535_a2a56c3410bfe1d9/results/02_analyze.txt`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-durable",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Durable Pi \u2014 the durable agent network: hub web UI, device backend daemon, TUI admin and the shared SDK, packaged as one runnable monorepo install",
5
5
  "license": "MIT",
6
6
  "repository": {