Concepts can now carry relations. An opt-in ingest pass extracts them, packs ship the
resulting graph, and queries follow it. Alongside that: synto find, three new trace
subcommands, and alias-level concept curation. Most of the smaller work here — alias
management, advisory acks, and the Unicode and code-fence fixes — came out of
@VibeSan7's report from a real-world setup (#94).
Existing vaults migrate to schema v29 automatically on first run.
Added
-
Concept relation extraction, opt-in (#109). A third fast-model pass during ingest
extracts directed relations between known concepts ("Raft depends_on Consensus") into
newrelations/relation_evidencetables, each with a verbatim evidence
quote. Endpoints are restricted to known concepts and their aliases — relations the
model invents never enter the graph, though every raw candidate is kept in
relation_candidatesas an audit trail. Compiled articles surface their top 10
relations in arelations:frontmatter block. Off by default, since it adds one LLM
call per chunk; enable withrelation_extraction = trueunder[pipeline]. Relations
from a removed source are not garbage-collected yet. -
Concept graph in packs, and graph-aware queries (#109). Packs now include
graph/graph.json(nodes = concepts, edges = relations) and agraphcapability in
the manifest whenever relations exist; every edge endpoint resolves to a node, so
consumers can assume a closed graph.synto queryand the MCPanswer_questiontool
pull in up to 2 related articles via 1-hop expansion (confidence ≥ 0.7) — a no-op on
vaults without relations. -
synto find <query>(#109). Reverse lookup over the wiki, ranked: concept
name/alias match first, then title match, then first-paragraph body match. -
synto trace term|relation|citation(#109). Trace where a term occurs, the
verbatim evidence behind an extracted relation, or which articles consumed a source
segment. Extends the existingtrace article. -
synto concept alias add|remove|move(#101). The fast model sometimes attaches a
surface to the wrong entity (an npm package name landing as a project alias), with no
CLI remedy short of a merge.removedetaches the alias and records a denial tombstone
so the next ingest can't silently re-attach it;movere-points it to the right entity
in one step. Both un-rewrite the[[Canonical|Alias]]links the wrong alias had
produced, and denials survive astate.dbrebuild via the.synto/INDEX.jsonseed. -
synto maintaincan acknowledge known advisories (#102). Long-running vaults carry
advisories that are true by construction (e.g.graph_noise), which clutter the output
and hide new issues.[maintain] ack = ["graph_noise"]insynto.tomlcollapses them
into a one-line count; scope one to a single file with
"graph_noise:wiki/Some Note.md". Acks are display-only — the health score and
advisory count are unchanged.
Fixed
-
Article filenames are Windows-safe, with automatic drift repair (#107). A title
likeNULorFoo.produced a file Windows cannot create or round-trip, breaking
vault sync.sanitize_filenamenow de-reserves device names and strips trailing
dots/control chars; files named by the old rules are reported asfilename_drift
andmaintain --fixrenames them and repoints inbound links. -
CI now tests Python 3.14 (#107). Ubuntu runs the full suite on 3.11/3.12/3.14;
the Windows job runs the path-portability subset — widened to cover these changes —
on 3.11 and 3.14. The reporter's Windows + Python 3.14 environment was previously
untested anywhere; making the full suite Windows-clean is tracked in #108. -
missing_media,inline_tag, and orphan-mention scans skip code regions (#106).
Example embeds,#include-style directives, and titles inside code fences or inline
code were reported as advisories on code-heavy vaults. All three now mask code first
(like the #96 scanners), and the inline-tag regex accepts any-script letters so real
non-Latin tags are finally reported. -
Tags in any script survive sanitization and
maintain --fix(#105). Tag rules
stripped every non-ASCII character, so on non-English vaults LLM-proposed tags were
silently dropped and--fixdeleted valid Unicode tags (каталог,日本語,café)
from frontmatter. Rules are now script-agnostic and idempotent (NFC + lowercase
before filtering); ASCII tags behave exactly as before. Decomposed (NFD, macOS-style)
tags are normalized to NFC — a one-time frontmatter rewrite under--fixthat then
converges. -
Name matching folds non-ASCII case (#105). SQLite's
lower()folds ASCII only, so
the rename collision check and the concept-lookup substring fallback missed non-ASCII
case variants (e.g.Приветvsпривет). Both now compare via Pythoncasefold();
remaining legacylower()sites are tracked in #104. -
StateDB._tx()now opens a real transaction at depth 0 (#100). sqlite3's legacy
isolation mode only implicit-BEGINs before DML, so a nested_tx()'s SAVEPOINT issued
before any outer write silently became the outermost transaction and committed early —
a failedquery --synthesizefile write could leave a phantom DB row that blocked
re-synthesizing that question, and migration hooks documented as atomic were not.
Depth-0 frames now BEGIN explicitly, making nesting and DDL frames atomic as documented.
Full Changelog: v0.6.3...v0.7.0