# Stasher — Agent Instructions

Stasher is a VS Code extension that provides a visual Activity Bar UI for Git stash management. It is written in TypeScript, bundled with esbuild, and shipped as a single CJS file.

## Build & Run

```bash
npm install          # install deps
npm run build        # node esbuild.js → dist/extension.js
npm run watch        # rebuild on save
npm run typecheck    # tsc --noEmit (type-check only, no emit)
```

Press **F5** in VS Code to launch the Extension Development Host (runs `npm run build` first via `preLaunchTask`).  
Packaging: `vsce package` after a production build (`node esbuild.js --production`).

## Architecture

```
src/extension.ts          Entry point — activate(), registers all commands, wires providers
src/gitHelper.ts          All git I/O; owns the singleton _api + _repo state, fires onDidChangeStashes
src/stashProvider.ts      StashTreeDataProvider + StashTreeItem / StashFileTreeItem
src/workingChangesProvider.ts  WorkingChangesProvider + WorkingFileItem / ChangeGroupItem
src/diffViewer.ts         showFileDiff() — diff via api.toGitUri(), no custom content provider
src/hunkPicker.ts         openHunkPicker() — opens diff with a temp file for hunk-level picking
src/partialStash.ts       partialStashCommand() — multi-step QuickPick file picker then git stash push
src/conflictHelper.ts     withConflictHandling() wrapper + handleConflicts() notification
src/gitEnums.ts           Runtime-bundlable copy of VS Code git Status enum values
src/git.d.ts              Type-only declarations for VS Code built-in git API (not bundled)
```

## Key Conventions

### Git Operations Split
- **VS Code Git API** (`_repo.*`) for write ops: apply, pop, drop, branch, diff
- **`spawnSync`** (synchronous) for reads the API doesn't expose: `listStashes`, `getStashFileContent`  
  Never make `spawnSync` calls async or switch them to `spawn`.

### The `vscode` Module is Always External
esbuild is configured with `external: ['vscode']` — never import anything from `vscode` expecting it to be bundled. It is provided at runtime by VS Code.

### `gitEnums.ts` vs `git.d.ts`
`git.d.ts` is type-only and **not bundled**. The `Status` enum values live in `gitEnums.ts` so esbuild can include them. Always import `Status` from `./gitEnums`, never from `./git`.

### Adding a New Command
1. Add git logic to `src/gitHelper.ts` (export the function)
2. Register the command in `src/extension.ts` inside `activate()` using `context.subscriptions.push(vscode.commands.registerCommand(...))`
3. Call `provider.refresh()` after any stash-mutating operation
4. Add the command to `package.json` → `contributes.commands`
5. Add menu entries to `package.json` → `contributes.menus` using the correct `when` clause and `viewItem` guard

### Tree `contextValue` Strings
These strings in tree items **must exactly match** the `when` clause conditions in `package.json`:
| Value | Item type |
|---|---|
| `stashEntry` | A stash in the Stashes view |
| `stashFile` | A file inside a stash |
| `stashLoading` | Shown while stash files are loading |
| `stashError` | Shown if loading stash files fails |
| `workingFile` | A file in Working Changes |
| `changeGroup` | "Staged" / "Working" group header |

### Stash Refresh Pattern
After any operation that mutates stashes, call `provider.refresh()`. The provider also auto-refreshes via `onDidChangeStashes` (debounced 400 ms) when the git repo state changes.

### Error Handling Pattern
All command handlers follow:
```typescript
try {
  await someGitOperation();
  provider.refresh();
} catch (err) {
  const msg = err instanceof Error ? err.message : String(err);
  void vscode.window.showErrorMessage(`Stasher: <action> failed — ${msg}`);
}
```

Conflict-producing operations (apply, pop) use `withConflictHandling()` from `conflictHelper.ts`.

### Diff Pattern
- **Stash file diff** (stash vs HEAD): `showFileDiff()` in `diffViewer.ts` via `api.toGitUri(uri, stashRef)` and `api.toGitUri(uri, \`${stashRef}^1\`)`
- **Hunk picking** (stash vs working tree): `openHunkPicker()` in `hunkPicker.ts` writes a temp file to `os.tmpdir()` and opens `vscode.diff`; temp files are cleaned up on `onDidCloseTextDocument`

## Common Pitfalls
- **Only one repo tracked**: `_repo` is set to the first opened repository. Multi-root stash operations are not supported.
- **No async spawnSync**: `spawnSync` is intentionally synchronous — don't convert to async `execFile`.
- **`contextValue` typo**: A mismatched `contextValue` silently hides context menu items.
- **Forgetting `provider.refresh()`**: Stash list will not update after mutations.
- **`git.d.ts` imports**: Only use it for types (`import type`). Never import values from it.
- **`vscode.git` activation**: `initGitApi()` handles the case where `vscode.git` is not yet active — always call it first in `activate()`.
