Repository navigation
Releases: ArcticFox2029/chamnan
Release list
chamnan 1.36.1 — real usage on the dashboard, two fixes from testing 1.36.0
A patch with three fixes. Two were found by testing 1.36.0 against other tools in the same space: a guard that refused a harmless macOS edit, and one slow pattern in chamnan-schedule. The third makes the dashboard answer its own first question with real usage.
| What was measured | 1.36.0 | 1.36.1 |
|---|---|---|
sed -i '' 's/…\.env/…/' notes.md (a macOS in-place edit whose script names .env) |
refused as a read of .env |
runs |
A duration with 100 spaces before a stray character, through chamnan-schedule |
1.2 s (200 spaces: over 20 s) | milliseconds at 5,000 |
| The dashboard's opening panel on a repository that never used a local model | 0 saved, two identical bars | sessions before the plugin against sessions since, from real usage |
What you will notice
- The dashboard compares your own sessions before the plugin with those since. Page 1 splits this repository's days at the first day the plugin's logs show it running, compares weighted tokens per request on each side, and prices the chosen month at both rates. Page 4 shows the same figures. It used to price only what a local model read, so a repository that never used one showed nothing saved.
- A macOS in-place
sededit is no longer refused for what its script says. BSDsed -itakes the backup suffix as the next word; the empty one was read as the script and the script as a file. chamnan-scheduleanswers at once whatever spacing it is given. Five optional whitespace runs could all claim the same spaces; each now has one owner.
Under the hood
The new dashboard comparison is computed by the builder from the transcripts' own usage blocks, the same per-day sums every other panel uses, so it cannot disagree with them. The panel states what it cannot separate: anything else that changed between the two periods, such as the work or the model, is in the numbers too. A credential file given to sed as its real file argument is still refused.
Research
Between v1.36.0 and v1.36.1 no new research round was dispatched; 6 defects were fixed, 2 tied to an earlier round and 4 found by chamnan's own testing. The two defects in this patch came from re-running chamnan against other tools in its space with two new probes: every Python pattern chamnan compiles, fired at in CPython with 2,000-character whitespace runs (it finds four of the patterns 1.36.0 fixed when pointed at 1.35.1), and chamnan's real PreToolUse hook given 21 read and command events. The citations from code back to rounds are in the attached INDEX_CITED_IN_CODE.md.
Verification
| Checks | Gotcha |
|---|---|
| 6,988/6,988 | 2/2,587 — 2 recorded since v1.36.0, 2,587 in total |
6,988/6,988 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.9 and 3.13 — on the same code. Each fix carries a check that fails without it (520, 521, 522). The count went from 5,847 to 5,856 check( calls in the source: 3 sections added, none removed. The corpus gate passed again (608 files, 0 leaked) and the redactor is unchanged: recall 99.0%, precision 100.0%, boundary 97.9%.
Known limitations
- The command guard recognises a short list of commands that dump secrets; an indirect form — a script, an alias, another interpreter — is not stopped, and the guard does not redact what a command prints.
- The dashboard's before-and-since comparison needs sessions from before the plugin; a repository where every recorded session ran with it shows no figure, and says why.
- The redactor still misses a bare AWS secret access key with no name beside it (98 of 99 corpus shapes caught).
- Windows behaviour is verified on CI, not on a Windows machine here.
Upgrade
/plugin update chamnan. The dashboard picks up the comparison the next time it is built.
chamnan 1.36.0 — the agent's own reads meet the guard
This release is about what reaches the session: the agent's own reads and commands now meet the same guard chamnan's commands always had, the redactor recognises secrets in the forms text actually carries them, and a long run of whitespace can no longer stall a scan. 75 changelog entries across 41 files; each fix is recorded in the code with the research round or measurement that found it.
| What was measured | Before | After |
|---|---|---|
The agent's own Read, cat, grep, < file or PowerShell Get-Content of a real .env, a private key or a kubeconfig |
read into the session | refused before it runs |
| A shell history, an agent tool's sign-in file, an MCP or package-registry config that holds a token | read | refused, with chamnan-peek for the rest |
| AWS SSO and CLI caches, kubelogin, GKE and Azure token caches, Terraform state and saved plans | read | refused |
printenv with no name, git credential fill, docker compose config |
run | refused, with the narrower command that still works |
| A credential written through HTML or URL escaping, a private key with its BEGIN/END lines stripped | printed | redacted |
A YAML api_token: >- block value in a file saved with CRLF line ends |
printed, and passed the commit guard | redacted |
A session cookie pasted as curl -H 'Cookie: …', Set-Cookie: or a HAR entry |
printed | redacted |
A line of 2,000 spaces after [ or -, through the redactor |
6-12 s | milliseconds |
| One line of 2,000 spaces in a JavaScript or Dart file, through the map | 22-30 s | milliseconds |
chamnan-impact on a misspelt file name typed without its extension |
61 of 311 answered | 311 of 311 |
| Recall on words from a note's body, over synthetic known-item queries | MRR 0.834 | MRR 0.912 |
What you will notice
- A refused read says what to do instead. The agent is told how to hand the file to the program that needs it (
set -a; . ./.env,--env-file,ssh -i), or to usechamnan-peek, which shows a settings file with its token replaced. - Files that hold only settings stay readable. A
.npmrcwith just a registry, an.envrcwithuse nix, a.zshrcthat only setsPATHare read as before; the guard opens the file and refuses it only when something in it would be redacted. - Commands that print every secret at once are a short, named list — not a filter on what commands print.
printenv PATH,env FOO=1 <command>anddocker compose config --no-interpolatestill work. - The staged-secret warning now says how to take the value out, and to replace the credential if it was ever pushed.
- Recall finds a note by what it says, answers a misspelt Thai word with the whole word, and is no longer blind to a word with a zero-width space in it.
.geminiignoreand.claudeignorekeep a file out of the map, like.gitignoreand.cursorignore.
Under the hood
The agent's own reads. Refused when they really hold a secret: shell and REPL histories; Claude Code, Codex, Gemini, gh, Copilot and gcloud sign-in files; MCP server configs; npm, PyPI, pip, Yarn, Docker, Podman, Helm, Maven, Poetry, Bundler, NuGet and Composer registry configs; AWS SSO/CLI, kubelogin, GKE, Azure, Doppler, Bitwarden CLI and Vault token caches; Terraform state, variables and saved plans; direnv's .envrc and shell start-up files; a .git/config with a token in its remote; Git Credential Manager's plaintext store. Refused by what they are, whatever their name: cloud service-account keys, PuTTY .ppk keys, sops age identities, Firefox's key database, browser cookie jars, an editor's backup or swap copy of a credential file, and a credential file reached through a symlink. PowerShell's Get-Content meets the same guard as Bash's cat.
The redactor. Secrets are now redacted wherever they repeat after being found by name, inside a SQL dump, a cookie string or a URL query, when used as a URL's whole login, when split by an invisible joiner or across string literals, when written through HTML or URL escaping, right after an escape sequence (\n, %22), as a private key's DER opening without its armour, as a shell default (${TOKEN:-…}), in Makefile ?= and := assignments, in JSON carried as a string, and as a YAML block value on CRLF lines. HashiCorp Vault tokens, every GitLab token type, IFTTT and Zapier webhook URLs, an Azure publish profile's password and a Thai ID number labelled the usual way are recognised. Ordinary text stays as written: a word before an HTML closing tag, a sentence in Thai, Japanese or Chinese after a password word, a setting quoted in Markdown backticks, a count written with digit separators, a sentence inside a JSON string.
Reliability. Six patterns could hand the same run of spaces to several of their parts and try every split, so their cost grew with the cube of the run; each space now has one owner. A hook no longer waits on an input pipe the host left open, and on Windows a program it starts afterwards no longer hangs behind that pipe. A crashed process's lock is broken without removing a live one, and a write through a symlink keeps the link. A record written today reads "today" in time zones beyond +12. A JSON number longer than 4,300 digits no longer hides a file on Python 3.11+, and no file is opened without being closed.
Context & knowledge. Recall weighs a note's title three times its body instead of six; a misspelt bare file name is compared without its extension; front matter after a byte-order mark or with CRLF line ends is read; a symlinked file is mapped once; chamnan-peek reads UTF-16 files of any extension and says when a file is only a Git LFS pointer; a failed command is remembered once, by what actually went wrong.
Research
Between v1.35.1 and v1.36.0, 665 research rounds were dispatched and 6,650 findings read and given a verdict — tested, already there, or not worth it. 189 defects were fixed: 167 name the round that found them, 22 were found by the release gate itself. Every one is a 🐛 record at the line it was fixed on; the attached INDEX_CITED_IN_CODE.md links each to its commit.
Where the larger fixes came from:
- Measuring the guard on real traffic. Run over 18,354 real Bash, Read and Grep calls from the project's own sessions, the first version of the read guard refused 10, and 9 were false — a grep pattern read as a file, a heredoc line read as a command. Both were fixed before the guard shipped.
- A published static detector. Regexploit, run over every chamnan pattern, flagged seven as polynomial; six were real when timed on CPython, the seventh was correctly set aside. Those six are the whitespace fixes above.
- Public incident and scanner reports. 24,008 secrets found in MCP config files on public GitHub, and malware that enumerated agent CLI sign-in files, led to the sign-in and MCP refusals; a support-ticket HAR file used to hijack a session led to cookie redaction; GitGuardian's detection of base64-only private keys led to the armourless-key rule; VCR's warning that recorded values appear escaped led to the HTML/URL decoding; Gitleaks' handling of
?=and:=led to the Makefile rule; Docker's own warning aboutcompose configoutput led to its refusal; CWE-598 led to the URL-query rule. - Windows CI, at the release gate. The two Windows legs found two defects macOS and Linux could not: the CRLF YAML leak, and the inherited-stdin hang, traced with
faulthandleron a temporary runner.
Measured and not adopted: a query-matched snippet under each recall hit, because storing note bodies would make the index several times larger for every query; a SQLite index, because the JSON index loads in 15 ms at present sizes; and decoding arbitrary base64 to look for secrets, which widens false positives where a key's own opening bytes are enough.
Verification
| Checks | Gotcha |
|---|---|
| 6,979/6,979 | 173/2,585 — 173 recorded since v1.35.1, 2,585 in total |
6,979/6,979 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.9 and 3.13 — on the same code; the tagged commit adds only the README figures re-derived after the gate. The corpus gate (608 files, 98 extensions, 7 writing systems) planted credentials and found 0 in the index; redaction checks 52/52; redactor recall 99.0%, precision 100.0%, boundary 97.9%, unchanged. The count went from 5,644 to 5,847 check( calls in the source: 69 sections added, none removed — every fix above carries a check that fails without it.
Known limitations
- The command guard recognises a short list of commands that dump secrets; an indirect form — a script, an alias, another interpreter — is not stopped, and the guard does not redact what a command prints.
- A macOS in-place edit,
sed -i '' 's/…/…/' file, whose script names a credential file (… .env/) is refused as if it read that file. Fixed for the next release. - Chromium browser profiles are not refused: their values are encrypted with a key held by the operating system.
- The redactor still misses a bare AWS secret access key with no name beside it (98 of 99 corpus shapes caught).
- Windows behaviour is verified on CI, not on a Windows machine here.
Upgrade
/plugin update chamnan. The recall index then reports itself as behind, because its folding changed; chamnan-recall --reindex rebuilds it.
chamnan 1.35.1 — credentials where the name and the value sit apart
This patch closes the gaps 1.35.0 listed as known limitations and a handful found since: credentials held where the name and the value sit apart, files that are credentials by their name alone, and text that reached the model without anyone reviewing it seeing it.
| What was measured | Before | After |
|---|---|---|
| First redaction in a process, on text naming no secret word | 55 ms | 8.5 ms |
A kubeconfig user's random token: |
printed | redacted |
A value under a TOML/INI section named for secrets ([secrets], [api_keys]) |
printed | redacted |
| A value defined at a YAML anchor and used through an alias under a password key | printed | redacted |
An Azure parameters file's adminPassword value, a .NET "Value" member |
printed | redacted |
| A bearer token or API key in a Postman collection or a Bruno request file | printed | redacted |
A .env, prod.env, Google client_secret*.json or kubeconfig opened by a printing command |
read | refused unread |
A hidden <!-- … --> in STATE.md, a rule or a skill |
injected | stripped |
| Jupyter notebooks in the map | listed as unindexed | code and markdown cells indexed |
What you will notice
- Credential files are refused unread.
chamnan-peekand the other printing commands decline a real.env(.env.local,prod.env…), a kubeconfig or a Google OAuth client file, as they already declinedcredentials.json..env.example,.sample,.templateand.distare still read, and the environment catalog still lists a.env's variable names without ever capturing a value. - More credentials are caught where the name and the value sit apart — under a secret-named TOML/INI section, behind a YAML anchor, inside a kubeconfig's
users:, under a camelCase name'svaluemember, and in an API client's auth block. Ordinary values next to them (a user name, a host, a port, a{{variable}}) stay as written. - Hidden HTML comments no longer reach the model. A comment renders as nothing, so a person reviewing the file never sees it; chamnan now strips it from everything it injects. File names in the "Last edited" line now sit inside the fence that marks repository text.
- A
.chamnanthat links outside the repository is refused, not followed — one line from every command, silence from every hook, nothing read or written through it. - A typo gets a did-you-mean in
chamnan-impactandchamnan-recall. - Notebooks are indexed from their code and markdown cells; outputs are never read.
- Python 3.9 is the floor. 3.8 can no longer be tested on GitHub's runners; CI runs 3.9 and 3.13.
Research
Every finding screened since 1.35.0 ends in a verdict — tested, already there, or not worth it. The citations from code back to rounds are in the attached INDEX_CITED_IN_CODE.md.
Measured and not adopted: a bare @token = variable in a .http file is left, because a bare token names nothing outside an auth block and redacting it would cost precision; a post-build artefact scan, because chamnan ships its git tree and the pre-push scan reads exactly that tree.
Verification
| Checks | Gotcha |
|---|---|
| 6772/6772 | 16/2,411 — 16 recorded since v1.35.0, 2,411 in total |
6772/6772 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.9 and 3.13 — on the same commit. The corpus gate (608 files, 98 extensions, 7 writing systems) planted credentials and found 0 in the index; redaction checks 52/52; redactor recall 99.0%, precision 100.0%, boundary 97.9%, unchanged. The count went from 5,618 to 5,644 check( calls in the source: 11 sections added, none removed.
Known limitations
- The redactor still misses a bare AWS secret access key with no name beside it (98 of 99 corpus shapes caught).
- A Spring key written in a relaxed kebab spelling of the secret word itself (
pass-word) is not recognised; no real configuration was found using it. - Python 3.8 may still work but is no longer claimed.
- Windows behaviour is verified on CI, not on a Windows machine here.
Upgrade
/plugin update chamnan. Nothing to do afterwards.
chamnan 1.35.0 — protection and context that hold in real-world text
This release makes chamnan's protection hold in the shapes real text actually takes — commands in shell history, infrastructure state, keys cut off mid-file, names typed in another Unicode form — and makes its heavier steps cheap enough to stop noticing, rather than adding new features.
| What was measured | Before | After |
|---|---|---|
| Refreshing a 584,000-character map after a one-file edit (scrub step) | 5.9 s | 0.02 s |
| Editing a requirements.txt with 200 past revisions (removed-package check) | 18.9 s | 0.15 s |
Scrubbing one line of 8,000 repeated key- |
27 s | 0.94 s |
chamnan-recall median query |
90-100 ms | 52-55 ms |
curl -u user:PASS, mysql -pPASS, sshpass -p, docker login -p, ConvertTo-SecureString '…' -AsPlainText |
printed | redacted |
A private key cut off before its END line (a capped read, head, a truncated paste) |
body printed | redacted |
Terraform plan variables and state outputs ("db_password": {"value": …}) |
printed | redacted |
chamnan-impact café.py typed in the other Unicode form than the file was created in |
"nothing recorded" | found |
| An instruction file that quotes one path outside the repository | drift check silently off for the file | checked |
Together they say one thing: the protection and the context hold in the shapes text really arrives in, and the steps that ran on every edit no longer cost seconds.
What you will notice
- Secrets in commands are caught. A password passed as a flag or positional to
curl,mysql,sshpass,docker/helm login,redis-cli,smbclient,htpasswdor PowerShell'sConvertTo-SecureStringis redacted — the shapes shell history and pasted commands carry.ssh -p 2222,mkdir -pand$VARreferences are left alone. - Private keys are redacted in every form tried, including a PEM block cut before its end, a PuTTY
.ppk, and a JSON Web Key's private members. Public keys and certificates are untouched. - Terraform plans and state are safer to map. A value Terraform stores under a secret-named object — every input variable in a plan, every output in a state,
sensitiveor not — is redacted. - Accented and non-Latin file names are found however they were typed. A file created with a decomposed name (as macOS keeps some) is found by
chamnan-impact, the file pointer and the history join whether you type it composed or decomposed. - The map refreshes quickly after a small edit, and editing a dependency manifest with a long history no longer stalls.
chamnan-recallfinds a note whatever the case or accents, and the automatic pointer stops offering notes that match only common words.- Repeated advice is capped at two notices a session per kind, and
AGENTS.mdis no longer rewritten by the pre-commit hook when nothing it describes changed.
Most of the rest is deliberately invisible on the happy path; its purpose is to make unusual text and unusual setups fail less often.
Under the hood
Security
- Command-anchored rules for passwords given as flags or positionals; a bare
-p(a port, or "parents" to most tools) is never touched. - A PEM block with no
ENDline is redacted through its body, stopping at the first line that is not key material; PuTTYPrivate-Lines:and JWKd/p/q/dp/dq/qi/kinside an object withkty. - The
"value"member inside a secret-named object; header-style names (X-Api-Key:) open the list rules; a secret split by a shell line continuation is redacted on both lines; stripping invisible characters can no longer reassemble a credential; an uncaught traceback is scrubbed before it reaches stderr. - The commit guard also guards commits made through the PowerShell tool, scans every staged file git calls binary (including BOM-less UTF-16 and the string literals of
.pycfiles), says so when it runs out of time, and names the commits only the reflog still holds. - Injected lines lose Unicode tag characters and invisible operators; a commit subject quoted to the agent is fenced as repository text.
Performance
- The map re-scrubs only the sections an edit changed (5.9 s → 0.02 s on a 584,000-character map).
- A manifest's history is read with one batched git process (18.9 s → 0.15 s on a 200-revision fixture).
- Every secret-word name run is bounded, so a long value after a secret word scrubs in linear time (8,000 characters of
key-: 27 s → 0.94 s). - Recall stems each query term once (90-100 ms → 52-55 ms, identical results);
subprocessis no longer imported by the two hooks that run on every Bash call; the session start readsHEADoff.gitinstead of spawning git.
Reliability
- One quoted path outside the repository no longer switches off the instruction-file drift check for the whole file.
- Locks record their host, so another machine's live lock is not broken; a sync client's conflict copies are not read as notes; git's "dubious ownership" refusal and the file git will not keep are named with their fixes.
- A Python file chamnan cannot parse keeps its docstring and top-level names; a source file that declares a legacy encoding keeps its summary; a capped preview never ends in half a character.
- Shallow clones, sparse checkouts and linked worktrees of shallow clones are recognised as what they are.
Cross-platform
- Path lookups compare both sides in NFC and answer with the spelling on disk; an NFD-named module keeps its used-by edge; non-ASCII commit text arrives intact whatever
i18n.logOutputEncodingsays; a stamped date ages by the calendar beyond UTC+12.
Context and knowledge
- Recall folds case and Latin, Greek and Cyrillic accents (Thai untouched), drops words most notes share from scoring (MRR 0.727 → 0.780 on title words), and rebuilds an index scrubbed by an older redactor.
- The map honours AI-ignore files and lists the test and build commands a repository declares;
chamnan-impactshows why a file last changed;chamnan-wheresees__all__, entry points and Thai or differently normalised identifiers.
What we noticed on the way
- A redaction that announced itself and missed. When a key file was read only in part, the redactor replaced the
BEGINline and printed the body under it — a<REDACTED>saying the key had been handled, over the key. Every capped reader in the package could produce that input. - A drift check one path could turn off. The instruction-file check asks git about every quoted path in one call, and git refuses the whole call if any path lies outside the repository. Instruction files routinely quote
/var/log/…or../shared/…, so the check reported nothing for exactly the files people maintain most. - The same file under two names. APFS keeps a name in the form it was created; git and the keyboard hand over the other. Each was internally consistent, and the lookup between them compared bytes.
Research
Every research finding screened for this release ends in a verdict — tested, already there, or not worth it — and the tests led to most of the fixes above. The citations from code back to rounds are in the attached INDEX_CITED_IN_CODE.md; python3 .chamnan/tools/research_citations.py --write regenerates it.
Measured and not adopted: starting hooks with python3 -S — 1.5 ms of a 120 ms hook, within noise; shipping chamnan as a single zipapp — the outside measurement found a warm zipapp 2.76× slower than a directory layout; a near-duplicate detector for notes — on a real store of a few dozen notes no pair came within reach of any published threshold; indexing notebook outputs — a 400 KB notebook's base64 never reaches the map, which lists notebooks as unindexed source.
Verification
| Checks | Gotcha |
|---|---|
| 6746/6746 | 152/2,395 — 152 recorded since v1.34.0, 2,395 in total |
6746/6746 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.8 and 3.13 — on the same commit. The corpus gate (608 files, 98 extensions, 7 writing systems) planted credentials and found 0 in the index; redaction checks 52/52. The count went from 5,401 to 5,618 check( calls in the source: 68 sections added and three shortened by six calls, where functions that only their own tests called were removed.
Known limitations
- The command-flag rules cover the named tools; a password passed positionally to a tool not on the list is not recognised by shape.
- The redactor still misses a bare AWS secret access key with no name beside it (98 of 99 corpus shapes caught), and a kubeconfig
token:with a random value. - Notebooks are listed as unindexed source; their code cells are not read.
- Windows behaviour is verified on CI, not on a Windows machine here.
Upgrade
/plugin update chamnan. Nothing to do afterwards; a recall index built before this is rebuilt on the next chamnan-recall --reindex.
This release is about protection and context holding in the shapes text really arrives in.
chamnan 1.34.0 — the unusual setup behaving like the ordinary one
This release makes chamnan harder to break and more careful with what it reads — on Windows, under odd clocks and file names, and around secrets — rather than adding new features.
| What was measured | Before | After |
|---|---|---|
| Mapping a generated 1.47 MB Python module full of f-strings (Python 3.14) | 134 s | skipped with a reason |
A failing command run through its Windows .cmd shim (py launcher installed) |
exit 0 | the command's own exit code |
| NuGet, Cloudflare and Supabase secret keys standing alone in text | passed through | redacted |
| Redacting a 543 KB file | 9.1 s | 6.3 s |
chamnan-recall scoring 297 queries |
88 ms | 49 ms |
chamnan-recall --reindex when nothing changed |
4.4 s | 0.14 s |
| Command-log records kept, 6 sessions × 300 writes (macOS/Linux) | 843 of 900 | 900 of 900 |
| Writes after a crashed process left a lock and the clock moved back | each gave up after 2 s | lock recovered at once |
| Time Claude Code allows chamnan's end-of-session hook | 1.5 s | 10 s |
Together they say one thing: the unusual setup — a Windows machine, a generated file, a clock that jumped, a key in an odd place — now behaves like the ordinary one.
What you will notice
- On Windows, a failing chamnan command fails. Every
.cmdshim returned 0 on machines with thepylauncher, sochamnan-guard --strictcould never stop anything there. - On Windows, scripts keep their language in the map. A CRLF first line kept its
\r, so every#!script in a Windows checkout was indexed with no language. - More secret shapes are caught on sight — NuGet keys, Cloudflare's 2026 tokens and Supabase secret keys — and a secret named in one field and held in a sibling field (
{"name": …, "value": …}, XMLname/value) is redacted. - Token counts are no longer redacted as secrets, and a redacted JSON value keeps its comma, so the text still parses.
chamnan-guard --historychecks every branch and tag, and says how many commits it actually read.- The end-of-session digest is no longer cut off by Claude Code's default hook time.
- Files with Thai, accented or unusual names are named correctly everywhere chamnan reads git: churn, the hand-off line, dependency history, drift and the commit guard.
- Claude Code no longer receives chamnan's block twice when it also reads a generated
AGENTS.md.
Most of the rest is deliberately invisible on the happy path; its purpose is to make unusual setups fail less often.
Under the hood
Security
- New provider rules (NuGet
oy2…, Cloudflarecfk_/cfut_/cfat_, Supabasesb_secret_); secrets held in sibling fields; the guard scans the whole of every added line, including text after a form feed or U+2028. - A symlink waiting at a staging file's name is no longer followed;
chamnan-contexterrors pass the terminal filter. - Precision: plural
*_tokenscounts are left alone, and five bare-value rules no longer swallow a trailing JSON comma.
Reliability
- Every git path reader either uses
-zor reverses git's quoting, and a check derives the set so a new reader cannot skip it. - A lock is judged abandoned by its distance from now, so a future-dated lock left by a dead process is recovered.
- A Python module with more than 5,000 f-strings is listed without being parsed, because parsing is quadratic in f-strings on Python 3.12+.
- Log records survive Unicode line separators; appends are locked on every platform; a corrupt store copied aside twice in one second keeps both copies; commands started in a deleted folder print a sentence.
- A scheduled resume that can no longer fire is announced; a rule check with a backtracking pattern is refused.
Performance
- Redaction about a third faster on large files; recall scoring about twice as fast;
--reindexreuses unchanged notes; the session start filters the tree once (1.69 s to 1.16 s for that step on 816 files); the tree walk sorts by a key computed once.
Cross-platform
- Windows shims read the exit code after the script runs; a CRLF shebang is read;
chamnan-contextrefuses a path Windows cannot represent instead of crashing; saves wait up to about 1 s for a scanner holding a file; a repository path with an accented letter is recognised in either Unicode form; two names macOS stores as one are warned about as one.
Context and knowledge
"subagent_pointer": falseworks; a full Claude model id gets its family's window;envshebangs with options are read; dependency tables such as[dependencies.serde_json]are read; two more generator headers are recognised.
What we noticed on the way
- A Windows shim that answered before the question. cmd.exe expands
%errorlevel%when it reads a parenthesised block, before the command inside runs, so the shim always returned the value from itswhere pyprobe. CI only ever ran shims on commands that succeed; it now runs one that fails. - A lock that could never grow old. Abandonment was judged by
now - mtime; a lock dated in the future had a negative age, so its dead holder was never checked. - A parse that got slower the more f-strings a file had. A 71 KB generated module parsed in 0.42 s and a 1.47 MB one took 134 s on Python 3.14 — inside both of the map's size limits.
Research
Every research finding screened for this release ends in a verdict — tested, already there, or not worth it — and the tests led to most of the fixes above. The citations from code back to rounds are in the attached INDEX_CITED_IN_CODE.md; python3 .chamnan/tools/research_citations.py --write regenerates it.
Measured and not adopted: marking every word of injected repository text (datamarking) — about a third more tokens on every session; starting hooks with python3 -S — about 4 ms per hook once re is counted; loading rules only for the paths being edited — almost every rule here is repository-wide; skipping cloud-placeholder files — none could be reproduced on the test machine.
Verification
| Checks | Gotcha |
|---|---|
| 6536/6536 | 115/2,243 — 115 recorded since v1.33.0, 2,243 in total |
6536/6536 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.8 and 3.13 — on the same commit. The corpus gate (608 files, 99 extensions, 7 writing systems) planted credentials and found 0 in the index; redaction checks 52/52. The count went from 5,150 to 5,401 check( calls in the source: 53 sections added, one resized, and one removed — the section for a mixed-script function deleted because nothing called it.
Known limitations
- The Windows shim fix is verified on CI, not on a Windows machine here.
- The f-string limit counts openings, an upper bound; a file past it is listed with its reason, not parsed.
- The redactor still misses a bare AWS secret access key with no name beside it (98 of 99 corpus shapes caught).
Upgrade
/plugin update chamnan. Nothing to do afterwards; on Windows, the regenerated shims arrive with the update.
This release is about the unusual setup behaving like the ordinary one.
chamnan 1.33.0 — finding what you stored, and saying only what is true
This release makes chamnan find what you stored more reliably, and stops it saying things that are not true — rather than adding new features.
| What was measured | Before | After |
|---|---|---|
| Thai phrase search, right note ranked first | 85.3% | 95.1% |
| English word search, right note ranked first | 39.7% | 46.1% |
Notes found by boundaries among the 14 that say boundary/boundaries |
3 | 14 |
| Dependency names read correctly from real TOML manifests | 17 of 35 | 34 of 35 |
| Sessions opening with "the last block was cut" after one real cut | every session | none |
| Ordinary code removed by the redactor beside a secret word (corpus) | 100 of 304 removals | 33 |
| Logging one Read in the file-pointer hook | 19 ms | 1.3 ms |
Together they say one thing: what you wrote down comes back when you ask for it, and what chamnan tells you is accurate.
What you will notice
- Search finds your notes more often. Rare words and phrases outrank common ones, other forms of a word are found (
boundariesfindsboundary), Thai words containing ำ match, and a deleted note leaves the results at once. - The false "last block was cut" line at the top of every session is gone.
- Environment settings are visible.
chamnan-report --fullandchamnan-doctorname anyCHAMNAN_*variable in force. - Scheduled times keep their hour across a daylight-saving change.
chamnan-peekon a font-encoded PDF says it needs a PDF text tool instead of printing glyph codes as text.- An agent re-adding a package your repository removed is told which commit removed it.
Most of the rest is deliberately invisible on the happy path; its purpose is to make unusual setups fail less often.
Under the hood
Search and the map
- Query words and non-Latin phrases are weighted by inverse document frequency; other indexed forms of a word match at half weight, so exact matches still rank first.
- Hits whose file is gone are dropped and counted in the reindex notice.
- An
Owner:header is not used as a file's summary; a summary with replacement characters is dropped; UTF-16 source files are indexed; more environment reads are listed.
Reliability
- A store that cannot be parsed is copied aside as
<name>.corrupt.<time>before the next write, in every shared writer that parses JSON. - A wall-clock appointment takes the UTC offset in force on the day it fires.
- The session block's "cut" flag matches only the hook's own exception sentence.
- A crashed hook is recorded; no home directory is not a crash; a stray
.chamnanor.gitin the home folder no longer claims the folders under it; a misspelt config key is named and kept.
Security
- The redactor keeps argument labels, fields and type annotations beside secret words, and no longer reads "author" as "auth". Recall on the corpus benchmark is unchanged.
Dependencies
- Cargo dotted keys (
serde.workspace = true) are one package; a#or]inside a quoted TOML string is not syntax.
The dashboard and cross-platform
- Print, keyboard and screen-reader fixes, readable small text in both themes, UTF-8 on Windows, and a build that skips one bad log line. The README's Windows PATH step is safe to paste; skills with a byte-order mark are read correctly.
What we noticed on the way
- A warning that kept itself alive. The detector for a cut block looked for the words "stopped early", and the warning it printed says "the last block stopped early" — so each session's block carried the evidence of the previous one's cut, indefinitely.
- A reader that read everything except the text. Glyph codes from a macOS PDF are printable characters, so they looked like text; what gave them away was that almost every string was one character long.
Research
Every research finding screened for this release ends in a verdict — tested, already there, or not worth it — and the tests led to most of the fixes above. The citations from code back to rounds are in the attached INDEX_CITED_IN_CODE.md; python3 .chamnan/tools/research_citations.py --write regenerates it.
Measured and not adopted: following links between notes to widen search (0 of 24 misses would have been found); starting hooks with python3 -S (no gain on the real hooks); a default of 10 search results instead of 6 (93.6% vs 89.0% found, for four more lines on every query); typo-tolerant search (the queries come from sessions, which do not mistype).
Verification
| Checks | Gotcha |
|---|---|
| 6221/6221 | 88/2,127 — 88 recorded since v1.32.0, 2,127 in total |
6221/6221 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.8 and 3.13 — on the same commit. The corpus gate (608 files, 99 extensions, 7 writing systems) planted credentials and found 0 in the index; redaction checks 52/52. check( calls went from 4,983 to 5,150: 46 sections added, none removed.
Known limitations
- The word-form rule is a small English suffix list, not a stemmer; irregular forms are not joined.
- A font-encoded PDF is reported, not read.
- Windows behaviour is verified on CI only.
Upgrade
/plugin update chamnan. Nothing to do afterwards; notes changed since your last chamnan-recall --reindex are flagged by the command itself.
This release is about finding what you stored, and saying only what is true.
chamnan 1.32.0
This release makes what chamnan already does behave correctly where people actually use it — your own git setup, your time zone, your machine, very large or very odd files — rather than adding new features.
| What was measured | Before | After |
|---|---|---|
git add beside a hook's git status, 150 tries |
68 failed on index.lock |
0 failed |
Git settings a session opened by chamnan-open inherited from chamnan |
23 overrides (hooks off, credentials refused) | 0 — yours |
| A rule check over one minified 80,000-character line | 34 s at session start, and it said "holds" | 11 ms, "not checked" |
| Redacting a 200 KB base64url value | 16.0 s | 3.2 s |
| A 2-hour scheduled resume after the machine moves from +07:00 to +09:00 | fired after 1 hour | fires after 2 hours |
| Commands the dashboard showed for a busy day | 316 (a log's daily cap) | 1,623 (every Bash call) |
| Tokens counted twice after a rewind | +3.1% | 0 |
chamnan-where on a 1,194-file repository |
3.74 s | 3.13 s |
Bash PreToolUse hook |
246 ms | 153 ms |
Together they say one thing: the plugin stays out of the way of the work beside it, and the numbers it shows you are the real ones.
What you will notice
git push, your hooks and signed commits work in sessions chamnan opens for you. For its own reads chamnan switches off repository-supplied programs — hooks, credential helpers, ssh commands, signing. Sessions started bychamnan-openandchamnan-scheduleused to inherit all of that. They now start with your environment exactly as it was.- Your own
git addandgit commitno longer fail because a hook was reading. A read-onlygit statusstill takesindex.lockfor a moment; chamnan's git reads now skip that optional lock. - A session no longer stalls on a repository with minified or bundled files that a
**Check:**rule covers. - The dashboard is right, and it speaks your time zone. It builds its lesson index itself, counts commands from the session transcripts, counts each request once, puts UTC-stamped events on your local day, keeps history after Claude Code deletes old transcripts, and has a time-zone menu — your browser's zone by default, with half- and quarter-hour zones handled.
- Scheduled resumes keep their time when you travel or the clocks change.
AGENTS.mdmade on Windows keeps its line endings and byte-order mark, and a file with two chamnan regions is refused instead of half-updated.- A
MAP.mdmerge conflict tells you the fix — rebuild withchamnan-map, thengit add. chamnan-where … | headno longer ends inBrokenPipeError.
Most of the rest is deliberately invisible on the happy path; its purpose is to make unusual setups fail less often.
Under the hood
Git, as a guest in your repository
- Every git process chamnan starts runs with
GIT_OPTIONAL_LOCKS=0unless you set it yourself. Measured with a status loop beside 150git adds: 68 failures before, 0 after, andgit statustook the same time. workspacenow snapshots your environment before changing anything for its own reads, and the two places that start a program on your behalf (chamnan-open,chamnan-schedule) hand that snapshot back. Before: 24GIT_*variables reached a scheduled session and 23 reached achamnan-opensession.- The lesson index's new
git ls-filesasks first whether git can speak for the directory, like every other git call. The README's Git paragraph now lists 31 call sites serving 23 read-only purposes.
Performance
- Redactor: each secret word found inside a long identifier re-walked the whole run. A 200 KB base64url value: 16.0 s → 3.2 s; 50 KB: 1.4 s → 1.0 s. Hit spans identical on 3,209 inputs.
- Rule checks: a file with a line over 2,000 characters is not searched. A one-quantifier pattern took 21.2 s on an 80,000-character line; the worst allowed pattern takes 12 ms at 2,000.
chamnan-where: 3.74 s → 3.13 s on 1,194 files, identical answers on 15,772 symbol–file pairs.- Bash
PreToolUsehook: 246 → 153 ms. Redactor import: 41.7 → 15.9 ms.
The dashboard
- The lesson panels read an index nothing in the plugin wrote; it is now built on every dashboard build, from what git lists — including scripts with no file extension, large files and nested repositories, and skipping generated indexes, workspace logs and byte-identical copies.
- Commands are counted from transcripts (a log capped at 300 a day was showing the cap); each request is counted once even when a rewind writes it again (3.1% of tokens were double); UTC stamps land on the local day; feature rates compare counts from one window; bar charts share one track; a deleted transcript keeps its days until the one-year window passes.
- Counts are kept in 15-minute UTC slots and the page adds them up in the chosen zone: in the build zone, 0 of 400 figures differ from the previous per-day numbers.
Reliability and cross-platform
AGENTS.md: CRLF and BOM kept; two regions refused. Schedules: stored as instants with their offset. Every command exits quietly into a closed pipe. Hooks run by hand no longer wait forever on a terminal. A half-written transcript line is read next time, not lost. The atomic writer fsyncs before it renames. Full-width:and=count as separators.*TestCase.javais recognised as a test.
What we noticed on the way
- The biggest defect of the release was found while screening research about output formats, not by the research itself: tracing what a scheduled command receives showed it inherited every guard chamnan sets for its own reads. The sibling that execs a session had the same leak — and both were turned into tests before any code moved.
- The dashboard audit recomputed every figure from the raw logs. Three of its panels were drawing a log's retention cap, a UTC offset or a double count, not the thing they were named for.
- A rule the checker allowed took 34 seconds and then reported success. Bounding the pattern was never enough; the input line is what a backtracking engine pays for.
Research
Rounds screened for this release each end in a verdict per finding — tested, already there, or not worth it — and the tests led to the fixes above. The citations from code back to rounds are in the attached INDEX_CITED_IN_CODE.md; python3 .chamnan/tools/research_citations.py --write regenerates it.
Measured and not adopted: parsing the dashboard's inline data through JSON.parse (3.75 → 3.56 ms, not worth a change); batching git calls at session start (10 spawns, about 0.3 s of a 2 s start); a union merge driver for MAP.md (it would leave a silently wrong map); GPU acceleration (chamnan does no numeric work — its time is text, files and git).
Verification
| Checks | Gotcha |
|---|---|
| 6055/6055 | 16/2,039 — 16 recorded since v1.31.2, 2,039 in total |
6055/6055 on the release gate (macOS), and CI green on all five legs — macOS · Linux · Windows on Python 3.8 and 3.13 — on the same commit. The corpus gate (608 files, 99 extensions, 7 writing systems) planted credentials and found 0 in the index; redaction checks 52/52. The check branch caught one thing on the way, fixed before the tag: on Windows with Python 3.8 a closed pipe is reported as Errno 22, so a command piped into head still exited 120 there.
Why the check count went down
The totals were 6,138 in 1.31.1, 6,104 in 1.31.2 and 6,055 now. Nothing was switched off to get there, and every section that left is accounted for below.
- 1.31.1 → 1.31.2 (−34). The checks that need key-shaped test data moved to chamnan-corpus, because the plugin must not carry fake keys. They still run there.
- 1.31.2 → 1.32.0 (−49). In the source,
check(calls went from 5,024 to 4,983. Twenty-one sections left (−99) and twenty-six new ones arrived (+61). The twenty-one, one by one:- 19 sections tested this project's own development tools, which are not part of the plugin and do not ship. They moved out of the public suite into the development workspace. Each file keeps the same number of checks, and they still run there.
- 1 section did not leave. Its heading was reworded, so the census counts it as one section removed and one added. It still has the same 12 checks.
- 1 section (12 checks) was deleted with the code it tested.
chamnan-recallused to look for two files that exist only in this project's own workspace. That lookup is gone, so there is nothing left for those checks to test.
Known limitations
- The redactor still removes some ordinary identifiers that sit beside a secret word (about 255 of 616 removed spans on the corpus benchmark); fixing it without losing recall is next.
- Windows behaviour is verified on CI only.
- The feature-rate denominators on the dashboard still come from a command log capped at 300 a day on busy days.
Upgrade
/plugin update chamnan. Nothing to do afterwards; the dashboard rescans transcripts once under its new cache key.
This release is about behaving well in other people's setups, and showing numbers that are true.
chamnan 1.31.2 — counting honestly, and research that earned its way in
Fixes and measured improvements on top of 1.31.1 — the full list is in CHANGELOG.md.
This release is about counting honestly, and about research that had to earn its way into the
code.
The dashboard was counting every token about twice. Claude Code writes one transcript record
per content block, each repeating the response's usage, and the page added them all. It now counts
each response once, reads only the sessions a person worked on the account in use — including
sessions started in a subdirectory, which it used to miss, and leaving out sessions a script
started — and every panel on the first page describes the same period in the same unit. A new
panel ranks the five things that filled the context, and it adds up, to the token, to what was
actually written: on the repository it was built on, the largest row turned out to be context
rewritten after the cache expired, which no transcript shows.
Every finding from outside research now leaves with a verdict — test it, already there, or not
worth it — and the tests found real defects. A property test (scrubbing twice must equal
scrubbing once) found the redactor damaging an ordinary line of code that followed a line ending
in ..._key:. The pointer was naming a thread closed weeks earlier — the most-named entry in its
log, and in 35 of 56 appearances the only thing named. A config value of the wrong type was
silently replaced by the default on the next session, which could start deleting logs. A new
session now hears which files were committed after the old one stopped, and /chamnan:review
names a change too large, or too tangled, to review whole.
The plugin carries no key-shaped test data. The checks that need a credential's shape, and the
recall benchmark with its tables, moved to chamnan-corpus,
where key-shaped data belongs; the checks that stay here use plain values.
Fixed and improved
-
The session block holds still between firings, so it does not cost a cache write. "Last edited" counted minutes when the last change was under an hour old, so the block changed every minute and a second firing of the same session re-wrote the whole cached block. Under an hour it now says
<1h. -
A config value of the wrong type is kept, and named — no longer replaced by the default. Writing
"log_retention_days": "30"(a quoted number) used to be overwritten with 7 on the next session, silently, which began deleting logs the user meant to keep. The file is now left as written, the session says which setting is being ignored and what it wants, and a config that does not parse says the line and column. -
The dashboard's notes are held to a readable line length. On a wide panel a note ran to about 130 characters; measured comprehension peaks near 55 and long lines read slower. Notes stop at 78 characters and source lines at 90, as the headline's sentences already did.
-
/chamnan:reviewchecks the size of a change first. Above about 400 changed lines it says so and reviews in parts: measured defect detection falls from 87% on 1-100 lines to 28% above 1,000, and a review of a large change finds less while reading as complete. A change that mixes a rename or move with a behaviour change is named too, and the behaviour reviewed on its own. -
An ordinary line of code after a line ending in
..._key:is no longer redacted. A Pythonif … != last_user_key:made the redactor treat the NEXT line's assignment as that key's value, sostate["gap_ping_anchor"] = last_user_keyreached the index as<REDACTED>— and a second pass then redacted the dict key too. Found by a property test: scrubbing twice must equal scrubbing once. Recall and precision on the corpus benchmark are unchanged (98/99, 100%, 0/48 damaged). -
A new session is told what moved while it was away. When
chamnan-openstarts fresh, the handoff now names the files committed after the old conversation's last response — by another session or by hand — so the new one does not work from a picture of the repository that is already out of date. Nothing is added when nothing was committed. -
A closed thread is no longer named when you open a file it lists. The pointer treated a finished thread like live knowledge: on the repository it was built on, the most-named entry in 24 days was a thread closed weeks earlier, and in 35 of its 56 appearances it was the only thing named. Threads whose status is closed are skipped; on that repository's 136 plugin files the only change is that thread disappearing.
-
The dashboard counts the sessions you worked, and shows what filled them. Its totals now come from one account's interactive sessions — including sessions started in a subdirectory, which it used to miss — and leave out sessions a script started with
claude -p, which on the repository it was built on were a third of September. A new panel ranks the top five things that went into the context in the chosen period: the model's own output, each tool's results, hook and reminder context, the host's prompt, and what you typed. -
The dashboard counts each response once. Claude Code writes one transcript record per content block — a thinking block, a text block and each tool call of one response are separate lines, each repeating the response's usage — and the dashboard counted every line. On the repository it was built on that was 35,356 requests counted against 17,569 real ones, so every token total was about double. A record repeating the previous response's request id is now skipped, including across an incremental read, and the scan cache is re-read once under a new key.
chamnan-reportalready kept one record per request and was not affected. -
The plugin carries no key-shaped test data. The checks that need a credential's shape, and the recall benchmark with its tables, moved to chamnan-corpus; the checks kept here use plain values.
Verification
6104/6104 checks on the release gate (macOS, with the live workspace), and CI green on all five legs — macOS · Linux · Windows on Python 3.8 and 3.13 — on the same commit. The check branch found seven things on the way, all fixed before this: a session-expiry test that passed only before noon UTC, a bare next(), the dashboard missing every Windows path and any path with a dot or a space, a byte offset shifted by Windows line endings, a concurrency harness decoding as cp1252 with a 30-second timeout, and the session block's "Last edited" line changing every minute — the last one a real cache cost for users.
Install or update: claude plugin marketplace update chamnan && claude plugin install chamnan@chamnan
1.31.1 — a tool that can show you it worked
1.31.0 was never published: this is its first release, with what was found on the way out.
This release is about the difference between a tool that works and a tool that can show you it
worked. Four pages of your own numbers, a doctor that says whether the install is actually wired
up, and a corpus of a hundred ways a context tool can cost the person who installed it — which is
how five of the six fixes below were found; the sixth was reported by somebody using it.
Found and fixed on the way out
- A new conversation starts where the old one stopped. When
chamnan-openstarts fresh while
an earlier conversation exists — a new date and a night's gap, or--fresh— it writes
.chamnan/logs/handoff.mdfrom the earlier transcript's last typed messages (redacted, harness
turns left out) and opens the new session with one instruction: read that andSTATE.md, then
carry on. The old transcript is kept and named, so it can still be resumed. chamnan-open --freshnow does what it says. The flag was parsed and never read, so it
resumed the old conversation whenever the rule said to.- A filter a repository names in its own config no longer runs.
.gitattributescan name a
clean/process filter whose program sits in the repository's.git/config;git statusran it
whenever a file's stat info changed, which is every session start in a repository somebody is
working in. Drivers set in a repository scope are stood down; your own global ones (git-lfs) are
left alone. - The dashboard is yours, and stays in your repository. It is built at session end into
.chamnan/statistic/of the repository you are working in, which the workspace gitignores. It
used to be written into the plugin's own directory, shared by every repository on the machine,
and it looked for your repository in the wrong place on any install but the developer's. chamnan-doctorno longer runs scripts from the repository it is pointed at. It ran two
workspace tools that do not ship with chamnan from whatever checkout it was asked about.- Two guards were silent on Windows. The warning about writing outside the repository skipped
every~/…path there (expanduserran before the shape test), and the notice about reading one
file slice by slice never counted a Windows path (POSIXshlexate the backslashes). Both found
by the Windows CI legs on the way to this release. - The index no longer describes your dashboard as source. With the dashboard built into
.chamnan/statistic/, the nextchamnan-mapindexed its pages; that directory is now skipped by
path, and only under.chamnan/. - The plugin carries no fake credentials. Its redaction test cases moved to
chamnan-corpus 1.1.1 (redaction/), where
key-shaped test data belongs; the suite no longer depends on another repository.
You can now see what it cost, on your own numbers
Four pages are built out of your workspace's own logs, into your repository's own gitignored .chamnan/statistic/ at the end of every session: what the plugin
changed about a period's token weight, which features fire and how often, what the redactor caught
and what no pattern can, and an editor for the weights. A fresh clone opens to greyed panels
that name the file each one reads — nothing is sampled, nothing is estimated, and no sample
dataset ships to make the pages look populated.
The top line shows its working, because it is the one worth arguing with: the bars start at the
common floor rather than at zero and say so, the saving is weighted the way a cache write is
weighted, and the 25% that turns "characters a local model read" into "tokens saved" is named as
a judgement, not a measurement, with the page that replaces it. No money and no model names
appear anywhere, because the plugin does not know which model you run.
chamnan-doctor — is this install actually doing anything
Four copies of chamnan at four versions were found on one laptop, .version reading whichever
wrote last, and nothing downstream could tell. The doctor asks whether the hook is registered,
whether the recorders are writing, and whether the index describes the tree. It found a dead
feature on its first run: the failure recorder had been registered on an event that fires when a
TOOL CALL fails, which is not what a command exiting 1 is, so logs/failures.jsonl had never been
written at all.
The questions a workspace could not answer before
chamnan-explain-context answers "why did the session not know that" from the shape of the block
rather than from a guess — which sections arrived as names only, how much of each was kept, and
how often that has happened lately. It reads the shape log and nothing else: no prompt and no
file content is stored anywhere by this package, which is what makes the audit safe to keep.
chamnan-where, chamnan-vs and chamnan-setup answer three more.
The README is now an index of what this does
Seven groups, by what you are trying to do rather than by the order things were written, every row
linking to its detail. Neither list is typed: bin/ and skills/ are the population and a check
asserts both directions, because a command that ships and is missing cannot be found by anybody
reading the page, and a row for something that was removed is worse — it reads as real.
Under the hood
Three recorders that were writing nothing, and the lessons already in the code became readable.
- The failure recorder now listens where failures arrive.
chamnan_tool_failed.pyhad been
taught to read a non-zero exit code and stayed registered onPostToolUseFailurealone — an
event that fires when a TOOL CALL fails, not when a command exits 1. A shell command that exits
non-zero is a tool call that succeeded and reported a failure, so it arrives onPostToolUse,
and the branch written for it could never run.logs/failures.jsonldid not exist at all, and
everything built on top of it had no input. secrets.tomlis refused by name.secrets.ymlandsecrets.yamlwere listed and the
third spelling was not — the one Streamlit puts live API keys in. The entry is the stem now,
so every config spelling is covered whilesecrets.pystays an ordinary module.- A
statistic/site, four pages built from the workspace's own logs: what the plugin
changed about a period's token weight, which features fire and how often, what the redactor
catches and what no pattern can, and an editor for the weights. No money and no model names on
any page, because the plugin does not know which model a reader runs. Nothing is generated into
the repository — a fresh clone opens to panels that name the file they read and say it is
missing, which is the honest first view. tools/gotcha_index.pyreads every recorded lesson in the tree instead of counting them.
3,118 of them across 533 files, with the file, the line, the date and what went wrong.bin/chamnan-doctoris pinned to LF, so a Windows checkout undercore.autocrlf=truedoes
not receive a shebang no shell recognises.
Six defects found by running chamnan against a corpus nobody on this project wrote.
chamnan-corpus gained a hundred cases asked from the other side — somebody installed this in a
codebase they care about, and the repository was already in one of these states. Sixty-seven are
planted, twelve were already there, and the twenty-one no corpus can hold are listed with the
reason rather than dropped.
- A credential the source split in half no longer reaches
MAP.md. A deploy key written the
way a formatter leaves it —("ghp_"on one line, the rest on the next — was copied into the
index complete, because every prefix rule needs the prefix and the body contiguous and the
language rejoins them where a pattern cannot.MAP.mdis the file this plugin tells people to
commit. Adjacent string literals are now joined before matching, and the joined form is kept
only when it actually redacts more, so ordinary text is returned untouched. - Session records are named before they are deleted.
prune_sessionsremoves a handoff
somebody wrote on the retention window at every session start; in a committed workspace that is
aDin a teammate'sgit statusnobody asked for, and a failedgit diff --exit-codein CI.
The sweep overlogs/had named its files first since the release before; sessions had not. - Four warnings were being deleted on their way out of the block. Standalone notices are moved
to the end of the block for the prompt cache, and the fitter treats a bare line after a section
as that section's footnote — so a session-wide warning that merely sat behind the last section
was dropped with it, whiledroppedreported nothing at all. Measured on the corpus: log
expiry, session expiry, "source has changed since this index was built" and "25 of 576 file(s)
this index names no longer exist", gone in one firing from a block that looked complete. - Three state files left everybody's diff.
drift.jsonis a function of HEAD,.temps-swept
is this machine's last sweep, andnotices.jsoncounts how often one person has been shown a
notice — so committing it let the first teammate who saw one silence it for the whole team. The
rule that already excluded two derived files stopped one short of the identical cases beside
them; the population is now declared and derived from the source instead of enumerated. chamnan-explain-contextreads both shapes of its own log. The writer began recording
{whole, rank}where it had written a plain size, and the reader was never taught; any
workspace with two shortened sections crashed it, which is every recent firing here.
Verification
6138/6138 checks passed, 3 block(s) skipped on this platform
Quoted from the run on the tagg...
1.30.0 — five questions the repository can now answer
Five new questions you can ask the repository, and the answers arrive faster than the last release
could give them.
| 1.29.0 | 1.30.0 | |
|---|---|---|
| Naming an edit that will not survive | 33.5 ms per Edit | 0.5 ms |
| Cache hit ratio behind the similarity path | 23.5% | 90.3% |
| Languages a reference scan can read | 1 | 21 |
| Noise in a cross-language reference scan | a plain grep's | 98% of it removed |
| Instruction files checked for claims that stopped being true | none | every one in the tree |
Together those say one thing: the cost of asking fell far enough that asking became worth doing.
A question that takes a third of a second gets asked; the same question at thirty milliseconds a
file does not.
- You can ask where a name is actually used, not where it is mentioned.
chamnan-wherereads
21 languages and excludes comments, strings and docstrings before it answers. - You can ask what this repository costs a model, measured on this repository.
chamnan-vs
prints three numbers from your own tree rather than quoting ours. - You can ask whether the problem is the machine or the code, before an hour goes into the wrong
one./chamnan:why. - You can have a change reviewed against what the repository already knows — its rules, its
recorded decisions, the sibling that solved this before./chamnan:review. - You can have a decision counted rather than argued.
/chamnan:decideputs the evidence on
each side and shows the count, with no model in the loop. - Resuming a session stops paying twice. The hook recognises its own block in the transcript and
sends one line instead of the whole thing.
What you should notice
Most of this is deliberately invisible on the happy path. What you will see: a resumed session that
does not re-send what it already told you, a repository whose stale index asks once instead of every
time, and one notice per tool call instead of one per hook that fired.
What you will not see, and that is the point, is the chamnan-where that used to take long enough
that you would have run grep instead.
Under the hood
Asking the repository a question
Five commands, and the common thread is that each answers a question somebody was already asking
badly. chamnan-where answers "where is this name used" — a comment, a string and a docstring all
mention a name and none of them uses it, and that distinction is the whole difference between a
list you can act on and a list you have to filter by hand. Python is answered by its own parser;
twenty other languages are answered lexically, and labelled as lexical so nobody mistakes one for
the other. Comments and string literals are blanked before the identifier is matched: against the
AST's verdict on six real symbols in this package — 92 true uses against 178 raw grep hits, 86 of
them noise — that removes 98% of the noise, reporting 94 against the AST's 92.
chamnan-vs prints three numbers measured on the tree it is run in: every indexed file
concatenated, the block chamnan injects, and the largest file read whole against the same file
peeked. It names no other tool, deliberately — a reader gains nothing from a competitor's name and
loses the ability to check the figure against their own code. A repository with no recorded sessions
gets the block's byte ceiling rather than a token figure that would have to be invented from a
chars-per-token ratio this package refuses to use.
/chamnan:why separates "the machine is wrong" from "the code is wrong" before either is debugged.
/chamnan:review reviews a change against the repository's own rules and recorded decisions.
/chamnan:decide counts evidence on each side of a question and shows the count.
Performance
Naming an edit that will not survive went from 33.5 ms to 0.5 ms per Edit. The old path paid a
git subprocess on every edit of every file, and 32 ms of that is process spawn alone and cannot be
optimised away — it had to stop being spawned.
One similarity threshold existed in two copies that could disagree. Consolidating it and moving it
from 0.99 to 0.75 took the cache hit ratio behind it from 23.5% to 90.3%, because a threshold
that strict meant almost nothing was ever recognised as the thing it had just seen.
The reference scan no longer walks a function's whole subtree once per enclosing function. On a
module where every function shadows the searched name, the old shape visited 1,852 AST nodes against
520 now — and produced the identical answer on all 5,016 (file, symbol) pairs it was checked against.
Reliability
A repository whose index has fallen behind now asks once and stops. Measured over 400 real sessions,
the index was behind on 78 of them (19.5%), median 42 minutes, p90 3.6 hours, and a maximum of
16.6 hours — so the notice was firing constantly and being ignored constantly, which is the worst
state for a notice to be in.
Instruction files are now checked for claims that stopped being true. On this machine that check
reported 11 of 24, 8 of 12 and 19 of 47 statements no longer matching the tree they describe —
between 50% and 80% of each file. The check reports and never rewrites: an instruction file is the
owner's, and a tool that edits one is a tool nobody can trust with anything else.
A tool call produces one notice now, across hooks that cannot see each other and each believed
itself the only speaker.
Security
Homoglyph detection closes the half of Trojan Source this package never answered. The bidirectional
and zero-width halves were already scrubbed; a Cyrillic character standing in for a Latin one was
not, and a path mixing two scripts is exactly how a reviewer is shown one thing while the machine
reads another. It detects and names the segment — it never rewrites a path, because a rewrite is a
decision about somebody else's filename.
Per-segment, not per-path, and that is load-bearing: this repository's own tree contains Thai
directory names, and a per-path test would flag every one of them.
Cross-platform
The long-document notice told every reader to run a script that exists only on the machine that
wrote it. Windows paths that git C-quotes are decoded before they are compared, rather than compared
as quoted bytes against unquoted ones.
Interesting findings
A check can assert the opposite of its own name and pass for its entire life. A guard was found
beside the feature it was written for, having never once run — its population was empty, so it
reported success over nothing every time. The fix was not to the guard: the new check derives its
population from the source rather than trusting a list, because a list is what let the population go
empty unnoticed.
A warning that fires on healthy files teaches people to ignore it. Two warnings were measured
firing on files with nothing wrong with them, and both were found by the feature that had just been
built to warn about things — it warned about itself. Both were removed rather than tuned; a
threshold that needs tuning to stop crying wolf is usually answering the wrong question.
Three claims the index made about the tree were false. An absent reference, a swallowed commit,
and a rebuild that reported success without running. All three now check rather than assert, which
is the whole difference between a document and a claim.
The right answer to "which language is this" was already written down. A declaration scanner was
about to get its own rules for naming things, one file away from mapper.py, which had answered the
same question for twenty languages already. Terraform scored 0 of 19 under the new rules and 19 of
19 under mapper's — nine resources of type aws_kms_key are not nine things named aws_kms_key.
Dogfood and real-world discovery
Every number in the performance section came from running this package on real repositories rather
than on a fixture. The 400-session index-staleness figure is this machine's own session history. The
33.5 ms edit cost was noticed as a session that felt slow before it was ever measured, and was turned
into a reproducible case before any code moved.
The five new commands each exist because the same question was asked badly three times in a row in
real work, and the third time it was written down instead of answered again.
Research-driven improvements
Two techniques that a repository index is expected to adopt were measured against this one and are
deliberately not in it.
Incremental rebuild. Rebuilding only what changed is the standard answer for an index, and three
separate literatures — incremental recomputation, content-addressed identity, differential dataflow —
arrive at it. Measured here, a full index rebuild is 11.5 seconds across 660 files. Against that
base, the published conditions on those techniques bite: the first incremental build is slower, each
configuration needs its own cache, and a coarse task erases the benefit entirely. The staleness
class it would introduce costs more than the seconds it would return, so the index still rebuilds
whole and stays simple enough to reason about.
Late-interaction retrieval. Token-level matching with a reranker is the current answer to
retrieval quality, and every published variant requires a neural encoder, a multi-vector index and
accelerator-backed inference. This package is standard library, with no model and no index service,
which is what lets it install into any repository and run the same way. Retrieval here stays lexical
and exact rather than trading that away.
Both decisions, their sources and the conditions that would reopen them are in the research index
attached to this release.
Research & evidence
INDEX_CITED_IN_CODE.md— attached to this release. Every defect record in the source that names
the research round or the measurement that found it, resolved back to the line it was fixed on.tests/run_tests.py— the whole suite, runna...