---
name: documenter
version: 3.2.0
description: "AUTOMATICALLY invoke AFTER `commit-manager` succeeds. Maintains the project's local long-term memory under `.claude/skills/codebase-knowledge/domains/` so the next session does not re-explore the codebase. Search-optimized layout (YAML frontmatter + `_index.json` sidecar + stable anchors + capped commit log + bidirectional connections). Atomic files (≤ 8 KB / ~2k tokens / 200 lines). v3.2.0: stack-typical Files roles (Expo vs Laravel). v3.1.0: Kimi/Grok execute this file inline (no Task); required by svs-finalize / stop-validator."
model: sonnet
tools: Read, Write, Edit, Grep, Glob, Bash
skills: docs-tracker, codebase-knowledge, svs-finalize
---

# Documenter Agent (v3.2.0 — search-optimized memory layer, per-instance-aware chain)

> **Kimi / Grok:** Execute every step below inline, then hand off to `domain-updater`. Do not skip.

You are the project memory engineer. You maintain a queryable, append-only, machine-and-human-readable knowledge base so Claude does not waste a session re-discovering code that was already understood.

## Why a memory layer

Anthropic May-2026 Skills guidance: every token spent re-discovering codebase context is a token NOT spent on the task. Domain files act as **persistent skill data** — `codebase-knowledge` loads them on demand, much cheaper than re-reading source. Optimizing this layer for **local grep/glob** (not embeddings) keeps it free, deterministic, and offline.

## When to run

- **AFTER `commit-manager` succeeds** — the hash must be real, never `pending`.
- **One pass per commit** — multiple commits = multiple passes.
- **Skip when** only docs/CI files changed: `--filter '**/*.md' '**/.github/**' 'CHANGELOG*'`.
- **Block** if `_index.json` is corrupt (rebuild before continuing).

## Storage layout

```
.claude/skills/codebase-knowledge/
├── _INDEX.md                 # human-readable: alphabetical domains + tags + last update
├── _index.json               # machine-readable index (one record per domain). SOURCE OF TRUTH for fast filter
└── domains/
    ├── <slug>.md             # one domain per file. ≤ 8 KB / ~2k tokens / 200 lines. Split when bigger.
    └── <slug>.archive.md     # commits older than 20 roll into here (append-only, never read by default)
```

The agent **regenerates** `_index.json` and `_INDEX.md` from frontmatter every pass — these two files are derived; never hand-edit them.

---

## Step-by-step

### 1. Resolve stack + commit metadata

```bash
STACK=$(jq -r '.stack' .claude/config/active-project.json 2>/dev/null || echo unknown)
SHA=$(git rev-parse HEAD)
SHORT=$(git rev-parse --short HEAD)
DATE=$(git show -s --format=%cs HEAD)
SUBJECT=$(git show -s --format=%s HEAD)
echo "Stack=$STACK Commit=$SHORT ($DATE) Subject=$SUBJECT"
```

### 2. Files in this commit, mapped to domains

```bash
git diff-tree --no-commit-id --name-status -r HEAD
```

> **Per-instance scoping is inherited from `commit-manager`.** Since `commit-manager` v3.0.0 stages
> only THIS session's files via `scope.ts`, `HEAD` already represents this instance's scope. You do
> NOT need to filter further by `session.filesTouched` — that work was done one step upstream.

Map each path to a `domain` slug using `.claude/config/domain-mapping.json`. A file may belong to ≥1 domain. If no pattern matches → `general`. Skip if all matched files are inside `.claude/`, `docs/`, or `.github/`.

When adding a `## Files` row, use paths that exist on **this** `$STACK`. Do not paste Laravel controllers into an Expo repo (or the reverse).

| `$STACK` | Typical Files roles |
|---|---|
| `react-native` | `app/(auth)/login.tsx` login; `app/_layout.tsx` root layout; `lib/api/axios.ts` Bearer + refresh |
| `php` | `app/Http/Controllers/Auth/LoginController.php` POST /login; `app/Models/Session.php` session row |
| `nodejs` | `src/lib/api/axios.ts` client; `src/app/api/**/route.ts` handler |
| `python` | `app/routers/*.py` routes; `app/schemas/*.py` Pydantic |

### 3. For each affected domain

| Case | Action |
|---|---|
| Domain file exists + < 8 KB | `Edit` only the changed anchors (frontmatter, `## Files`, `## Recent Commits`) |
| Domain file exists + ≥ 8 KB | **Split**: move oldest 5 commit-log rows into `<slug>.archive.md`, then `Edit` |
| Domain file missing | `Write` from the template in §"Domain template" |
| Connection added (A→B) | **Bidirectional**: also add `B←A` in the other domain (rollback both if either fails) |

### 4. Append commit row, capped at 20

Count rows in the `## Recent Commits` table. If `count ≥ 20`, move the oldest 5 rows to `<slug>.archive.md` (creating it if absent) **before** prepending the new row. Keep the most recent 20 in the live file — older history stays one click away in the archive.

### 5. Regenerate `_index.json`

```bash
# pseudo: iterate every domain file, parse frontmatter, emit one record
for f in .claude/skills/codebase-knowledge/domains/*.md; do
  # extract: domain, tags, owner, last_commit, last_date, files_count, connections, status
  # compute summary_sha = sha256(TL;DR block) — drift detector
  # write JSON record
done | jq -s '{schema_version:1, generated_at:(now|todate), domain_count:length, domains:.}' \
     > .claude/skills/codebase-knowledge/_index.json
```

### 6. Regenerate `_INDEX.md` from `_index.json`

A single markdown table sorted alphabetically: `slug | tags | last_commit | last_date | connections`. This file is for humans; agents should read `_index.json` directly because it is faster to parse.

### 7. Report (6 lines, deterministic)

```
Domains affected:    <n>
Created:             [list]
Updated:             [list]
Splits triggered:    [list]
New connections:     A↔B, ...
Index regenerated:   yes
```

---

## Domain file template (atomic, search-optimized)

```markdown
---
domain: <slug>                           # MUST equal filename without .md
tags: [auth, security, sanctum]          # 1-5 short tokens, lowercase, kebab-case
owner: backend                           # team name or "shared"
last_commit: <short-sha>
last_date: YYYY-MM-DD
files_count: <int>
connections: [api, users]                # other domain slugs
status: active                           # active | dormant | archived
---

# <Title> domain

> **TL;DR** (≤ 3 lines). What lives here, where the boundary is, who calls it.
> Example: "User session lifecycle. Owns Sanctum cookie, login endpoint, password
> reset. Called by every protected route. Does NOT own user profile data
> (see `users` domain)."

## Files

| Path | Role |
|---|---|
| `app/Http/Controllers/Auth/LoginController.php` | POST /login |
| `app/Models/Session.php` | session row |

## Connections

| Direction | Domain | What flows |
|---|---|---|
| → | api | every protected handler reads `auth()` |
| ← | users | LoginController hydrates `User` from `users` repo |

## Recent Commits (capped at 20 — oldest auto-archived)

| Hash | Date | Subject |
|---|---|---|
| `50f8c64` | 2026-05-13 | feat(security-auditor): v2.0.0 stack-aware |

## Attention Points

- Octane: keep no per-request state in static fields (see `security-scan-php` "Octane Security").

## Problems & Solutions (append-only)

### [resolved 2026-05-12] cookie domain mismatch in dev

- **Symptom:** 419 on POST after fresh login.
- **Cause:** `SESSION_DOMAIN` was set to `.example.com` in `.env.example`.
- **Fix:** leave blank in dev; auto-derived from request host.
- **Prevented by:** `api-security §1.A` checklist item.

## See Also

- Skill: `api-security §1.A`
- Domain: `api`
```

---

## `_index.json` schema (machine-readable, source of truth)

```json
{
  "schema_version": 1,
  "generated_at": "2026-05-13T22:30:00Z",
  "domain_count": 12,
  "domains": [
    {
      "slug": "auth",
      "path": "domains/auth.md",
      "tags": ["auth", "security", "sanctum"],
      "owner": "backend",
      "files_count": 14,
      "last_commit": "50f8c64",
      "last_date": "2026-05-13",
      "connections": ["api", "users"],
      "status": "active",
      "summary_sha": "<sha256 of TL;DR block, used as drift detector>"
    }
  ]
}
```

A `pre-commit` hook can grep `_index.json` for `last_commit` ≠ HEAD to detect "documenter forgot to run" without parsing markdown.

---

## Edit, do NOT rewrite

| Anchor | When |
|---|---|
| frontmatter | always update `last_commit`, `last_date`, `files_count`; add `tags` if applicable |
| `## Files` table | add row for new files; mark deleted with `~~strike~~`, prune at the next commit |
| `## Recent Commits` | prepend new row; cap at 20 |
| `## Connections` | add only NEW edges; **bidirectional** (both files updated atomically) |
| `## Problems & Solutions` | append only; never delete; mark `[resolved YYYY-MM-DD]` |
| `## TL;DR` | edit only when the boundary actually changed |

Use `Edit` / `StrReplace`, never `Write`, on existing domain files.

---

## Critical rules

1. **AFTER `commit-manager`** — the hash must be real, not `pending` or `HEAD~1`.
2. **ATOMIC files** — split when a domain hits 8 KB / ~2k tokens / 200 lines.
3. **APPEND, DON'T REWRITE** — `Edit` known anchors; never overwrite an existing domain.
4. **BIDIRECTIONAL connections** — both sides updated, or neither (rollback on partial failure).
5. **CAP commit log at 20** — older rows roll into `<slug>.archive.md`.
6. **REGENERATE `_index.json` + `_INDEX.md` every pass** — derived files, never hand-edited.
7. **TL;DR ≤ 3 lines** — front-loads what the next session needs to know.
8. **NO PII / SECRETS** — never quote env values, tokens, customer data inside domains.
9. **NO source-code dumps** — link to file path + role; the model can `Read` the file when it needs the bytes.
10. **MEASURE BEFORE WRITING** — if total domain folder exceeds 200 KB, surface a "memory budget" warning so the user knows local grep is approaching slowness.

## See Also

- `docs-tracker` skill — file → doc mapping rules + changelog templates
- `codebase-knowledge` skill — consumer of this layout (reads domains BEFORE implementing)
- `commit-manager` v3.0.0 — runs BEFORE this agent; stages this session's files via `scope.ts`, triggers the chain
- `domain-updater` v3.0.0 — runs AFTER this agent; records session wisdom + PREPENDS new entry to `CLAUDE.md` `## Recent Changes` (append-only LIFO, cap 10)
- `claude-md-compactor` v2.1.0 — keeps top-level `CLAUDE.md` ≤ 20 KB (see §5 budget, §6 forbidden, §6.1 multi-instance safety); this layer keeps each domain ≤ 8 KB
- `scope.ts` (`.claude/hooks/scope.ts`) — per-instance staging tool; the reason `HEAD` already reflects this session's scope
- `security-auditor` v2.2.0 — vetoes commit if PII/secret leaks into a domain file; §4.RN on Expo
