Files
boocode/docs/features/git-diff-panel/feature-implementation-plan.md
indifferentketchup ca028a4024 docs: add git-diff-panel implementation planning artifacts
Implementation decision log, iteration history, synthesis input, the
implementation plan, and discovery notes for the git-diff-panel feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 02:26:04 +00:00

198 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Feature Implementation Plan: Git Diff Panel
A Files / Git tab in the right-side file panel that reads the project repository's diff (two modes) and stages, unstages, commits, and discards whole files — built entirely in `apps/server` ([D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver)), shipped via a single docker rebuild, in two phases (read/display, then write actions) with no schema change, no migration, and no `@boocode/contracts` change.
## Source Specification
- **Feature specification:** [feature-specification.md](feature-specification.md)
- **Specification decision log:** [artifacts/decision-log.md](artifacts/decision-log.md)
- **Specification team findings:** [artifacts/team-findings.md](artifacts/team-findings.md)
- **Specification decisions this plan inherits:** D1D18
- **Specification open items this plan must respect or resolve:** None — the spec's only open item (commit identity) was settled at spec time (F3, D12).
## Outcome
When this plan is executed, the right-side file panel gains a Files / Git tab. Selecting **Git** shows the project repository's changed files (Uncommitted or Committed mode), each expandable to a syntax-highlighted unified diff. The user can stage, unstage, commit (with a server-derived identity), and discard whole files without leaving the session. The work is delivered by a new `apps/server/src/services/git_diff.ts` (read logic + git-write helpers + pure helpers), new read and write routes in `apps/server/src/routes/projects.ts`, and new `apps/web` UI (`GitDiffView`, a tab in `RightRail.tsx`, a `useGitDiff` hook, and a `git_diff_refresh` sessionEvent). No apps/coder change, no Postgres schema change, no new env var.
## Context
- **Driving constraint:** A direct user request (2026-06-02) to add a Paseo-style git diff panel shown "instead of the file browser." Not deadline-bound; scoped to ship as a coherent v1.
- **Stakeholders:** The single session user (reviews and commits their own repo changes in-session). The project's security posture (the write surface must not become an assistant-reachable path). On-call/operability (the panel must not stall on large or slow repos or fail opaquely under index contention).
- **Future-state concern:** The git-write surface now lives in `apps/server`. Watch for a third consumer of git ops (would trigger a shared-`packages/` extraction, [D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver)) and for working-tree diffs routinely exceeding the 10MB read buffer ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)).
- **Out-of-scope boundary:** No remote operations (push/pull/PR/merge), no side-by-side layout, no per-hunk staging, no continuous file-watch streaming, no rename of the pending-changes panel, no per-line review/re-prompt surface — all deferred in the spec under YAGNI.
## Team Composition and Participation
| Specialist | Status | Key Input |
|------------|--------|-----------|
| `project-manager` | Coordinator | Facilitated R1, corrected the service-owner decision against evidence, synthesized the plan. |
| `software-architect` | Active | Recommended a read-server/write-coder split (R1); the split's premise was refuted by evidence and recorded as the rejected alternative on [D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver). Owns the read shape, base resolution, refresh wiring, two-phase sequencing. |
| `adversarial-security-analyst` | Active | Argv-safety, path-traversal, server-derived identity, tool-registry exclusion, artifact-sandbox evidence ([D-3](artifacts/implementation-decision-log.md#d-3-write-ops-via-argv-safe-rungitexecfile-with----separators)[D-5](artifacts/implementation-decision-log.md#d-5-commit-identity-server-derived-request-schema-strict-with-no-author-fields), [D-13](artifacts/implementation-decision-log.md#d-13-write-endpoints-excluded-from-the-assistant-tool-registry-artifact-sandbox-closes-the-indirect-path)). |
| `on-call-engineer` | Active | Read deadline + maxBuffer, index-lock 409, refresh coalescence, in-progress detection, partial-failure honesty ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes), [D-8](artifacts/implementation-decision-log.md#d-8-refresh-via-client-git_diff_refresh-sessionevent-coalesced--not-a-ws-frame)). |
| `test-engineer` | Active | T1T12: pure-helper units + temp-repo integration; skip Shiki/layout (no web harness). |
| `junior-developer` | Reframer | Flagged the cross-service coupling of the write-in-coder split, which seeded the evidence correction on [D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver); confirmed the read-only-session Git tab needs no extra label (F21). |
## Implementation Approach
Both the git read and the git write operations live in `apps/server` ([D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver)) — reusing the existing safe argv `runGit` and the `project_bootstrap.ts` server-side git-write precedent, and avoiding a cross-service proxy hop, a second deploy surface, and coder coupling. The feature reuses Shiki (`lang:'diff'`) already in `apps/web`, the `useProjectGit` dirty signal, and the existing `RightRail.tsx` file-panel host; it introduces one new server service, new routes, one new web view + hook, and one new client `sessionEvents` event.
### Architecture and Integration Points
- **New `apps/server/src/services/git_diff.ts`** — read logic, git-write helpers, and TDD-first pure helpers (`parseNameStatus`, `splitDiffByFile`, `resolveCommittedBase`, `autoSelectMode`, `classify`, `detectInProgress`) ([D-2](artifacts/implementation-decision-log.md#d-2-read-route--git_diffts-pure-helpers-tdd-first)). `autoSelectMode` and `canCommit` are inline tested helpers, not separate modules ([D-12](artifacts/implementation-decision-log.md#trivial-decisions)).
- **New routes in `apps/server/src/routes/projects.ts`**, beside `GET /api/projects/:id/git`: read `GET /api/projects/:id/git/diff?mode=<uncommitted|committed>` ([D-2](artifacts/implementation-decision-log.md#d-2-read-route--git_diffts-pure-helpers-tdd-first)); writes for stage / unstage / commit / discard ([D-3](artifacts/implementation-decision-log.md#d-3-write-ops-via-argv-safe-rungitexecfile-with----separators)). The repository root is derived server-side via `path_guard.ts` `resolveProjectRoot`, never from the request ([D-4](artifacts/implementation-decision-log.md#d-4-path-validation--repo-relative-pathguard-reject-escape-and-repo-root-discard)).
- **Frontend** — a Files / Git tab in the existing `RightRail.tsx` header (replacing the static "Files" label, fitting one line); a new `GitDiffView` in the same slot as the file tree; a `useGitDiff` hook; the dirty dot fed by `useProjectGit`'s existing `is_dirty` ([D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)). Per-file expand/collapse is local state in `GitDiffView`, not a shared hook ([D-11](artifacts/implementation-decision-log.md#trivial-decisions)).
### Data Model and Persistence
None. The panel reads git state at request time and writes the project repository directly via git; nothing is persisted in Postgres — no schema change, no migration, no new env var ([D-10](artifacts/implementation-decision-log.md#trivial-decisions)).
### Runtime Behavior
- **Read:** the read route runs argv `runGit` calls under a 30s deadline and a 10MB `maxBuffer` ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)); `parseNameStatus` builds the file list, `splitDiffByFile` segments the unified diff, `classify` marks binary/large bodies, `detectInProgress` reads `.git` sentinels, and the response carries the resolved base label and the in-progress flag.
- **Mode/base:** `autoSelectMode` picks Uncommitted (dirty) or Committed (clean) on first open only ([D-12](artifacts/implementation-decision-log.md#trivial-decisions)); `resolveCommittedBase` resolves `@{upstream}``origin/HEAD` → null, falling back to a labeled Uncommitted on null ([D-6](artifacts/implementation-decision-log.md#d-6-committed-mode-base-resolution-via-upstream--originhead--null)).
- **Refresh:** the client `git_diff_refresh` sessionEvent fires on the five triggers, coalesced behind an in-flight ref so a running refresh absorbs later triggers ([D-8](artifacts/implementation-decision-log.md#d-8-refresh-via-client-git_diff_refresh-sessionevent-coalesced--not-a-ws-frame)).
- **Diff render:** `GitDiffView` highlights a file's diff via Shiki `lang:'diff'` lazily on expand, with a per-file loading state ([D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)).
### External Interfaces
New HTTP routes only (read + four writes) under `/api/projects/:id/git/…`. Refresh is a client-side `sessionEvents` event (`git_diff_refresh`), **not** a WS frame, so `@boocode/contracts` is not touched or rebuilt; the only parity step is a no-op `case 'git_diff_refresh'` in `useSidebar.ts` `applyEvent` ([D-8](artifacts/implementation-decision-log.md#d-8-refresh-via-client-git_diff_refresh-sessionevent-coalesced--not-a-ws-frame), [D-9](artifacts/implementation-decision-log.md#trivial-decisions)).
## Decomposition and Sequencing
Two phases on a single deploy surface ([D-14](artifacts/implementation-decision-log.md#d-14-two-phase-build--readdisplay-phase-1-then-write-actions-phase-2)); both ship via `docker compose up --build -d boocode`.
| # | Work Unit | Delivers | Depends On | Verification |
|---|-----------|----------|------------|--------------|
| 1 | `git_diff.ts` pure helpers (TDD-first) | `parseNameStatus`, `splitDiffByFile`, `resolveCommittedBase`, `autoSelectMode`, `classify`, `detectInProgress` ([D-2](artifacts/implementation-decision-log.md#d-2-read-route--git_diffts-pure-helpers-tdd-first), [D-6](artifacts/implementation-decision-log.md#d-6-committed-mode-base-resolution-via-upstream--originhead--null)) | — | Unit tests T1T7 |
| 2 | Read route `GET /api/projects/:id/git/diff?mode=` | Diff payload (files, counts, base label, in-progress flag) under 30s/10MB bounds ([D-2](artifacts/implementation-decision-log.md#d-2-read-route--git_diffts-pure-helpers-tdd-first), [D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)) | 1 | Temp-repo integration T8 |
| 3 | `useGitDiff` + RightRail Files/Git tab + dirty dot | Read-only panel, tab switch, dirty indicator from `useProjectGit` ([D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)) | 2 | Manual (no web harness) |
| 4 | `GitDiffView` read-only + lazy Shiki + refresh wiring | Per-file expand, lazy `lang:'diff'`, `git_diff_refresh` sessionEvent + `useSidebar.ts` no-op case, coalescence ref ([D-8](artifacts/implementation-decision-log.md#d-8-refresh-via-client-git_diff_refresh-sessionevent-coalesced--not-a-ws-frame), [D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)) | 3 | Manual |
| 5 | Write helpers + routes (stage/unstage/commit/discard) | Argv-safe writes with `--` separators, `pathGuard`, server-derived `-c` identity, `.strict()` commit schema, 409 on index-lock ([D-3](artifacts/implementation-decision-log.md#d-3-write-ops-via-argv-safe-rungitexecfile-with----separators), [D-4](artifacts/implementation-decision-log.md#d-4-path-validation--repo-relative-pathguard-reject-escape-and-repo-root-discard), [D-5](artifacts/implementation-decision-log.md#d-5-commit-identity-server-derived-request-schema-strict-with-no-author-fields), [D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)); endpoints kept out of `ALL_TOOLS` ([D-13](artifacts/implementation-decision-log.md#d-13-write-endpoints-excluded-from-the-assistant-tool-registry-artifact-sandbox-closes-the-indirect-path)) | 1, 2 | Temp-repo integration T9T12 |
| 6 | Write affordances + in-progress disable | Stage/unstage/commit/discard UI, tracked/untracked discard confirmation, in-progress disable ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes), [D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)) | 4, 5 | Manual |
Phase 1 = units 14 (read + display); Phase 2 = units 56 (write actions).
## RAID Log
### Assumptions
| ID | Assumption | What Changes If Wrong | Verifier | Status |
|----|------------|-----------------------|----------|--------|
| A1 | The boocode container keeps the `/opt:/opt` read-write mount, so apps/server can write project paths. | [D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver) reopens — writes would need a host service. | `docker-compose.yml:16` | Verified (R1) |
| A2 | A realistic single-user working-tree diff stays under 10MB. | The read truncates/errors; the streaming reader reopens (Deferred YAGNI). | `on-call-engineer` | Unverified — bounded by [D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes) |
### Dependencies
| ID | Dependency | Owner | Status |
|----|------------|-------|--------|
| Dep1 | Shiki `^1.29.2` (`lang:'diff'`) already in `apps/web`. | `apps/web` | Present (no install) |
| Dep2 | `useProjectGit` already returns `is_dirty` for the dirty dot. | `apps/web` | Present (no new fetch) |
## Testing Strategy
Sourced from `test-engineer` (T1T12). Server-side only — `apps/web` has no test harness, so Shiki rendering and layout/tab behavior are verified manually, not in the suite. Vitest conventions: `globals:false` (import `describe`/`it`/`expect`), `.js` import suffixes, include glob `src/**/__tests__/**/*.test.ts`.
- **Observable behaviors to test:** porcelain → file-list parse with change types (T1); unified-diff split per file (T2); base resolution `@{upstream}``origin/HEAD` → null (T3); auto-select mode by dirty/clean (T4); binary/large classify (T5); in-progress sentinel detection (T6); `canCommit` gating (T7); read route over a temp repo (T8); stage/unstage round-trip (T9); commit with server-derived identity (T10); discard tracked vs untracked (T11); path-guard rejection of `..`/absolute/symlink-escape and repo-root discard (T12).
- **Test doubles posture:** pure helpers tested directly with fixture strings (no git spawn); route/write tests use a real temp repo via `mkdtemp` + `git init` (the `path_guard.test.ts` integration pattern).
- **Edge cases requiring coverage:** binary file, oversized diff, no resolvable base, in-progress state, index-lock 409, untracked-discard partial failure, path-escape attempts ([D-4](artifacts/implementation-decision-log.md#d-4-path-validation--repo-relative-pathguard-reject-escape-and-repo-root-discard), [D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)).
- **Test levels:** unit (T1T7, pure helpers) + integration (T8T12, temp-repo). No end-to-end / web-layer automation.
## Security Posture
`adversarial-security-analyst` contributed the full write-surface posture. Threat vectors and the mitigations this plan commits to:
- **Command/flag injection** — every git invocation uses discrete argv with explicit `--` separators between options and user-supplied paths and a flag-injection guard (reject path args starting with `-`); never `hostExec(shell)` ([D-3](artifacts/implementation-decision-log.md#d-3-write-ops-via-argv-safe-rungitexecfile-with----separators)). User text (commit message, file targets) is always discrete argv.
- **Path traversal** — `pathGuard(repoRoot, file)` resolves each per-file argument and rejects absolute paths, `..` traversal, and symlink escape outside the server-derived root; discarding the repo root (`.`) is rejected ([D-4](artifacts/implementation-decision-log.md#d-4-path-validation--repo-relative-pathguard-reject-escape-and-repo-root-discard)). The root is derived from the project record via `path_guard.ts`, never the request.
- **Identity spoofing** — commit identity is server-derived (`-c user.name`/`-c user.email` from git config, falling back to the `project_bootstrap.ts` constants); the commit request schema is Zod `.strict()` with `{message, files?}` only — no author/email/date field can be supplied ([D-5](artifacts/implementation-decision-log.md#d-5-commit-identity-server-derived-request-schema-strict-with-no-author-fields)).
- **Assistant-driven invocation** — the write endpoints are never registered in `ALL_TOOLS`, so no assistant tool surface reaches them; the indirect artifact path is already closed by the artifact iframe's `connect-src 'none'` sandbox (F1), so no new CSRF mitigation is built ([D-13](artifacts/implementation-decision-log.md#d-13-write-endpoints-excluded-from-the-assistant-tool-registry-artifact-sandbox-closes-the-indirect-path)).
## Operational Readiness
- **Deploy surface:** single — `docker compose up --build -d boocode` (rebuilds web + server from the working tree). No `sudo systemctl restart boocoder`, because no apps/coder code changes ([D-1](artifacts/implementation-decision-log.md#d-1-both-git-read-and-git-write-live-in-appsserver)).
- **Schema / migration / env:** none — no Postgres change, no migration, no new env var ([D-10](artifacts/implementation-decision-log.md#trivial-decisions)).
- **Contracts package:** not touched — refresh is a client `sessionEvents` event, not a WS frame, so `packages/contracts/` is not rebuilt ([D-9](artifacts/implementation-decision-log.md#trivial-decisions)).
- **Feature flag / rollout:** none — the panel is additive and inert until the Git tab is opened; rollback is reverting the build.
## On-Call Resilience Posture
`on-call-engineer` contributed; each commitment maps to a flagged failure mode.
- **Timeouts and deadlines:** the read path runs under a 30s execution deadline and a 10MB `maxBuffer`, both distinct from the D5 per-file display-size cap; a read that exceeds the deadline exits loading, shows an error, and offers Refresh ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)).
- **Retry strategy:** none on the server. An index-lock write failure returns **HTTP 409 "repository busy"**; retry is user-driven, so no timer state or hidden busy state is introduced ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)).
- **Concurrency / coalescence:** the client holds an in-flight coalescence ref so concurrent `git_diff_refresh` triggers absorb into the running refresh — one read at a time, settling to a single final snapshot ([D-8](artifacts/implementation-decision-log.md#d-8-refresh-via-client-git_diff_refresh-sessionevent-coalesced--not-a-ws-frame)).
- **Graceful degradation:** `detectInProgress` (`.git` MERGE_HEAD / rebase / CHERRY_PICK_HEAD / BISECT_LOG sentinels) folds an in-progress flag into the read response; the client disables write affordances when set, so stage/commit/discard never fail with raw git errors mid-operation ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)).
- **Data integrity:** an untracked-file discard that fails partway reports honest partial failure on the next refresh rather than claiming an unenforceable "state unchanged" ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes)).
## Definition of Done
- [ ] Opening the Git tab shows the project repo's changed files in the auto-selected mode; switching to Files restores the tree in the same slot ([D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)).
- [ ] Committed mode labels its resolved base (`@{upstream}``origin/HEAD` → labeled Uncommitted fallback) ([D-6](artifacts/implementation-decision-log.md#d-6-committed-mode-base-resolution-via-upstream--originhead--null)).
- [ ] Stage / unstage / commit / discard operate at whole-file granularity in Uncommitted mode; discard prompts the tracked vs untracked confirmation; Committed mode is read-only ([D-3](artifacts/implementation-decision-log.md#d-3-write-ops-via-argv-safe-rungitexecfile-with----separators), [D-15](artifacts/implementation-decision-log.md#d-15-frontend--rightrail-filesgit-tab-gitdiffview-with-lazy-shiki-dirty-dot-from-useprojectgit)).
- [ ] Commit identity is server-derived; the request cannot set it; the schema is `.strict()` ([D-5](artifacts/implementation-decision-log.md#d-5-commit-identity-server-derived-request-schema-strict-with-no-author-fields)).
- [ ] Path-escape and repo-root discard are rejected; writes use argv + `--` separators ([D-3](artifacts/implementation-decision-log.md#d-3-write-ops-via-argv-safe-rungitexecfile-with----separators), [D-4](artifacts/implementation-decision-log.md#d-4-path-validation--repo-relative-pathguard-reject-escape-and-repo-root-discard)).
- [ ] Write endpoints are absent from `ALL_TOOLS` ([D-13](artifacts/implementation-decision-log.md#d-13-write-endpoints-excluded-from-the-assistant-tool-registry-artifact-sandbox-closes-the-indirect-path)).
- [ ] Read respects the 30s/10MB bounds; index-lock returns 409; in-progress disables writes; refresh coalesces ([D-7](artifacts/implementation-decision-log.md#d-7-read-deadline-30s--maxbuffer-10mb-index-lock--http-409-in-progress-detection-disables-writes), [D-8](artifacts/implementation-decision-log.md#d-8-refresh-via-client-git_diff_refresh-sessionevent-coalesced--not-a-ws-frame)).
- [ ] Tests T1T12 pass (`pnpm -C apps/server test`).
- [ ] Phase 1 ships and is reviewable before Phase 2 lands ([D-14](artifacts/implementation-decision-log.md#d-14-two-phase-build--readdisplay-phase-1-then-write-actions-phase-2)).
- [ ] Post-ship owner: the session user (single-user app); no separate on-call rotation.
## Specialist Handoffs for Implementation
- **`test-engineer`** — dispatch at the start of unit 1 to own T1T12; needs the helper signatures from `git_diff.ts` and the temp-repo integration pattern (`path_guard.test.ts`).
- **`adversarial-security-analyst`** — dispatch to review unit 5 before merge; needs the write-route handlers and the `.strict()` commit schema to confirm argv-safety, path-guard coverage, and identity derivation.
- **`user-experience-designer`** — dispatch during units 34 and 6; needs the `RightRail.tsx` header layout to confirm one-line fit (D18), tap-target minimum, and the tracked/untracked discard wording.
## Deferred (YAGNI)
### Shared `packages/` runGit extraction
- **Why deferred:** Rule of Three not met — only apps/server consumes the git ops for this feature (coder is untouched); a single-consumer abstraction is premature.
- **Reopen when:** a third consumer needs the same git ops.
- **Source:** R1, software-architect.
### New WS frame for refresh
- **Why deferred:** Replaced by the simpler client `sessionEvents` event `git_diff_refresh` — no trigger needs a server push, so a WS frame and a `@boocode/contracts` rebuild are unnecessary (simpler-version test).
- **Reopen when:** a refresh trigger genuinely originates server-side (an out-of-band repo mutation the client cannot observe).
- **Source:** R1, software-architect / junior-developer.
### Server-side retry on index-lock
- **Why deferred:** "Try again" is user-driven; a server retry hides the busy state and adds timer state (evidence test — no observed contention).
- **Reopen when:** index-lock contention is observed frequent in practice.
- **Source:** R1, on-call-engineer (F5).
### Streaming diff reader (cap mid-stream)
- **Why deferred:** `execFile` maxBuffer 10MB covers any realistic single-user working-tree diff; ~4050 LoC for a transient memory spike that does not matter at this scale (simpler-version test).
- **Reopen when:** working-tree diffs routinely exceed 10MB.
- **Source:** R1, on-call-engineer.
### CSRF custom-header on the write routes
- **Why deferred:** `connect-src 'none'` on artifacts + the SameSite=Lax Authelia cookie + a same-origin SPA cover it at single-user scale (evidence test — no exposed cross-origin path).
- **Reopen when:** the write routes are exposed to a less-controlled origin.
- **Source:** R1, adversarial-security-analyst.
### Separate per-file expand-state hook
- **Why deferred:** Replaced by local state in `GitDiffView` — one consumer (single-implementation abstraction).
- **Reopen when:** a second component needs the same expand state.
- **Source:** R1, software-architect.
### `autoSelectMode` / `canCommit` as separate modules
- **Why deferred:** Kept as inline tested pure helpers in `git_diff.ts` — splitting into modules adds files without a second consumer (simpler-version test).
- **Reopen when:** a second module needs to import them independently.
- **Source:** R1, software-architect / test-engineer.
## Open Items
- None. Every Round-1 open question resolved from evidence; the spec's only open item (commit identity) was settled at spec time. The plan is shippable as written.
## Summary
- **Outcome delivered:** A Files / Git tab in the right-side file panel that reads and (whole-file) stages, unstages, commits, and discards the project repository's changes, built entirely in `apps/server`.
- **Team size:** 6 specialists — see [artifacts/implementation-iteration-history.md](artifacts/implementation-iteration-history.md)
- **Rounds of facilitation:** 1 — see [artifacts/implementation-iteration-history.md](artifacts/implementation-iteration-history.md)
- **Decisions committed:** 15 — see [artifacts/implementation-decision-log.md](artifacts/implementation-decision-log.md)
- **Decisions settled by evidence:** 15 — see [artifacts/implementation-decision-log.md](artifacts/implementation-decision-log.md)
- **Decisions settled by junior-developer reframing:** 0 (the junior-developer's coupling flag seeded the evidence correction on D-1, but the resolution was evidence) — see [artifacts/implementation-decision-log.md](artifacts/implementation-decision-log.md)
- **Decisions settled by user input:** 0 — see [artifacts/implementation-decision-log.md](artifacts/implementation-decision-log.md)
- **Rejected alternatives recorded:** 22 — see [artifacts/implementation-decision-log.md](artifacts/implementation-decision-log.md)
- **Open items remaining:** 0
- **Recommendation:** Ship as planned — build Phase 1 (units 14) first, then Phase 2 (units 56).