New /opt/boocode/codecontext/ directory holding the codecontext sidecar
that BooCode's tool wrappers (track B.2) will talk to. No BooCode-side
changes yet — this commit lands the sidecar standalone.
- Dockerfile: multi-stage golang:1.24-alpine → alpine:3.20. Clones
codecontext at v3.2.1 from github.com/nmakod/codecontext (cgo build for
tree-sitter bindings), builds the shim alongside (CGO_ENABLED=0).
- shim.go: stdlib-only Go HTTP server wrapping codecontext's stdio MCP
child. Newline-delimited JSON framing per the MCP transport spec
(NOT LSP-style Content-Length). 8 POST /v1/* endpoints, one per MCP
tool, plus GET /health. Child supervised via child.Wait() goroutine
that os.Exit's on death so the container's restart: unless-stopped
policy fires (Signal(0) on a zombie returns nil and is not a liveness
check — discovered during kill-restart testing).
- go.mod: no third-party deps; future Go security advisories don't apply.
docker-compose service: joins boocode_net (no host port), mounts
/opt:/opt:ro (BooCode projects live at /opt/<slug>, not exclusively
under /opt/projects), healthcheck on /health.
Verified: build clean, healthcheck reports healthy ~15s after up,
multi-project queries return valid markdown, target_dir swap works on
subtree paths. Kill-restart cycle completes in ~200ms with one failed
health poll observed (no misleading "ok" during the gap). Memory: 24.6
MiB after 5 search_symbols calls, 5.6 MiB after 30 min idle — codecontext
releases the per-call graph between target_dir swaps, so the shim doesn't
hold the indexed state.
- /data/skills mount (host: /opt/skills)
- skill_find, skill_use, skill_resource added to default read-only
tool set; opt-in for agents with explicit tools: whitelist
- AGENTS.md builtin agents drop explicit tools: arrays to inherit
the new default (now includes skill tools)
- POST /api/chats/:id/skill_invoke for slash-command flow
- 19 SKILL.md files seeded at /opt/skills/ across 6 source groups
Builtins move out of code into /data/AGENTS.md (always-on, mounted ro
into the container); per-project AGENTS.md is now an optional override.
agents.ts merges global + project entries with project-wins-by-name and
caches per-source mtimes (60s TTL). Parser switches to per-block
try/catch and returns AgentsResponse { agents, errors[] } so one
malformed block no longer fails the file. AgentPicker shows a
non-blocking amber chip listing skipped blocks and only fires a gray
toast when zero agents loaded.
WS reconnect UX (useUserEvents + useSessionStream) now silent on the
first disconnect; createWsReconnectToast escalates to gray after 3
failures or 15 s, then to red with a Retry Now action after 60 s.
useSessionStream also gained the exponential-backoff reconnect it was
missing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Dockerfile: install git + openssh-client in runtime image; pre-populate
/root/.ssh/known_hosts with the Tailscale ssh-keyscan for
100.114.205.53:2222 (Gitea SSH). Without these, the bootstrap push
step from inside the container fails with "command not found" or
host-key prompts.
- docker-compose.yml: mount ./secrets/boocode_gitea as
/root/.ssh/id_ed25519:ro so the container can authenticate to Gitea
over SSH for the initial push.
- .gitignore: add secrets/ so the keypair never lands in the repo.
- project_bootstrap.ts: rewrite the Gitea-returned ssh_url's hostname
from git.indifferentketchup.com to 100.114.205.53 before adding it
as origin, so the push hits the Tailscale interface that the
known_hosts entry covers.
- CreateProjectModal.tsx: preview label now reads "Folder:
/opt/projects/<name>" to match the new BOOTSTRAP_ROOT (was /opt/).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Splits the previous /opt:/opt:rw bind into two mounts to narrow the
writable scope of the container:
- /opt:/opt:ro — read-only mount for legacy/existing project
add-existing flow. resolveProjectPath still uses
PROJECT_ROOT_WHITELIST (/opt by default) so existing projects under
/opt/<name> (analytics, boolab, boocode itself) continue to resolve
and serve their file-tree via the read-only tools.
- /opt/projects:/opt/projects:rw — writable mount targeted at the
create-new-project bootstrap path.
Picked Option B from the spec (simpler than two scan roots):
PROJECT_ROOT_WHITELIST stays /opt, new BOOTSTRAP_ROOT env var defaults
to /opt/projects and is used by project_bootstrap.ts as the mkdir
target. Bootstrap path-escape check now compares against
BOOTSTRAP_ROOT.
Prereq: host must `mkdir -p /opt/projects` before next container
restart. Documented in CLAUDE.md and .env.example.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server:
- projects.status + projects.gitea_remote (additive) with CHECK ('open','archived')
- GET /api/projects?status=archived; PATCH /api/projects/:id (rename);
POST /api/projects/:id/archive | unarchive; POST /api/projects/create
- POST /api/projects ON CONFLICT (path) DO UPDATE SET status='open': re-add
of archived path restores existing row (preserves id + FKs); already-open
path returns 409. Detected-repos picker now excludes only status='open'.
- New gitea.ts (createGiteaRepo + GiteaRepoExistsError) and
project_bootstrap.ts (sanitize name, mkdir under PROJECT_ROOT_WHITELIST,
git init -b main + first commit with -c user.name/email per-command, optional
Gitea repo create + remote add + push; all via execFile, no shell).
- 3 new user-stream frames: project_archived, project_unarchived, project_updated.
- sidebar.ts now selects path + gitea_remote and filters status='open'.
- Gitea env added to config.ts (GITEA_BASE_URL, GITEA_USER, GITEA_TOKEN,
GITEA_SSH_HOST).
- docker-compose.yml /opt mount flipped to rw so create-project can mkdir.
- auto_name.ts gate relaxed from `!== 1` to `< 1` (fires on every turn while
chat name is empty, not only the first).
Web:
- ProjectSidebar: project rows use proper Radix ContextMenu; items Rename /
Archive / Open in Gitea. Inline rename, archive confirm dialog.
Removed obsolete handleRemove + DropdownMenu hack.
- Home: Add-existing + Create-new buttons; collapsible Archived Projects
section with Restore.
- New CreateProjectModal: name + live folder preview, commit msg, Private/
Public radio, create-Gitea-remote checkbox, toast on success/warnings.
- New projectUrls.ts giteaUrlFor() — uses gitea_remote when present,
falls back to convention URL.
- 3 new event types in sessionEvents.ts with idempotent useSidebar handlers.
- SidebarProject extended with path + gitea_remote so Open-in-Gitea can
resolve without a separate fetch.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>