Skip to content

Releases: xlightxyearx/anton-core-plugin

v1.11.1

20 Jun 05:26

Choose a tag to compare

Fixed

  • memory recall (and the dashboard Memory search) no longer fault on queries containing FTS5 metacharacters. A query bearing -, ., :, (, ), *, or a bare-word AND/OR/NOT — e.g. cut-release, v1.11.0 — was bound straight into items_fts MATCH ? unescaped, so FTS5 parsed the metacharacters as query syntax and raised SQLITE_ERROR, surfaced to the user as the opaque internal: 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's buildQuery, 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 a retrieval.fts.skipped warning — symmetric with the existing vec-arm skip log — instead of binding an empty MATCH ''.
  • memory recall no longer faults on the first recall against a cold/empty vector index when a real embedder is configured. vec1's flat items_vec index 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 like foo — raised SQLITE_ERROR (vec1: unexpected vector blob size N bytes, expected 0), surfaced to the user as the opaque internal: vec knn: sqlite error (code 1): internal. This hit the cold-start path: a real embedder enabled but nothing embedded yet (empty vec_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 (via intake.ErrStubEmbedderUnavailable) before KNN ever runs. vecKNN now short-circuits to an empty result when vec_index_map is empty — the bridge is co-populated with items_vec on 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 as internal. Independent of the FTS5-escaping fix above; discovered while testing it.

v1.11.0

20 Jun 02:15

Choose a tag to compare

Added

  • core --wrapper-check now reports bin_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/bin binaries left in superseded Claude Code plugin-cache version dirs (never the current version). The rm -rf is gated on the canonicalised target (cd … && pwd -P, plus a ..-component refusal), so a ..-laden CLAUDE_PLUGIN_ROOT or a symlinked version-dir/data component 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 a core-shim.sh signpost in the deferred-error detail instead of the generic network/setup string.
  • Build-tool versions are now pinned — golangci-lintv2.12.2, gofumptv0.10.0 — with the Makefile as source of record and a new verify-tool-versions gate (in verify-checks) that fails the build if the Makefile, verify.yml, and release.yml install 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 (discriminator bin_dir_in_managed_cache) and signposts the operator to run anton-core via ~/.local/bin/core (core-shim.sh) instead of the bundled scripts/core. Prevents the unbounded cache leak (~55 MB per orphaned copy).

v1.10.1

19 Jun 16:04

Choose a tag to compare

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 as internal. The four verbs' error-kinds sets are widened accordingly (cli-contract). Fixes the original database is locked-as-internal misclassification on the recall write path.
  • SQLite extension registration is unified onto driver-global auto-extensions. vec1 (and now fts5) are registered via sqlite3.AutoExtension in internal/db's package init() (internal/db/autoext.go), so every connection the ncruces driver opens carries both modules — replacing the per-connection loadVecExtension shim that ran inside db.Open between attachEvents and Apply(core). One mechanism, every connection; the MaxOpenConns(1) pin is no longer load-bearing for module presence. Recorded in ADR 0042.

Fixed

  • stale_lock_files health 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 sibling update.lock.pid holder record) and the bootstrap mkdir-mutex .bootstrap.lock (via the pid record inside its directory) — and detects staleness by probing the recorded holder PID with kill(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 for update.lock, but for .bootstrap.lock only 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 meaningless size_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. The lock-primitive glossary 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 locked on recall.
  • Data-dir resolver normalizes a root whose basename is data (e.g. over-specified CORE_DATA_DIR=<root>/data) down to <root>, so it can't mint a stray nested data/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_fts external-content tables plus sync triggers on every items / items_content write), so the bump would otherwise fault every recall and item write with no such module: fts5. The weekly dependency group now lands with ext/fts5 registered: github.com/ncruces/go-sqlite3 0.34.4 → 0.35.1 (its indirect wasm asset go-sqlite3-wasm/v2/v3) plus six golang.org/x/* minors (crypto 0.53.0, net 0.56.0, sync 0.21.0, sys 0.46.0, term 0.44.0, tools 0.46.0). Supersedes Dependabot #75.

v1.10.0

18 Jun 22:41

Choose a tag to compare

Added

  • CLAUDE.md routing-fragment auto-apply + sole applier. A new internal/fragment package and core fragment apply / core fragment status verbs are now the single writer/reader of the user's global ${CLAUDE_CONFIG_DIR:-$HOME/.claude}/CLAUDE.md routing fragment — sentinel-bounded splice (<!-- anton-core:start/end -->), atomic write, fragment.version read-back verify, --dry-run preview, and a FRAGMENT_APPLIED audit-events row. The SessionStart hook auto-applies a newer shipped fragment, bumps the pin, and announces via the envelope's top-level systemMessage ("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 own Read/Edit, eliminating the dual-applier drift risk and the hardcoded ~/.claude path. 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.enabled seeds true; migration 0018_core_embedder_enabled_default_on.sql flips pre-existing installs from the old false). 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 as embedder.load.failed (Warn) so the silent degrade stays diagnosable (a deliberately-disabled embedder logs nothing); core health reports the vec-arm state ("staged" / "enabled — run core system warm --target embedder" / "stub") from a cheap on-disk stat without loading the model. To light up an existing corpus after upgrading, run maintenance reindex --target knowledge (and --target code); new writes embed automatically. Set embedder.enabled=false to opt out. A new CORE_EMBEDDER_OFFLINE=1 env 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 structured EvaluateFragmentDrift; the dotted-numeric version compare consolidates into internal/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 the 08-hooks side-effects updated accordingly.

Fixed

  • Embedder batch reindex no longer exhausts the inference graph cache. internal/embed forward now pads input_ids/attention_mask/token_type_ids up 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-lived maintenance reindex --target {knowledge,code} previously accumulated >32 distinct sequence lengths and died at the 33rd with maximum 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 new embedmodel-tagged padding-identity test). On the batch path, maintenance reindex now 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 — WarmUp stays load-only).

v1.9.0

18 Jun 06:42

Choose a tag to compare

Added

  • Fail→success pattern mining: SessionEnd mines error→success tool-call pairs from the session transcript into linked error/pattern graph memories joined by a RESOLVES edge (items_pattern sidecar, 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); the SessionEnd envelope reports a patterns_redacted count. Additive, non-blocking, embedder-free.
  • recall --on-error: an error-shaped query boosts the RESOLVES-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 the RESOLVES walk and the bonus fires deterministically. Promotes retrieval.freshnessExponent / retrieval.hopDecay from hardcoded constants to operator-tunable config reads.
  • patterns list read verb and patterns.* config keys (patterns.enabled, patterns.pair_window_minutes (now >= 1), patterns.bash_target_tokens).
  • Maintenance purge_legacy_stubs job now also reaps orphan items_content rows (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-in intake.ModelAdapter.Embed that lights up the vector arm of recall, dark since launch (vec_index_map=0). Runs bge-small-en-v1.5 (384-dim, CLS-pooled + L2-normalized) via onnx-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 seeded embedder.{enabled,model,max_seq_tokens} keys (default off → byte-identical FTS-only behavior). Adds the missing knowledge-side items_vec write (shared memory.WriteItemVec, lifted from the codegraph indexer, which now embeds before its write tx for single-connection safety), a maintenance reindex --target {knowledge,code} [--dry-run] backfill emitting a reindex/BACKFILL_COMPLETE events row, and a core health "vec arm: live|stub" banner. No schema migration (items_vec is dimension-agnostic). The maintenance reindex envelope reports skipped_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-LLM Extract arm stays stubbed (separate task).
  • Automatic error-recall reflex (internal/recallonerror): an always-on PostToolUseFailure/Bash hook that, when a Bash command fails, recalls the RESOLVES-linked prior fix mined from your own history and injects it as additionalContext so 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 a recall_on_error.min_score floor; 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. Seeded recall_on_error.{enabled,cooldown_seconds,min_score} keys (default true / 30 / 0.5). Every terminal decision (injected or suppressed_*) is appended to the new events.recall_on_error_log ledger (migration 0017); recall-on-error stats rolls the ledger up by outcome (json/text) and recall-on-error doctor --error <text> reports the candidate pattern + score + would-inject for min_score calibration. The query is FTS5-escaped (raw stderr metacharacters would otherwise break items_fts MATCH), and pattern detection is by items_pattern sidecar 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/*_key form) that previously leaked through unmasked, plus JWTs and Authorization: Basic headers. 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 RESOLVES edge 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.
  • SessionEnd pattern-mining failures now emit a WARN/PATTERNS_FAILED row to the unified events log under the patterns source (matching the subsystem spec), and the PATTERNS_MINED success event is filed under patterns rather than hooks.
  • 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 SessionEnd envelope reports patterns_lines_malformed / patterns_decode_degraded so silent reader-side drops are diagnosable. A typo'd patterns.enabled value is logged before falling open.

v1.8.0

12 Jun 03:02

Choose a tag to compare

Added

  • core item list: multi-type filtering, absolute created bounds, sort, and pagination. --type is now repeatable and OR-joins within the set (--tag stays AND-joined); new inclusive --created-after / --created-before epoch-ms bounds compose (AND) with the duration-typed --since window; --sort created|updated|title|type|access_count is validated against an allowlist (unknown key → invalid_argument) with a deterministic id ASC tie-break; --asc flips the default DESC direction; --offset pages through the filtered set (negative → invalid_argument). The envelope gains total — the filtered count independent of limit/offset — so callers can drive pagination, and rows now carry summary + access_count. Spec: cli-shape:item-list updated (including the stale --since TIMESTAMP → duration correction), acceptance rows A-item-5/A-item-6 added, and the off-vocabulary --type Document fixtures in A-item-1..4 lowercased to the stored type vocabulary.
  • Dashboard /memory browse + 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=all clears 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/?before calendar date range (UTC day-start / day-end). Browse adds sortable column headers (title / type / created / access count, direction-flipping, backed by the item list sort allowlist), a total-items count, page-size-50 pagination via the newly vendored templUI pagination component (filter-preserving links, aria-current on 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 machine prefix:value tags 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 inclusive CreatedAfter/CreatedBefore epoch-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 through localStorage["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 + /graph view): ?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 a data-focus-id attribute 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 a console.warn evidence trail. Two isolate overrides land as an internal layer over the checkbox state (the code group 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 payload summary under the title (absent summary renders nothing). Riders from review: the in-graph search gains a visually-hidden role="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 + /graph view): 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 1200ms cameraPosition fly-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-shake keyframes in input.css, shortened to ~instant under prefers-reduced-motion) and carries a title explaining why; both clear on the next keystroke. The heat slider (0–100, default 0 with a live % readout) hides nodes whose heat falls below the cutoff — composed as a third conjunct inside the SAME single filter pass as the type/repo facets (one graphData() re-feed per change, links keep the both-endpoints-visible rule), with the same clampHeat normalisation the renderer's brightness curve uses.
  • Dashboard 3D graph (graph.js): gravity-well group clustering — every distinct node group gets 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 exposes Graph.d3Force but no global d3, so no forceX/Y/Z to 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-filter graphData re-feeds cannot reshuffle which well a group lives in.
  • /anton-core:dashboard [surface] skill — launch the browser dashboard without tying up a terminal. Probes 127.0.0.1:7777/healthz and 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 starts core dashboard as 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). The session-end hook 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-dir dashboard-reap.log; the match-and-kill guard is pinned by scripts/lib/wrapper_test/session_end_reap.bats. Skill count 21 → 22 (files_checked 27 → 28); CLAUDE.md fragment gains the Intent-Routing row, fragment-version 1.1.0 → 1.2.0. No binary changes — the skill orchestrates the existing core dashboard command.

Changed

  • core item get (and RunItemGet): item rows are now fully resolved — every row carries its sorted tags plus created / last_accessed ms-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-get replaces the stale never-emitted accessed_at prop...
Read more

v1.7.0

10 Jun 00:28

Choose a tag to compare

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-purpose verify-vendored-assets gate (wired into verify-supply-chain) recomputes each hash and asserts the co-located license text, closing the non-Go asset gap go-licenses cannot 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 committed output.css is embedded by a plain go build; a path-filtered css-drift job in verify.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-map gains a vendored-path exclusion so the vendored templUI tree is not held to the first-party doc-citation floor. Rationale in docs/adr/0039-dashboard-tailwind-templui.md.
  • Docs: docs/plugin-spec/09-subsystems/dashboard.md — operator usage doc for core 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-graph v1.80.0 WebGL recipe — the UMD bundle plus three.js r0.183.0 (ESM module + core), the UnrealBloomPass glow pass + its shader deps, and a first-party three-bloom-bootstrap.js ESM bridge — is vendored under internal/dashboard/assets/vendor/ and go:embed-ed (no Node build; shipped binary stays plain go build). THIRD_PARTY_LICENSES.md sha-pins all seven files (3d-force-graph MIT + three.js MIT) and verify-vendored-assets gates 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 resolved relationships edges, 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 JSON truncated flag and a server-log warning — never silently capped. Each node also carries a heat in [0,1] for the frontend's bloom heat-map: a degree floor (graphHeatDegreeFloor, structural — so hubs are legible before any access data accrues) plus an items.access_count freshness 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 shared ReadGraphData read fn (internal/codegraph/cmd).
  • Dashboard 3D graph (canvas): the /graph page 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 ESM three-bloom-bootstrap.js first so window.THREE is set before the UMD bundle reads it — graph renderer and UnrealBloomPass then 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 in 3d-force-graph stay pinned to the same revision (r183), and UnrealBloomPass keeps exposing the _basic blit material the transparency fix reaches into.
  • Dashboard 3D graph (heat-driven bloom): the graph blooms per-node by heat rather than glowing flatly, and renders the full code graph. Each node is an unlit sphere whose warm --graph-* hue is scaled by its server-side heat (0..1, via a scaleHex helper from a cold-visible warm floor HEAT_DIM up 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-link token, so the whole graph reads amber, not grey). The UnrealBloom pass is tuned (strength 0.9, threshold 0.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-scaled nodeVal).
  • Dashboard 3D graph (filter + inspect): the /graph view 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/symbol callers/callees page. A node-click also fires POST /graph/touch?id= — the one write surface under /graph, so every GET stays read-only — which bumps items.access_count via retrieval.Apply (source dashboard-graph), so exploration warms nodes: freshness (hence heat, hence bloom) now accrues from real graph use, not only recall. The endpoint 400s a missing id and 404s a present-but-unknown one (the same existence guard /graph/symbol uses, so no phantom access-log row); the client-side bump is best-effort (a failed POST never breaks inspection). The app shell's main also gains min-w-0 so 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-read Execute dispatch (no query_log write) plus typed ExecuteWalk/ExecutePaths/ExecuteCycles/ExecuteDependents wrappers, and refactor Runner.Run to wrap Execute. Behavior-preserving for the CLI (one log row per fire); enables the read-only dashboard Graph surfaces.
  • setup skill: reworked /anton-core:setup into 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 --check implemented as a status probe (no install-state change). No behavior is removed; all existing flags keep working.

Fixed

  • core dashboard Memory 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 any summary.RunSummary caller): resolve the owner from config before acquiring the rollup's pooled connection. Previously RunSummary held the single SetMaxOpenConns(1) connection across a DB-backed Config.GetOwner, self-deadlocking whenever the config reader was cfgpkg.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/templ v0.3.1020 — typed HTML views for the dashboard (runtime library + a go tool directive for its code generator).
  • Consolidated the weekly Dependabot batch into one PR (supersedes #60, #61, #62, #63):
    • bump github.com/odvcencio/gotreesitter 0.19.1 → 0.20.2.
    • bump github.com/ncruces/go-sqlite3 0.34.3 → 0.34.4 (pulls indirect github.com/ncruces/go-sqlite3-wasm/v2 2.5.35301 → 2.6.35302).
    • bump actions/create-github-app-token v2.2.2 → v3.2.0 (release workflow).
    • bump actions/checkout v6.0.2 → v6.0.3 (verify + release workflows).
  • Regrouped .github/dependabot.yml under a multi-ecosystem-groups weekly group so future gomod + github-actions bumps arrive as a single PR instead of fanning out.

v1.6.1

05 Jun 23:41

Choose a tag to compare

Fixed

  • operator-shell: a bare-shell core (via the ~/.local/bin/core PATH symlink)
    no longer goes stale after a /plugin update. The symlink previously pointed
    at ${CLAUDE_PLUGIN_ROOT}/scripts/core in 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 at data/bin/core and points
    ~/.local/bin/core at it; the launcher execs the self-update system's live
    data/versions/current pointer, which the orchestrator repoints on every
    applied update — so the operator entry stays live with no further refresh.
    Setup materializes current (via the read-only core 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 /health operator_launcher panel 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 when versions/current does not resolve to an
    executable, plus a core --launcher-check diagnostic.

v1.6.0

05 Jun 14:51

Choose a tag to compare

Added

  • code-graph: capture Go call/reference target import-path qualifiers on pending_edges (target_module) and classify external edges by go.mod module 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, AMBIGUOUS producer, cross-repo deferred) — ADR-0038, Layer B.
  • code-graph: qualified-target capture for TypeScript (def-side pkg: qualifier from the repo-relative file path + call-side target_module from the import specifier), extending package-qualified resolution beyond Go; the resolution-rate external classification is now language-aware so non-Go qualifiers classify by pkg:-existence rather than the Go go.mod membership test — ADR-0038, Layer C.
  • code-graph: qualified-target capture for Python (def-side dotted-module pkg: qualifier + call-side target_module from import statements, relative imports normalized against the source file) — ADR-0038, Layer C.
  • code-graph: definition-side namespace pkg: qualifier for C# symbols (enclosing block namespace or file-scoped namespace), extending package-qualified resolution — ADR-0038, Layer C.
  • code-graph: call-side C# qualified-target capture — configs/csharp.json supplies the @reference.call captures the inferred tagger pool omits for invocation_expression via an additive extra_tags_query (appended to, never replacing, the resolved query). A using-alias receiver yields a namespace target_module (the alias target's FQN minus its final type segment); a bare type resolved by using-namespace search, a namespace-rooted member chain (ambiguous namespace/type split), and this/local-variable receivers stay unqualified under the authoritative-qualifier invariant — ADR-0038, Layer C.
  • code-graph: RunResult.Ambiguous counter and RESOLVER_AMBIGUOUS diagnostic event (candidate set) make the qualified-resolution ambiguous producer observable (ADR-0038 follow-up).
  • code-graph: graph retag backfills def-side pkg: 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=false for tag-only runs. C# requires a re-index (ADR-0038 follow-up).
  • code-graph: relationships.target_module records the module qualifier that pinned each qualified-resolved edge (provenance) — ADR-0038 follow-up.
  • code-graph: the /health code-graph-resolution panel now reports the terminal-external edge 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__.py re-export following — from .mod import X in a package init resolves pkg.X to 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 the package declaration (and call-side target_module from 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 successful save --source-path / bulk-import, so raw inputs survive DB loss (Layer 1 durability). bulk-import summary gains copies_written / copy_failures and its per-file jsonl line gains copy_failed; save reports the durable copy location as saved_path. The item save / item bulk-import output 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-side pkg:Acme.Web, not just the innermost pkg:Web. The enclosing-namespace walk concatenates every namespace_declaration ancestor 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 a using alias could never resolve. Single dotted declarations (namespace Acme.Web) and file-scoped namespaces were already correct — ADR-0038, Layer C.
  • code-graph: a CALLS edge now resolves against a method definition, not only a free function. The candidate lookup matched the call's conservative function expected-kind exactly against items.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 a CALLS target 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-side pkg: 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 tests pkg:<target_module> existence, so a persisted raw specifier silently dropped an in-corpus relative/aliased-import gap as external and under-counted unresolved edges — ADR-0038, Layer C.
  • code-graph: nested-go.mod walk — def-side pkg: 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.ts implicit resolution — an index module's def-side pkg: is its directory (src/feature), matching ./feature imports (ADR-0038 follow-up).
  • code-graph: guard the indexer's per-repo module-path cache (Indexer.repoRootBySlug) with a mutex — repos sync indexes 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_FAILED event 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 the code_graph_resolution panel falsely healthy — ADR-0038 follow-up.
  • code-graph: graph retag now reports status: "partial" for a repo whose pkg: 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_path now records the durable copy location (<item-type>/<id><ext>) instead of the user's original path; retry --target extraction and purge --target legacy-stubs resolve it against the data root and re-read the copy. The save --saved-path flag is removed (the path is computed now).
  • report health — the code_graph_resolution panel 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 pegging warning over 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 reads N% of code-graph edges unresolved rather than N% 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

03 Jun 05:19

Choose a tag to compare

Changed

  • Distribution: anton-core now installs and self-updates from the public
    xlightxyearx/anton-core-plugin repo over unauthenticated HTTPS. The binary
    fetch (wrapper + self-update orchestrator) drops the gh/token dependency;
    first-run core setup is 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 fresh core.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.Open now takes a blocking cross-process flock(2) on a core.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_timeout already cover steady-state access; (2)
    app.Run skips the auto-construct DB-open (and its resolutionWarnThreshold
    read) for --version/--help/help invocations via argsNeedNoDB, so a
    version/help query never opens — let alone migrates — a database.
  • CI (.github/workflows/verify.yml, .github/workflows/release.yml) — pin
    setup-go to an exact 1.26.4 instead of the floating 1.26.x. setup-go
    forces GOTOOLCHAIN=local (so go.mod's toolchain go1.26.4 directive is inert
    in CI), and 1.26.x still resolves to 1.26.3 in setup-go's version manifest, so
    govulncheck analyzed the unpatched 1.26.3 stdlib and the supply-chain job
    failed on the two stdlib CVEs GO-2026-5037 / GO-2026-5039 (both fixed in 1.26.4).