Releases: xlightxyearx/anton-core-plugin
Releases · xlightxyearx/anton-core-plugin
v1.11.1
Fixed
memory recall(and the dashboard Memory search) no longer fault on queries containing FTS5 metacharacters. A query bearing-,.,:,(,),*, or a bare-wordAND/OR/NOT— e.g.cut-release,v1.11.0— was bound straight intoitems_fts MATCH ?unescaped, so FTS5 parsed the metacharacters as query syntax and raisedSQLITE_ERROR, surfaced to the user as the opaqueinternal: fts: sqlite error (code 1): internal. The fusion FTS arm now escapes the query at a single seam (internal/ftsquery): each whitespace token is wrapped as a double-quoted FTS5 string literal (embedded quotes doubled; NUL bytes and invalid UTF-8 stripped, since SQLite treats text as NUL-terminated and an embedded NUL truncates the MATCH expression), and the literals are joined — implicit-AND for general recall, OR under--on-error(preserving the RESOLVES-walk seeding). The error-recall reflex'sbuildQuery, which previously carried its own copy of this escaping, now passes raw blended text through that same seam, removing the duplicate. Latent since the retrieval layer was introduced; the escaping previously existed only on the error-recall path and was never retrofitted to the general path. A query that escapes to no matchable token (whitespace-only, or content that is entirely metacharacters / NUL / invalid UTF-8) now skips both FTS arms and logs aretrieval.fts.skippedwarning — symmetric with the existing vec-arm skip log — instead of binding an emptyMATCH ''.memory recallno longer faults on the first recall against a cold/empty vector index when a real embedder is configured.vec1's flatitems_vecindex has dimension 0 until its first vector insert, so a KNN bound with a non-zero-dim query vector — e.g. the embedding of an ordinary query likefoo— raisedSQLITE_ERROR(vec1: unexpected vector blob size N bytes, expected 0), surfaced to the user as the opaqueinternal: vec knn: sqlite error (code 1): internal. This hit the cold-start path: a real embedder enabled but nothing embedded yet (emptyvec_index_map/items_vec), or any otherwise-empty vector index — the first recall on a fresh DB before anything is saved. It was masked until now only because the default OFFLINE / stub embedder short-circuits the vec arm (viaintake.ErrStubEmbedderUnavailable) before KNN ever runs.vecKNNnow short-circuits to an empty result whenvec_index_mapis empty — the bridge is co-populated withitems_vecon every save and is the KNN's JOIN target, so an empty map yields an empty result regardless — enforcing the function's already-documented "empty result is not an error" contract for the cold-index case. A populated index still runs the KNN unchanged, so a genuine vec fault on a non-empty index still propagates asinternal. Independent of the FTS5-escaping fix above; discovered while testing it.
v1.11.0
Added
core --wrapper-checknow reportsbin_in_managed_cache— whether the resolved binary path sits inside the Claude Code plugin cache / marketplace clone.- session-end now best-effort reaps orphaned
data/binbinaries left in superseded Claude Code plugin-cache version dirs (never the current version). Therm -rfis gated on the canonicalised target (cd … && pwd -P, plus a..-component refusal), so a..-ladenCLAUDE_PLUGIN_ROOTor a symlinked version-dir/datacomponent cannot escape the verified…/plugins/cache/anton-core/subtree.
Changed
- Operators who hit the managed-cache bootstrap refusal (
bin_dir_in_managed_cache) now receive acore-shim.shsignpost in the deferred-errordetailinstead of the generic network/setup string. - Build-tool versions are now pinned —
golangci-lint→v2.12.2,gofumpt→v0.10.0— with theMakefileas source of record and a newverify-tool-versionsgate (inverify-checks) that fails the build if theMakefile,verify.yml, andrelease.ymlinstall steps drift apart.
Fixed
- Bootstrap no longer installs the binary into the Claude Code plugin cache / marketplace clone. When the resolved binary directory falls under
…/plugins/cache/…or…/plugins/marketplaces/…— Claude-Code-managed paths it never prunes — the wrapper now refuses to bootstrap (discriminatorbin_dir_in_managed_cache) and signposts the operator to run anton-core via~/.local/bin/core(core-shim.sh) instead of the bundledscripts/core. Prevents the unbounded cache leak (~55 MB per orphaned copy).
v1.10.1
Changed
- Recall, item get, item log-access, and memory explore now surface real SQLite fault kinds —
db_locked(busy),db_corrupt,io_error— instead of masking them asinternal. The four verbs' error-kinds sets are widened accordingly (cli-contract). Fixes the originaldatabase is locked-as-internalmisclassification on the recall write path. - SQLite extension registration is unified onto driver-global auto-extensions.
vec1(and nowfts5) are registered viasqlite3.AutoExtensionininternal/db's packageinit()(internal/db/autoext.go), so every connection the ncruces driver opens carries both modules — replacing the per-connectionloadVecExtensionshim that ran insidedb.OpenbetweenattachEventsandApply(core). One mechanism, every connection; theMaxOpenConns(1)pin is no longer load-bearing for module presence. Recorded in ADR 0042.
Fixed
stale_lock_fileshealth panel no longer false-positives every session. It previously watched the two persistent flock sentinels (.maintenance.lock,.repos-sync.lock) and flagged any present-but-unheld file via a non-blocking flock probe — but those sentinels are designed to persist on disk unheld between runs (the kernel releases the flock on fd close; the empty file stays so the next caller re-attaches to the same inode), so the panel fired on essentially every idle session: a guaranteed false positive, not a lock leak. It now watches the two PID-fenced, removed-on-release locks instead —update.lock(via its siblingupdate.lock.pidholder record) and the bootstrap mkdir-mutex.bootstrap.lock(via thepidrecord inside its directory) — and detects staleness by probing the recorded holder PID withkill(pid, 0): an absent record (a clean, idle system) or a record naming a live holder yields no row, and only a record naming a dead holder surfaces as a warning (the owning subsystem reclaims it the next time it acquires that lock — routine forupdate.lock, but for.bootstrap.lockonly on a re-bootstrap when the installed binary is missing, so a lingering bootstrap row is safe to remove manually after confirming no live holder). Rows now carry the dead holder's PID and how long the lock has lingered (age_ms) in place of a meaninglesssize_b. An empty/torn pid record is skipped quietly (it races the bootstrap pid file's non-atomic write), while a non-empty but malformed record is logged at WARN; a watched path that exists but cannot be read now surfaces as a distinct warning rather than rendering the panel falsely clean. On Windows the panel is a structural no-op (no reliable holder-liveness probe), so a clean panel there is not a positive all-clear. Thelock-primitiveglossary entry is corrected to match (the persistent flock sentinels are explicitly not flagged).- Dashboard self-terminates when orphaned (parent reparented to init) so an abnormal session exit can't leave a server holding core.db/events.db and blocking WAL checkpointing — the cause of intermittent
database is lockedon recall. - Data-dir resolver normalizes a root whose basename is
data(e.g. over-specifiedCORE_DATA_DIR=<root>/data) down to<root>, so it can't mint a stray nesteddata/data/core.db. - Release bundle now ships
post-tool-use-failure.sh; hooks.json referenced a script the allowlist omitted, so the PostToolUseFailure (error-recall) hook failed with "No such file or directory". - Recall layer survives the go-sqlite3 v0.35.0 FTS5 split. v0.35.0 removed FTS5 (and R*Tree/Geopoly) from the base driver — FTS5 must now be registered separately via
ext/fts5. The recall/memory layer rides entirely on FTS5 (items_fts/items_content_ftsexternal-content tables plus sync triggers on everyitems/items_contentwrite), so the bump would otherwise fault every recall and item write withno such module: fts5. The weekly dependency group now lands withext/fts5registered:github.com/ncruces/go-sqlite30.34.4 → 0.35.1 (its indirect wasm assetgo-sqlite3-wasm/v2→/v3) plus sixgolang.org/x/*minors (crypto0.53.0,net0.56.0,sync0.21.0,sys0.46.0,term0.44.0,tools0.46.0). Supersedes Dependabot #75.
v1.10.0
Added
- CLAUDE.md routing-fragment auto-apply + sole applier. A new
internal/fragmentpackage andcore fragment apply/core fragment statusverbs are now the single writer/reader of the user's global${CLAUDE_CONFIG_DIR:-$HOME/.claude}/CLAUDE.mdrouting fragment — sentinel-bounded splice (<!-- anton-core:start/end -->), atomic write,fragment.versionread-back verify,--dry-runpreview, and aFRAGMENT_APPLIEDaudit-events row. The SessionStart hook auto-applies a newer shipped fragment, bumps the pin, and announces via the envelope's top-levelsystemMessage("Routing fragment updated X → Y."); on failure it degrades to a visible warning and never blocks (exit 0). The setup skill (Stage-2/Update/Repair) now calls the verbs instead of doing its ownRead/Edit, eliminating the dual-applier drift risk and the hardcoded~/.claudepath. ADR-0041 records the trust-boundary decision. - Session-start context now reaches the model. The SessionStart hook delivers its surfaced tasks / improvements / recall reminder through
hookSpecificOutput.additionalContext(the documented model-context channel) — previously the JSON envelope had no recognized hook field and was inert.
Changed
- The local embedder is now ON by default (
embedder.enabledseedstrue; migration0018_core_embedder_enabled_default_on.sqlflips pre-existing installs from the oldfalse). Recall gains its vector-similarity arm out of the box instead of staying FTS-only until manually enabled. The model is loaded lazily — the adapter is bound at startup but the model is fetched/loaded only on the first command that actually embeds (recall,item save,maintenance reindex), so commands that never embed (tasks add,report health) pay nothing and a fresh install does not download the ~127 MB model until a vector is genuinely needed. A load/acquire failure degrades that embed to FTS (the stub sentinel) rather than failing the command, and is logged once per process asembedder.load.failed(Warn) so the silent degrade stays diagnosable (a deliberately-disabled embedder logs nothing);core healthreports the vec-arm state ("staged" / "enabled — runcore system warm --target embedder" / "stub") from a cheap on-disk stat without loading the model. To light up an existing corpus after upgrading, runmaintenance reindex --target knowledge(and--target code); new writes embed automatically. Setembedder.enabled=falseto opt out. A newCORE_EMBEDDER_OFFLINE=1env forbids the network model download (degrade to FTS when the model is absent) — set by the test Make targets so the suite never pulls the model, and usable on air-gapped hosts. - Fragment-version drift no longer emits a stderr banner (confirmed dead at SessionStart exit 0).
internal/hooks.CheckFragmentDrift(banner-returning) is replaced by structuredEvaluateFragmentDrift; the dotted-numeric version compare consolidates intointernal/fragment.CompareVersions; "older than pinned" warns without writing (no downgrade). Spec §04-paths-and-config, acceptance A-plugin-8/9/10 + A-setup-1/2/3, and the08-hooksside-effects updated accordingly.
Fixed
- Embedder batch reindex no longer exhausts the inference graph cache.
internal/embedforwardnow padsinput_ids/attention_mask/token_type_idsup to bucketed sequence lengths ({64,128,256,512}) before inference, so every forward pass shares one of ≤4 JIT-compiled SimpleGo graphs instead of recompiling per unique token length. A long-livedmaintenance reindex --target {knowledge,code}previously accumulated >32 distinct sequence lengths and died at the 33rd withmaximum cache size of 32 reached; per-save (fresh process, ~1 shape) was unaffected. Pad positions carry the tokenizer's[PAD]id and attention-mask 0 so the softmax ignores them, and CLS pooling reads position 0 — whose representation is invariant to masked trailing pads — so embeddings are unchanged from the prior unpadded output (cosine ≈ 1.0, gated by a newembedmodel-tagged padding-identity test). On the batch path,maintenance reindexnow pre-compiles the embedder's bucket graphs before its item loop, so no early item pays a mid-loop JIT stall (interactive commands are unaffected —WarmUpstays load-only).
v1.9.0
Added
- Fail→success pattern mining:
SessionEndmineserror→successtool-call pairs from the session transcript into linkederror/patterngraph memories joined by aRESOLVESedge (items_patternsidecar, migration 0016). Mined error text and tool args are passed through best-effort secret redaction before storage (raw text still feeds the fingerprint, so dedup is unaffected); theSessionEndenvelope reports apatterns_redactedcount. Additive, non-blocking, embedder-free. recall --on-error: an error-shaped query boosts theRESOLVES-linked fix above the error node (retrieval.resolveOnErrorBonus, default 3.0). Under--on-error,pattern-type items are excluded from the FTS seed pool so a mined fix is reached only via theRESOLVESwalk and the bonus fires deterministically. Promotesretrieval.freshnessExponent/retrieval.hopDecayfrom hardcoded constants to operator-tunable config reads.patterns listread verb andpatterns.*config keys (patterns.enabled,patterns.pair_window_minutes(now>= 1),patterns.bash_target_tokens).- Maintenance
purge_legacy_stubsjob now also reaps orphanitems_contentrows (content with no referencing item) so cosmetic-variant re-mines of the same logical error do not strand rows. - Local pure-Go embedder (
internal/embed): an opt-inintake.ModelAdapter.Embedthat lights up the vector arm of recall, dark since launch (vec_index_map=0). Runsbge-small-en-v1.5(384-dim, CLS-pooled + L2-normalized) viaonnx-gomlx+ the SimpleGo pure-Go backend — no CGO, no cloud, inference 100% local (only the one-time SHA256-pinned model acquisition touches the network). Gated by seededembedder.{enabled,model,max_seq_tokens}keys (default off → byte-identical FTS-only behavior). Adds the missing knowledge-sideitems_vecwrite (sharedmemory.WriteItemVec, lifted from the codegraph indexer, which now embeds before its write tx for single-connection safety), amaintenance reindex --target {knowledge,code} [--dry-run]backfill emitting areindex/BACKFILL_COMPLETEevents row, and acore health"vec arm: live|stub" banner. No schema migration (items_vecis dimension-agnostic). Themaintenance reindexenvelope reportsskipped_empty(candidate rows with an empty body are accounted for, not silently dropped), and the knowledge intake path skips embedding an empty/whitespace body so it agrees with the backfill's skip rule. The headless-LLMExtractarm stays stubbed (separate task). - Automatic error-recall reflex (
internal/recallonerror): an always-onPostToolUseFailure/Bashhook that, when a Bash command fails, recalls theRESOLVES-linked prior fix mined from your own history and injects it asadditionalContextso it is in front of the model on the next turn. FTS-only recall (RunRecall(OnError:true), no embedder,NoRerank) gated by a per-error-fingerprint cooldown sentinel and arecall_on_error.min_scorefloor; the recalled fix is prefixed with an untrusted-input marker (treat-as-hint, not instruction). The hook ALWAYS exits 0 and emits{}on any fault — it never blocks or delays the tool. Seededrecall_on_error.{enabled,cooldown_seconds,min_score}keys (defaulttrue/30/0.5). Every terminal decision (injectedorsuppressed_*) is appended to the newevents.recall_on_error_logledger (migration 0017);recall-on-error statsrolls the ledger up by outcome (json/text) andrecall-on-error doctor --error <text>reports the candidate pattern + score + would-inject formin_scorecalibration. The query is FTS5-escaped (raw stderr metacharacters would otherwise breakitems_fts MATCH), and pattern detection is byitems_patternsidecar presence (walked RESOLVES nodes carry no type).
Security
- Pattern-mining redaction now masks compound OAuth credential keys (
client_secret,refresh_token, and any*_secret/*_token/*_keyform) that previously leaked through unmasked, plus JWTs andAuthorization: Basicheaders. The HTTP-Basic rule is anchored to the auth header so it does not over-mask the common adjective "basic".
Fixed
- Pattern mining writes each pair's error node, pattern node (with its sidecar), and
RESOLVESedge in a single transaction, so a mid-pair failure no longer strands a half-written pair — previously the walk-critical edge committed outside any transaction. SessionEndpattern-mining failures now emit aWARN/PATTERNS_FAILEDrow to the unified events log under thepatternssource (matching the subsystem spec), and thePATTERNS_MINEDsuccess event is filed underpatternsrather thanhooks.- A transcript line larger than the 4 MB cap is now drained-and-skipped rather than aborting the whole scan (a single giant tool result no longer costs a session's patterns), and the
SessionEndenvelope reportspatterns_lines_malformed/patterns_decode_degradedso silent reader-side drops are diagnosable. A typo'dpatterns.enabledvalue is logged before falling open.
v1.8.0
Added
core item list: multi-type filtering, absolute created bounds, sort, and pagination.--typeis now repeatable and OR-joins within the set (--tagstays AND-joined); new inclusive--created-after/--created-beforeepoch-ms bounds compose (AND) with the duration-typed--sincewindow;--sort created|updated|title|type|access_countis validated against an allowlist (unknown key →invalid_argument) with a deterministicid ASCtie-break;--ascflips the default DESC direction;--offsetpages through the filtered set (negative →invalid_argument). The envelope gainstotal— the filtered count independent of limit/offset — so callers can drive pagination, and rows now carrysummary+access_count. Spec:cli-shape:item-listupdated (including the stale--since TIMESTAMP→ duration correction), acceptance rowsA-item-5/A-item-6added, and the off-vocabulary--type Documentfixtures inA-item-1..4lowercased to the stored type vocabulary.- Dashboard
/memorybrowse + search rebuild: type facet chips with global counts plus an "All" chip; browse defaults to the knowledge facet (document/reference/project/feedback) so code symbols stop swamping the page — an explicit?type=allclears the filter and round-trips as such (an absent param would re-apply the default facet); search (?q=) never default-narrows. Both modes take repeatable AND-joined?tag=filters and an inclusive?after/?beforecalendar date range (UTC day-start / day-end). Browse adds sortable column headers (title / type / created / access count, direction-flipping, backed by theitem listsort allowlist), a total-items count, page-size-50 pagination via the newly vendored templUI pagination component (filter-preserving links,aria-currenton the current page, bfcache-safe loading skeleton), and richer rows (summary, created date, access count). The detail page (/memory/{id}) gains created/last-accessed dates, tag chips sorted human-authored-first with machineprefix:valuetags grouped last, full content, and a "View in graph" link deep-linking/graph?focus=<id>. Under the hood, the facet counts come from a new global per-type count read (RunItemTypeCounts, dashboard-internal — no CLI verb), and the search date range rides new inclusiveCreatedAfter/CreatedBeforeepoch-ms bounds on the recall filters (applied in both the no-query seed path and the post-filter pass); no CLI flags expose the recall bounds. - Dashboard 3D graph (
graph.js): filter-rail state persistence. The rail's durable state — type/repo checkbox truth plus the heat-slider cutoff — now round-trips throughlocalStorage["anton.graph.rail"]({types, groups, heatCutoff}, written on every manual checkbox change, slider input, and?focus=force-enable; a Go asset-guard pins the key), so a reload lands on the same filtered view. Restore is defensive (storage throws, parse errors, and shape/range violations all fall back to defaults and are overwritten by the next persist); stale keys for vanished types/repos are dropped, payload values missing from the snapshot default to checked (a new repo appears visible), and the sessions-default-off rule now fires only on a first-ever visit — an existing snapshot is the user's curated view.?focus=wins over stored state for the target's facets and re-persists the result; isolate/neighborhood overrides stay deliberately session-transient. Riders from Task 16's review: a slider drag during an active isolate/neighborhood now behaves like any manual rail gesture — it drops the override (restoring the pre-solo checkbox snapshot, since a drag carries no checkbox intent) before applying the cutoff, so a "blind" cutoff can never be set or persisted under an override; the global Escape handler skips the in-graph search box (Escape there means "clear my input", not "clear the isolate"); and the drawer route-prefix guard test asserts the code-level'<prefix>' + encodeURIComponent(expressions instead of bare prefixes that comments would satisfy. - Dashboard 3D graph (
graph.js+/graphview):?focus=deep-link, isolate modes, and type-aware drawer routing + summaries.GET /graph?focus=<id>(the target of memory detail's "View in graph" link) embeds the id as adata-focus-idattribute on the canvas mount (HTML-escaped by templ; a Go test pins the escaping); after the first render graph.js looks the node up in the FULL set —?focus=wins over current facet state, so a default-hidden session node gets its type/group facets force-enabled — then flies the camera to it and flashes it (the search's fly/highlight helpers, after polling briefly for the node's seeded coordinates + mesh); an unknown id — a legitimate state, since the node cap can evict the link's target — surfaces a visible notice strip plus the aria-live announcement, and poll exhaustion leaves aconsole.warnevidence trail. Two isolate overrides land as an internal layer over the checkbox state (thecodegroup shares its types with every repo well, so a solo is NOT expressible through checkboxes), exposed via the rail's closed verb API (kept/ensureVisible/isolate/neighborhood/clearIsolate): legend rows become the wells' click-to-solo labels (event-delegated — rows are rebuilt every filter pass; repo checkboxes mirror the solo where expressible, from a snapshot; re-clicking the soloed row toggles off), and the drawer gains a "Focus neighborhood" button showing exactly the node ∪ its direct link-neighbors (facets + heat cutoff deliberately bypassed). Escape or the new Clear chip (data-graph-clear-isolate) restores the pre-isolate state; a manual checkbox change instead drops the override and keeps the visible state. The drawer's detail link now routes by node type —symbol|module→/graph/symbol?id=…, everything else →/memory/<id>(a string-level Go guard pins both prefixes in the served asset) — and non-code nodes render their payloadsummaryunder the title (absent summary renders nothing). Riders from review: the in-graph search gains a visually-hiddenrole="status"live region for its no-match message, its placeholder now mentions Enter, and a pending highlight is preempted before the capability guards so a failed follow-up highlight can't strand the previous node white. - Dashboard 3D graph (
graph.js+/graphview): in-graph search + heat-threshold slider. A client-side search box above the filter rail (distinct from the header's/graph?q=symbol-search form, which navigates server-side — both stay): Enter substring-matches node labels case-insensitively over the visible (filter-kept) set, and the first match gets a 1200mscameraPositionfly-to plus a 2.5s white colour-flash on its mesh (base colour stored + restored; a second search before the restore fires restores the previous node first, so no highlight leaks — fly/highlight helpers are reused by the upcoming?focus=deep-link). No match → the input shakes (.graph-shakekeyframes ininput.css, shortened to ~instant underprefers-reduced-motion) and carries atitleexplaining why; both clear on the next keystroke. The heat slider (0–100, default 0 with a live % readout) hides nodes whoseheatfalls below the cutoff — composed as a third conjunct inside the SAME single filter pass as the type/repo facets (onegraphData()re-feed per change, links keep the both-endpoints-visible rule), with the sameclampHeatnormalisation the renderer's brightness curve uses. - Dashboard 3D graph (
graph.js): gravity-well group clustering — every distinct nodegroupgets a fixed anchor on a horizontal ring (radius 320; groups sorted so anchor assignment is deterministic across loads), and a weak hand-rolled positional force (the vendored bundle exposesGraph.d3Forcebut no global d3, so noforceX/Y/Zto borrow) nudges each node toward its group's anchor per simulation tick. Repo wells, knowledge, and sessions settle into visually distinct clusters while the deliberately weak strength (0.045) lets link forces win locally, so cross-group edges arc between wells. Anchors are computed once from the full dataset, so type-filtergraphDatare-feeds cannot reshuffle which well a group lives in. /anton-core:dashboard [surface]skill — launch the browser dashboard without tying up a terminal. Probes127.0.0.1:7777/healthzand reuses a live server (LISTEN-filtered + anchored binary-basename match across install layouts, so a foreign app on the port is reported, never reused, never killed) or startscore dashboardas a session-scoped background task (deliberately not detached: the server dies with the Claude Code session — verified empirically; an orphan from an abnormal exit is absorbed by the next probe as a warm start). Includes a guarded cross-session stop gesture with post-kill verification, a wedged-server path (busy port + dashboard-identified listener → report unresponsive, offer stop, not a port suggestion), and explicit-only port handling (no silent port bouncing). Thesession-endhook wrapper gains a best-effort default-port reap as deterministic teardown insurance (scripts/session-end.sh:21-41), logging each kill or failed attempt to the data-dirdashboard-reap.log; the match-and-kill guard is pinned byscripts/lib/wrapper_test/session_end_reap.bats. Skill count 21 → 22 (files_checked27 → 28); CLAUDE.md fragment gains the Intent-Routing row,fragment-version1.1.0 → 1.2.0. No binary changes — the skill orchestrates the existingcore dashboardcommand.
Changed
core item get(andRunItemGet): item rows are now fully resolved — every row carries its sortedtagspluscreated/last_accessedms-epoch timestamps (previously declared-but-never-populated outside the dashboard detail handler, which carried its own back-fill reads; that back-fill is deleted).cli-shape:item-getreplaces the stale never-emittedaccessed_atprop...
v1.7.0
Added
core dashboard— read-only browser dashboard served on localhost. Overview plus the Tasks, Sessions, Improvements, News, Repos, Health, Memory, and Graph surfaces (Graph: roots/coverage orientation, symbol browse, callers/callees/explore, ranked impact, paths-between, cycles), each backed by an exported package read fn the CLI verb also calls.core graph symbols [--query --repo --limit]— list indexed code symbols/modules; the read fn (ListSymbols) is shared with the dashboard Graph symbol-search surface.- Supply-chain:
THIRD_PARTY_LICENSES.md(repo root) attributes and sha-pins the dashboard's vendored non-Go assets — the self-hosted Outfit + Geist fonts (OFL-1.1) and the templUI static primitives (MIT); a new single-purposeverify-vendored-assetsgate (wired intoverify-supply-chain) recomputes each hash and asserts the co-located license text, closing the non-Go asset gapgo-licensescannot see. - Dashboard design system: the UI adopts anton-go's shadcn stack — Tailwind v4 (compiled by the standalone CLI, a dev/CI-only tool the shipped binary never runs) + vendored templUI static primitives (
card/badge/table/button/input/label/separator/skeleton/icon) + a warm terracotta/amber oklch palette + self-hosted Outfit/Geist fonts. The committedoutput.cssis embedded by a plaingo build; a path-filteredcss-driftjob inverify.yml(make css-check) keeps it honest without coupling the release pipeline to Tailwind. A collapsible sidebar rail + dark/light theme toggle are driven by ~20-line vanilla no-FOUC scripts (theme-bootstrap.js/sidebar-state.js; no Alpine/HTMX).cmd/coverage-mapgains a vendored-path exclusion so the vendored templUI tree is not held to the first-party doc-citation floor. Rationale indocs/adr/0039-dashboard-tailwind-templui.md. - Docs:
docs/plugin-spec/09-subsystems/dashboard.md— operator usage doc forcore dashboard(launch flags, the surface map by nav group, and the/graph*sub-surfaces), cross-linked from the overview and glossary. - Dashboard 3D graph (vendoring): the
3d-force-graphv1.80.0 WebGL recipe — the UMD bundle plus three.js r0.183.0 (ESM module + core), theUnrealBloomPassglow pass + its shader deps, and a first-partythree-bloom-bootstrap.jsESM bridge — is vendored underinternal/dashboard/assets/vendor/andgo:embed-ed (no Node build; shipped binary stays plaingo build).THIRD_PARTY_LICENSES.mdsha-pins all seven files (3d-force-graph MIT + three.js MIT) andverify-vendored-assetsgates them. - Dashboard 3D graph (data):
GET /graph/data— a JSON node-link endpoint ({nodes:[{id,label,type,heat}], links:[{source,target,kind}], total, truncated}) that feeds the 3D force-graph view. Nodes are symbol/module items, links the resolvedrelationshipsedges, with a surfaced node cap (a high safety ceiling, default 20000 — the full graph renders, and truncation engages only for a pathologically large graph): when a graph exceeds it the most-connected nodes are kept and the truncation is signalled both in the JSONtruncatedflag and a server-log warning — never silently capped. Each node also carries aheatin[0,1]for the frontend's bloom heat-map: a degree floor (graphHeatDegreeFloor, structural — so hubs are legible before any access data accrues) plus anitems.access_countfreshness boost (graphHeatFreshGain), each normalized against the kept-set maxima and the sum clamped to 1. Links are filtered to those whose both endpoints survive the cap so the frontend never receives a dangling edge. Backed by a sharedReadGraphDataread fn (internal/codegraph/cmd). - Dashboard 3D graph (canvas): the
/graphpage now renders an interactive 3D force-graph, driven by a single vanilla-JS island (internal/dashboard/assets/js/graph.js). It injects the vendored recipe in load-bearing order (the ESMthree-bloom-bootstrap.jsfirst sowindow.THREEis set before the UMD bundle reads it — graph renderer andUnrealBloomPassthen share one three instance), fetches/graph/data, and renders the graph with an UnrealBloom glow. Node/link colours resolve at runtime from the dark-only--graph-*oklch palette (rasterised to sRGB via a 1×1 canvas, since three can't parse oklch); the bloom blit material is marked transparent so the warm card surface shows through. When the server caps the node set the page shows a "showing N of M nodes" banner — the visible half of the never-silent truncation contract. Two Go guard tests protect the vendored invariants: the standalone three and the three bundled in3d-force-graphstay pinned to the same revision (r183), andUnrealBloomPasskeeps exposing the_basicblit material the transparency fix reaches into. - Dashboard 3D graph (heat-driven bloom): the graph blooms per-node by
heatrather than glowing flatly, and renders the full code graph. Each node is an unlit sphere whose warm--graph-*hue is scaled by its server-sideheat(0..1, via ascaleHexhelper from a cold-visible warm floorHEAT_DIMup to full colour) — unlit so on-screen luminance equals the heat-scaled colour and the bloom gates on heat, not scene lighting. Hot (hub/accessed) nodes glow; the cold majority recedes into a dim warm mesh (the ~5000-edge link web is dimmed from a warm tone, not the near-grey--graph-linktoken, so the whole graph reads amber, not grey). The UnrealBloom pass is tuned (strength0.9,threshold0.55) so only genuinely hot nodes cross it — no dense-core white blowout — turning the glow into a real freshness signal. Hot nodes also read subtly larger (heat-scalednodeVal). - Dashboard 3D graph (filter + inspect): the
/graphview gains a type-filter rail (toggle module vs symbol nodes — re-feeds the graph with the kept nodes plus only the links whose both endpoints survive, so none dangle; a missing rail falls back to the full graph) and a click-to-inspect drawer that deep-links the clicked node to its/graph/symbolcallers/callees page. A node-click also firesPOST /graph/touch?id=— the one write surface under/graph, so every GET stays read-only — which bumpsitems.access_countviaretrieval.Apply(sourcedashboard-graph), so exploration warms nodes: freshness (hence heat, hence bloom) now accrues from real graph use, not onlyrecall. The endpoint 400s a missing id and 404s a present-but-unknown one (the same existence guard/graph/symboluses, so no phantom access-log row); the client-side bump is best-effort (a failed POST never breaks inspection). The app shell'smainalso gainsmin-w-0so the graph canvas sizes to its own column rather than its full pixel width — removing a horizontal-scroll overflow that had pushed the graph off-centre and would otherwise strand the right-anchored drawer off-screen.
Changed
internal/codegraph/templates: extract a pure-readExecutedispatch (noquery_logwrite) plus typedExecuteWalk/ExecutePaths/ExecuteCycles/ExecuteDependentswrappers, and refactorRunner.Runto wrapExecute. Behavior-preserving for the CLI (one log row per fire); enables the read-only dashboard Graph surfaces.- setup skill: reworked
/anton-core:setupinto a state-aware concierge — state probe + classification (with a classification line recorded to the events log), smart-default intent menu, plumbing hidden behind named progress stages, single-panel onboarding that remembers declined steps, repair/update framing, a guided uninstall with a keep-data-vs-erase scope menu and typed confirmation for data-erase, and--checkimplemented as a status probe (no install-state change). No behavior is removed; all existing flags keep working.
Fixed
core dashboardMemory search: a title-resolution (RunItemGet) failure now returns 500 instead of silently rendering results with raw item IDs — matching the Memory-detail handler and the dashboard's uniform error contract.core summary(and anysummary.RunSummarycaller): resolve the owner from config before acquiring the rollup's pooled connection. PreviouslyRunSummaryheld the singleSetMaxOpenConns(1)connection across a DB-backedConfig.GetOwner, self-deadlocking whenever the config reader wascfgpkg.SQLConfig(the production wiring) — masked until now because every summary test injects an in-memory config. The dashboard Overview surface drops its pre-fetch workaround as a result.
Dependencies
- Add
github.com/a-h/templv0.3.1020 — typed HTML views for the dashboard (runtime library + ago tooldirective for its code generator). - Consolidated the weekly Dependabot batch into one PR (supersedes #60, #61, #62, #63):
- bump
github.com/odvcencio/gotreesitter0.19.1 → 0.20.2. - bump
github.com/ncruces/go-sqlite30.34.3 → 0.34.4 (pulls indirectgithub.com/ncruces/go-sqlite3-wasm/v22.5.35301 → 2.6.35302). - bump
actions/create-github-app-tokenv2.2.2 → v3.2.0 (release workflow). - bump
actions/checkoutv6.0.2 → v6.0.3 (verify + release workflows).
- bump
- Regrouped
.github/dependabot.ymlunder amulti-ecosystem-groupsweekly group so future gomod + github-actions bumps arrive as a single PR instead of fanning out.
v1.6.1
Fixed
- operator-shell: a bare-shell
core(via the~/.local/bin/corePATH symlink)
no longer goes stale after a/plugin update. The symlink previously pointed
at${CLAUDE_PLUGIN_ROOT}/scripts/corein the version-pinned plugin cache,
frozen at the last/anton-core:setup; an update rotated the cache but nothing
repointed the symlink, so the shell silently ran the binary current at setup
time. Setup now installs a minimal self-locating launcher (shipped
scripts/core-shim.sh) into the stable data dir atdata/bin/coreand points
~/.local/bin/coreat it; the launcher execs the self-update system's live
data/versions/currentpointer, which the orchestrator repoints on every
applied update — so the operator entry stays live with no further refresh.
Setup materializescurrent(via the read-onlycore update status, which
triggers the existing legacy→versioned migration) before installing the
launcher, and uninstall removes the symlink only when it points at the
launcher. Adds a/healthoperator_launcherpanel that verifies the whole
~/.local/bin/core→ launcher →versions/current→ binary chain and warns
when the symlink resolves anywhere other than the launcher, when the launcher
file is missing, or whenversions/currentdoes not resolve to an
executable, plus acore --launcher-checkdiagnostic.
v1.6.0
Added
- code-graph: capture Go call/reference target import-path qualifiers on
pending_edges(target_module) and classifyexternaledges bygo.modmodule membership in the resolution-rate metric (ADR-0038, Layer A). - code-graph: definition-side
pkg:qualifier on Go symbols + package-qualified resolution (precise cross-package linking,AMBIGUOUSproducer, cross-repodeferred) — ADR-0038, Layer B. - code-graph: qualified-target capture for TypeScript (def-side
pkg:qualifier from the repo-relative file path + call-sidetarget_modulefrom the import specifier), extending package-qualified resolution beyond Go; the resolution-rateexternalclassification is now language-aware so non-Go qualifiers classify bypkg:-existence rather than the Gogo.modmembership test — ADR-0038, Layer C. - code-graph: qualified-target capture for Python (def-side dotted-module
pkg:qualifier + call-sidetarget_modulefrom import statements, relative imports normalized against the source file) — ADR-0038, Layer C. - code-graph: definition-side namespace
pkg:qualifier for C# symbols (enclosing blocknamespaceor file-scoped namespace), extending package-qualified resolution — ADR-0038, Layer C. - code-graph: call-side C# qualified-target capture —
configs/csharp.jsonsupplies the@reference.callcaptures the inferred tagger pool omits forinvocation_expressionvia an additiveextra_tags_query(appended to, never replacing, the resolved query). Ausing-alias receiver yields a namespacetarget_module(the alias target's FQN minus its final type segment); a bare type resolved byusing-namespace search, a namespace-rooted member chain (ambiguous namespace/type split), andthis/local-variable receivers stay unqualified under the authoritative-qualifier invariant — ADR-0038, Layer C. - code-graph:
RunResult.Ambiguouscounter andRESOLVER_AMBIGUOUSdiagnostic event (candidate set) make the qualified-resolution ambiguous producer observable (ADR-0038 follow-up). - code-graph:
graph retagbackfills def-sidepkg:on already-indexed Go/TS/Python symbols without re-extraction and (by default) drains the per-repo resolver, closing the Layer B/C rollout resolution-rate dip;--resolve=falsefor tag-only runs. C# requires a re-index (ADR-0038 follow-up). - code-graph:
relationships.target_modulerecords the module qualifier that pinned each qualified-resolved edge (provenance) — ADR-0038 follow-up. - code-graph: the
/healthcode-graph-resolution panel now reports the terminal-externaledge count alongside the unresolved split (informational; the resolution-rate gate is unchanged) — ADR-0038 follow-up. - code-graph: one-hop re-export following — TypeScript barrel re-exports (
export * from,export { X } from) resolve through the barrel to the defining module, staying package-qualified (ADR-0038 follow-up). - code-graph: Python
__init__.pyre-export following —from .mod import Xin a package init resolvespkg.Xto its defining module via the shared one-hop follow (ADR-0038 follow-up). - code-graph: Go value-method calls on explicitly-typed receivers (
var x pkg.T, parameters) capture the receiver type's package qualifier;:=-inferred receivers stay unqualified (ADR-0038 follow-up). - code-graph: Java qualified-target capture — def-side
pkg:from thepackagedeclaration (and call-sidetarget_modulefrom imported FQNs where Java call edges exist), generalizing the C# lexical-namespace model. Kotlin/F# remain grammar-gated (ADR-0038 follow-up). - intake: copy the verbatim source file into
~/.anton-core/data/knowledge/<item-type>/<id><ext>after a successfulsave --source-path/bulk-import, so raw inputs survive DB loss (Layer 1 durability).bulk-importsummary gainscopies_written/copy_failuresand its per-filejsonlline gainscopy_failed;savereports the durable copy location assaved_path. Theitem save/item bulk-importoutput contracts now enumerate these keys.
Fixed
- code-graph: nested C# block namespaces now qualify with their full dotted path —
namespace Acme { namespace Web { … } }stamps def-sidepkg:Acme.Web, not just the innermostpkg:Web. The enclosing-namespace walk concatenates everynamespace_declarationancestor instead of returning at the first; the truncated form mismatched the call-side qualifier (full FQN minus its last segment), so nested-namespace symbols referenced through ausingalias could never resolve. Single dotted declarations (namespace Acme.Web) and file-scoped namespaces were already correct — ADR-0038, Layer C. - code-graph: a
CALLSedge now resolves against a method definition, not only a free function. The candidate lookup matched the call's conservativefunctionexpected-kind exactly againstitems.kind, so a call whose target is a method (every C# call, and most calls in any OO language) never resolved; the candidate-kind match now treats aCALLStarget as a callable (function or method). Pre-existing and latent — every prior resolve fixture used free functions — first exercised by C# call-side capture (ADR-0038, Layer C). - code-graph: the resolver now persists the normalized TypeScript/Python
target_module(def-sidepkg:form, e.g.src/util) on a pending edge that stays unresolved, instead of leaving the raw import specifier (./util). The language-aware resolution-rate reader testspkg:<target_module>existence, so a persisted raw specifier silently dropped an in-corpus relative/aliased-import gap asexternaland under-counted unresolved edges — ADR-0038, Layer C. - code-graph: nested-
go.modwalk — def-sidepkg:and resolution-rate module membership resolve against the nearest enclosing module, fixing monorepo misclassification (ADR-0038 follow-up). - code-graph: Python multi-segment relative imports (
from .pkg.mod import x) resolve to the nested module (ADR-0038 follow-up). - code-graph: TypeScript
index.tsimplicit resolution — an index module's def-sidepkg:is its directory (src/feature), matching./featureimports (ADR-0038 follow-up). - code-graph: guard the indexer's per-repo module-path cache (
Indexer.repoRootBySlug) with a mutex —repos syncindexes multiple repos concurrently through one shared indexer, so the unsynchronized cache read/write was a data race (pre-existing; mirrors the mutex the slug cache already takes) — ADR-0038 follow-up. - code-graph: a failed best-effort module-path persist (or stale-key sweep) during indexing now emits a
WARN MODULE_PATH_PERSIST_FAILED/MODULE_PATH_SWEEP_FAILEDevent instead of a bare log line. The Go resolution-rate metric classifies edges by module membership read from those rows, so a silently-lost write could otherwise leave thecode_graph_resolutionpanel falsely healthy — ADR-0038 follow-up. - code-graph:
graph retagnow reportsstatus: "partial"for a repo whosepkg:tags committed but whose post-tag resolver drain failed, so a status-only scan no longer reads a half-completed repo as fully successful — ADR-0038 follow-up.
Changed
- intake:
saved_pathnow records the durable copy location (<item-type>/<id><ext>) instead of the user's original path;retry --target extractionandpurge --target legacy-stubsresolve it against the data root and re-read the copy. Thesave --saved-pathflag is removed (the path is computed now). report health— thecode_graph_resolutionpanel no longer counts calls
into the standard library or unindexed dependencies as unresolved. The
resolution-rate reader excludes any pending edge whose target name is defined
nowhere in the indexed corpus (generalising the language-level externals
filter), so a single-repo index stops peggingwarningover expected
external call targets; a target the corpus does define stays counted, so the
ratio measures edges the resolver could link but has not. The panel detail
now readsN% of code-graph edges unresolvedrather thanN% orphaned items
— the figure counts edges, not graph nodes. Precise classification via
fully-qualified target monikers is tracked in
docs/adr/0038-code-graph-qualified-target-capture.md.
v1.5.0
Changed
- Distribution: anton-core now installs and self-updates from the public
xlightxyearx/anton-core-pluginrepo over unauthenticated HTTPS. The binary
fetch (wrapper + self-update orchestrator) drops thegh/token dependency;
first-runcore setupis tokenless. Go source stays private; the private repo
builds, signs, gates, and mirrors an allowlisted static surface + signed
binaries to the public repo. See docs/adr/0037-public-distribution.md.
Fixed
internal/db,internal/app— concurrent first-init of a freshcore.db
could fault the ncruces SQLite WASM driver (SIGSEGV) when two processes
raced to create+migrate the same file — the real shape being SessionStart +
UserPromptSubmit firing together on a first install. Two defenses: (1)
db.Opennow takes a blocking cross-processflock(2)on acore.db.init.lock
sidecar around the create+migrate sequence (internal/db/initlock_unix.go;
Windows no-op stub), so one process initializes and others wait then open the
migrated DB — WAL +busy_timeoutalready cover steady-state access; (2)
app.Runskips the auto-construct DB-open (and itsresolutionWarnThreshold
read) for--version/--help/helpinvocations viaargsNeedNoDB, so a
version/help query never opens — let alone migrates — a database.- CI (
.github/workflows/verify.yml,.github/workflows/release.yml) — pin
setup-goto an exact1.26.4instead of the floating1.26.x. setup-go
forcesGOTOOLCHAIN=local(so go.mod'stoolchain go1.26.4directive is inert
in CI), and1.26.xstill resolves to 1.26.3 in setup-go's version manifest, so
govulncheckanalyzed the unpatched 1.26.3 stdlib and thesupply-chainjob
failed on the two stdlib CVEs GO-2026-5037 / GO-2026-5039 (both fixed in 1.26.4).