Journal — 2026-06-22
OKF (Google Open Knowledge Format) alignment — step 1: store frontmatter
Google published OKF v0.1 (2026-06-12): an org-knowledge format that is
near-identical to demarkus's own model — directory bundles of markdown,
cross-link relationship graph, index.md hubs, path-minus-.md as concept
identity. Spec: github.com/GoogleCloudPlatform/knowledge-catalog/blob/main/okf/SPEC.md
The one real seam
OKF carries metadata in-body as YAML frontmatter (required type;
recommended title/description/resource/tags (a YAML list)/timestamp).
demarkus carries metadata out-of-band and treats a body that opens with
--- as literal content. So syntactically identical (--- frontmatter), but
different data model: in demarkus the on-disk --- block is a store-owned
envelope (version/previous-hash/archived + publisher data), it is
stripped before serving, and the catalog reads only the publisher keys.
Naively publishing a raw OKF file double-wraps the frontmatter and the OKF
type/tags never reach the catalog.
Decision — split namespace in store frontmatter (protocol/store/store.go)
Pre-1.0, so changed the storage format. Promote the recognized OKF field names to bare frontmatter; keep everything else namespaced:
- Recognized OKF keys (
type,title,description,resource,tags,timestamp) → written bare.tagsserialized as a YAML flow list[a, b]to match spec. - Non-spec publisher keys (
importance, custom) → keep themeta.prefix. - Store-operational (
version,previous-hash,archived) → bare, unchanged.
The non-obvious why
The meta. prefix was not decoration — it was the integrity boundary that
stopped a publisher from supplying archived: true / version: 999 and forging
store state (extractMetadata read only meta.*). Dropping it for the OKF
fields means that boundary now has to be enforced by name: added a
reservedMetaKeys denylist (validateMeta rejects them) and extractMetadata
excludes them. Keeping arbitrary publisher keys under meta. preserves the
boundary for the open-ended set — only a closed, known set of OKF names goes
bare.
Key property that kept the change contained: the in-memory metadata map stays
bare-keyed, so catalog/handler/filter were untouched. Only buildVersionFile
(write) and extractMetadata (read) changed. tags round-trips list↔csv, so
the map contract ("a,b") is unchanged.
Back-compat: extractMetadata still reads old meta.tags/meta.type writes —
live soul.demarkus.io data and existing immutable versions keep working.
What this does NOT do
Vocabulary alignment only. The frontmatter is still stripped before serving
and the store's versions/ layout is not an OKF bundle tree — so demarkus does
not yet serve or ingest OKF bundles. That is the next layer: an import/export
codec (and possibly server-native bundle serving). Because the stored field
names are now the literal OKF names, that codec is 1:1 rather than a translation
table.
Open scoping question parked: import-only vs round-trip codec vs server-native
content-negotiated bundle serving; and where it lives (tools/demarkus-okf CLI
vs client/okf package).
OKF — spec + ADR follow-up
Documented the alignment rather than claiming compatibility:
docs/SPEC.md§9.4 rewritten to match the code (was already stale — never documentedarchivedor the oldmeta.keys). Now specifies reserved operational fields, bare OKF field names,tagsas a YAML flow list, themeta.prefix for non-spec keys, and that the store block is stripped before serving. §8.1 notes arbitrary publisher metadata + reserved-key rejection. §13 Future Extensions adds OKF interop as a planned codec, not a conformance claim.docs/adr/0002-okf-metadata-alignment.mdrecords the split-namespace decision and the integrity-boundary reasoning.
Framing decision (Fritz agreed): do not assert "OKF compatible / superset" in normative spec text yet — it would overclaim. Accurate framing is two-layer: a single demarkus document's content model is OKF-compatible (the doc itself), but at the system level demarkus is a superset — it builds versioning + hash chain + QUIC + capability auth + LOOKUP around an OKF-compatible document. "The doc is OKF; we built more around it." Earn the compatibility badge with the codec + a conformance check against OKF sample bundles.
Publisher metadata caps raised (10/512 → 50/1024)
Bumped while sizing for OKF producer-defined fields. Key insight: MaxMetaBytes
(sum of key+value lengths) is the binding limit, not key count — the 6 OKF
fields + importance already use ~7 keys / ~350 bytes, so you hit the byte wall
before the key wall. Doubling keys alone would have bought little.
Final: MaxMetaKeys 10→50, MaxMetaBytes 512→1024, maxStoreFrontmatter
1024→2048 (must cover 1024 bytes meta + operational fields + per-line
serialization overhead; worst case ~1.5 KB on disk). 50 keys is generous
headroom but harmless — the 1024-byte total still bounds real on-disk size.
Pre-1.0 is the cheap moment to right-size a wire limit. Constants in
protocol.go + store.go; both store and handler validation read the shared
constants. SPEC §9.4 updated. Boundary test now ties to MaxMetaKeys+1 with
short keys so it isolates the key-count limit and won't rot on future bumps.
Gotcha: tags counted as csv but stored as a longer YAML list
MaxMetaBytes (1024) is validated on the metadata map, where tags is a csv
string ("a,b"), but on disk tags serializes as a YAML flow list
([a, b]) — N+1 bytes longer for N tags. So the byte cap undercounted
the real on-disk size, and a tag-heavy doc could pass validation then bloat the
frontmatter. Adversarial worst case (~460 single-char tags + 49 tiny keys)
reached ~2005 of the 2048 maxStoreFrontmatter budget — safe but only ~40 B
margin, far thinner than the 1024 number suggests.
Fix: extracted store.SerializedMetaSize(key, value) — counts the value at its
on-disk length (tags → formatTagsList) — and used it in BOTH the store
validateMeta and the handler size check (they duplicated the accounting; per
guidelines, the tricky piece now has one source of truth). The 1024 cap is now
an honest on-disk bound, so maxStoreFrontmatter's margin is provable (worst
case ~1542 < 2048) and a doc that passes meta validation always fits the
frontmatter. Regression test: 400 one-char tags (csv 803 B passes, serialized
~1204 B rejected). protocol.go MaxMetaBytes comment updated to say values are
counted as serialized.
OKF codec — first slice: demarkus okf validate landed
New package client/internal/okf (parse primitives import/export will reuse) +
demarkus okf validate command (wired into the main.go subcommand switch).
Primitives: SplitFrontmatter (CRLF + EOF-delimiter tolerant), ParseFrontmatter
(flat string map; flow [a,b] and block - a lists → csv; tolerates comments;
errors on malformed lines), ConceptID, IsReserved. Reuses client/links
(goldmark) for link extraction — no new deps.
ValidateBundle checks OKF v0.1 conformance: every non-reserved .md has
parseable frontmatter with non-empty type (Error); non-root index.md with
frontmatter (Error); broken/escaping internal .md links (Warn, per spec's
tolerate-broken-links rule); log.md non-ISO date headings + non-okf_version
root-index keys (Warn). Findings sorted (path, message) for stable output.
Command: --strict (warns→failure), --quiet; exit 1 on errors.
Deliberately hand-rolled the frontmatter parser (no YAML dep) to match the
project's existing manual-frontmatter approach in the store. Limitation: handles
scalars/flow/block lists, not nested maps or multi-line scalars — fine for OKF
v0.1's flat field set; revisit if a sample bundle needs more. Next slices:
import (bundle → world, enforcing the metadata caps with sanitize-on-overflow)
then export. Tested against synthetic bundles; should run it against Google's
GA4/StackOverflow/Bitcoin reference bundles once import exists.
OKF codec — second slice: demarkus okf import landed
client/internal/okf/import.go + demarkus okf import [--dry-run] [--auth] [--insecure] <bundle-dir> <mark://host/prefix>.
Pure transform BuildImport(root, prefix) []PublishItem (no network, fully
tested): per file — SplitFrontmatter, ParseFrontmatter, map OKF fields → demarkus
metadata (recognized names are identity), strip frontmatter from body, rewrite
bundle-absolute links under the prefix. The command publishes each item via
fetch.Client.Publish with expectedVersion -1 (upsert; content-hash dedup means
re-import of an unchanged bundle creates no new versions). The target URL's path
IS the prefix (via ParseMarkURL) — no separate flag.
Decisions / non-obvious bits:
- Metadata sanitation, never silent. Invalid keys (e.g.
data_steward) sanitized todata-stewardwith a warning; un-sanitizable or reserved keys (version/archived/previous-hash) dropped with a warning. Addedstore.IsReservedMetaKeyso import reuses the store's reserved set rather than duplicating it. - Cap enforcement reuses
store.SerializedMetaSize(honest tags-as-list accounting). Overflow drops lowest-priority keys first via a priority ladder (title>tags>type>importance>description>resource>timestamp>producer), each drop warned. Kept caps at 50/1024 per earlier decision; sanitize-on-overflow. - Reserved files (index.md/log.md) published verbatim; root index.md's
okf_version frontmatter is stripped from the body (else it double-wraps) and
kept as
okf-versionmetadata. - Link rewriting: only bundle-absolute (
/x.md) links get the prefix — relative links resolve unchanged since the tree is preserved. Limitation: empty-text links[](/x.md)aren't rewritten (ExtractWithPositions reports no bracket span); rare, noted.
Deferred: live server round-trip. Server-side acceptance of bare OKF metadata +
tags-list round-trip is already covered by handler/store unit tests, and the
wire path by other CLI tests, so the marginal risk is low and the setup (TLS +
TOML tokens + 2 procs) is high. Becomes natural once export lands: import →
export → diff. Next slice: export.
OKF codec — third slice: demarkus okf export + verified live round-trip
client/internal/okf/export.go + demarkus okf export [--auth] [--insecure] <mark://host/prefix> <out-dir>. Completes the codec.
Pure BuildExport(docs, prefix) []BundleFile (no network): per doc — strip the
world prefix from path and bundle-absolute links, reattach OKF frontmatter from
metadata (canonical field order: type, title, description, resource, tags,
timestamp, then producer keys sorted). Synthesizes a type (default "Document")
when absent and timestamp from the doc's modified time. tags re-serialized
via the shared store.FormatTagsList (one source of truth with the on-disk
form). Reserved files written body-only; root index.md re-emits okf_version
from the okf-version metadata the importer stashed.
Refactors / reuse:
- Extracted
mapAbsLinks(body, fn)from import's link rewriter; import prepends the prefix, export strips it (boundary-safe: only/pfxor/pfx/...). yamlScalarquotes only when a bare scalar would reparse differently (:, trailing:,#, surrounding space, empty, leading YAML indicator). URLs (no:) stay bare.- Command:
enumerateDocsrecurses LIST (the LIST verb always lists, never serves index.md — confirmed in handler;versions/already filtered by ListDir).publisherMetastrips server-owned response keys (status/version/ modified/etag/content-hash/current-version/entries).
Bug found + fixed via the live test: import treated PUBLISH status created as
failure — a new-doc publish returns created, only an update returns ok. Now
accepts both.
Live round-trip verified (self-signed server on :16309, minted read+publish
token): import bundle → on-disk frontmatter exactly as designed (bare
tags: [sales, revenue], title, type; meta.data-steward for the sanitized
producer key; absolute links rewritten under /vendor, relative preserved) →
re-import upserts with content-hash dedup (no new versions) → export → validate
reports 0 errors / 0 warnings → exported body byte-identical to original, links
restored to bundle-relative, root okf_version round-tripped.
Codec complete: validate + import + export, all three demarkus okf
subcommands, shared logic in client/internal/okf, no protocol/server changes
beyond the two small exported store helpers (IsReservedMetaKey, FormatTagsList).
Next: point validate/round-trip at Google's real GA4/StackOverflow/Bitcoin
sample bundles as fixtures; consider log.md generation from VERSIONS on export.
OKF codec — timestamp format handling closed
Closed the gap where incoming timestamp values were passed through unchecked.
- validate:
validateConceptnow warns when a presenttimestampdoesn't parse as RFC 3339 (isRFC3339). Stays a Warn — OKF lists timestamp as recommended, not a hard conformance rule. Refactored validateConcept to accumulate findings (type error + timestamp warn) instead of returning early. - export:
normalizeTimestamp(declared, modified)re-emits a present value in canonical RFC 3339 (accepts RFC 3339 or date-onlyYYYY-MM-DD, converts offsets to UTC); empty or unparseable → falls back to the doc'smodified(already RFC 3339). So exported timestamps are always canonical.
Required type was already covered: validate errors on missing/empty; export
synthesizes type: Document; import warns but stays permissive. Tests added for
both timestamp paths. All client tests + pre-commit green.
OKF made a core write-time value: default type on publish
Fritz wanted OKF conformance to be an opinionated, core demarkus value enforced on write. Pushed back on the obvious hard-reject gate: it would break every existing writer (soul.demarkus.io, knowledge broker worlds, journals, ADRs, docs site — all publish typeless markdown via mark_publish), and OKF's own first principle is "minimally opinionated." There was also no existing policy/gate machinery in the codebase to hook into.
Chose opinionated-by-construction: on PUBLISH, when no type is declared, the
server assigns Document (protocol.OKFDefaultType) — applyOKFTypeDefault in
handlePublish, after extractPublisherMeta. Reserved files (index.md, log.md)
exempt; explicit types preserved. Same constant backs export's synthesis (one
source of truth). ADR 0003 records it; SPEC §6.4 + §14 updated.
Now every served demarkus document carries an OKF type by construction — maximal per-doc conformance at write time. Still not a served bundle (frontmatter stripped, versions/ layout); full bundle conformance stays an export concern.
Gotcha caught by the existing dedup test: the default is real metadata, so a
legacy doc (written without type) republished identically now differs by the
added type → one new version, then dedups. Fixed the test to seed v1 with
type:Document; documented the one-time bump in SPEC + ADR. Deferred: a strict
reject mode as opt-in config/per-world policy (never global default).
OKF type default folded into APPEND too
Extended applyOKFTypeDefault to handleAppend (after extractPublisherMeta), so
every write path — PUBLISH and APPEND — guarantees a typed OKF concept.
Noteworthy finding while doing it: store.Append does not merge metadata —
it passes the request's metadata straight to WriteVersion, so an appended
version's publisher metadata comes from the APPEND request alone, replacing the
prior version's. An append with no metadata therefore drops the doc's
tags/title/type. With the default applied, a typeless append at least lands
type: Document instead of nothing. The broader "append replaces rather than
carries forward metadata" behavior is pre-existing and orthogonal — flagged as a
possible PR-comment item (should append inherit the prior version's metadata when
the request omits it?). SPEC §6.4/§6.6 + ADR 0003 updated to cover APPEND; test
added. All suites + pre-commit green; restaged on feat/okf-compatibility.
Shipped — OKF compatibility merged (#195)
Merged to main as squash commit 69c1473 feat(okf): Open Knowledge Format compatibility (#195). Whole feature in one PR: store metadata alignment (bare
OKF field names, tags-as-list, 50/1024 caps, reserved-key boundary), the
demarkus okf validate/import/export codec, server default type on publish +
append, SPEC updates, ADR 0002/0003. CodeRabbit review addressed (7 fixed:
export traversal guard, import symlink rejection, LIST decode/cycle hardening,
reserved-key read guard, cap re-validation→bad-request, link query strip, test
assertions; 1 skipped: SPEC markdownlint, not CI-gated, deferred to roadmap Docs
Hygiene backlog). Local feature branch deleted.
/soul-join — managed remote souls + catalog + project binding (built, branch feat/soul-join)
Problem Fritz raised: three demarkus surfaces configurable at once (hand-wired
.mcp.json "pure MCP", local memory plugin, knowledge plugin) all expose
identical mark_* tools, so the agent gets confused which to write to. Live
proof: this project uses demarkus-soul → soul.demarkus.io via hand-wired
.mcp.json, while the memory plugin's local backend is down — two "soul"
surfaces, one tool name. I conflated them in conversation, which is the bug.
Decisions (Fritz): build /soul-join into demarkus-memory so there's no manual
MCP for souls; token storage = wrapper + 0600 file (not inline); maintain a
catalog of souls + a per-project binding for routing; detect existing
hand-wired entries and offer to adopt. Plan at /plans/soul-join.md.
Shipped (plugin v0.9.0 → v0.10.0):
lib.sh: catalog (~/.demarkus/souls, tab rowsslug\thost\tinsecure\ttoken-file)- binding (
~/.demarkus/project-souls,dir\tslug) helpers (register/fields/list/is-registered/bind/binding, all idempotent upserts). Extendedpublish_gate_scopeso the tag-gate fires on joined remote souls too.
- binding (
scripts/soul-join.sh: normalize host (mark:// scheme, reject https→knowledge), derive+sanitize slug from first DNS label, reject reserveddemarkus-memory, token→0600 file, register, optional--bind. Best-effort reachability only (QUIC has no HTTP metadata to probe). Test hatchDEMARKUS_SOUL_JOIN_SKIP_BINARIES.scripts/soul-remote-wrapper.sh: self-contained (sources nothing), installed to the STABLE path~/.demarkus/bin/so a plugin upgrade can't strand the registered MCP server. Reads the catalog, injectsDEMARKUS_AUTHfrom the 0600 file, execsdemarkus-mcp -host … [-insecure].scripts/detect-manual-souls.sh: parsesclaude mcp listfor direct demarkus-mcp+mark:// entries (excludes wrapper-managed ones), emits name/host/insecure/has-token — token VALUE never echoed (transcript safety).commands/soul-join.md, session-guidance "which soul to write to" section,tests/soul-join_test.sh(16 tests).
Bug caught in smoke test: wrapper didn't shift the slug arg, so it leaked into
demarkus-mcp's argv as a stray positional. Fixed.
Verified: 16 new + all existing plugin tests pass; pre-commit green; shellcheck
clean; wrapper smoke (auth+insecure / no-auth+secure both correct);
detect-manual-souls correctly finds demarkus-soul + demarkus-hub and skips
the managed local soul.
Open / deferred (phase 2): destination GATE (PreToolUse on mark_publish
enforcing the project→soul binding, not just guiding); full auto-migration of a
hand-wired entry (currently guided re-join + claude mcp remove); reachability
hardening; wrapper auto-reinstall on plugin upgrade. NOT bumped: lib.sh
SERVER/CLIENT/TOOLS binary pins — convention says bump per plugin change, but
this is plugin-only with no binary release; left for Fritz to decide at release.
Fritz commits.
/soul-join follow-ups: binary-upgrade server restart + destination gate
Two more on feat/soul-join.
Bug: binary swap didn't restart the local server (all modes)
ensure_binaries upgrades the on-disk binary but only session-start's managed
arm restarted the server (via the .server-version stamp in
ensure_managed_server). Two gaps: (1) /soul-join calls ensure_binaries for
demarkus-mcp and could swap the SERVER binary mid-session without restarting —
a bug I introduced; (2) reuse mode is never restarted by design, so a binary bump
leaves it on the stale binary (this is exactly the down-server-at-content
symptom).
Fix: ensure_binaries sets a DEMARKUS_BINARIES_REPLACED global only when it
actually swaps a binary. New restart_local_server_on_upgrade (lib.sh) reads it,
loads the config in a subshell (no var clobber), and restarts the configured
server with its OWN recorded root+port. Relaxes the old "never restart a reuse
server" policy per Fritz's ask ("find the local server config and restart with
the same config"): for an externally-started/reuse server (no .pid of ours) it
stops the process found at the root first so the respawn binds the freed UDP
port, then ensure_managed_server. Wired after ensure_binaries in both
session-start and soul-join. Non-fatal (warns, never aborts the caller). 5 tests
stubbing ensure_managed_server/pid_of_server_at_root (incl. a real
stop-external-then-respawn using a sleep stand-in).
Phase 2 shipped: destination gate (binding enforced, not just guided)
New PreToolUse hook hooks/dest-gate.sh on mark_publish AND mark_append.
When a project has a binding (project_soul_binding), a write to a different
soul is a misroute → denied at write time. Scope is narrow + fails open: acts
only when the tool targets a soul this plugin routes (soul_target_id →
demarkus-memory for local, slug for a joined remote, empty for knowledge/
unrelated → deferred) AND a binding exists. No binding → defer, so the vast
majority of projects see zero change. Strictness block (default — agent
self-corrects to the bound soul, no human interrupt) | ask | warn, via
DEMARKUS_MEMORY_DEST_STRICTNESS / plugin-memory.dest-strictness. Separate
axis from the tag-gate strictness. 11 tests.
Design note: default block (not ask) is deliberate — an explicit
/soul-join binding means writes belong to that soul, and for an agent a deny +
reason triggers a silent retry to the right server, which is less disruptive
than asking the human every time. Relaxable per-repo/env.
Session-guidance updated: "which soul to write to (catalog + binding)" now states the binding is enforced. Full suite green (112 tests across 12 files), shellcheck clean, pre-commit green. Still plugin-only; binary pins untouched (Fritz's call at release). Phase-2 remainder: full auto-migration of a hand-wired entry; append-metadata carry-forward is an orthogonal pre-existing item.
/soul-join — reuse-restart softened + #197 pin-automation reconciled
Two corrections to the above.
Reuse restart: only start a DOWN server, never kill a healthy one
Reversed the earlier "stop the external server then respawn" behavior (Fritz:
"only start a down reuse server, never kill a healthy one").
restart_local_server_on_upgrade now branches by ownership: OUR managed server
(our .pid) → ensure_managed_server restarts it; a server we don't own
(reuse/external) → if healthy, leave it untouched and warn it's still on the old
binary (user restarts on their terms); only if DOWN do we start it on the new
binary with the recorded config. No kill path at all now — simpler and safer.
Test renamed to reuse_healthy_external_left_alone (asserts not-killed AND
not-respawned); managed test now writes a .pid to exercise the owned branch.
Binary-pins note was moot — #197 already automated it
My "binary pins untouched, Fritz's call at release" caveat is obsolete: #197
(chore(plugin-memory): bump to OKF release + automate future pin bumps, already
in main and this branch's base) added .github/scripts/bump-plugin-pins.sh +
.github/workflows/plugin-pin-bump.yml — a daily/on-release workflow that opens
a PR bumping SERVER/CLIENT/TOOLS pins to the latest releases and the plugin patch
version to match. So NOT hand-bumping pins was correct, and it's now mechanized.
The old conventions.md rule "bump plugin pin versions on every update" is
superseded by that automation. My manual 0.9.0→0.10.0 minor bump (for the
feature) doesn't conflict — the workflow no-ops when pins already match latest
(they do: 0.18.0/0.13.0/0.2.0). Branch verified current with main (nothing ahead),
full suite 112 green.
/soul-default — standalone default-binding command (built)
Fritz wanted a way to set this project's default write target without
re-running /soul-join. Until now the binding (~/.demarkus/project-souls) was
only ever written as a side-effect of /soul-join --bind, so re-pointing an
already-joined repo meant a full re-join. New /soul-default: list the catalog,
pick one, save it. Naming his (soul-default, clearer than "bind").
Design question he posed and we settled: "is the plugin smart enough to use
another server, or does that need a tool?" — neither a router nor a dispatch
tool. Routing is already expressed by which mcp__<slug>__mark_* the model
calls; each joined soul is its own MCP server. The plugin's job is only the
default binding + the dest-gate guardrail. A mark_publish_to(target,…)
tool would force the local soul's server to proxy to remotes — duplicating
MCP's per-server routing and breaking "each soul = its own server." So: the
catalog is the discovery surface (agent reads ~/.demarkus/souls to know
what exists), the binding is just the default, reads/one-off writes to any
other joined soul go direct to its tools. Confirmed reads are already ungated
(dest-gate fires only on publish/append).
Built (follows the soul-join script+command+test pattern):
lib.sh:local_soul_present,soul_catalog(unions local managed soul + remote rows →id\ttier\thost\tinsecure— fulfills thesoul_catalog()promise the SOULS_REGISTRY header comment already made),is_catalog_soul(validates a slug is actually joined before binding, so a binding can't point at an unjoined soul).scripts/soul-default.sh:--list --bind DIR(catalog, current default marked*) and--set SLUG --bind DIR(validate →bind_project_soul). Joins nothing, touches no MCP config/token, reuses the awk upsert so only this project's row changes.commands/soul-default.md: list → AskUserQuestion → set.tests/soul-default_test.sh: 10 tests.
Verified: soul-default 10/10, soul-join 16/16 (lib changes safe), dest-gate
11/11; shellcheck clean (-x).
Gotcha — the "one-off override" premise is false in current code
The original /soul-default management prompt (and earlier conversation framing)
claimed "the destination gate honors explicit per-call targeting, so a one-off
write to another soul needs no file edit." Not true today. dest-gate scopes
to any joined soul (is_registered_remote_soul) and compares soul_target_id
to the binding — so a deliberate mcp__hub__mark_* while bound to soul is
denied (strictness block). There is no override path in the code. Fritz
scoped this build to list+select+save only ("that is it"), so the override was
deliberately NOT built. Two ways to make the claim real if revisited later:
(a) a gate-honored one-shot signal (e.g. a ~/.demarkus/dest-override sentinel
the gate consumes once), or (b) default the gate to warn. Parked. The stale
"honors per-call targeting" wording in the management prompt should be corrected
when the override is actually built.
NOT committed (Fritz commits). Binary pins untouched (plugin-only; #197 automation no-ops since pins already match latest).
/soul-default — shipped (PR #202 merged to main)
Follow-up to the build entry above. Bumped plugin 0.10.1 → 0.11.0 (plugin.json +
marketplace.json), branch feat/soul-default, PR #202.
CodeRabbit (ASSERTIVE) flagged 3, all valid, all taken:
- lib.sh soul_catalog masked read errors as empty (Major). Was
awk … 2>/dev/null || true— a present-but-unreadable SOULS_REGISTRY would print nothing and the CLI--listwould report EMPTY ("run /soul-join") when the real fault is permissions. Fix:[[ -e ]] || return 0(absent = no remotes) then[[ -r ]] || return 1(unreadable = propagate), awk unguarded. Note this makes soul_catalog stricter than its siblinglist_remote_souls(still fails-open) — accepted, since soul_catalog feeds the user-facing list. - soul-default.sh --set accepted a flag as the slug (Minor).
--set --bind Xtook--bindas SLUG then failed later with a misleading "unexpected argument". Fix: reject-*values (slugs are sanitized [a-z0-9-], never lead with-) → direct "requires a soul slug". - No CLI test for the local branch (nit). Added
test_set_local_when_configured(end-to-end--set demarkus-memoryvia seed_local) +test_set_rejects_flaglike_slug. 12 tests now.
Title check also flagged: corrected PR title scope tools → plugin-memory
(repo convention) + grammar via gh api -X PATCH (plain gh pr edit hit the
Projects-classic GraphQL deprecation bug). Skipped CodeRabbit's "docstring
coverage 50%" pre-merge warning — bot heuristic miscount; every function carries
a comment block per repo convention.
Merged squash ce700d8. Verified post-merge: all three fixes present on main,
cache 0.11.0 byte-matches main, /reload-plugins picked it up, /soul-default
live (this repo bound to soul). Fritz merged (I don't commit/push).