From 369bceb0a56fd38ae20a4100a56d78f73cfc0335 Mon Sep 17 00:00:00 2001 From: v0agent Date: Thu, 2 Jul 2026 08:54:51 +0000 Subject: [PATCH 1/3] feat: add git_fetch, git_pull, git_push tools and sync UI in Git panel Co-authored-by: Jordan Harrison <76656637+jouwdan@users.noreply.github.com> --- TODO.md | 557 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 557 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..def171d --- /dev/null +++ b/TODO.md @@ -0,0 +1,557 @@ +# TODO + +Prioritized backlog for meith, produced from a full codebase audit (v0.4.0). Items are +in the order they should be tackled: fixes and completions of existing surfaces first, +then platform/quality work, then new features. Each item is written to be +self-contained — an agent picking one up should not need any other context beyond this +file and the repository. + +Repo orientation (applies to every item): + +- pnpm monorepo. Main process authority lives in `packages/desktop/src/main/`. All + capabilities are typed tools registered in `packages/desktop/src/main/tools/` and + dispatched through `ToolRegistry` (`packages/desktop/src/main/tools/registry.ts`). +- Tools are defined with `defineTool({ name, description, capabilities, inputSchema, + execute })` using Zod input schemas. Every mutating tool MUST declare at least one + privileged capability (`writes-files`, `controls-browser`, `starts-process`, or + `destructive`) — a test sentinel in `packages/desktop/src/main/__tests__/toolFactories.test.ts` + enforces this at build time. +- Shared persisted data shapes live in `packages/shared/src/schemas.ts`. Protocol/tool + contracts live in `packages/protocol/src/`. +- The renderer (`packages/desktop/src/renderer/src/`) never mutates services directly; + it calls tools through the preload bridge (`window.meith`, see + `packages/desktop/src/renderer/src/bridge.ts`) and re-renders from pushed app state. +- The CLI (`packages/cli/src/`) maps commands onto the same tools over an NDJSON socket. +- Tests are Vitest, colocated under `__tests__` in each package. Run scoped: + `pnpm --filter @meith/desktop test`. Full gate: `pnpm check` (lint + typecheck + + build + test). Biome formatting: 2 spaces, double quotes, line width 90. +- Use Conventional Commit messages (Release Please derives versions from them). +- Never weaken tool permissions, browser ownership, plugin grants, or ACP/MCP approval + checks (see AGENTS.md). + +--- + +## 1. Add `git_push`, `git_pull`, and `git_fetch` tools plus sync UI in the Git panel + +**Problem.** The git tooling in `packages/desktop/src/main/tools/gitTools.ts` covers +status, diff, stage/unstage, commit, branch list/create/switch, log, blame, worktrees, +and Meith checkpoints — but there is no way to push, pull, or fetch. `computeStatus()` +already calculates `ahead`/`behind` against the upstream, and +`packages/desktop/src/renderer/src/components/GitPanel.tsx` (lines ~397–398) renders +`+ahead / -behind` counts, yet the user has no action to resolve them. A user can +commit inside meith but must leave the app to sync with a remote. Agents equally cannot +push a branch after committing. + +**What to do.** + +1. In `gitTools.ts`, add three new tools following the existing patterns in that file + (see `git_commit` / `git_branch` for reference; use the local `git()` helper which + wraps `execFile` with a 64 MB buffer): + - `git_fetch` — inputs: `cwd` (reuse `cwdSchema`), optional `remote` (default + `origin`), optional `prune` boolean. Capability: `read-only` is NOT correct here + because it touches the network and updates refs; use `writes-files`. Return + updated ahead/behind counts by re-running the status helpers. + - `git_pull` — inputs: `cwd`, optional `remote`, optional `rebase` boolean + (default false → `--ff-only` to avoid surprise merge commits; when `rebase` is + true use `--rebase`). Capability: `writes-files`. On failure (diverged and not + ff-able, or conflicts) throw a structured error message that names the failure + mode so the renderer can show it. Do NOT auto-resolve conflicts. + - `git_push` — inputs: `cwd`, optional `remote`, optional `branch`, optional + `setUpstream` boolean (when the current branch has no upstream, push with + `-u `), optional `force` boolean that must be paired with a + `confirm: z.literal(true)` field (mirror the guardrail pattern used by + `git_restore` in the same file). Capability: `writes-files` (`destructive` when + `force` is set — split into behavior inside execute or declare both). + - Register all three in the returned array at the bottom of `createGitTools()`. +2. Authentication caveat: pushes/pulls over HTTPS may prompt for credentials. Run git + with `GIT_TERMINAL_PROMPT=0` in the env (the `git()` helper accepts an `env` + option) so a missing credential fails fast with a clear error instead of hanging. + Surface that error text to the UI ("Push failed: authentication required. Configure + a credential helper or SSH key."). +3. In `GitPanel.tsx`, next to the existing ahead/behind indicator, add Sync actions: + a "Pull" button (visible when `behind > 0`), a "Push" button (visible when + `ahead > 0` or the branch has no upstream — label it "Publish branch" in that + case and pass `setUpstream: true`), and a fetch/refresh affordance. Wire them + through the same `callTool` helper the panel already uses (around line 297), show + a spinner while running, refresh status on completion, and surface errors with the + panel's existing error presentation. +4. Add CLI mappings in `packages/cli/src/commands.ts` (`meith git push`, `meith git + pull`, `meith git fetch`) following the existing mapped-command patterns there. +5. Tests: extend `packages/desktop/src/main/__tests__/gitTools.test.ts`. That file + already builds throwaway repos in temp dirs; add cases using a local bare repo as + the remote (`git init --bare` + `git remote add origin `) to test push, + pull ff, pull non-ff failure, publish-with-upstream, and fetch prune. +6. Update docs: `docs/developer/ADDING_TOOLS.md` does not need changes, but list the + new tools in `docs/user/TOOLS.md` and mention sync in the Git section of + `README.md` and `docs/user/USING_MEITH.md`. + +**Acceptance.** A user on a branch with upstream commits behind/ahead can pull and +push entirely from the Git panel; an agent can call `git_push` (gated by the normal +permission prompt because it declares `writes-files`); `pnpm --filter @meith/desktop +test` passes with the new cases. + +--- + +## 2. Add `git_stash` support so branch switching with a dirty worktree doesn't dead-end + +**Problem.** `git_branch` (in `packages/desktop/src/main/tools/gitTools.ts`) supports +`switch`, and the renderer has a top-bar branch switcher +(`packages/desktop/src/renderer/src/components/TopBarBranchSwitcher.tsx`). But `git +switch` fails when local modifications conflict with the target branch, and there is +no stash facility anywhere in the codebase (verified: zero matches for "stash" in +`packages/desktop/src`). The user hits a raw git error with no recovery path inside +the app. + +**What to do.** + +1. Add a `git_stash` tool in `gitTools.ts` with an `action` enum: + `list | push | pop | apply | drop`. Inputs: `cwd` (reuse `cwdSchema`), optional + `message` (for push), optional `includeUntracked` boolean (default true for push, + maps to `-u`), optional `stashRef` (for pop/apply/drop, e.g. `stash@{0}`), and for + `drop` require `confirm: z.literal(true)` (mirror `git_restore`'s guardrail). + Capabilities: `writes-files` for push/pop/apply, `destructive` for drop — simplest + compliant choice is declaring `["writes-files", "destructive"]` on the tool. + For `list`, parse `git stash list --format=%gd|%at|%s` into structured entries. +2. In `TopBarBranchSwitcher.tsx`, when a branch switch fails because of local + changes (detect via the error message from `git_branch`), present a small dialog: + "Stash changes and switch", "Cancel". The stash-and-switch path calls + `git_stash {action:"push"}`, retries the switch, and offers "Pop stash" after + landing on the new branch. Reuse the dialog primitives in + `packages/desktop/src/renderer/src/components/ui/dialog.tsx`. +3. Optionally surface stash entries in `GitPanel.tsx` as a collapsed "Stashes" + section under the staged/unstaged trees, with apply/pop/drop actions. +4. CLI mapping `meith git stash ` in `packages/cli/src/commands.ts`. +5. Tests in `gitTools.test.ts`: stash push with untracked files, list parse, pop + restores content, drop requires confirm. + +**Acceptance.** Switching branches with dirty files offers a stash-and-switch flow +instead of a dead-end error; stash list/pop/apply/drop work via tool, panel, and CLI. + +--- + +## 3. Add workspace file-management tools (delete, rename/move, create directory) and a file-tree context menu + +**Problem.** `packages/desktop/src/main/tools/fileTools.ts` registers only +`workspace_read_file`, `workspace_write_file`, `workspace_apply_patch`, +`workspace_undo`, `workspace_list_files`, `workspace_search`, and `get_diagnostics`. +There is no way — for the user in the editor file tree, for agents, or for the CLI — +to delete a file, rename/move a file, or create a directory inside a workspace. Agents +building features routinely need renames and deletions; today they would have to shell +out via a terminal tool, bypassing the workspace boundary checks and the file-edit +event stream that `WorkspaceFileService` +(`packages/desktop/src/main/services/WorkspaceFileService.ts`) maintains (the editor +listens to those events to react to external changes — see the handling around +`EditorView.tsx` line ~224). + +**What to do.** + +1. In `WorkspaceFileService.ts`, add methods `deleteFile(root, path)`, + `renameFile(root, fromPath, toPath)` (must also handle moves across directories, + creating intermediate directories on the destination), and + `createDirectory(root, path)`. Every method must enforce the same trusted-workspace + boundary checks that `readFile`/`writeFile` use (path resolution + rejection of + escapes; note the service deliberately skips symlinks in walks — do not follow + symlinks on delete/rename either). Each mutation should emit the same file-edit + event stream used by writes so the editor, Git panel refresh counter + (`GitPanel.tsx` line ~31), and undo bookkeeping stay coherent. For delete, record + the prior content so `workspace_undo` can restore it (the undo store already + handles "deleted newly-created file" for undoing creations — see + `WorkspaceFileService.ts` line ~508; extend it symmetrically). +2. In `fileTools.ts`, register `workspace_delete_file` (capabilities: + `["writes-files", "destructive"]`, require `confirm: z.literal(true)`), + `workspace_rename_file` (`["writes-files"]`), and `workspace_create_directory` + (`["writes-files"]`). Follow the input-schema style of the existing tools + (`root` + repository-relative `path`). +3. In `packages/desktop/src/renderer/src/components/EditorView.tsx`, add a context + menu (right-click) on file-tree entries with New File, New Folder, Rename, and + Delete. New File can reuse `workspace_write_file` with empty content. Rename of a + file that has unsaved local edits should carry the dirty buffer to the new path or + prompt to save first. Delete must confirm via dialog. Use the dropdown/dialog + primitives already in `components/ui/`. +4. CLI mappings (`meith files delete|rename|mkdir`) in `packages/cli/src/commands.ts`. +5. Tests: extend `packages/desktop/src/main/__tests__/workspaceFiles.test.ts` with + boundary-escape attempts (`../` traversal, absolute paths, symlinked dirs), rename + across directories, delete + undo restore, and event emission assertions. +6. Docs: add the tools to `docs/user/TOOLS.md`. + +**Acceptance.** Users can manage files from the editor tree; agents can rename/delete +inside workspace boundaries with permission gating; traversal attempts are rejected; +undo restores deleted files; all desktop tests pass. + +--- + +## 4. Add a Gemini ACP preset alongside Claude and Codex + +**Problem.** `packages/shared/src/schemas.ts` defines +`AcpPresetSchema = z.enum(["custom", "claude", "codex"])` and `ACP_PRESETS` with only +Claude (`@zed-industries/claude-code-acp`-style launch) and Codex +(`@agentclientprotocol/codex-acp`). Google's Gemini CLI speaks ACP natively +(`gemini --experimental-acp`), and meith's whole pitch is provider independence, so +the third major agent should be one preset click away instead of requiring manual +"custom" command configuration. + +**What to do.** + +1. In `packages/shared/src/schemas.ts`: extend `AcpPresetSchema` to include + `"gemini"`, and add a `gemini` entry to `ACP_PRESETS` (id, label "Gemini", + description, command/args). Verify the current correct launch invocation from the + Gemini CLI docs at implementation time (historically `npx -y + @google/gemini-cli --experimental-acp`, but confirm — do not trust this from + memory). Note the desktop app resolves `npx` to its packaged runtime (see the + comment above `ACP_PRESETS`), so use the same `npx -y` pattern as the Codex preset. +2. Renderer updates — every place that enumerates presets: + - `packages/desktop/src/renderer/src/components/AgentSelector.tsx`: + `SELECTABLE_PRESETS` (line ~13) and `PRESET_ICON` (line ~16). + - `packages/desktop/src/renderer/src/components/AgentIcon.tsx`: add a + `GeminiMark` SVG component rendered with `currentColor` (match how + `ClaudeMark`/`CodexMark` are built; source a brand mark similarly). + - `packages/desktop/src/renderer/src/overlay/icons.ts`: register the icon name. + - `packages/desktop/src/renderer/src/components/SettingsView.tsx`: add an + `