End-user walkthrough: from a fresh TypeScript project to seeing registry hits in Claude Code in under 15 minutes.
This document is for people who want to use yakcc in their own project. If you want to contribute to yakcc itself, see README.md and CONTRIBUTING.md.
node --version.npm install -g pnpm if needed.yakcc init auto-detects them. See §8 IDE adapters for the full list.Note (2026-05-12): A standalone npm distribution is in flight (issue #361). Until that lands, install from the monorepo. Steps will collapse to
npm install -g @yakcc/clionce #361 ships.
git clone https://github.com/cneckar/yakcc.git ~/.yakcc-cli
cd ~/.yakcc-cli
pnpm install --frozen-lockfile
pnpm -r build
Wire the CLI onto your PATH:
# bash / zsh
export PATH="$HOME/.yakcc-cli/packages/cli/dist:$PATH"
# Permanent: add the line above to ~/.bashrc or ~/.zshrc
Confirm it works:
yakcc --help
You should see the command list. If yakcc is not found, double-check the PATH export or use the absolute path node ~/.yakcc-cli/packages/cli/dist/bin.js.
yakcc initFrom your project root:
cd ~/my-project
yakcc init
What this does (per DEC-CLI-INIT-001 and DEC-CLI-INIT-002 in packages/cli/src/commands/init.ts):
.yakcc/ for operational data — an empty SQLite registry at .yakcc/registry.sqlite, a telemetry/ directory, and a config/ directory..yakccrc.json at the project root with the registry path, mode, installed hooks list, and (optionally) federation peers.DEC-CLI-IDE-DETECT-SEMANTICS-001), then wires the yakcc hook for each detected IDE.DEC-CLI-INIT-002). Pass --no-seed to skip this step.Verify the install:
ls .yakcc/ # registry/ telemetry/ config/ registry.sqlite
cat .yakccrc.json # { "version": 1, "mode": "local", "registry": { "path": ".yakcc/registry.sqlite" }, "installedHooks": [...] }
grep -A 5 yakcc .claude/settings.json # PreToolUse hook entry with the yakcc-hook-v1 marker (if Claude Code detected)
| Flag | Default | Description |
|---|---|---|
--target <dir> | . (current dir) | Initialize a different directory |
--peer <url> | none | Connect to a team registry peer on first boot (http/https only) |
--local | (default) | Explicit local mode — offline-first, no peer |
--airgapped | off | Explicit airgap mode — semantically equivalent to --local today |
--skip-hooks | off | Skip IDE hook auto-install entirely |
--ide <list> | (auto-detect) | Comma-separated explicit IDE list, e.g. --ide claude-code,cursor. Skips auto-detection. Valid values: claude-code, cursor, cline, continue |
--no-seed | off | Skip the bootstrap corpus seed step |
To initialize a different directory or connect to a team registry on first boot:
yakcc init --target ./some-other-project --peer https://registry.example.com
--peer is validated strictly (must be http: or https:); the peer URL is stored under federation.peers[] in .yakccrc.json and immediately mirrored. See §7 Federation.
To wire hooks for specific IDEs only (skip auto-detection):
yakcc init --ide claude-code,cursor
yakcc init auto-seeds the yakcc bootstrap corpus by default (per DEC-CLI-INIT-002). This gives you ~3,800 real-shaved atoms covering yakcc’s parsers, registry primitives, hook helpers, federation transport, and more — enough that your first Claude Code sessions hit the registry immediately instead of returning synthesis-required for every emission.
Confirm the seed ran:
yakcc query "store a block by content address"
You should see at least one registry hit. If the query returns nothing and you did not pass --no-seed, check that bootstrap/yakcc.registry.sqlite exists in your repo clone (git status bootstrap/).
To re-seed manually (the operation is idempotent):
yakcc seed --yakcc
Opt-out:
yakcc init --no-seedskips the seed step, restoring the quiet-init shape from beforeDEC-CLI-INIT-002. Use this if you only want your own project’s atoms and prefer to seed on your own schedule.
Wall-time: expect 20-60 seconds on a modern dev machine (mostly BLAKE3 + sqlite-vec embedding insertion).
If you only want the 20-block JSON integer-list parser seed (the original smaller corpus):
yakcc seed
Both yakcc seed and yakcc seed --yakcc are idempotent; running them together is safe.
Start (or restart) Claude Code in the project root. Then in the project, ask Claude to write something the registry knows about — e.g. “parse a JSON array of integers from this string.”
What you’ll see:
With the bootstrap corpus seeded, the hook returns outcome: "registry-hit" for queries that match existing atoms. Claude will reference an existing atom by its BlockMerkleRoot instead of generating new code.
The hook records every emission to ~/.yakcc/telemetry/<session-id>.jsonl. Check that it fired:
ls ~/.yakcc/telemetry/
tail -1 ~/.yakcc/telemetry/*.jsonl | jq .
You should see a JSON line with outcome: "passthrough" or outcome: "registry-hit" and a sub-millisecond latencyMs. If the file does not exist, the hook is not wired — re-run yakcc init from the project root and restart Claude Code.
yakcc creates two directories named .yakcc in normal use. They are in different parent directories and hold different state:
| Directory | Contents | Created by |
|---|---|---|
<project>/.yakcc/ | registry.sqlite (atom store), config/, registry/ | yakcc init |
~/.yakcc/ | telemetry/<session-id>.jsonl — one per Claude Code session | Hook on first Edit/Write |
Telemetry is always written to ~/.yakcc/telemetry/, not <project>/.yakcc/. This is intentional: a single Claude Code session can touch multiple projects, so telemetry is aggregated per-user rather than fragmented per-project (D-HOOK-5).
Use the yakcc telemetry command to inspect it without having to remember the path:
yakcc telemetry # list session files with event counts + timestamps
yakcc telemetry --tail 5 # print the last 5 events from the latest session
yakcc telemetry --path # print the resolved telemetry directory
When the hook substitutes a registry atom, the emitted code carries an inline contract comment showing exactly which atom was used (per D-HOOK-4 in docs/archive/developer/adr/hook-layer-architecture.md):
// @atom parseIntList (string => number[]; never throws on well-formed input) — yakcc:7f3a1c20
import { parseIntList } from "@yakcc/atoms/parseIntList";
const ints = parseIntList(input);
The format is // @atom <name> (<signature>; <key-guarantee>) — yakcc:<hash[:8]> per DEC-HOOK-PHASE-3-001 in packages/hooks-base/src/substitute.ts.
The contract comment is the audit trail: every assembled program can be re-verified by re-fetching atoms by their content-address and re-running their property tests. Nothing was generated; nothing can drift.
To inspect the atom by hand:
yakcc query "parse a JSON array of integers" --top 5
This returns up to 5 atoms ranked by semantic similarity, each annotated with its BlockMerkleRoot, IntentCard, and structural distance. The --rerank flag adds a structural score on top of the vector score for tighter ranking.
Free-text vs spec-file search:
yakcc search "parse a JSON array of integers" # structural search by free text
yakcc query "..." --card-file ./my-intent.json # exact IntentCard JSON
When the registry has no good match, the hook returns synthesis-required and Claude Code writes the code from scratch. Your role as the operator is to decide whether the new code is atom-worthy — does it solve a general problem the registry should learn?
When #362 (hook atom-capture) lands, every novel emission will be auto-atomized into the registry, closing the flywheel automatically. Until then, the manual path is:
# Shave the file Claude just wrote into atoms
yakcc shave src/util.ts
yakcc shave extracts every JSDoc-annotated exported function, validates each as a strict-TS-subset atom, computes its BlockMerkleRoot, and stores it (idempotently — re-shaves are a no-op when content is unchanged). Confirm:
yakcc search "debounce" --top 5
The atom you just shaved is now discoverable in subsequent Claude Code sessions.
If your team has a shared registry, point your local copy at it.
yakcc federation mirror \
--remote https://team-registry.example.com \
--registry .yakcc/registry.sqlite
Every transferred block is integrity-checked by recomputing its BlockMerkleRoot from the received bytes (per FEDERATION.md F1 axis). Tampered transfers fail loud.
yakcc federation serve \
--registry .yakcc/registry.sqlite \
--port 8080
This starts a read-only HTTP server. Co-workers point their yakcc federation mirror --remote http://your-host:8080 at it. F1 has no auth — run it behind a reverse proxy or on a private network only.
yakcc federation pull \
--remote https://team-registry.example.com \
--root <BlockMerkleRoot> \
--registry .yakcc/registry.sqlite
Useful for cherry-picking a known atom without mirroring the whole peer.
Edit .yakccrc.json:
{
"version": 1,
"registry": { "path": ".yakcc/registry.sqlite" },
"federation": {
"peers": ["https://team-registry-new.example.com"]
}
}
Then re-run yakcc federation mirror --remote <new-url> --registry .yakcc/registry.sqlite.
yakcc init auto-detects all four supported IDEs by probing their config directories (per DEC-CLI-IDE-DETECT-SEMANTICS-001). The explicit per-IDE commands below are for re-wiring after a fresh IDE install, troubleshooting, or targeted control.
yakcc init wires this automatically when ~/.claude/ is detected. To manage it explicitly:
yakcc hooks claude-code install [--target <dir>] # re-wire (idempotent)
yakcc hooks claude-code install --uninstall # remove the yakcc entry only
The installer reads .claude/settings.json, adds (or removes) a single PreToolUse block marked _yakcc: "yakcc-hook-v1", and writes it back. Any other hook configuration you have is left untouched.
yakcc init wires this automatically when the Cursor config directory is detected (platform-specific path per DEC-CLI-IDE-DETECT-SEMANTICS-001). To manage it explicitly:
yakcc hooks cursor install [--target <dir>]
yakcc hooks cursor install --uninstall
Same shape as the Claude Code installer; targets the Cursor settings surface.
yakcc init writes a marker file ~/.config/cline/yakcc-cline-hook.json when the Cline config directory is detected (per DEC-CLI-HOOKS-CLINE-INSTALL-001). The marker records the hook intent for when Cline’s VS Code extension API surface stabilizes. No live settings.json wiring yet.
To manage it explicitly:
yakcc hooks cline install # write (or refresh) the marker file
yakcc hooks cline install --uninstall # remove the marker file
Cline detection probes ~/.config/cline/ and the VS Code extension dir ~/.vscode/extensions/saoudrizwan.claude-dev.
yakcc init writes a marker file ~/.continue/yakcc-continue-hook.json when the Continue.dev config directory is detected (per DEC-CLI-HOOKS-CONTINUE-INSTALL-001). Same pattern as Cline — the marker documents intent for when Continue.dev’s extension API stabilizes. No live settings.json wiring yet.
To manage it explicitly:
yakcc hooks continue install # write (or refresh) the marker file
yakcc hooks continue install --uninstall # remove the marker file
Continue.dev detection probes ~/.continue/ and ~/.vscode/extensions/continue.continue.
Not yet wired. Tracked at issue #220 (closed not-planned, conditional on demand).
To remove yakcc hooks from your project (preserves the local registry and .yakcc/ data directory):
yakcc uninstall
What this does (per DEC-CLI-UNINSTALL-COMMAND-001 and DEC-CLI-UNINSTALL-DETECTION-001 in packages/cli/src/commands/uninstall.ts):
.yakccrc.json to discover which IDEs were installed (the installedHooks field written by yakcc init)..yakccrc.json to clear the installedHooks field.The local registry (.yakcc/registry.sqlite and related data), telemetry files, and .yakccrc.json are preserved by default.
| Flag | Default | Description |
|---|---|---|
--target <dir> | . (current dir) | Uninstall from a different directory |
--purge | off | Also remove .yakcc/ and .yakccrc.json — destructive; removes all local registry data |
--ide <list> | (all installed) | Comma-separated list of specific IDEs to uninstall. Valid values: claude-code, cursor, cline, continue |
To remove all yakcc data from a project (hooks + registry + config):
yakcc uninstall --purge
To remove hooks for a specific IDE only:
yakcc uninstall --ide claude-code
yakcc uninstall --ide cline,continue
Re-running yakcc uninstall is safe — it is idempotent. Running it on a project where yakcc was never initialized exits 0 with a no-op summary.
Every hook invocation appends one JSON line to ~/.yakcc/telemetry/<session-id>.jsonl (per D-HOOK-5 in docs/archive/developer/adr/hook-layer-architecture.md). The file is local-only; nothing leaves your machine.
Schema (one event per emission):
{
t: 1715568000000, // unix-ms timestamp
intentHash: "blake3:...", // BLAKE3 of the emission text
toolName: "Edit" | "Write" | "MultiEdit",
latencyMs: 12,
outcome: "registry-hit" | "synthesis-required" | "passthrough",
substituted: true, // true iff Phase 2 substitution fired
substitutedAtomHash: "7f3a1c..." // BMR[:8] of the substituted atom, or null
}
Quick hit/miss tally for the current session:
jq -s 'group_by(.outcome) | map({outcome: .[0].outcome, count: length})' \
~/.yakcc/telemetry/<session-id>.jsonl
For a rolling 7-day view across all sessions:
jq -s 'map(select(.t > (now * 1000 - 7*24*3600*1000)))
| group_by(.outcome) | map({outcome: .[0].outcome, count: length})' \
~/.yakcc/telemetry/*.jsonl
A dedicated yakcc telemetry subcommand is on the roadmap; until then jq is the read surface.
If you already have a TypeScript package with conventions you want represented in the registry, bulk-ingest the whole tree:
yakcc bootstrap
This shaves every file in the workspace, decomposes JSDoc-annotated exports into atoms, and writes a manifest at bootstrap/expected-roots.json. Add --verify to also byte-compare the produced manifest against a committed one (the self-hosting proof; see docs/archive/developer/V2_SELF_HOSTING_DEMO.md):
yakcc bootstrap --verify
After an embedding-model upgrade (the default is bge-small-en-v1.5 per DEC-EMBED-MODEL-DEFAULT-002), regenerate vectors without re-shaving:
yakcc registry rebuild --path .yakcc/registry.sqlite
rebuild is idempotent and preserves all atom content byte-for-byte — only the embedding index is regenerated.
| Symptom | Likely cause | Fix |
|---|---|---|
yakcc: command not found | PATH not set or build skipped | Re-run pnpm -r build and re-export PATH (see §2). |
| Claude Code does not invoke the hook | .claude/settings.json missing the yakcc entry, or Claude Code not restarted after yakcc init | Run yakcc hooks claude-code install and restart Claude Code. Verify with grep yakcc .claude/settings.json. |
Registry seems empty after yakcc init | Seed was skipped (--no-seed) or the bootstrap corpus is missing | Run yakcc seed --yakcc to seed manually. If that fails, check that bootstrap/yakcc.registry.sqlite exists in your repo clone (git status bootstrap/). |
federation mirror fails with integrity error | Peer returned bytes that don’t match the advertised BMR | Refuse the transfer (correct behavior). Contact the peer operator. |
Every emission shows outcome: "passthrough" | Embedding model mismatch after a yakcc upgrade | Re-embed the registry: yakcc registry rebuild --path .yakcc/registry.sqlite. |
| Hook latency feels high (>200ms) | Cold embedding model on first call | Expected once per session; subsequent calls are warm-cached. If persistent, file an issue with a telemetry sample. |
yakcc shave <file> fails with DidNotReachAtomError | A CallExpression in the source is neither atomic nor decomposable | Refactor the offending expression to a named helper, or add the file to bootstrap/expected-failures.json if you’re contributing to yakcc itself. |
yakcc shave rejects a file with IntentCardSchemaError: behavior must not contain newline characters | A function’s JSDoc @behavior field spans multiple lines | Collapse @behavior to a single line; long descriptions go in the function body or a separate doc. |
yakcc uses SQLite with WAL (Write-Ahead Logging) mode as its registry backend. Understanding the concurrency model helps when running multiple yakcc processes against the same registry.
Many concurrent readers are fine. WAL mode allows unlimited simultaneous read operations
with no interference between readers or between a reader and a single writer. Run as many
yakcc query, yakcc search, yakcc compile, or hook-intercept processes as you like —
they never block each other or block a writer.
Only one writer at a time per registry. The commands that write to the registry are:
yakcc shaveyakcc bootstrapyakcc registry rebuildyakcc federation mirroryakcc federation pull (only when --registry is supplied)yakcc enforces this with an advisory write lock — a .write.lock file in the same
directory as registry.sqlite. The first writer acquires the lock; subsequent writers
wait (polling) and error after a configurable timeout if the lock is never released.
Default timeout: 30 seconds. Override with YAKCC_WRITE_LOCK_TIMEOUT_MS=<ms>.
If a writer is killed mid-run the lock file may be left behind. yakcc detects this
automatically (the recorded PID is no longer alive) and steals the lock on the next write.
If automatic detection fails, remove .yakcc/.write.lock manually.
Don’t share a single .yakcc/registry.sqlite over NFS or a network mount. Each
developer’s local clone has their own registry; the yakcc federation mirror command
handles cross-developer atom sharing. Lock files over NFS are notoriously unreliable and
are explicitly not supported.
bootstrap-accumulate.yml is single-process by design — one CI job writes to the
registry at a time. If you need parallel CI jobs that each read from a shared registry,
have them all use yakcc search / yakcc compile (reader paths that don’t acquire the
write lock).
README.md — yakcc-the-project overview, monorepo layout, contributor quickstart.docs/CONTRIBUTING.md — contributor orientation and pointers into the developer-docs archive.docs/archive/developer/MASTER_PLAN.md — architecture decisions and work-item history.docs/archive/developer/DESIGN.md — extended design rationale and contract philosophy.docs/archive/developer/VERIFICATION.md — verification ladder, triplet identity, TCB.docs/archive/developer/FEDERATION.md — F0..F4 federation trust/scale axis.docs/archive/developer/MANIFESTO.md — project voice and intent.docs/archive/developer/PRIOR_ART.md — defensive publication of the substrate’s novel mechanisms.docs/archive/developer/V2_SELF_HOSTING_DEMO.md — the yakcc bootstrap --verify self-hosting proof.docs/archive/developer/adr/hook-layer-architecture.md — D-HOOK-1..6 hook-layer decisions.By default yakcc uses a local BGE-small-en-v1.5 model (~25 MB, no network, no API key). This is free, private, and works offline. It is the right choice for most users.
If you find that abstract or mathematical code doesn’t surface well in search results, you can opt in to a hosted embedding API. This sends your code’s intent text and atom source to a third-party service.
| Provider | Air-gap | Cost | Notes |
|---|---|---|---|
| local (default) | ✅ fully offline | Free | BGE-small-en-v1.5, ~80 MB model download on first use |
| openai | ❌ sends code to OpenAI | ~$0.10–$1 / M tokens | Best recall; text-embedding-3-large recommended |
| voyage | ❌ sends code to Voyage | ~$0.10–$0.50 / M tokens | voyage-code-2 specialized for code retrieval |
| openai-compatible | Depends on server location | Free (self-hosted GPU) | Ollama, LM Studio, vLLM — keeps data local if server is local |
# OpenAI
export YAKCC_EMBEDDING_PROVIDER=openai
export YAKCC_EMBEDDING_MODEL=text-embedding-3-large
export OPENAI_API_KEY=sk-...
yakcc init # initializes registry with OpenAI embeddings
# OpenAI with dimension reduction (keeps 384-dim schema, reduces cost)
export YAKCC_EMBEDDING_PROVIDER=openai
export YAKCC_EMBEDDING_MODEL=text-embedding-3-small
export YAKCC_EMBEDDING_DIMENSIONS=384
export OPENAI_API_KEY=sk-...
yakcc init
# Voyage (code-specialized)
export YAKCC_EMBEDDING_PROVIDER=voyage
export YAKCC_EMBEDDING_MODEL=voyage-code-2
export VOYAGE_API_KEY=pa-...
yakcc init
# Self-hosted Ollama (local GPU, no data leaves your machine)
export YAKCC_EMBEDDING_PROVIDER=openai-compatible
export YAKCC_EMBEDDING_BASE_URL=http://localhost:11434/v1
export YAKCC_EMBEDDING_MODEL=nomic-embed-text
export YAKCC_EMBEDDING_DIMENSION=768
yakcc init
First use of a hosted provider prints a one-time warning:
warning: YAKCC_EMBEDDING_PROVIDER=openai sends emission intent text + atom
impl source to openai. This breaks the air-gap (B6) cornerstone for embedding
operations. Set YAKCC_EMBEDDING_DISCLOSURE_ACK=1 to silence this warning.
Set YAKCC_EMBEDDING_DISCLOSURE_ACK=1 to silence it after you’ve read it.
If you initialized with the default local provider and want to switch to OpenAI:
export YAKCC_EMBEDDING_PROVIDER=openai
export YAKCC_EMBEDDING_MODEL=text-embedding-3-large
export OPENAI_API_KEY=sk-...
yakcc registry rebuild
For cross-dimension migrations (e.g. local 384-dim → Voyage 1536-dim), registry rebuild automatically drops and recreates the embedding index with the new schema. Atom content is preserved; only the embedding vectors are regenerated.
You can also pass the provider directly as CLI flags without setting env vars:
yakcc registry rebuild \
--embedding-provider openai \
--embedding-model text-embedding-3-large
yakcc init writes the embedding config to .yakccrc.json when env vars are set at init time (API keys are NOT stored). Subsequent CLI invocations read the stored provider without requiring env vars to be re-set.
{
"version": 1,
"mode": "global",
"embeddings": {
"provider": "openai",
"model": "text-embedding-3-large"
}
}
API keys must still be provided via env vars on each session (OPENAI_API_KEY, VOYAGE_API_KEY).
registry.yakcc.comEvery novel atom captured by your install — via yakcc shave, an IDE hook intercept, or the bootstrap corpus walk — is automatically POSTed to the shared public commons at https://registry.yakcc.com. This is the load-bearing flywheel of the yakcc ecosystem: every install everywhere contributes every novel atom always, and every install reads from the same growing pool.
This is by design, not a privacy concern. Atoms are atomic functional blocks: pure functions with documented contracts and property tests, identified by content-addressed BlockMerkleRoot (BLAKE3 hash). They have no per-user identity, no project-specific code, no secrets — the strict-subset shave gate enforces that. (DEC-COMMONS-ALWAYS-ON-001 / DEC-COMMONS-NO-AUTH-001)
For each new atom your install captures:
BlockMerkleRoot), the submission is a server-side no-op. Re-running yakcc shave over the same files is free.Each storeBlock that produces a novel insert triggers an async fire-and-forget POST to <commons>/v1/blocks/submit. The POST runs on a detached promise so your local capture latency is unchanged whether or not the commons is reachable.
If the commons is unreachable when the atom is stored, the atom is queued locally (submitted_at IS NULL in the registry’s blocks table). A later CLI invocation that reaches the network drains the queue — no atom is lost.
There is exactly one supported escape hatch: air-gap mode, a deployment posture for sensitive environments. Two ways to enable:
# Per-install (writes mode="airgapped" to .yakccrc.json)
yakcc init --airgapped
# Per-shell (overrides any rc mode)
export YAKCC_AIRGAP=1
Air-gap mode disables ALL network operations: no commons submission, no federation pull, no federation mirror, no embedding model download. It is structural — the only way an install does not contribute. There is no middle ground where the install has network access but withholds atoms.
A consent prompt for content with no owner, no privacy surface, and no project-specific information would be a security-theater step that adds friction without protecting anything real. The commons is the product. Restricting contribution defeats the architecture. If you need air-gap, use air-gap; otherwise, your install joins the flywheel.
For self-hosted commons (yakforge deployments, internal mirrors), override the URL:
export YAKCC_COMMONS_URL=https://commons.internal.example.com
The override applies until you unset it. Tests and dev/staging mirrors use this to route POSTs away from the public commons.
Each row in your local blocks table carries a submitted_at timestamp (NULL = pending). Inspect via:
sqlite3 .yakcc/registry.sqlite "SELECT block_merkle_root, submitted_at FROM blocks ORDER BY submitted_at LIMIT 5;"
Where submitted_at IS NULL, the next invocation that reaches the network will retry. Already-submitted rows are skipped (the markBlockSubmitted write happens after the 2xx response).
This slice (#794 slice 4) wires the commons submitter into yakcc shave only. Follow-up slices will extend the wiring to yakcc bootstrap, yakcc seed, yakcc propose, IDE hook intercepts, and the manual yakcc atom add flow. Until then, those paths still store atoms locally but do not push them to the commons — atoms remain queued (submitted_at IS NULL) and will be flushed once the rest of the wiring lands.
If something in this walkthrough doesn’t match what you observe, please file an issue. The walkthrough lives at docs/USING_YAKCC.md and is intended to track shipped software exactly.