Changelog & versions

What each version of ugraph actually contains, and the reasoning behind the decisions that shaped it. Entries are written for someone deciding whether to upgrade, so each one says what it protects, not just what it touched.

Current version: 0.1.0. This is the first complete build of ugraph as a standalone, portable tool. It is not yet tagged or published to PyPI (that is UG-02 in the backlog), so no release dates are shown. When 0.1.0 is tagged, this page gets its date and nothing else about the entry changes.
0.1.0
untagged · current build
current

The release that turns a personal Obsidian script into a tool someone else can run. Every path is resolved from configuration, every check is a library function the CLI merely formats, and the claim the whole format rests on, that a quote is verbatim, is finally verified by a program instead of asserted by an agent.

The command surface

  • Ten subcommandsinit, ingest, index, lint, verify, status, graph, ledger, extract, skills. The CLI stays deliberately thin: each one resolves a config, calls a single library function, and formats the result. Logic that lives in a CLI cannot be tested or reused, so none does.
  • Every command is importablefrom ugraph import config, indexes, lint, status, verify. Formatting, exit codes and argument parsing belong to the CLI and nowhere else.
  • Shared selectors--newest N, --since 7d|2w|3m|1y|YYYY-MM-DD and --channel are defined in one place so they cannot come to mean different things in different commands.

Verification — the headline change

  • ugraph verify checks every quote against the immutable transcript. A verbatim_quote must be a literal substring; a timestamp must be a marker that really exists, with the quote inside that marker's paragraph. Extraction agents reported verifying this. Nothing ever checked it.
  • Two passes, deliberately asymmetric. Machine output is held to a strict standard because a program cut it from the transcript. Human-edited prose is checked timidly, because quotes get re-wrapped, elided, and surrounded by paraphrase, and the checker stays silent rather than accusing a page wrongly. A checker that cries wolf gets muted, and this one has to survive being trusted.
  • Nothing is ever repaired. Machine captions carry their own disfluencies and mistranscriptions, preserved inside quotes on purpose.
  • A weak model became safe to use. Because a paraphrase fails a substring test rather than a trust check, a small local model can do Phase A at the cost of retries: the generation–verification loop applied to the tool that documents it.

Portability

  • The KB root is configuration, not an import. Resolution order: --kb, $UGRAPH_KB, the kb key in the nearest ugraph.toml, that file's own directory, then the working directory if it looks like a KB. The previous implementation hardcoded one vault path; that single fact was all that stood between a personal script and a tool.
  • store.py replaces the borrowed vault_io module. Markdown with YAML frontmatter, JSON state, plain-text logs. No database, because the premise of the format is that an agent needs nothing but a filesystem.
  • Page identity is a POSIX-separated relative path on every platform, since that string appears in links and has to be the same on every machine.
  • init refuses to scaffold into a vault root. Pointing it at an Obsidian vault used to scatter concepts/, entities/, raw/ and sources/ among real folders, write an index.md that could clobber a note, drop the config file outside the vault, and then report every personal note as malformed. It now refuses and names where to put it instead.
  • Relative candidates paths resolve against the KB root, like every other path in ugraph.toml. They previously resolved against the working directory, so the setting appeared to do nothing depending on where you ran the command from.

The format, made executable

  • Seven page types with enforced contractsconcept, entity, source, raw-transcript, moc, overview, and note for human-facing pages held to almost no contract on purpose.
  • Eight typed relationship headings with loose reciprocity: if A links to B under a typed heading, B must link back under some typed heading. sources is the exception: a transcript never links forward to its readers.
  • Wikilinks are a schema error inside the strict tree. A [[wikilink]] only resolves inside the tool that invented it, and the format's premise is that a plain filesystem is enough to follow an edge.
  • _mocs/ is scaffolded. It is part of the strict tree and policed by the linter, so leaving it uncreated meant the first person to add one discovered the rule by tripping over it.
  • A closed taxonomy, falling back to the packaged vocabulary so a bare init produces something that lints.

Gates you can wire into CI

  • lint, verify and index --check exit non-zero and stay quiet on success, so they drop into a pre-commit hook or a CI job unchanged. lint --warnings promotes warnings to failure; lint --report PATH writes markdown.
  • Index rebuilds are byte-identical. Same contents in, same bytes out, which is what makes --check meaningful: if a rebuild would change any file, the working tree is lying about what the KB contains.
  • Unrecognized groups land under "Other" rather than being silently dropped.
  • Parse failures are never silently skipped in a structural check. Dropping unreadable pages would produce a confident answer about a KB that was never fully read, so strict is the default and the caller has to opt out deliberately.

Ingest

  • Any-input capture — clipboard, paste, pipe, file, or URL lands as an immutable raw/ capture plus a stub sources/ page. No model involved; ingest only transports content, it never invents it.
  • Incremental and resumable. State is checkpointed after every written source rather than once at the end, so large runs are safe to interrupt and safe to re-run in whatever batch size you like.
  • --newest N on batch ingest for bounded pulls without paying for a full metadata scan first.
  • --retry-failed reconsiders sources previously recorded as unfetchable.

Extraction — opt-in, never in the core

  • Three backends. claude-code hands the work to the agent through the installed skill; ollama runs locally over plain HTTP with no new dependency; api uses Anthropic or OpenAI through the optional extra.
  • Phase A only. Reading one transcript and emitting candidates with verbatim quotes and timestamps is mechanical work. Phase B, deciding which candidates merge into which canonical page, is deliberately excluded: it needs every candidate in view at once, it is where the value of the format lives, and there is no mechanical check for getting it wrong.
  • One prompt contract shared by the API backend and the agent skill, so the two cannot drift.
  • The deterministic core calls no model at all. Everything here is an extra.

Knowing where you stand

  • ugraph ledger — six lifecycle stages (discovered → pulled → extracted → synthesized → linked → verified) derived from the files on every run, never stored in frontmatter. A stored stage would be a second source of truth and would drift the first time somebody edited a page by hand.
  • Transitions are appended to a log, because derivation can say where a source is but not when it got there or what failed on the way. --stuck DAYS surfaces material pulled but unprocessed.
  • The ledger keys on the universal slug, not on anything video-shaped, so a future blog, newsletter or podcast adapter appears in every report the day it lands, with nothing added to the ledger.
  • ugraph status splits cleanly into collect() (every number, no formatting) and render() (one terminal view), with --thin for concepts still resting on a single source.
  • Undated pages sort last and are dropped by --since. A naive descending sort put undated pages first, so --newest 3 would confidently return pages whose date nobody knows as the three most recent. A page that cannot prove it falls inside the window is not in the window.

Seeing the graph

  • Six export formatsjson, graphml, dot, canvas for Obsidian, obsidian-groups, and d3 for a standalone interactive HTML page with force layout, zoom and drag.
  • --concepts-only and --no-provenance, because sources outnumber concepts and swamp a visual.
  • The export stays derived and disposable. Markdown is the source of truth; regenerating a 64 KB export costs milliseconds, so there is no reason to make it authoritative.

First run

  • An interactive ugraph init that gets a stranger from nothing to a working knowledge base without reading anything. Every question has a sensible default and every answer is also a flag, so scripts and CI never see a prompt.
  • ugraph skills install writes the agent instructions into .claude/skills, so the agent doing Phase A learns the format from the same contract the linter enforces.

Known gaps in this version

  • More automatic fetch adapters (RSS, podcast, web page) are still on the backlog; today the primary path is any text you can land via the CLI into the same raw/ and sources/ pair — UG-05, UG-06.
  • templates/ and skills/ resolve from a source checkout when the packaged copy is absent — UG-01.
  • No published package and no tagged release yet — UG-02.
  • No automated test suite over the three gates — UG-03.
0.0.x
pre-tool · superseded
history

The scripts ugraph grew out of. Kept here because every constraint in 0.1.0 is a direct answer to something that went wrong at this stage.

What it was

  • A single hardcoded Obsidian vault path, imported rather than configured. Everything else in the toolchain was already portable; only the root was not.
  • A borrowed vault_io module from a personal setup, replaced wholesale by store.py.
  • Extraction agents that reported verifying their own quotes, with no program ever checking the claim. This is the gap verify.py was written to close.
  • No conformance gate, so structural drift was only discovered by reading pages.

Versioning policy

ugraph versions two things at once: a CLI and an on-disk format. The second is the one that can cost you work, so it governs.

BumpMay changeMust not change
PATCH
0.1.x
Bug fixes, output wording, performance. A finding the linter should always have reported. Frontmatter contracts, exit-code meanings, index byte output, the library API.
MINOR
0.x.0
New commands, new flags, new ingest adapters, new export formats, new optional lint warnings, additive frontmatter fields. Anything that makes a knowledge base valid under the previous version stop linting. No new errors without a major bump.
MAJOR
x.0.0
Required-field changes, renamed directories or relationship headings, removed commands, changed exit-code meanings. Ships without a documented migration path and a version of lint that names every page needing a change.
Below 1.0. The format is still settling. Until 1.0, a minor bump may tighten a warning but never promote one to an error, and every change that touches SCHEMA.md is called out in its changelog entry by name.
Your transcripts are safe across every bump. raw/ is immutable by contract. No version of ugraph, past or planned, rewrites a transcript. It is the evidence everything else is checked against.
How to read an upgrade. Run ugraph index --check and ugraph lint before and after. Any new finding is either listed in the entry above or a bug worth reporting.

Next up is on the backlog.

Packaging, a test suite over the three gates, then a second ingest adapter, with the reasoning for each written down.

Product backlog Back to overview