Wires the Phase 1 vocabulary pilot into the canonical agent instructions (tapir#18). CLAUDE.md is canonical here (no .context/ source, no generation header). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
133 lines
8.6 KiB
Markdown
133 lines
8.6 KiB
Markdown
# CLAUDE.md — Agent operating instructions for Tapir
|
||
|
||
Read this first if you are an agent (or human) starting a work session in this repo.
|
||
It tells you how to work here. For *what* and *why*, read `README.md` and the guardrail
|
||
docs it indexes.
|
||
|
||
## Orientation order
|
||
|
||
1. `README.md` — what Tapir is, links to all guardrails.
|
||
2. `VISION.md` — the staged Definition of Success. **Stage 0 ("useful to me") is the gate.**
|
||
Do not build Stage 1+ machinery before Stage 0 holds.
|
||
3. `DECISIONS.md` — the ADRs. Decisions are settled here; do not re-litigate without a new ADR.
|
||
4. `docs/architecture/architecture.md`, `docs/data-model.md`, `docs/use-cases/*.feature`.
|
||
5. `docs/homelab-integration.md` — the concrete endpoints/conventions you'll need.
|
||
6. `LANGUAGE.md` — the project vocabulary. Apply the caveman rubric before destructive operations.
|
||
|
||
## How to work in this repo
|
||
|
||
- **Trunk-Based Development (ADR-009).** Commit directly to `main`. One logical change per
|
||
commit. Every commit deployable. No feature branches or PRs for solo/agent work — the only
|
||
exception is a short-lived `agent/<desc>` branch when another agent is *simultaneously*
|
||
active on this repo, merged within the same session.
|
||
- **Run the quality gate before every push.** `task check`. CI is the gate, not branch
|
||
protection — do not enable branch protection on this repo.
|
||
- **Conventional commits.** `feat:`, `fix:`, `chore:`, `docs:`, `refactor:`. Subject line says
|
||
what; body says why.
|
||
- **Language is Go (ADR-001).** Do not introduce Python or a second language. If you think you
|
||
need one, that's a new ADR with a real justification, not a default.
|
||
|
||
## Things that look reusable but are NOT — read before "reusing"
|
||
|
||
These caused real mistakes that were caught and corrected; the corrections are load-bearing.
|
||
|
||
- **The `llm` package is COPIED from `hyperguild/ingestion`, not imported (ADR-004).** Tapir
|
||
owes that repo nothing at the dependency level. Do not add `hyperguild/ingestion` as a Go
|
||
module dependency to "share" code. If the copied `llm` needs changes, change Tapir's copy.
|
||
- **The brain sink is HTTP to brain-mcp, NOT the filesystem `brain` package (ADR-005).**
|
||
`hyperguild/ingestion`'s `internal/brain` writes files into a brain git checkout on disk.
|
||
That is the wrong model for Tapir. The brain sink calls brain-mcp's `brain_ingest` tool over
|
||
HTTP. Do not copy or replicate the filesystem brain package.
|
||
- **YouTube/Vimeo OAuth is written fresh (ADR-006).** `hyperguild/ingestion`'s `internal/oauth`
|
||
is the MCP *server's inbound* auth (client_credentials). It has nothing to do with *outbound*
|
||
OAuth to video providers despite the shared name. Use `golang.org/x/oauth2`.
|
||
|
||
## Settled decisions you should not "helpfully" reopen
|
||
|
||
(See `DECISIONS.md` for full rationale. Listed here so you don't propose them.)
|
||
|
||
- **No Supabase** — reuse Dex / ESO+1Password / Postgres (ADR-002).
|
||
- **No global cross-tenant *video* table** — videos stay per-user (data-model). Transcripts ARE
|
||
shared since ADR-021 (public caption content, keyed by `(provider, provider_video_id)`, non-RLS)
|
||
so re-analysis never re-fetches; the *videos* half of cross-tenant dedup stays a Future C concern.
|
||
- **No audio-download + speech-to-text in the core path** — captions-first (ADR-007). STT is a
|
||
deferred, bounded optional component.
|
||
- **No public SaaS / sign-up / billing / Google OAuth verification at scale** — Future C,
|
||
deferred behind the Stage 0 gate (ADR-008).
|
||
|
||
## Architecture stance for new code
|
||
|
||
- **Clean Architecture, dependencies point inward.** The engine (use cases) depends only on the
|
||
ports (`VideoSource`, `Summarizer`, `Sink`, `SecretStore`). Concrete providers, the AI router,
|
||
stores, and sinks are adapters. Adding a video provider or a sink = a new adapter implementing
|
||
the interface, nothing in the engine changes. This is what keeps "standalone vs homelab" a
|
||
wiring choice (ADR-003).
|
||
- **BDD.** The `docs/use-cases/*.feature` files are the behavior spec (design records — there is
|
||
no godog runner). New behavior gets a scenario; the use-case core is tested through fake
|
||
adapters, not live YouTube/brain. A name-coverage gate (`test/acceptance/scenario_coverage_test.go`,
|
||
`TestScenarioCoverage`) keeps the two from drifting: every non-`@pending` scenario must be
|
||
mapped to an existing Go test in `scenarioCoverage`. When you add a scenario, either map it to
|
||
its covering test or tag it `@pending` in the `.feature` with a one-line reason. It checks the
|
||
*link*, not that the test exercises the scenario — that's the deliberate trade for not running
|
||
godog (see issue #5 / the BDD-runner decision).
|
||
|
||
## Skills (engineering discipline)
|
||
|
||
Skills live in the canonical library `mathias/skills` and are wired into this repo as
|
||
**gitignored symlinks** — do not commit them, do not add a skills manifest to this repo.
|
||
|
||
- **Install/refresh:** `task skills` (or `curl -fsSL https://gitea.d-ma.be/mathias/skills/raw/branch/main/install.sh | bash`). Idempotent.
|
||
- **Most relevant skills for Tapir work:**
|
||
- `tdd`, `atdd`, `test-design` — the build is test-first; the `.feature` files are the ATDD spec.
|
||
- `clean-code`, `solid` — the ports-and-adapters structure depends on these.
|
||
- `gitea-ci` — for the CI workflow (and its act_runner gotchas) and the TBD section.
|
||
- `debug` — when `task check` goes red for a non-obvious reason.
|
||
- Reviewer-side: `code-review`, `refactoring`, `cognitive-load`.
|
||
- Session close: `session-retrospective` (surface learnings into brain before context is lost).
|
||
|
||
## Current build state (start here for the first task)
|
||
|
||
The repo is **green and shipping** — last tag `v0.15.0`. `task check` passes (fmt, vet, lint,
|
||
`go test -p 1 ./...`). Go is `1.26.1` (see `go.mod`).
|
||
|
||
- Clean Architecture core is implemented: `internal/domain` (entities), `internal/ports`
|
||
(interfaces), `internal/usecase.Engine.ProcessNewVideo` (resolve transcript → summarize →
|
||
deliver to sinks | skip on no-transcript). The acceptance tests in `test/acceptance/` are
|
||
green against it.
|
||
- Adapters present under `internal/adapters/`: `youtube` (captions-first `VideoSource`,
|
||
timedtext/InnerTube acquisition per ADR-010), `summarizer` + `llm` (the copied AI router,
|
||
now a resilient endpoint chain — local primary → local fallback → external worst-case,
|
||
parse-failure-aware, ADR-004 + ADR-022), `store` (Postgres, golang-migrate migrations 001–015),
|
||
`secrets` (file-backed `SecretStore`). The brain HTTP sink (ADR-005) is the remaining
|
||
optional sink.
|
||
- Stage 1 is open (ADR-012): multi-user with **DB-enforced** isolation — Postgres RLS `FORCE`d
|
||
on all user-owned tables (migration 003), two-user isolation test in
|
||
`internal/adapters/store/rls_test.go`. Registration gate, per-user YouTube web connect, and
|
||
account management (disconnect / delete, ADR-013) all shipped.
|
||
- Transcript persistence (ADR-021, migration 015): transcripts are a **shared, non-RLS** store
|
||
keyed by `(provider, provider_video_id)` — the single exception to the isolation boundary
|
||
(`TestTranscriptsTableIsSharedNotRLS`). The engine reads stored transcripts before any caption
|
||
fetch (`usecase.resolveTranscript`), so re-analysis — re-summarize, paste-a-URL, onboarding
|
||
burst — never re-touches YouTube. Per-user summaries/videos stay RLS-scoped.
|
||
- `cmd/tapir` subcommands: `list`, `show`, `auth` (interactive host-side OAuth), `run` (batch
|
||
watch→summarize), `serve` (the HTMX+Templ web reader/writer under `internal/web`, a new
|
||
transport over the unchanged engine/ports — ADR-003). `tapir env` prints config.
|
||
- **Build/run:** `task check` is the gate; `task build` produces the binary. Local dev uses
|
||
`StubAuth` (allow-all) and a `TAPIR_DB_DSN` Postgres; the deployed service uses Dex OIDC.
|
||
|
||
**Setup facts** (resolved — see `docs/homelab-integration.md` for the live values): LiteLLM is
|
||
off-cluster at `koala:30401/v1/` with `LITELLM_MASTER_KEY` from 1Password; the summarization
|
||
model is config (`TAPIR_SUMMARIZER_MODEL`, default `koala/phi4-mini`), never hardcoded. The
|
||
brain-mcp base URL and ESO ref scheme are pinned in that doc; check it before wiring rather than
|
||
re-deriving.
|
||
|
||
## Provenance (where this design came from)
|
||
|
||
- The reuse decisions came from **Spike S5**, recorded at
|
||
`infra/docs/superpowers/handoffs/2026-06-02-video-adapter-placement.md` (read it for the
|
||
per-package lift-vs-copy analysis).
|
||
- The `llm` package source is `hyperguild/ingestion/internal/llm` (`Client` + `Router`).
|
||
- The standalone-first framing, the Supabase/Python rejections, and the staged success
|
||
definition came from a planning + grill session on 2026-06-02 (claude.ai). The conclusions are
|
||
in `VISION.md` and `DECISIONS.md`; this file is the operational distillation.
|