chameleon 3.1.2
Whole-plugin from-scratch real-invocation QA campaign: 36 parallel testers
exercised every hook, skill, and MCP tool against the real profiled repos
across TypeScript/Ruby/Python and every supported framework, weighted toward
the freshest v3.1.0/v3.1.1 memory-channel code. 26 findings confirmed (each
independently adversarially re-verified) and fixed. The fix wave itself went
through two further verification passes before shipping: a per-fix adversarial
re-check (which caught and repaired a fix-introduced test regression, see
"Fixed" below) and a second independent review of the cumulative diff (which
caught a fix-introduced false-positive bug in the PR-review auto-pass fix
itself, plus two smaller parity gaps — all repaired, see "Fixed").
Fixed
- Daemon proxy silently defeated
CHAMELEON_TRUST_REVALIDATE=1(P1). The
long-lived chameleon-mcp daemon's environment is frozen at spawn time, so
preflight-and-adviseproxyingget_pattern_contextto it never observed a
per-call revalidate override — a profile that genuinely diverged from its
trust grant kept reading astrustedinstead ofstalewhenever a daemon
spawned without the flag was already running, the default steady-state
condition.CHAMELEON_TRUST_REVALIDATE=1now bypasses the daemon proxy and
runs the check in-process.hook_helper.py. - PR-review auto-pass missed a removed-but-still-imported export (P1).
get_autopass_verdict's blast-radius fact only counted a file's CURRENT
importers, so deleting an export five other files still imported could
scoreauto_pass_eligible=true/risk=lowwith no mention of the break —
the exact defect class the safety net exists to catch. It now folds
query_symbol_importers's "broken" entries into the verdict as a real
contract break, scoped to names that were actually exported at the diff's
ownold_ref(an independent review of this same fix caught that the first
version compared only against the CURRENT export set with no notion of
old_ref, so a file touched for an unrelated reason with a pre-existing
dangling import — present at bothold_refandHEAD— would have been
misattributed to the diff under review; both directions are now covered by
a real two-scenario regression test).tools.py. bootstrap_repo(force=True)had no too-new-schema guard. It would
silently downgrade/destroy a profile written by a newer chameleon engine —
the exact caserefresh_repoalready refuses even underforce. Bootstrap
now applies the same refusal.tools.py..chameleon/conventions.mdwas missing from the merge-driver routing.
Absent from.gitattributes-template, a real two-branch merge with
divergent taught idioms left raw git conflict markers embedded in the
mirror instead of the driver ever running. Routed alongside
COMMITTED/principles.md/profile.summary.md(decline-cleanly,
refresh-repairs), and added totest_gitattributes_template.py's tested
sets.- Method/class/constant naming conventions never reached the model.
method_casing/class_casing/constant_casingwere derived and stored in
conventions.jsonbut rendered into none of the three convention-delivery
channels (SessionStart, the conventions.md mirror, the Tier-1 PreToolUse
echo) — a model could be flagged for violating a naming rule it was never
shown. All three now surface it, mirroring how file-naming already does.
conventions.py. refresh_repounder-repaired corrupted/stale artifacts. A corrupted
(non-empty garbage)profile.summary.mdpassed the repair guard's
emptiness-only check and stayed corrupt indefinitely; an old-but-readable
profile.jsonschema_version noop'd instead of re-deriving; a stale
reverse_index.json/exports_index.jsonschema_version was never
validated. All three now force a re-derive like their sibling artifacts do.
tools.py.detect_repo/get_pattern_contextmisreported a torn-down profile.
Whenprofile.jsonwas missing but core artifacts remained, both reported
a cleanno_profileinstead ofprofile_corrupted— inconsistent with
get_statuson the identical directory, contradicting the code's own
stated invariant. Both now call the same corruption check regardless of
whetherprofile.jsonitself exists.tools.py.detect_repo/get_pattern_contextsilently resolved a relative
file_pathagainst the MCP server's own CWD, contrary to their
documented absolute-path contract (the siblingget_archetypealready
guards against this exact failure mode). Both now reject a non-absolute
path with a validation-error envelope instead of disclosing whatever repo
happens to sit at the resolved location.tools.py.preflight-and-advisecrashed on a non-stringsession_id. The one
call site (of ~11 in the file) without anisinstance(str)guard let a
malformed payload reachstr.encode()and raise an unhandled
AttributeErrorpast the hook's own try/except. Guarded to match every
other call site.hook_helper.py.- The idiom-review off-switch silenced an unrelated reminder.
enforcement.idiom_review: falsealso killed the independent "no passing
test run this turn" reminder, because both were computed inside the same
gate function. The reminder now fires independently of the idiom-review
switch.hook_helper.py. multi_lens_review's "ran" check event dropped its route reason, unlike
the "skipped" branch — no way to audit after the fact whether a turn's
judge spawn was intent-forced. The reason now rides in both.hook_helper.py.detect_repomisreported a monorepo workspace's production-ref lock
immediately after init, reading only the workspace's own (nonexistent)
config instead of walking up to the toplevel's lock the wayrefresh_repo
already does.tools.py.- An idiom taught with
source=rendered a corrupted gist — the
citation text leaked into the directive because_summarize_idiom_block's
metadata-line regex didn't recognizeSource:, unlike the sibling parser
inidiom_coverage.py.tools.py. lint_file's node-kind-mismatch violation embedded an unbounded
repr()of every top-level node kind, with no cap unlike sibling
violation classes in the same module.lint_engine.py.- A bare
python -m chameleon_mcp.daemon(nostartsubcommand) never
wrote a pidfile, sodaemon_status()/stop_daemon()reported a live,
request-serving daemon as not-running.daemon.py. - Intent capture missed realistic checkable constants — the identifier
regex never matched slash-delimited paths, and the numeral regex required
2+ digits. Broadened both.intent_capture.py. - Python's async/await signal was computed but never surfaced in
search_codebase/get_pattern_context— an async FastAPI endpoint
rendered indistinguishably from a sync one.symbol_signatures.py. search_codebasemislabeled a stale calls index as(corrupt)
instead of the sharedcalls-index-stalereason sibling tools already use.
A review pass on this fix found the identical(corrupt)mislabel three
lines above for a stale symbol index, missed in the same edit — fixed too,
now distinguishingsymbol-index-stalefrom genuine corruption.tools.py._profile_needs_rederivevalidatedconstant_index.json(Ruby's
cross-file existence index) for JSON-parseability only, not schema_version,
unlike the exports/reverse index check right above it — currently latent
(the format has only ever shipped schema_version 1) but would have silently
survived every refresh once a newer schema ships. Now validated the same way
its siblings are; found and fixed during the same review pass as the two
items above.tools.py.- The orphaned merge-driver tmp-file sweep only ran on full bootstrap,
not the partial-refresh path/chameleon-refreshtakes for small diffs.
tools.py. /chameleon-doctor's recent-hook-errors check could misattribute a
benign banner as the error, dropping the real timestamped line that
pulled it into the surfaced tail.tools.py.using-chameleon/SKILL.mdmisdocumentedidiom_judge's default as
"opt-in, default off"; the code default is on, matching every other doc
that states it.
Full unit suite green (5545 passed, 3 skipped), ruff clean.