chamnan
The figures above are a summary. Every one of them, and how it was measured, is in Evidence and The chaos test below — read those rather than the picture if a number matters to you.
ชำนาญ (cham-nan) — Thai for the fluency that only comes from doing something again.
A Claude Code plugin that makes a repository know itself and preserve the engineering context built while you work with it, so an agent stops rediscovering both. It builds an index the agent reads instead of scanning files, keeps the work state and the decisions that would otherwise be lost between sessions, and accumulates the procedures and tools you keep re-deriving.
Using Kiro instead of Claude Code? There is a Kiro Power, in its own repository: → chamnan for Kiro
Same scanner, same artifacts. The context reaches a session through Kiro's steering files rather than hooks, and the bulk-read notice arrives at the next
catch-uprather than before the read.
Read this before installing
chamnan is for one main folder you work in over and over, doing work that repeats.
Everything it does is amortised. It spends tokens once — building the index, writing down a procedure, keeping a tool — and collects on every session after that. Both halves of the sentence above are load-bearing, and they are load-bearing for different reasons:
| why it matters | |
|---|---|
| One main folder | The index is built once and read at the start of every session in that repo. Across a hundred sessions it is close to free. On a repo you open once, you paid the whole cost and collected nothing. |
| Work that repeats | The procedures and tools fill up from things you hit more than once. If nothing recurs, they stay empty and there is nothing to collect. |
If that describes your day, this was built for you. If it does not, it will cost you more than it returns, and you should not install it — that is not modesty, it is arithmetic. There is no setting that makes a one-off repo pay off.
A five-second test — if you answer no to either, close this page:
- Will you still be working in this same folder next month?
- Have you explained the same thing about this codebase to Claude more than twice?
The real problem: agents forget
An agent working in your repository keeps arriving at the same conclusions, because everything it worked out last time is gone:
- a new session starts with nothing. It has your files and no idea which ones matter.
- a long session compacts. Whatever it had figured out about the codebase goes with it.
- the reasoning disappears. Why a fix took the shape it did, what was ruled out and why — none of that is in the diff.
- the repository does not explain its own experience. Code says what. Git says when. Neither says why, or what has already been tried.
So the same four questions get answered from scratch, over and over: where does this live · why was it built this way · how did we solve this before · what happened last session.
The core idea
chamnan turns what gets discovered during the work into repository-local artifacts — plain markdown, committed beside the code:
MAP.md |
what exists, and what depends on what |
STATE.md |
what is being worked on right now |
sessions/ |
where the last stretch of work stopped |
memory/ |
decisions, lessons and standing rules |
skills/ · tools/ |
procedures and scripts worth keeping |
milestones.md |
the changes that reshaped the repository |
The agent does not learn. Nothing is trained, nothing persists outside the directory, and the next session still starts from zero — it just starts from zero in a repository that explains itself. The continuity is in the artifacts, not in the model.
Two kinds of cost
| what it is | what answers it | |
|---|---|---|
| Discovery cost | finding where code lives and how it connects | MAP.md, the Impact section |
| Re-solving cost | working out again what was already worked out | procedures, tools, memory, decisions, session records |
Token reduction is the consequence, not the aim. An agent that already knows where the payment logic lives does not grep for it; one that can read why the retry was written that way does not re-derive it. Fewer tokens is what less repeated work looks like on a bill.
That said, the arithmetic is worth seeing, because it is the reason this approach targets reading rather than writing. Measured on one developer's 34 days of real Claude Code usage:
| share of cost | |
|---|---|
| context read in | 91.2% |
| output written | 8.8% |
The most popular output-compression plugin advertises 65% savings; JetBrains benchmarked it across 86 tasks and measured 8.5% of output tokens — roughly 0.7% of a bill, with no loss of quality. It does what it says; it is just aimed at the smaller half.
The compounding effect
chamnan spends once and collects on every session afterwards, so what it is worth depends on how long you stay:
| what the repository holds | |
|---|---|
| Day 1 | MAP.md — the agent stops scanning the tree |
| Day 30 | + STATE.md, session records, the first procedures and tools |
| Day 180 | + decisions, + lessons, + rules, + milestones, and the workflows that turned out to repeat |
Nothing here is automatic accumulation of everything that happens. Each artifact is written deliberately, by you or by Claude at your request, because it was worth keeping. What grows is repository-specific knowledge, and it grows because you keep coming back to the same code.
The same arithmetic cuts the other way, and it is the reason the first section of this README is about whether your repository is the kind that keeps coming back: on a four-file repository this costs more than it saves. There is nothing to amortise.
What it does
Four capabilities. Everything listed is shipped and running today.
Understand — what exists, and what is connected to it
| Index | MAP.md — one line per file, generated from the code. The agent reads the index; it greps the detail; it stops reading the tree. |
| Impact | Who depends on a file, and which tests cover it. A file's own imports are already at the top of that file; the reverse edge is what costs a search. Grep it for one path before changing it. |
| Data model | Table and model names with a one-line summary, pulled from DDL, migrations and ORM models — instead of a schema dump. Only appears if the repo defines one. |
| API surface | Method, path and handler, from route decorators, OpenAPI documents and .proto service definitions — instead of the whole spec. |
| Configuration | The environment variable names the repo reads. Names only, never values — and it warns if .env is not gitignored. |
| Deployment | What actually runs, read from Kubernetes, Ansible, Compose, Helm and CI manifests: kinds and names, images, roles, pipelines. A Secret contributes its name and nothing under it. |
| Stored material | The non-source trees — scanned paperwork, exports, archives — as counts, sizes and dominant extensions. It exists to stop an agent going to look, which costs far more than the section does. Never opened, never read. |
Remember — what was being done, and why
| State | STATE.md — what is being worked on right now, injected at session start so compaction stops erasing it. |
| Resume | One record per session under .chamnan/sessions/. Only what was unfinished reaches the next session; a session that finished cleanly injects nothing at all. |
| Memory | decisions/, lessons/, rules/. Rules are standing constraints, so they go in front of the agent every session; decisions and lessons contribute a title and are read when the title looks relevant. |
Reuse — what has already been solved
| Procedures | Skills the agent writes itself when it hits something complex or repeated. Not a shipped library — a mechanism. |
| Tools | Notices when the same scratch script is written a third time, and offers to keep it. |
| Workflows | Notices when the same commands run in the same order on a third separate day, and offers to write the sequence down. |
Evolve — what the repository has learned about itself
| Milestones | The handful of changes that reshaped the repository: what moved, why it was worth doing, which areas it touched. |
Repeated engineering work becoming reusable repository knowledge — not model training, and not automation of the developer. It is a mechanism for preserving work that would otherwise only exist in whoever did it.
Supporting
| Measurement | Reports context-per-turn for your repo, before and after. Your number, not ours. |
| Routing | Its own agents run on a cheap model, because "read this file, write one line" does not need an expensive one. |
Every part can be switched off independently in .chamnan/config.json. They do not depend on each
other, and they do not have equal evidence behind them — see below.
Who this is for
The same folder, most days, and the same shapes of work coming round again. Concretely:
- A developer on one codebase for months. The repo is large enough that you cannot hold it in your head, so every session starts with the agent re-learning where things are.
- A tester re-running the same checks. The steps are the same each time and they live in your head, in a note, or in a script you rewrite.
- Infra, ops and IT. Runbooks, deploys, the same six procedures, and a deployment tree the agent has to re-read before it can say anything useful about it.
- A team handing sessions to each other. What the last session worked out has to survive into the next one, and today it does not.
- Anyone who wants the agent to accumulate context about their project — weeks or months on the same system, repeatedly extending it, tired of explaining the same things.
The thread is repetition in one place. That is the only thing chamnan converts into savings.
Who this is not for
Stated plainly, because installing this on the wrong repo makes your bill worse, not better:
- You move between many repos and rarely return. The index is paid for on the session that builds it and collected on the sessions after. If there are no sessions after, you only paid.
- One-off scripts and throwaway prototypes. Same arithmetic, faster. Genuinely net-negative.
- Every task is different. Procedures and tools accumulate from recurrence. Nothing recurs, nothing accumulates, and two of the six parts never do anything.
- Writing, chat, fiction, anything without code. There is no structure here for it to index.
- Repos with no comments and no intention of adding any. The index degrades to filenames, which the agent could already see.
- Anyone wanting a token discount without changing how they work. The saving comes from the agent reading an index instead of a tree. If it goes back to reading the tree, nothing is saved.
Requirements
| Claude Code with plugin support | Required. chamnan is a plugin, and it uses four hook events: SessionStart, PreToolUse, PostToolUse, SessionEnd. No minimum Claude Code version is declared in plugin.json; if your build supports claude plugin install and those events, it will run. |
| Python 3.8 or newer | Required, and it must be on PATH as python3. The hooks are launched by path, relying on their #!/usr/bin/env python3 line and executable bit. 3.8 is the floor because the assignment expression (:=) is the newest syntax used; nothing later appears anywhere in the plugin. |
| Third-party packages | None. Standard library only — ast, pathlib, re, json, csv, sqlite3, zipfile, tarfile, zlib, struct, subprocess. Nothing to install, nothing to keep updated, and no virtualenv. |
| Git | Not required. The plugin never invokes the git binary. The one exception is opt-in: chamnan-map --install-git-hook needs a .git directory to write into, and the hook it writes is a /bin/sh script that calls git diff and git add. |
| Disk | Whatever .chamnan/ holds — an index, a state file, a config file, and logs pruned on a retention window. Nothing outside the repository. |
Platforms
| macOS | Supported and tested. Developed and exercised on macOS (arm64) with Python 3.12; the test suite and the polyglot run below were both done there. |
| Linux | Expected to work, not tested. Same launch path as macOS — POSIX shebang, executable bit, standard library only — and nothing in the code is platform-specific. If you hit a problem there, it is a bug worth reporting rather than an expected gap. |
| Windows | Not tested, and not expected to work as-is. The hooks are invoked as bare paths to .py files, which depends on the #!/usr/bin/env python3 line and the executable bit; Windows honours neither. The optional Git hook is a /bin/sh script. Under WSL it is the Linux row above. |
Quick start
claude plugin marketplace add ArcticFox2029/chamnan
claude plugin install chamnan@chamnan
Then open Claude Code in a repository you actually work in, and run it once:
/chamnan:bootstrap
That is the whole setup. What happens next, in order:
| 1 | Builds the index. Scans the repository and writes .chamnan/MAP.md — one line per file, plus a section for the data model, API surface, configuration, deployment and stored files, each written only if the repo actually has one. |
| 2 | Measures how well the code describes itself. If fewer than 70% of files have an opening comment, it offers to fill them in and waits for you to say yes — see Bootstrap does not rewrite your code. |
| 3 | Records a baseline with chamnan-report. On a fresh repository there is no history yet and it says so. |
| 4 | Writes the first .chamnan/STATE.md — a short note on what you are working on right now. |
| 5 | Offers the optional Git hook that refreshes the index on commit. Opt-in, and it never overwrites a hook you already have. |
Afterwards, every session in that repository starts with the index and the state file already in
context. You do not run anything again until the shape of the repo changes, and then it is
/chamnan:remap.
What it creates
Everything lives in one directory at the repository root, and nothing outside it is touched:
.chamnan/
├── MAP.md the architecture index (written by chamnan-map)
├── STATE.md what you are working on (written by Claude, at milestones)
├── milestones.md changes that reshaped the repo (written by /chamnan:milestone)
├── config.json which parts are on (written on first run, merged on upgrade)
├── sessions/ where each session stopped (written by /chamnan:resume)
├── memory/
│ ├── decisions/ a choice, and why (written by /chamnan:remember)
│ ├── lessons/ something that cost time once
│ └── rules/ standing constraints — injected every session
├── skills/ procedures you chose to keep (starts empty)
├── tools/ scratch scripts you kept (starts empty)
└── logs/ bounded by log_retention_days (starts empty)
MAP.md, config.json and every directory appear the moment the index is first built. The rest
are written when you ask for them: STATE.md during bootstrap, the others by their skills. The
session-start hook skips whatever is absent, so a repository that only ever builds an index stays
exactly that simple.
Add .chamnan/logs/ to .gitignore if you would rather not carry it. Everything else is worth
committing — that is how the next person, and the next session, gets it.
Trying it without installing
From the parent directory of a clone:
git clone https://github.com/ArcticFox2029/chamnan
claude --plugin-dir ./chamnan
The plugin is active for that session only, and nothing is written until you run
/chamnan:bootstrap or chamnan-map.
What's new in 1.3
Six additions, all repository-local markdown, all bounded at the injection rather than in the store. Measured with every one of them populated: 507 tokens reach a session.
Better Resume Work
One record per session under .chamnan/sessions/, written by /chamnan:resume. Only
Remaining and Blockers reach the next session — Done is history and the file list is
recoverable from git. A session that finished cleanly injects nothing at all, because an empty
record is worse than none.
It does not replace STATE.md. STATE.md is one overwritten file about the present; a session
record is one of many about a particular stretch of work.
Smart Session Memory
.chamnan/memory/ with three categories, used three different ways:
| reaches a session as | ||
|---|---|---|
rules/ |
a standing constraint | the full text, capped |
decisions/ |
a choice, and why | its title |
lessons/ |
something that cost time once | its title |
Titles cost a line each and buy the ability to load the right file; injecting the bodies would cost everything and buy nothing extra. Not pruned by age — a session record stops mattering, a decision does not, and a timer would delete the oldest entries, which are the ones nobody can reconstruct.
Impact Map
Who depends on a file, and which tests cover it — in MAP.md, below the Full Detail marker, so
it is grepped when you are about to change one path and never injected into sessions that will not
touch it.
- **`payment/service.py`** — used by `checkout/api.py`; **tested by** `tests/test_payment.py`
One hop, capped. No transitive closure, no cycle analysis, no database. Imports are collected while the scanner already has each file open, so it adds no second read: measured at 0.673 s across 529 files, 9.5% of scan time.
Better Capture
The existing hint noticed the same script written a third time. This notices the same commands, in the same order, on a third separate day — the deployment check or debugging routine that leaves no file behind at all.
Four guards keep it quiet: arguments and paths are discarded so the same routine matches across branches; 33 commands too common to mean anything are ignored; three distinct steps minimum; three distinct days, so repeating something three times in one sitting counts once. It speaks once, and never in the same turn as the script hint.
Project Milestones
.chamnan/milestones.md — the handful of changes that reshaped the repository, with why it was
worth doing and which areas moved together. A git log rarely says the first and never says the
second.
Not project management: no status, no owner, no due date. Only the two most recent titles are injected, so forty milestones cost the same per session as two.
Better Language Support
Prioritised by measuring symbols per thousand lines across a 529-file polyglot corpus, then inspecting each low number before touching anything:
| before | after | ||
|---|---|---|---|
| PHP | 82 symbols | 163 | the rule matched only a bare function, so 66 of 139 declarations were invisible |
| Rust | 66 | 150 | only an optional pub was allowed, missing every async fn |
| TypeScript / JS | 173 | 191 | class methods are indented; every rule was anchored at ^ |
| shell | 14 | 14 | left alone — its scripts are commands, not functions, so the low number is honest |
MIN_YIELD now asserts a minimum symbol count for twelve languages against ordinary-code fixtures,
so a language partially understood beats one falsely claiming full support is a test rather than
a slogan.
Bootstrap does not rewrite your code
Worth being precise about, because an indexing tool that quietly edits your files is not one you would install. There are three categories, and only the middle one touches source.
Read-only
Scanning is a read. chamnan-map opens each source file, takes its opening comment and its
top-level symbols, and writes nothing back. chamnan-peek reads the shape of one file on request.
chamnan-report reads its own logs. None of these modify anything they read, and none of them can:
they never open a source file for writing.
Written automatically — all of it inside .chamnan/
| when | |
|---|---|
.chamnan/ and skills/, tools/, logs/ |
created on the first index run |
.chamnan/config.json |
written on the first run; on a later upgrade it is merged — keys you set are kept, keys the plugin no longer has are dropped |
.chamnan/MAP.md |
rewritten on every index run |
.chamnan/logs/ contents |
pruned on every command, per log_retention_days |
Nothing outside .chamnan/ is written without you asking. There is one opt-in exception, below.
Optional, and only after you say yes
If fewer than 70% of files have an opening comment, /chamnan:bootstrap says so and offers to fix
it. The offer is a question, not a step it takes:
Never edit files for this without asking first. It touches every undocumented file in the repo. —
skills/bootstrap/SKILL.md
Given a yes, it dispatches the commenter agent, which is deliberately narrow:
| Tools | Read, Edit, Glob — and nothing else. No shell, no Write, no ability to create or delete a file |
| Model | haiku — "read this file, write one line about it" does not need an expensive model |
| Scope | only the specific files it is handed, never the whole repo |
| Rule | one line at the top of files that have no opening comment; a file that already has one is left exactly as it was, and code is never changed |
Two honest caveats. The tools list is a hard boundary enforced outside the agent — it genuinely cannot run a command or delete a file. The "one line, never touch existing comments, never change code" rules are instructions to a model, and a model following instructions is not the same thing as a guarantee: review the diff, as you would for any change you did not type. It is one line per file, so the diff is easy to read.
Prefer it never asks? Set "agents": false in .chamnan/config.json. chamnan then lists the files
missing a comment and leaves them to you.
The one write outside .chamnan/
chamnan-map --install-git-hook writes .git/hooks/pre-commit. Opt-in, never automatic, and it
does not clobber a hook you already have — it appends a block marked # >>> chamnan, which is also
how you remove it.
Language
chamnan writes the comments and procedures it generates in English by default. Those strings are re-read on every session, and English carries the same meaning in fewer tokens — measured at 1.53x for Thai versus English across three matched sentence pairs.
That figure was measured with a local model's tokenizer, not Claude's. Take it as a direction, not a number: the ratio is real, its exact size on Claude is unverified here.
It is a default, not a rule. A team whose reviewers do not read English is better served by comments they will actually read, and the plugin does not argue:
// .chamnan/config.json
{ "language": "th" }
Or just say so — "write the comments in Thai" is enough, and Claude sets it for you. Nothing else in the plugin changes: replies to you are in whatever language you are speaking, always.
One file, only what applies, and a ceiling
Everything above is a section inside a single MAP.md, not a folder of separate catalogues. A
section is written only when the repo actually has that thing — a directory of plain scripts gets a
code index and nothing else, no empty headings.
The part of MAP.md above ## Full Detail is what gets injected at session start, so it has a
budget: index_token_budget, 3,000 tokens by default, well under 1% of a 1M context window.
chamnan-map reports against it and says what to do when a repo exceeds it. This is the rule that
stops the plugin becoming the cost it exists to remove — that part is paid on every turn.
Everything below ## Full Detail — function signatures, table columns — is never injected. It is
grepped for one heading at a time.
When a repo is large enough that even the index exceeds the budget, it is rolled up by directory
rather than truncated. Cutting at a byte offset drops whatever sorts last, so on a 196-file repo
everything from roughly s onward disappears from the session with nothing to show that an entire
area of the code exists — and the agent greps for it, which is the cost this is meant to remove. The
roll-up keeps every directory visible with its file count and a sample, and the full entry for any
one of them is still a grep away. Measured on that repo: 8,762 tokens of index became 560, with all
seven top-level directories still named.
chamnan-map src game indexes several directories into one map when the whole tree is more than you
work in.
Keeping the index fresh
A stale index is worse than no index: it is confidently wrong, and the next session believes it. So
rebuild it whenever the shape of the repo changes — /chamnan:remap — or stop having to remember:
chamnan-map --install-git-hook
That refreshes the index on any commit touching tracked files, and never fails a commit if chamnan errors. It is opt-in, it appends to an existing hook rather than replacing it, and Update, disable, uninstall covers taking it back out.
Bulk reads
Before a Read pulls in a lock file, a minified bundle, or a very large file, chamnan says so and
suggests grep. It never blocks: the one time someone genuinely needs to read package-lock.json
is the one time refusing would be most wrong. Turn it off with warn_on_bulk_reads: false.
It does not strip comments or blank lines from files on the way in — partly because hooks cannot, and partly because comments are the highest-value tokens in a file for a reader trying to understand intent. This plugin's entire index is built out of them.
A checkout inside your checkout is not your code
If another repository is checked out inside this one — a vendored dependency, a sample project, a sibling you keep side by side — chamnan leaves it alone. Its files are not indexed, its size is not reported as yours, and its Kubernetes resources and Protobuf services do not appear in your architecture map.
The signal is the nested .git, not .gitignore. chamnan does not read .gitignore anywhere —
it is often absent, often wrong, and never covers a nested checkout's own build output.
Running chamnan from inside such a checkout builds that repository's index, not its host's. It also says which repository it measured whenever that is not the directory you ran it from:
chamnan: run from vendor/thing/ — scanning the repository above it, myapp/
Silence there was the dangerous default. A directory that is not itself a repository resolves to whatever repository contains it, and every number printed afterwards is about the wrong tree.
Configuration
Everything lives in .chamnan/config.json, written on the first index run with these defaults.
Every value below was read from lib/workspace.py, which is the only place defaults are defined.
| Option | Default | Valid values | What it controls |
|---|---|---|---|
map |
true |
true / false |
The architecture index — generating it, and injecting its Quick Index at session start. The part with the strongest evidence behind it. |
state |
true |
true / false |
Injecting .chamnan/STATE.md at session start, which is what survives compaction. |
capture |
true |
true / false |
Listing the procedures recorded in .chamnan/skills/ at session start, by name and description, so the agent can load one on demand. |
promote |
true |
true / false |
Noticing a scratch script written for the third time, offering to keep it in .chamnan/tools/, and listing kept tools at session start. |
report |
true |
true / false |
The chamnan-report before/after measurement. |
agents |
true |
true / false |
Whether chamnan may dispatch its own cheap-model agents. With false, low coverage is reported and the files are left to you. |
log_retention_days |
7 |
integer, days | Files under .chamnan/logs/ older than this are deleted on every command. Best-effort and silent — housekeeping never fails a command you asked for. |
language |
"en" |
any language, e.g. "th" |
The language chamnan writes in when it generates file comments and records procedures. It never rewrites anything already written, and it never affects the language of replies to you. |
index_token_budget |
3000 |
integer, tokens | Ceiling on the part of MAP.md injected every session. Over budget, the index is rolled up by directory rather than truncated, so nothing disappears silently. |
warn_on_bulk_reads |
true |
true / false |
A notice before a read pulls in a lock file, a minified bundle or a very large file. A notice, never a block. |
reply_style |
"off" |
"off" / "concise" / "terse" |
Injects a per-repo instruction on how answers should be written. off injects nothing; concise drops preamble, restatement and closing offers while keeping full sentences; terse adds fragments and tables over prose. An unrecognised value injects nothing. |
resume |
true |
true / false |
Session records under .chamnan/sessions/, and injecting the unfinished part of the most recent one. |
session_retention_days |
30 |
integer, days | Session records older than this are deleted on the next chamnan-map or chamnan-report. Longer than the log window, because a record from three weeks ago is still the answer to "what was I doing". |
memory |
true |
true / false |
.chamnan/memory/. Rules are injected in full; decisions and lessons contribute a title and are read on demand. Not pruned by age — a session record stops mattering, a decision does not. |
milestones |
true |
true / false |
.chamnan/milestones.md. Only the two most recent titles are injected, so the file's length costs nothing per session. |
Each part is independent — switching one off does not affect the others.
You rarely need to edit the file by hand. "Use Thai for the comments in this repo" or "keep answers terse here" is enough, and Claude edits it for you.
On upgrade
config.json is merged, not replaced: keys you set are kept, and keys the plugin no longer has
are dropped. So an option that disappears after an upgrade was removed from the plugin — it is not
a lost setting.
Commands
In Claude Code:
/chamnan:bootstrap |
first-time setup: index, coverage, fill comments, baseline. Once per repo |
/chamnan:remap |
rebuild the index after the repo's shape changed |
/chamnan:capture |
record a procedure worth keeping |
/chamnan:promote |
keep a scratch script as a tool |
/chamnan:resume |
write down where this session stopped, so the next one continues |
/chamnan:remember |
record why something is the way it is — a decision, a lesson, a rule |
/chamnan:milestone |
record a change that reshaped the repository |
/chamnan:report |
show context-per-turn, before and after |
From a shell, in the repository:
chamnan-map |
rebuild .chamnan/MAP.md, and report how it landed: source tokens, Quick Index size, Full Detail size, comment coverage, and whether the index is inside index_token_budget |
chamnan-map --preview |
print exactly what a session in this repo receives at start-up, followed by its token count. Nothing is written |
chamnan-map --install-git-hook |
opt-in: refresh the index on commit. Appends to an existing pre-commit hook rather than replacing it |
chamnan-peek <file> |
the shape of one file instead of the whole thing — columns, sheets, members, schema, pages |
chamnan-peek <file> --find PATTERN |
only the parts that match, with their line numbers |
chamnan-peek <file> --budget 800 |
raise the output ceiling from its default of 400 tokens |
chamnan-promote <file> <name> --desc "…" |
install a scratch script as a permanent tool in .chamnan/tools/ |
chamnan-promote --list |
what this repo already keeps |
chamnan-report |
weekly context-per-turn. On a repo with no Claude Code history it says so instead of inventing a trend |
Reading an attachment without reading it
The index says a directory holds twelve thousand documents so that nobody goes looking. peek is
the other half: when a task genuinely needs one of them, opening it whole is the wrong move and
skipping it is also the wrong move.
Measured on the corpus below: a 12,000-row shipment CSV is 418,607 tokens read whole and 204
read as a shape — its columns, its row count and three sample rows, which is the answer to almost
every question anyone asks of a CSV. A 20,000-row SQLite database gives up every table, column and
row count in 148, and a plain read cannot open it at all. --find narrows further: the matching
rows of a 2,400-row spreadsheet, and nothing else, in 214.
Understands CSV/TSV, JSON, ZIP-based formats including .xlsx/.docx/.apk, tar archives, SQLite, PDF (including text extraction via zlib), PNG/JPEG/GIF headers, and plain text. Formats with no standard-library reader — Parquet, Avro, ORC — are identified and measured, and say so rather than guessing. A malformed file reports what went wrong instead of raising.
Secrets
MAP.md is built by copying source comments, and this README suggests committing it. That
combination is a way to publish a password, so it is handled rather than assumed away.
- Some files the scanner never opens.
.pem,.key,.pfx,.p12,.crt,.cer,.jks,id_rsa*,.htpasswd,.netrc,*.db,*.sqlite,*.bak,*.dumpand similar are skipped outright while building the index..gitignoreis not relied on: it is often absent, often wrong, and the cost of being wrong is somebody's private key. chamnan-peekhas its own, narrower refusal list, because the two are answering different questions. The scanner indexes source and has no business opening a database;peekis handed one file by name, and a database's table and column names are exactly the useful answer — so peek shows a schema and never a row. What peek refuses outright is the set whose contents are the secret: keys, certificates,.asc/.gpg, and files namedcredentials*,secrets.yml,.netrc,id_rsa*. It names the file, says no, and reads nothing.- Everything chamnan emits passes a redactor — both what it writes into
MAP.mdand whatpeekprints into a session. One choke point on the finished output rather than one per extractor, so a section added later cannot bypass it. Provider tokens (sk-,ghp_,AKIA…,AIza…,xox…, Stripe, GitLab, npm, JWTs), private-key blocks, credentialed URLs, andpassword = …assignments — quoted or bare, because no.envon earth quotes them — become<REDACTED>. - Environment variable values are never captured in the first place. The patterns that find
them match the name and stop at the
=; a value is not in any capture group, so there is no code path that could carry one into the output even by mistake..envfiles still contribute their names, because which variables a service reads is exactly what an index should say — and if one is not covered by.gitignore, chamnan says so in the map.
Verified with a repository seeded with a live-looking Stripe key, a postgres://user:pass@host in a
comment, and an RSA private key — none reached MAP.md, while
postgres://admin:<REDACTED>@db.internal:5432/main stayed readable, because which database on
which host is exactly what an index should tell you.
The redaction patterns are narrow on purpose. Redacting everything high-entropy would eat commit
hashes, UUIDs and version strings, and a map full of <REDACTED> is not a map.
What this is not
chamnan is not a sandbox, and this is not defence in depth for your session. It defends the one
thing it controls: its own output. A plugin hook cannot rewrite what the Read tool returns —
PostToolUse exposes only additionalContext and systemMessage — so no plugin can filter what
Claude reads from your disk. If you ask Claude to open .env, it opens .env, and chamnan is not
in that path. Anything claiming otherwise is describing a capability Claude Code does not have.
Two more limits worth stating plainly:
- The patterns are narrow by design, and narrow means some things get through. A credential in a
shape nobody has seen before, or a bare high-entropy string with no assignment around it, will not
match. Widening until nothing escapes would replace commit hashes, UUIDs and version strings too,
and an index full of
<REDACTED>is not an index. That trade is chosen deliberately, not overlooked. - Review
MAP.mdbefore its first commit, the same way you would review any generated file you are about to publish. On the polyglot corpus below, 92 planted credentials across 13 categories produced no values in the map — good evidence, and still not a proof about your repository.
Evidence
Split by how much weight it can carry. The first tier you can reproduce in your own repo in about ten seconds; the second is one developer's history and is labelled as such.
Reproducible — run chamnan-map and see your own
The index against the source it indexes, on three real repositories:
| repo | languages | source | Quick Index | ratio |
|---|---|---|---|---|
| a Python app | Python, 33 files | 306,388 tok | 1,395 tok | 0.5% |
| a JS game | JS + shell + Python, 19 files | 270,466 tok | 863 tok | 0.3% |
| a small dashboard | JS + shell, 12 files | 19,467 tok | 596 tok | 3.1% |
Six navigation questions ("where is the shop economy tuned?", "what runs every 10 minutes?", "where are credentials stored?") were answered from the Quick Index alone, 6 out of 6, without opening a source file.
One repository, observed — not a controlled trial
On the repo where this was developed, holding the model constant (Sonnet 5 before and after):
| per API call | before | after | |
|---|---|---|---|
| context carried | 464,191 | 359,466 | −22.6% |
| new material read | 7,120 | 4,283 | −39.8% |
| output written | 860 | 843 | −2.0% |
The same weeks also brought a model change, different kinds of task, and Claude Code updates of its
own. This is an observation on n=1, not a benchmark. chamnan-report computes the same figures for
your repository, which is the number that should actually decide anything.
The condition this all depends on
The index is built from each file's opening comment. On the three repos above, 92–100% of files had one — because that codebase requires them. A repo without them gets an index of filenames and function counts, which is worth far less.
chamnan-map prints your coverage every run, and /chamnan:bootstrap offers to fill in what is
missing. That is not a footnote; it is the difference between this working and not.
The chaos test
Small repositories flatter an indexing too
No comments yet
Be the first to share your take.