diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile new file mode 100644 index 000000000..9e50f7092 --- /dev/null +++ b/.devcontainer/Dockerfile @@ -0,0 +1,73 @@ +# syntax=docker/dockerfile:1 + +# Base image: Microsoft's TypeScript+Node devcontainer image. Multi-arch +# (linux/amd64, linux/arm64), monthly security patching, ships the non-root +# `node` user (UID 1000), zsh + Oh My Zsh, eslint global, `gh` CLI. +FROM mcr.microsoft.com/devcontainers/typescript-node:1-22-bookworm + +# Build args drive both the image build and lifecycle-time references. The +# matching ENV declarations below promote each ARG into the container's +# runtime environment so postCreateCommand and shells can resolve them +# (Docker ARG values are build-only by default). +ARG CLAUDE_CODE_VERSION=2.1.153 +ARG CODEX_VERSION=0.134.0 +ARG CURSOR_VERSION=latest +ARG TZ=UTC +ARG USERNAME=node + +ENV CLAUDE_CODE_VERSION=${CLAUDE_CODE_VERSION} \ + CODEX_VERSION=${CODEX_VERSION} \ + CURSOR_VERSION=${CURSOR_VERSION} \ + TZ=${TZ} \ + DEVCONTAINER=true \ + NODE_OPTIONS=--max-old-space-size=4096 \ + CLAUDE_CONFIG_DIR=/home/${USERNAME}/.claude \ + POWERLEVEL9K_DISABLE_GITSTATUS=true + +# Native build toolchain required by gitnexus/postinstall: tree-sitter +# native bindings, vendored Dart/Proto/Swift grammars, @ladybugdb/core +# N-API addon. python3/make/g++ are non-negotiable; mirrors the apt block +# in the existing Dockerfile.cli / gitnexus/Dockerfile.test images. +RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + python3 make g++ git curl ca-certificates bash \ + && rm -rf /var/lib/apt/lists/* + +# Pre-create credential and history mount points owned by `node` BEFORE the +# devcontainer.json named volumes attach. Docker copies image-side ownership +# onto an empty named volume on first mount, so first-run `claude login`, +# `codex login --device-auth`, and `cursor-agent login` write into a +# node-owned directory instead of EACCES'ing on a root-owned volume. +RUN mkdir -p \ + /home/${USERNAME}/.claude \ + /home/${USERNAME}/.codex \ + /home/${USERNAME}/.cursor \ + /home/${USERNAME}/.npm \ + /home/${USERNAME}/.local/bin \ + /commandhistory \ + && chown -R ${USERNAME}:${USERNAME} \ + /home/${USERNAME}/.claude \ + /home/${USERNAME}/.codex \ + /home/${USERNAME}/.cursor \ + /home/${USERNAME}/.npm \ + /home/${USERNAME}/.local \ + /commandhistory + +USER ${USERNAME} + +# Install Codex CLI globally as the `node` user. The base image configures +# /usr/local/share/npm-global as the npm-global prefix with the `npm` group +# writable by `node`, so this works without sudo. +RUN npm install -g @openai/codex@${CODEX_VERSION} + +# Cursor CLI install. The official installer (cursor.com/install) does not +# expose a clean version-pinning flag; CURSOR_VERSION is carried for +# traceability only and the installer always pulls the latest at build time. +# To bump Cursor: rebuild the image. Auto-update in the running container is +# suppressed by not invoking `cursor-agent update`. +RUN curl -fsS https://cursor.com/install | bash + +# Ensure ~/.local/bin (where Cursor's installer drops `agent` and +# `cursor-agent` symlinks) is on PATH for interactive shells and lifecycle +# scripts. +ENV PATH=/home/${USERNAME}/.local/bin:${PATH} diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 000000000..1d0e2fd6e --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,141 @@ +# GitNexus Devcontainer + +A cross-platform Dev Container that pre-installs Claude Code, OpenAI Codex CLI, and Cursor CLI alongside the GitNexus native build chain. Designed for Windows 11 (Docker Desktop + WSL2 backend) as the primary host with first-class support for macOS and Linux. + +## Quick start + +1. Install [Docker Desktop](https://docs.docker.com/desktop/) (Windows/macOS) or Docker Engine (Linux). +2. Install [VS Code](https://code.visualstudio.com/) with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers). +3. Open the repo in VS Code → Command Palette → **Dev Containers: Reopen in Container**. +4. Wait for the first build (~3–6 minutes) and `postCreateCommand` to finish installing workspace dependencies. +5. Authenticate the three CLIs once — see [First-time CLI authentication](#first-time-cli-authentication) below. + +## Windows 11 (primary host) — WSL2 setup + +**Clone the repo inside WSL2, not on the Windows side.** Bind-mounting a Windows-side path (`C:\…`) through Docker Desktop's WSL2 backend works but suffers from poor IO and unreliable file watchers (Vite/jest `--watch` will miss changes). The fix is to clone into the WSL2 filesystem. + +```bash +# 1. Install WSL2 and a Linux distro if you haven't already. +wsl --install -d Ubuntu + +# 2. Enter WSL. +wsl + +# 3. Clone the repo inside your WSL2 home directory. +cd ~ +git clone https://github.com/abhigyanpatwari/GitNexus.git +cd GitNexus + +# 4. Launch VS Code from inside WSL — this opens VS Code attached to the WSL2 +# filesystem, so subsequent "Reopen in Container" uses the WSL2-side path. +code . +``` + +Then run **Dev Containers: Reopen in Container**. The workspace will be bind-mounted from `\\wsl$\Ubuntu\home\\GitNexus`, which is fast and gives reliable file-system events. + +**Make sure Docker Desktop's WSL integration is enabled** for your distro: Docker Desktop → Settings → Resources → WSL Integration → toggle on the distro you cloned into. + +## macOS + +Open the repo folder in VS Code → **Reopen in Container**. The image is multi-arch; on Apple Silicon you'll pull the `linux/arm64` variant automatically. + +## Linux + +Same as macOS — open in VS Code and reopen in container. `updateRemoteUserUID: true` (default) shifts the container's `node` user UID/GID to match your host user, so bind-mounted files stay writable without extra setup. + +## First-time CLI authentication + +Each CLI persists its config in a per-devcontainer named volume scoped by `${devcontainerId}`, so authentication survives container rebuilds. You only need to log in once per workspace. + +### Claude Code + +```bash +claude login +``` + +This opens a browser auth flow; VS Code's port forwarding handles the callback. After auth, `~/.claude/` is populated in the named volume and persists across rebuilds. The `DISABLE_AUTOUPDATER=1` env var prevents the running CLI from updating itself — rebuild the container to pick up a newer Claude Code. + +### OpenAI Codex CLI + +The browser-callback flow can be flaky inside containers; the device-code flow is more reliable: + +```bash +codex login --device-auth +``` + +Visit the URL it prints on your host browser and paste the displayed code. After auth, `~/.codex/auth.json` persists in the named volume. + +For CI/headless use: set `OPENAI_API_KEY` in your host shell before opening the devcontainer — VS Code's `${localEnv:OPENAI_API_KEY}` would resolve it, but it's currently not wired into `containerEnv`. If you need this, add it to `.devcontainer/devcontainer.json` and rebuild. + +### Cursor CLI + +Preferred path is the `CURSOR_API_KEY` environment variable: + +1. Generate an API key from the Cursor dashboard → Integrations. +2. Set it on the host shell that launches VS Code: `export CURSOR_API_KEY=...` (or set in your shell rc / Windows env vars). +3. **Rebuild the container** — `containerEnv` resolves `${localEnv:CURSOR_API_KEY}` at container-create time, so a new key only lands after a rebuild. + +Verify with `cursor-agent status` inside the container. + +If you'd rather log in interactively: + +```bash +cursor-agent login +``` + +This opens a browser flow and stores credentials under `~/.cursor/` in the named volume. + +## Port forwarding + +| Port | Service | Notes | +|------|---------|-------| +| `5173` | Vite dev server (`gitnexus-web`) | Auto-forwarded with notification | +| `4747` | `gitnexus serve` HTTP API | **Must not be remapped** — `gitnexus-web` hardcodes `http://localhost:4747` as the default backend URL | +| `4173` | Static web (Vite preview) | Silently forwarded | + +VS Code's Ports panel shows forwarded ports once their listener starts. + +## Known gotchas + +- **LadybugDB integration tests may fail in containers** (file-locking, `AGENTS.md` § Testing). Default to `npm run test:unit` inside the container; run integration tests on the host. Tracking issue: documented as a known limitation. +- **Single-writer LadybugDB constraint** (`GUARDRAILS.md` § LadybugDB lock). Don't run `gitnexus analyze` on the host and inside the container against the same `.gitnexus/` directory simultaneously — the second writer will get `database busy`. +- **Native grammar builds add ~30s to first install.** Tree-sitter Dart/Proto/Swift grammars build during `gitnexus`'s `postinstall`. To skip them (loses parsing for those three languages), set `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` in your shell or add it to `remoteEnv` and rebuild. +- **`tree-sitter-kotlin` warnings on install** are expected (per `AGENTS.md`). Ignore them. +- **`.mcp.json` works inside the container**: `npx -y gitnexus@latest mcp` resolves cleanly because npm registry is reachable and the workspace bind mount exposes the same `.mcp.json` the host sees. +- **Husky pre-commit fires inside the container** without extra setup. The root `npm install` (run automatically in `postCreateCommand`) installs the hook via `package.json` `prepare`. + +## Rebuild / reset + +- **Rebuild Container** (Command Palette) — re-runs the Dockerfile build and `postCreateCommand` against the existing named volumes (auth + history persist). +- **Rebuild Container Without Cache** — fresh image layers, same volumes. +- **To clear a stale named volume** (e.g., force a re-login): + ```bash + docker volume ls | grep gitnexus # find the per-devcontainer volume + docker volume rm + ``` + Then rebuild. + +## Bumping CLI versions + +Three build args control pinned versions: + +- `CLAUDE_CODE_VERSION` — informational. Anthropic's official Feature (`ghcr.io/anthropics/devcontainer-features/claude-code:1`) installs the latest stable at build time; rebuild to pick up a newer Claude Code. `DISABLE_AUTOUPDATER=1` keeps it locked between rebuilds. +- `CODEX_VERSION` — pinned in `.devcontainer/devcontainer.json` `build.args` and consumed by `npm install -g @openai/codex@${CODEX_VERSION}`. Bump the value and rebuild. +- `CURSOR_VERSION` — informational only. The Cursor installer (`cursor.com/install`) does not expose version pinning; it always pulls latest at build time. To bump, rebuild the container; auto-update inside the running container is suppressed by not invoking `cursor-agent update`. + +## What's not included (yet) + +- **Egress firewall.** The original plan included an opt-in iptables/ipset firewall adapted from Anthropic's reference devcontainer. It was deferred to a follow-up PR — `runArgs` is static in `devcontainer.json`, so toggling NET_ADMIN/NET_RAW capabilities cleanly requires either a separate `devcontainer-firewall.json` profile or an `initializeCommand`-generated overlay. Track at the project's issue tracker if you need this. +- **Codespaces tuning.** The current config works in Codespaces incidentally (no privileged capabilities, no host-mount assumptions), but isn't actively tested there. +- **Playwright e2e support.** `gitnexus-web`'s `npm run test:e2e` needs Chromium libs that the base image doesn't ship. Use the host for e2e until a Playwright layer is added. + +## Troubleshooting + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| `EACCES` on first `claude login` / `codex login` / `cursor-agent login` | Named volume mount got a stale state | `docker volume rm` the relevant `*-config-` volume and rebuild | +| Vite never hot-reloads on Windows | Repo cloned on Windows side, not WSL2 | Re-clone inside WSL2 (see [WSL2 setup](#windows-11-primary-host--wsl2-setup)) | +| `gitnexus-web` can't reach the backend | `4747` was remapped or backend isn't running | Verify the Ports panel shows `4747` forwarded with no remap; start the backend with `cd gitnexus && npx gitnexus serve` | +| `npm install` fails on tree-sitter-swift / proto / dart | Native build toolchain missing | This shouldn't happen in the devcontainer — verify the apt layer installed `python3 make g++`. If iterating, set `GITNEXUS_SKIP_OPTIONAL_GRAMMARS=1` to skip the vendored grammars | +| Integration tests fail with `database busy` | LadybugDB single-writer constraint | Don't run host-side `gitnexus analyze` while the container is also analyzing the same repo; choose one writer | +| `CURSOR_API_KEY` set on host but not visible in container | Host env not propagated, or container started before key was set | Rebuild the container after setting the host env var | diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..3bde747da --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,108 @@ +// Devcontainer for GitNexus. Pre-installs Claude Code, OpenAI Codex CLI, +// and Cursor CLI alongside the Node.js native build chain. Cross-platform +// (Win11+WSL2 + macOS + Linux), opened via the VS Code Dev Containers +// extension. +// +// First-time setup, auth flows, and troubleshooting: .devcontainer/README.md. +{ + "name": "GitNexus AI CLI Devcontainer", + + "build": { + "dockerfile": "Dockerfile", + "context": ".", + "args": { + "CLAUDE_CODE_VERSION": "2.1.153", + "CODEX_VERSION": "0.134.0", + "CURSOR_VERSION": "latest", + "TZ": "${localEnv:TZ:UTC}" + } + }, + + // Anthropic's official Claude Code Feature pulls the latest stable at + // build time; DISABLE_AUTOUPDATER below locks it inside the running + // container so rebuild is the only way the version changes. + "features": { + "ghcr.io/anthropics/devcontainer-features/claude-code:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} + }, + + "remoteUser": "node", + "updateRemoteUserUID": true, + + "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=delegated", + "workspaceFolder": "/workspace", + + // Named volumes scoped per-devcontainer keep auth tokens, history, and + // node_modules persistent across rebuilds without leaking between + // workspaces. Sub-workspace node_modules volumes keep tree-sitter native + // binaries and onnxruntime off the bind mount (the real Win/Mac perf win). + "mounts": [ + "source=claude-config-${devcontainerId},target=/home/node/.claude,type=volume", + "source=codex-config-${devcontainerId},target=/home/node/.codex,type=volume", + "source=cursor-config-${devcontainerId},target=/home/node/.cursor,type=volume", + "source=commandhistory-${devcontainerId},target=/commandhistory,type=volume", + "source=npm-cache-${devcontainerId},target=/home/node/.npm,type=volume", + "source=${localWorkspaceFolderBasename}-root-node-modules,target=/workspace/node_modules,type=volume", + "source=${localWorkspaceFolderBasename}-gitnexus-node-modules,target=/workspace/gitnexus/node_modules,type=volume", + "source=${localWorkspaceFolderBasename}-gitnexus-web-node-modules,target=/workspace/gitnexus-web/node_modules,type=volume", + "source=${localWorkspaceFolderBasename}-gitnexus-shared-node-modules,target=/workspace/gitnexus-shared/node_modules,type=volume" + ], + + // CURSOR_API_KEY resolves at container-create time from the host shell + // that opened VS Code (empty fallback triggers interactive cursor-agent + // login). Set CURSOR_API_KEY on the host and rebuild to inject a new key. + "containerEnv": { + "CLAUDE_CONFIG_DIR": "/home/node/.claude", + "DISABLE_AUTOUPDATER": "1", + "CURSOR_API_KEY": "${localEnv:CURSOR_API_KEY}", + "HISTFILE": "/commandhistory/.zsh_history" + }, + + "customizations": { + "vscode": { + "extensions": [ + "anthropic.claude-code", + "dbaeumer.vscode-eslint", + "esbenp.prettier-vscode", + "eamodio.gitlens" + ], + "settings": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "files.eol": "\n", + "terminal.integrated.defaultProfile.linux": "zsh", + "terminal.integrated.profiles.linux": { + "bash": { "path": "bash", "icon": "terminal-bash" }, + "zsh": { "path": "zsh" } + } + } + } + }, + + // 4747 (gitnexus serve) must not be remapped: gitnexus-web hardcodes + // http://localhost:4747 as the default backend URL. + "forwardPorts": [5173, 4747, 4173], + "portsAttributes": { + "5173": { + "label": "Vite dev (gitnexus-web)", + "onAutoForward": "notify" + }, + "4747": { + "label": "gitnexus serve HTTP API", + "onAutoForward": "notify", + "requireLocalPort": true + }, + "4173": { + "label": "Static web (Vite preview)", + "onAutoForward": "silent" + } + }, + + // Sequential setup: chown the workspace-side node_modules volumes (Docker + // creates them root-owned), then install in dependency order + // (gitnexus-shared must build before its consumers). + "postCreateCommand": "sudo chown -R node:node /workspace/node_modules /workspace/gitnexus/node_modules /workspace/gitnexus-web/node_modules /workspace/gitnexus-shared/node_modules && cd /workspace && npm install && cd /workspace/gitnexus-shared && npm install && npm run build && cd /workspace/gitnexus && npm install && cd /workspace/gitnexus-web && npm install" +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e048d4f2f..4cb50c8c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -18,6 +18,10 @@ This project uses the [PolyForm Noncommercial License 1.0.0](https://polyformpro 3. **Web UI (if needed):** `cd gitnexus-web && npm install` 4. Run tests as described in [TESTING.md](TESTING.md). +### Containerized development (optional) + +If you prefer an isolated environment with Claude Code, OpenAI Codex CLI, and Cursor CLI pre-installed, open the repo in VS Code with the [Dev Containers extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) and run **Dev Containers: Reopen in Container**. See [`.devcontainer/README.md`](.devcontainer/README.md) for first-time auth flows and Windows WSL2 setup. + ## Branch and pull requests - Use short-lived branches off the default branch of the repo you are targeting.