AGENTS.md
Critical Rules
- ALWAYS bump the version in
package.jsonafter every change merged to main. Don't wait for the user to ask. Use semver: patch for fixes/refactors, minor for features, major for breaking changes. - NEVER push directly to
mainwithout admin bypass. Use PRs with squash & merge. - When merging PRs with
gh pr merge, use--adminflag to bypass the protection ruleset. - Delete branches after merging (
--delete-branch). - Responses in Spanish, code comments in English.
Build & Dev Commands
# Development (two terminals)
npm run dev # Frontend: Vite dev server (port 5173, proxies /api → 4321)
npm run dev:server # Backend: tsx watch (port 4321)
# Build
npm run build # Both: vite build + tsc -p tsconfig.server.json
npm run build:frontend # Only frontend → dist/
npm run build:server # Only backend → dist-server/
# Test
npm test # vitest run (all tests)
npm run test:watch # vitest watch mode
# Run as CLI
node bin/cli.js # Launch from repo root
npx visualgit # From any git repo (production)
visualgit update # Self-update from npm
Architecture
CLI (bin/cli.js) → spawns Express server (dist-server/) → serves React SPA (dist/) → opens browser.
The CLI passes REPO_PATH, PORT, IS_GIT_REPO as env vars to the server process.
Backend (Express 5 + TypeScript)
server/
├── index.ts # App factory, static serving, SPA fallback
├── routes/
│ ├── git.ts # GET /api/git/{status,info,diff}
│ └── ai.ts # POST /api/ai/analyze (SSE streaming)
├── services/
│ ├── git.service.ts # simple-git wrapper (branch, diff, repo name)
│ └── ai.service.ts # Spawns claude/openai CLI, pipes stdin/stdout
└── utils/
└── diff-parser.ts # Regex-based unified diff parser
- AI analysis spawns
claude -p --model sonnetoropenaiCLI as child process withcwd: repoPath - Prompts are piped via stdin to avoid arg size limits
- Response is streamed as SSE:
data: {text?, done?, error?}
Frontend (React 19 + Vite 7 + Tailwind 4)
src/
├── App.tsx # Split-panel layout (resizable diff + AI panel)
├── hooks/
│ ├── useGitData.ts # Fetches /api/git/* on mount
│ └── useAiAnalysis.ts # SSE streaming from /api/ai/analyze
├── components/
│ ├── DiffViewer.tsx # File tree + diff lines + selection detection
│ ├── AiPanel.tsx # Markdown rendering (react-markdown) + controls
│ ├── FileHeader.tsx # Collapse toggle + viewed checkbox
│ ├── DiffLine.tsx # Single line renderer
│ ├── Header.tsx # Repo name + branch info
│ └── StatusBar.tsx # Footer stats
└── lib/
└── tokens.ts # Design tokens (colors, fonts)
- Inline styles for colors (GitHub dark theme), Tailwind for layout only
- AI analysis is manual (button click), never automatic
- Three analysis modes:
full(all changes),file(single file),selection(highlighted text)
TypeScript Configs
tsconfig.json— Client: ES2022, JSX react-jsx, path alias@/*→src/*tsconfig.server.json— Server: NodeNext modules, outputdist-server/, excludes teststsconfig.node.json— Vite config
Tests (Vitest 4)
- Environment:
node(no jsdom) - Location:
server/**/__tests__/*.test.ts - Backend only: GitService, AiService, DiffParser
- Mocks:
vi.mock('simple-git'),vi.mock('child_process') - No frontend component tests
NPM Distribution
- Package:
@jxtools/visualgit(public, scoped) - Published files:
bin/,dist/,dist-server/ prepublishOnlyruns full build- GitHub Actions auto-publishes on version bump to main (trusted publisher via OIDC)
- Branch protection: ruleset
protect-mainrequires code owner review (@juancruzrossi), admin bypasses
Design System
- Theme: GitHub dark (
#0D1117bg,#161B22secondary,#30363Dborders) - Text:
#E6EDF3primary,#9DA5AEmuted - Accent:
#58A6FFblue,#3FB950green,#F47067red - Font: JetBrains Mono, sizes 11-14px