chore: gitignore .claude (local Claude Code tooling, not published)

.claude/ (the tools-registry-context + dev-local skills) is local dev tooling
now — untracked from the repo and added to .gitignore. The design docs it
manages stay public in docs/context/. Fix the AGENTS/CONTRIBUTING/README
references that pointed at .claude/skills.
This commit is contained in:
UncleCode
2026-07-16 19:58:57 +08:00
parent ff9d85c993
commit b2373cfa86
11 changed files with 4 additions and 473 deletions
-39
View File
@@ -1,39 +0,0 @@
---
name: dev-local
description: One-command local dev stack for tools-registry. Use when asked to "start the dev server", "run treg locally", "test login locally", "bring the stack up", or before any manual/browser test against localhost.
---
# dev-local — the local treg stack in one command
`scripts/dev-local.sh` runs the FastAPI server in a tmux session with dev-safe
settings, and gives you a sandboxed CLI that never touches `~/.treg/config.json`.
| Service | Command (managed by the script) | Port |
|---|---|---|
| treg server | `uv run python -m treg --reload` + `TREG_EMAIL_DEV_MODE=true`, own sqlite `treg-dev.db` | 18790 |
No infra deps (sqlite). Prerequisites: `tmux`, `uv` (the script runs `uv sync` if `.venv` is missing).
## Subcommands
```
scripts/dev-local.sh up # start (idempotent) — prints URLs when healthy
scripts/dev-local.sh down # stop the session
scripts/dev-local.sh status # windows + port check
scripts/dev-local.sh logs # last 200 lines of the server window
scripts/dev-local.sh restart # respawn the server window
scripts/dev-local.sh attach # attach to tmux
scripts/dev-local.sh cli <args> # working-tree treg CLI, sandboxed HOME, pre-pointed at localhost
scripts/dev-local.sh reset # down + wipe treg-dev.db and the CLI sandbox
```
`cli` is the key trick for login testing: `scripts/dev-local.sh cli login` runs the
repo's CLI against localhost with `HOME=scripts/.dev-home`, so the real
`~/.treg/config.json` (usually pointing at production) is never overwritten.
Email OTP dev mode is on — codes appear in the login page / API response, no mail sender needed.
## Troubleshooting
- **Port 18790 in use, no session** → something else owns it: `lsof -i :18790`.
- **Server window died** → `scripts/dev-local.sh logs` for the traceback, then `restart`.
- **Stale dev state / want a fresh DB** → `scripts/dev-local.sh reset` then `up`.
@@ -1,55 +0,0 @@
# MAINTAINING — keep the tools-registry fragment set (and this skill) current
The "sub-skill": the protocol for updating `docs/context/` fragments after code changes. Read it when
running `/tools-registry-context sync`. (This system was scaffolded by the global `codemap` skill.)
## Scope boundary (read first)
This skill documents **only `docs/context/`**. Session **handoffs and plans live elsewhere** (e.g.
`.context/`) and are explicitly **not** part of the fragment set — never read them as input, fold them
into a fragment, or sweep them into the index. `build-map.py` walks `docs/context/` only, by design.
## The mental model
```
docs/context/**/*.md ← the fragments (SOURCE OF TRUTH; human-browsable, versioned)
frontmatter: title / status / sources: [...] / related: [...]
│ scripts/build-map.py reads all frontmatter + fragments.config →
▼
docs/context/README.md (human index, grouped by category)
.claude/skills/tools-registry-context/MAP.md (source file → fragment, for routing + drift)
```
Edit **fragments**, never the generated files (they carry a "GENERATED" banner and are overwritten).
## Sync workflow
1. **Scope:** default range `origin/main..HEAD`. Override with an explicit range if needed.
2. **Detect drift:** `bash .claude/skills/tools-registry-context/scripts/drift.sh [range]` → (a) changed
source → documenting fragment(s); (b) changed source with **no** fragment.
3. **For each affected fragment:** read it + `git diff <range> -- <file>`; update prose; re-grep moved
cited symbols still exist (grep them); add new files to `sources:`. Citations are **symbols, not
line numbers**, so an unrelated insert elsewhere does NOT make this fragment drift — only a genuine
behavior/rename change does. (This is why sync stays fast.)
4. **For gaps:** fold into an existing fragment (+ `sources:`), or write a new fragment from
`fragment.md.tmpl` in the right category folder (~70–130 lines, cited, present-tense).
5. **Show before applying.** Get user approval; apply only what's approved.
6. **Regenerate:** `python3 .claude/skills/tools-registry-context/scripts/build-map.py` — warns on any
fragment missing frontmatter (exit 1). Fix before committing.
7. **Commit with the code.** `docs(context):` scope; follow the repo's commit conventions.
## Fragment conventions (must hold for the tooling)
- **Frontmatter required** on every fragment: `title`, `status`
(shipped | reference | foundational | living | archived | backlog), `sources:` (repo-relative; `[]`
for narrative/reference), `related:` (other fragment paths).
- **Categories = subfolders** under `docs/context/`, ordered/labeled in `fragments.config`.
- **One subsystem per fragment.** Split anything past ~150 lines.
## Evolving this skill / config
- New category → add `{dir,label}` to `fragments.config` `categories` (controls index order + heading).
- New source area → add a pathspec to `source_globs` (and extension to `source_exts`) in the config.
- Pull upstream script improvements → re-run `codemap` in `refresh` mode (re-copies `build-map.py` /
`drift.sh` from the global skill without touching your fragments or config).
- Keep `SKILL.md` thin (a router). Detail belongs here or in fragments.
@@ -1,69 +0,0 @@
# tools-registry context MAP
<!-- GENERATED by scripts/build-map.py — do not edit by hand; edit fragment frontmatter instead -->
Reverse index: **source file → the fragment(s) that document it.** Used by the
`tools-registry-context` skill to load the right fragment when you touch a file and to detect drift.
Regenerate via `scripts/build-map.py`.
## Source file → fragment(s)
| Source file | Documented in |
|---|---|
| `README.md` | foundation/charter.md |
| `external:meetings/2026-06-30-jason-tools-registry.md` | foundation/charter.md, reference/glossary.md |
| `render.yaml` | ops/deploy.md |
| `src/treg/__main__.py` | ops/deploy.md |
| `src/treg/api.py` | architecture/multi-tenancy.md, architecture/proxy-model.md, architecture/super-admin.md, interface/api.md, interface/dashboard.md, interface/landing-sandbox.md |
| `src/treg/audit.py` | architecture/data-model.md, ops/deploy.md |
| `src/treg/cli.py` | interface/cli.md, interface/onboarding.md, interface/shell.md |
| `src/treg/config.py` | architecture/super-admin.md, ops/deploy.md |
| `src/treg/convert.py` | interface/cli.md |
| `src/treg/crypto.py` | architecture/auth-secrets.md |
| `src/treg/db.py` | architecture/data-model.md, architecture/multi-tenancy.md, ops/deploy.md |
| `src/treg/demo.py` | interface/onboarding.md |
| `src/treg/egress.py` | architecture/local-run.md |
| `src/treg/email.py` | interface/api.md, ops/deploy.md |
| `src/treg/fsjail.py` | architecture/local-run.md |
| `src/treg/health.py` | architecture/auth-secrets.md |
| `src/treg/injectors.py` | architecture/auth-secrets.md |
| `src/treg/localrun.py` | architecture/local-run.md |
| `src/treg/models.py` | architecture/data-model.md, architecture/multi-tenancy.md |
| `src/treg/oauth.py` | architecture/auth-secrets.md |
| `src/treg/providers.py` | interface/env-import.md |
| `src/treg/proxy.py` | architecture/proxy-model.md |
| `src/treg/ratestore.py` | architecture/data-model.md, interface/api.md |
| `src/treg/runner.py` | interface/api.md |
| `src/treg/sandbox.py` | interface/landing-sandbox.md |
| `src/treg/session.py` | interface/dashboard.md |
| `src/treg/shell.py` | interface/shell.md |
| `src/treg/skills.py` | interface/env-import.md |
| `src/treg/web/index.html` | interface/dashboard.md, interface/landing-sandbox.md, interface/onboarding.md |
| `src/treg/web/install.sh` | interface/landing-sandbox.md |
| `src/treg/web/skill.md` | interface/skill.md |
| `src/treg/web/tour/index.html` | interface/dashboard.md |
| `src/treg/web/tour/tour.js` | interface/dashboard.md |
| `src/treg/web/tutorial.html` | interface/dashboard.md |
| `src/treg/web/tutorial.js` | interface/dashboard.md |
## Fragment → sources
| Fragment | Sources |
|---|---|
| `architecture/auth-secrets.md` | `injectors.py`, `crypto.py`, `oauth.py`, `health.py` |
| `architecture/data-model.md` | `models.py`, `db.py`, `audit.py`, `ratestore.py` |
| `architecture/local-run.md` | `localrun.py`, `egress.py`, `fsjail.py` |
| `architecture/multi-tenancy.md` | `models.py`, `api.py`, `db.py` |
| `architecture/proxy-model.md` | `proxy.py`, `api.py` |
| `architecture/super-admin.md` | `api.py`, `config.py` |
| `foundation/charter.md` | `2026-06-30-jason-tools-registry.md`, `README.md` |
| `interface/api.md` | `api.py`, `email.py`, `runner.py`, `ratestore.py` |
| `interface/cli.md` | `cli.py`, `convert.py` |
| `interface/dashboard.md` | `index.html`, `tutorial.js`, `tutorial.html`, `tour.js`, `index.html`, `api.py`, `session.py` |
| `interface/env-import.md` | `providers.py`, `skills.py` |
| `interface/landing-sandbox.md` | `sandbox.py`, `api.py`, `index.html`, `install.sh` |
| `interface/onboarding.md` | `demo.py`, `cli.py`, `index.html` |
| `interface/shell.md` | `shell.py`, `cli.py` |
| `interface/skill.md` | `skill.md` |
| `ops/deploy.md` | `__main__.py`, `config.py`, `db.py`, `email.py`, `audit.py`, `render.yaml` |
| `reference/glossary.md` | `2026-06-30-jason-tools-registry.md` |
@@ -1,56 +0,0 @@
---
name: tools-registry-context
description: tools-registry context + doc upkeep. Use to warm up a fresh session (orient on the architecture + recent commits) or when working on tools-registry — changing code, rules, content, data, or process (proxy · auth/secrets · API · CLI · the registry skill) — loads the relevant fragment(s) from docs/context so you act with accurate, cited context. Accepts an optional focus query (e.g. `/tools-registry-context <area>`). Also runs `/tools-registry-context sync` to update the doc fragments after changes (show → approve → apply). Mention it whenever a push to the main branch is near.
argument-hint: "[sync | <focus query>]"
---
# tools-registry-context — load the right design fragment, keep docs honest
tools-registry's design docs are **fragments** under `docs/context/` (one per subsystem), each declaring the
source files it covers in frontmatter. [`MAP.md`](MAP.md) (next to this file) is the generated reverse
index: **source file → the fragment that documents it.** This skill has two modes.
## Mode A — LOAD context (default). Adapt to *when* you're called:
**Cold start (a fresh session, little/no prior context) → warm up.** Read
[`docs/context/README.md`](../../../docs/context/README.md) (the index), then the fragments that matter
(the `foundation`-style overview fragments plus whichever subsystems the query or repo state points at).
Run `git log --oneline -15` and `git status` to catch recent commits + uncommitted work. Then give a
short orientation — what tools-registry is, the subsystems in play, what changed recently — and say you're ready.
**Mid-chat (a task/topic is already in play) → stay targeted.** Map the artifacts/topic at hand via
[`MAP.md`](MAP.md)'s "Source file → fragment(s)" table, read **just** those fragment(s), and proceed.
Don't re-warm the whole tree.
**A focus query (`/tools-registry-context <query>`) always wins** — use it to pick the fragment(s) and
focus the warm-up on that area, in either case.
Always: read the data model / behavior / RCAs / symbol anchors before changing anything; load only
what's relevant (never dump the whole tree); if an artifact you touch has **no** fragment, note it as a
gap for Mode B.
## Mode B — SYNC docs (`/tools-registry-context sync`, or before a push)
Before a push to the main branch, **remind the user** to run this; proceed only on their yes. Then
follow [`MAINTAINING.md`](MAINTAINING.md) — the short version:
1. **Detect drift:** `bash .claude/skills/tools-registry-context/scripts/drift.sh` (defaults to
`origin/main..HEAD`). It prints, per changed source, which fragment(s) document it — plus gaps.
2. **Draft updates:** for each affected fragment, read it + the diff; update prose to match changed
behavior and verify cited **symbols** still exist (no line-number chasing — symbols don't drift).
New subsystem with no fragment → draft a new fragment from `fragment.md.tmpl`.
3. **Show, then apply:** present proposed changes and get approval **before** writing.
4. **Regenerate:** `python3 .claude/skills/tools-registry-context/scripts/build-map.py` (rewrites README + MAP).
5. **Commit together:** doc updates ride with the code in the same commit/push.
## Invariants
- **Docs are the source of truth; this skill is a lens.** Fragments live in `docs/context/`; never
duplicate them into the skill. The skill holds only the generated `MAP.md` + scripts + config.
- **Frontmatter drives everything.** A fragment's `sources:` feeds the index, the MAP, and drift. When a
fragment starts covering a new file, add it to `sources:` and rerun `build-map.py`.
- **Cite stable symbols, not line numbers.** Anchor every claim to a grep-able symbol; bare line
numbers drift on every edit and slow sync. Describe what shipped, not intent.
- **No automation behind the user's back.** Sync is reminder → approve → apply. There is no git hook.
- **Handoffs and plans are NOT documentation.** They live outside `docs/context/` (e.g. `.context/`) and
are out of scope — never read, fold in, or scan them as fragment input.
@@ -1,38 +0,0 @@
{
"project": "tools-registry",
"docs_dir": "docs/context",
"categories": [
{
"dir": "foundation",
"label": "Foundation"
},
{
"dir": "architecture",
"label": "Architecture (proxy, auth, data model)"
},
{
"dir": "interface",
"label": "Interfaces (API \u00b7 CLI \u00b7 skill)"
},
{
"dir": "ops",
"label": "Ops (deploy, scale)"
},
{
"dir": "reference",
"label": "Reference"
}
],
"source_globs": [
"src",
"skill"
],
"source_exts": [
"py",
"ts",
"js",
"sh",
"json",
"yaml"
]
}
@@ -1,164 +0,0 @@
#!/usr/bin/env python3
"""
build-map.py — single source of truth for a codemap fragment set (project-agnostic).
Reads `fragments.config` (next to this script's parent skill dir) and the YAML-ish frontmatter of
every fragment under <docs_dir>, then regenerates:
1. <docs_dir>/README.md — the human index, grouped by category
2. <skill_dir>/MAP.md — source file -> fragment(s) routing/drift map
Behavior is entirely config-driven, so this file is copied verbatim into every project. No third-party
deps (hand-rolled frontmatter parser over the small subset we author). Run after editing a fragment:
python3 .claude/skills/<proj>-context/scripts/build-map.py
Idempotent. Prints a summary + any fragment missing frontmatter (exit 1 on missing).
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
SCRIPT = Path(__file__).resolve()
SKILL_DIR = SCRIPT.parents[1] # <scope-root>/.claude/skills/<proj>-context
REPO = SCRIPT.parents[4] # scope root = the dir containing .claude/ (NOT necessarily git root)
CONFIG = SKILL_DIR / "fragments.config"
MARK = "<!-- GENERATED by scripts/build-map.py — do not edit by hand; edit fragment frontmatter instead -->"
def load_config() -> dict:
cfg = json.loads(CONFIG.read_text(encoding="utf-8")) if CONFIG.exists() else {}
cfg.setdefault("docs_dir", "docs/context")
cfg.setdefault("categories", []) # ordered [{"dir":..,"label":..}]
return cfg
def parse_frontmatter(text: str) -> dict | None:
if not text.startswith("---"):
return None
end = text.find("\n---", 3)
if end == -1:
return None
fm: dict = {}
key = None
for raw in text[3:end].strip("\n").splitlines():
line = raw.rstrip()
if not line.strip():
continue
if line.startswith(" - ") or line.startswith("- "):
if key:
fm.setdefault(key, [])
if isinstance(fm[key], list):
fm[key].append(line.split("- ", 1)[1].strip())
continue
if ":" in line:
k, _, v = line.partition(":")
key = k.strip()
v = v.strip()
if v in ("", "[]"):
fm[key] = []
elif v.startswith("[") and v.endswith("]"):
fm[key] = [x.strip() for x in v[1:-1].split(",") if x.strip()]
else:
fm[key] = v
return fm
def collect(docs: Path) -> list[dict]:
frags = []
for md in sorted(docs.rglob("*.md")):
rel = md.relative_to(docs)
if rel.name == "README.md":
continue
fm = parse_frontmatter(md.read_text(encoding="utf-8"))
frags.append({
"path": str(rel),
"category": rel.parts[0] if len(rel.parts) > 1 else "",
"title": (fm or {}).get("title") or md.stem,
"status": (fm or {}).get("status", "?"),
"sources": (fm or {}).get("sources", []) or [],
"related": (fm or {}).get("related", []) or [],
"has_fm": fm is not None,
})
return frags
def category_order(cfg: dict, frags: list[dict]) -> list[tuple[str, str]]:
"""Configured categories first (in order), then any extra dirs found, then root-level ('')."""
order = [(c["dir"], c.get("label", c["dir"])) for c in cfg["categories"]]
known = {d for d, _ in order}
extras = sorted({f["category"] for f in frags if f["category"] and f["category"] not in known})
order += [(d, d) for d in extras]
if any(f["category"] == "" for f in frags):
order.append(("", "Other"))
return order
def render_readme(cfg: dict, frags: list[dict]) -> str:
proj = cfg.get("project", "this project")
skill = SKILL_DIR.name # actual skill dir (e.g. arcterm-context) — not the display name
out = [f"# {proj} — design fragments", "", MARK, "",
"Small, focused fragments that mirror the codebase. Each declares the source files it",
"covers (frontmatter `sources:`). Regenerate this index with",
f"`python3 .claude/skills/{skill}/scripts/build-map.py`.", ""]
by_cat: dict[str, list[dict]] = {}
for f in frags:
by_cat.setdefault(f["category"], []).append(f)
for cat, label in category_order(cfg, frags):
items = by_cat.get(cat, [])
if not items:
continue
out += [f"## {label}", "", "| Fragment | Status | Covers |", "|---|---|---|"]
for f in sorted(items, key=lambda x: x["path"]):
covers = ", ".join(Path(s).name for s in f["sources"][:4]) or "—"
if len(f["sources"]) > 4:
covers += ", …"
out.append(f"| [{f['title']}]({f['path']}) | {f['status']} | {covers} |")
out.append("")
return "\n".join(out) + "\n"
def render_map(cfg: dict, frags: list[dict]) -> str:
proj = cfg.get("project", "project")
skill = SKILL_DIR.name
rev: dict[str, list[str]] = {}
for f in frags:
for s in f["sources"]:
rev.setdefault(s, []).append(f["path"])
out = [f"# {proj} context MAP", "", MARK, "",
"Reverse index: **source file → the fragment(s) that document it.** Used by the",
f"`{skill}` skill to load the right fragment when you touch a file and to detect drift.",
"Regenerate via `scripts/build-map.py`.", "",
"## Source file → fragment(s)", "", "| Source file | Documented in |", "|---|---|"]
for src in sorted(rev):
out.append(f"| `{src}` | {', '.join(sorted(set(rev[src])))} |")
out += ["", "## Fragment → sources", "", "| Fragment | Sources |", "|---|---|"]
for f in sorted(frags, key=lambda x: x["path"]):
srcs = ", ".join(f"`{Path(s).name}`" for s in f["sources"]) or "_(no source files — narrative/reference)_"
out.append(f"| `{f['path']}` | {srcs} |")
return "\n".join(out) + "\n"
def main() -> int:
cfg = load_config()
docs = REPO / cfg["docs_dir"]
if not docs.exists():
print(f"docs dir not found: {docs}", file=sys.stderr)
return 2
frags = collect(docs)
(docs / "README.md").write_text(render_readme(cfg, frags), encoding="utf-8")
(SKILL_DIR / "MAP.md").write_text(render_map(cfg, frags), encoding="utf-8")
missing = [f["path"] for f in frags if not f["has_fm"]]
n_src = len({s for f in frags for s in f["sources"]})
print(f"✓ {len(frags)} fragments · {n_src} mapped source files")
print(f" wrote {(docs / 'README.md').relative_to(REPO)}")
print(f" wrote {(SKILL_DIR / 'MAP.md').relative_to(REPO)}")
if missing:
print("⚠ fragments missing frontmatter:", ", ".join(missing))
return 1
return 0
if __name__ == "__main__":
sys.exit(main())
@@ -1,46 +0,0 @@
#!/usr/bin/env bash
# drift.sh [git-range] — map changed source files to the fragment(s) that document them.
# Project-agnostic: source pathspecs + extensions come from fragments.config. Reads the generated MAP.md.
# Default range: origin/main..HEAD (everything unpushed).
set -euo pipefail
RANGE="${1:-origin/main..HEAD}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
ROOT="$(cd "$SKILL_DIR/../../.." && pwd)"
MAP="$SKILL_DIR/MAP.md"
CONFIG="$SKILL_DIR/fragments.config"
[ -f "$MAP" ] || { echo "MAP.md not found — run build-map.py first" >&2; exit 1; }
# Pull source pathspecs + optional extension filter from the JSON config (python3 is a dependency).
globs=$(python3 -c "import json;print(' '.join(json.load(open('$CONFIG')).get('source_globs',[])))" 2>/dev/null || true)
extre=$(python3 -c "import json;e=json.load(open('$CONFIG')).get('source_exts',[]);print('\\.('+'|'.join(e)+')\$' if e else '')" 2>/dev/null || true)
cd "$ROOT"
changed=$(git diff --name-only "$RANGE" -- $globs 2>/dev/null || true)
[ -n "$extre" ] && changed=$(printf '%s\n' "$changed" | grep -E "$extre" || true)
if [ -z "$changed" ]; then
echo "No documented-source changes in $RANGE."
exit 0
fi
echo "Changed sources in $RANGE → fragments to review:"
gap=()
while IFS= read -r f; do
[ -z "$f" ] && continue
line=$(grep -F "\`$f\`" "$MAP" | grep ' | ' | head -1 || true)
if [ -n "$line" ]; then
docs=$(printf '%s' "$line" | sed -E 's/^\| `[^`]+` \| (.*) \|$/\1/')
printf ' %-48s → %s\n' "$f" "$docs"
else
gap+=("$f")
fi
done <<< "$changed"
if [ ${#gap[@]} -gt 0 ]; then
echo ""
echo "⚠ changed sources with NO fragment (possible doc gap — fold in or write a new fragment):"
printf ' %s\n' "${gap[@]}"
fi
+1 -1
View File
@@ -9,6 +9,6 @@ __pycache__/
.secrets/
.pytest_cache/
dist/
.claude/mode
/.playwright-cli
scripts/.dev-home/
.claude/
+2 -2
View File
@@ -5,8 +5,8 @@ This file orients an AI agent (Claude Code, Codex, Cursor, …) working in this
## Read first
- **Design docs are the source of truth.** `docs/context/` holds one fragment per subsystem, each citing
the source files it covers. Before changing code, load the fragment for that area. The
`tools-registry-context` skill (`.claude/skills/`) maps a source file → its fragment.
the source files it covers in its frontmatter (`sources:`). Before changing code, load the fragment for
that area; `docs/context/README.md` is the generated index (source file → fragment).
- **The charter:** tools-registry is a registry that turns a team's skills into shareable, callable tools;
the core mechanic is a proxy that injects credentials server-side so a consumer never holds the secret.
See `README.md`.
+1 -1
View File
@@ -15,7 +15,7 @@ No `.env` needed for dev — every setting has a working default (ephemeral encr
sqlite). The `TREG_*` knobs for persistence / a real deployment are documented in the README's
**Configuration** section (and `docs/context/ops/deploy.md`).
A one-command local stack is in `scripts/dev-local.sh` (also the `dev-local` skill under `.claude/skills`).
A one-command local stack is in `scripts/dev-local.sh` (`up` / `logs` / `cli` / `reset`).
## Project layout
-2
View File
@@ -290,9 +290,7 @@ tools-registry/
├── docs/
│ ├── context/ # design fragments (codemap system) + generated index
│ └── ONBOARDING.md # first-time bootstrap
├── .claude/skills/tools-registry-context/ # doc-maintenance skill (/tools-registry-context)
├── USAGE.md # full treg CLI reference
├── CLAUDE.md # project instructions
└── pyproject.toml
```