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>
9.1 KiB
Synthesis input — Round 1 aggregation + resolutions (git-diff-panel impl)
Deterministic aggregation of Round-1 (software-architect, adversarial-security-analyst, on-call-engineer, test-engineer, junior-developer). Team size: medium (round cap 2; converged in 1 — all open questions resolved from evidence). Spec-maturity gate: NOT tripped (0 spec-level findings — the spec committed all behaviors; every finding is a plan-level HOW). Next step: go to synthesis.
THE corrected key decision (D-1)
Both the git READ and the git WRITE routes live in apps/server, NOT a read-server/write-coder split.
The software-architect (A1) recommended read-in-server / write-in-coder, on the premise that apps/server is "read-only by posture" and adding writes there is a new surface. The junior-developer flagged the resulting coupling (a plain BooChat session reaching the coder write service). Evidence REFUTES the architect's premise:
docker-compose.yml:16mounts/opt:/optread-write into the boocode container — apps/server can already write project paths.apps/server/src/services/project_bootstrap.tsALREADY runs git writes from apps/server:git init+ commits via safeexecFilewith-c user.name/-c user.emailflags (GIT_USER_NAME='indifferentketchup',GIT_USER_EMAIL='samkintop@gmail.com'at :38-39, applied :122-123). Git-write from the server is an existing, proven pattern — not new.- D8's own logic: "read-only" governs the assistant's tools, not the container's filesystem or the user's own UI actions. A user-driven git-write route is not an assistant tool.
- apps/server has the safe
runGit(git_meta.ts:30,execFilediscrete argv); apps/coder has only the UNSAFEhostExec(shell string)(worktrees.ts). Writing in coder would require building a NEW safe wrapper there; writing in server reuses the existing safe one and the project_bootstrap-cprecedent. - Writing in server avoids: a cross-service
/api/coder/*proxy hop per write, a second deploy surface (coder restart), and making coder a dependency for a BooChat-session commit.
Decision: read + write both in apps/server. New apps/server/src/services/git_diff.ts (read) +
git-write helpers, new routes beside GET /api/projects/:id/git in routes/projects.ts. No apps/coder
changes. Single deploy surface (docker compose up --build -d boocode). Rejected: A1's read-server/
write-coder split (premise refuted); all-in-coder (needs a new safe wrapper, adds coupling).
Resolved open questions (all plan-level, settled by evidence)
- OQ-1 service split → D-1 above (read+write in apps/server).
- OQ-2 refresh wiring (F20/D10) → a client-side
sessionEventseventgit_diff_refresh, NOT a new WS frame. The five triggers are all client-side (tab open, post-mutation, message_complete, pending-apply, on-demand); no server push needed → no@boocode/contractsrebuild. Emit from themessage_completehandler + CoderPane apply/discard callbacks;useGitDiffsubscribes; add a no-opcaseinuseSidebar.ts:applyEventper the apps/web CLAUDE.md rule. (architect A4.) - OQ-6 commit identity (D12/F3) → server-derived
-c user.name=… -c user.email=…, read from git config at commit time, fallback to the project_bootstrap constants. Request schema is.strict(),{message, files?}only — no author/email/date fields. (security #4, project_bootstrap precedent.) - OQ-7 base resolution (D13) →
git rev-parse --abbrev-ref --symbolic-full-name @{upstream}(tracking branch; non-zero if none) → elsegit rev-parse --abbrev-ref origin/HEAD(default branch) → else null → fall back to Uncommitted, labeled. Pure helperresolveCommittedBase. (architect A2.) - OQ-3/8/9 UX placement → adopt architect A4: the Files/Git tab strip replaces the static "Files" label;
the FilePlus button shows only on the Files tab (keeps header one line, D18); dirty dot on the Git tab
button + the
Session.tsxFolderTree toggle, fed byuseProjectGit's existingis_dirty(no new fetch, D17); D14's "briefly notes the change" is a non-blocking inline line inside the panel (NOT a toast — avoids mobile-drawer z-index collision). - OQ-4 sequencing (F19) → adopt architect A5 two-phase: Phase 1 = read + display (git_diff.ts pure helpers TDD-first → read route → client + RightRail tab + GitDiffView read-only + refresh wiring); Phase 2 = write actions (write helpers + routes + stage/commit/discard affordances + in-progress disable). Both phases are now the SAME deploy surface (server+web) since writes are in apps/server.
- OQ-5 Shiki (D9) → lazy: highlight a file's diff only when expanded, with a per-file loading state; "expand all" triggers lazy highlight per file as rendered. (junior OQ-5.)
Claim ledger (consolidated)
| # | Claim | State | Spec-maturity | Supporting |
|---|---|---|---|---|
| C1 | Read+write both in apps/server; architect's write-in-coder premise refuted by /opt rw mount + project_bootstrap git-write precedent | Evidenced | plan-level | junior (flag) + corrected via evidence; security (safe-runGit-only-in-server) |
| C2 | Read route + git_diff.ts pure helpers (parseNameStatus, splitDiffByFile, resolveCommittedBase, autoSelectMode, classify binary/large, detectInProgress) — TDD-first | Evidenced | plan-level | architect, test-engineer |
| C3 | Write ops via argv-safe runGit/execFile with -- separators; NEVER hostExec(shell) |
Evidenced | plan-level | architect, security |
| C4 | Path validation: pathGuard(repoRoot, file) rejects ../ + absolute + symlink-escape; also reject discard of repo-root (.) |
Evidenced | plan-level | security |
| C5 | Commit identity server-derived (-c from git config, bootstrap fallback); request .strict() no author fields |
Evidenced | plan-level | security, on-call OQ4 |
| C6 | Refresh = sessionEvent git_diff_refresh + client in-flight coalescence ref (no WS frame, no debounce) | Evidenced | plan-level | architect, on-call |
| C7 | Read deadline 30s + maxBuffer 10MB (distinct from D5 size cap); timeout→error+Refresh | Evidenced | plan-level | on-call |
| C8 | Index-lock → HTTP 409 "repository busy", NO server retry (user-driven retry) | Evidenced | plan-level | on-call |
| C9 | In-progress detection via .git sentinel stats (MERGE_HEAD/rebase-merge/CHERRY_PICK_HEAD/BISECT_LOG) folded into read response → disable writes | Evidenced | plan-level | on-call, test-engineer |
| C10 | Write endpoints NOT in the assistant tool registry (ALL_TOOLS); artifact sandbox connect-src 'none' already blocks artifact→endpoint | Evidenced | plan-level | security |
| C11 | RightRail Files/Git tab + GitDiffView (Shiki lang:'diff' lazy-on-expand) + dirty dot from useProjectGit | Evidenced | plan-level | architect, junior |
| C12 | Two-phase build: read/display first, writes second (same deploy surface) | Evidenced | plan-level | architect, junior |
| C13 | Test plan T1-T12: pure-helper units + temp-repo integration (mkdtemp/git init, like path_guard.test.ts); skip Shiki/layout (no web harness) | Evidenced | plan-level | test-engineer |
YAGNI ledger
- Shared
packages/runGit extraction → DEFER. Rule of Three not met (now only apps/server consumes it for this feature; coder untouched). Reopen: a third consumer needs git ops. Source: architect. - New WS frame for refresh → REPLACE with the simpler client
sessionEventsevent (no server push, no contracts rebuild). Source: architect/junior OQ-2. - Server-side retry on index-lock → DEFER. "Try again" is user-driven; a server retry hides the busy state and adds timer state. Reopen: lock contention observed frequent in practice. Source: on-call F5.
- Streaming diff reader (cap mid-stream) → DEFER.
execFilemaxBuffer 10MB covers any realistic single-user working-tree diff; streaming is ~40-50 LoC for a transient memory spike that doesn't matter at this scale. Reopen: diffs routinely exceed 10MB. Source: on-call §6. - CSRF custom-header on the write routes → DEFER.
connect-src 'none'on artifacts + SameSite=Lax Authelia cookie + same-origin SPA covers it at single-user scale. Reopen: routes exposed to a less- controlled origin. Source: security #3. - Separate per-file expand-state hook → REPLACE with local state in GitDiffView (one consumer). Reopen: a second component needs the same expand state. Source: architect.
autoSelectMode/canCommitas separate files → keep as inline pure helpers in git_diff.ts (tested), not separate modules. Source: architect/test-engineer.
Notes for synthesis
- No
feature-technical-notes.md(no T# notes) — omit all T# handling. - Honor: deploy-by-surface (now single surface: docker rebuild), stage-commits-by-path, WS-frame/sessionEvent
parity steps (only a sessionEvent here —
useSidebar.tscase), pure-helper-then-wire TDD precedent,globals:false+.jsimport suffixes +src/**/__tests__/**glob for tests. - Security posture section: real content (argv-safety, path-traversal, identity, not-in-tool-registry, artifact-sandbox). On-call resilience section: real content (deadline, index-lock, coalescence, in-progress, partial-failure). Operational readiness: single deploy surface, no migration.