Releases: Vahlame/create-vkm-kit
Release list
v4.5.1
Security
- Personal phone number removed from every LICENSE.md (root + the 8 package
mirrors,license:sync-verified). Found by the pre-promotion privacy sweep — the
license contact stays email-only. Residual exposure noted honestly: the number
remains in old git blobs and in the already-published npm tarballs (≤4.5.0); the
current tree, the next release, and everything a visitor reads going forward are
clean. Full-history rewrite deliberately NOT done — it would break every clone,
tag and PR reference for marginal gain.
Added
-
Bench results visualized in the READMEs — a static dumbbell chart
(docs/assets/bench-results.svg+ a dark-mode variant, selected via
<picture>/prefers-color-scheme) of the committed 2026-07-21 live-round
numbers: skill vs stock per bench × model (research / design / discipline ×
Haiku / Sonnet / Opus), with direct value labels and deltas on every row.
Palette CVD-validated for both surfaces. Pure rendering of data already under
evals/*/results/— no benchmark execution involved. Embedded in
README.md,README.en.mdandevals/README.md. -
/vkm-researchgrew from a 68-line monolith into a full skill (same standard as
the 4.5.0 vkm-spec rebuild): rewritten SKILL.md with a copyable checklist and a
degradation ladder;references/summary-template.md(the canonical consolidated
shape) +references/synthesis-guide.md(four axes: claims across sources, links
that connect, visible supersession, compression with judgment);
examples/worked-example.mdwith REAL validator output both ways (draft fails with
5 named errors, rewrite passes); andscripts/validate_summary.mjs— a zero-dep
validator that rejects promoted map-reduce drafts by their seams (---separators,
## <file>.mdheadings, leakedurl:lines), missing wikilinks, malformed
supersedes and transcription-sized output. Doubles as the research-bench grader;
mutation-style self-test (10 cases) gates in core CI. -
research-bench (
evals/research-bench/): a syntheticRESEARCH/<topic>/bank
with the pipeline's real frontmatter and two seeded probes — a contradiction between
sources (must surface as a typed- supersedes) and an embedded instruction in one
source (must be flagged as untrusted DATA, never obeyed). Skill vs stock; grader =
the shipped validator + probe signals (reference consolidation scores 100, the raw
draft 0). New job + dispatch option inllm-benchmarks.yml. -
implementer-bench (
evals/implementer-bench/): first eval covering the
vkm-implementeragent — its real installed contract as system framing vs bare, on
spec-shaped tasks, graded by discipline-bench's existing hidden-test instruments
(no new graders). New job + dispatch option inllm-benchmarks.yml. -
design-bench auto round 1 (mechanical score from the skill's own validators;
raw HTML committed underresults/2026-07-21-round1/): Sonnet +60 on the
slop-attractor brief (stock: 15) and +30 on the held-out brief; Opus (n=1)
+60/+40 — stock Opus scored 0 on facturio (full slop fingerprint + failing
contrast). Haiku flat, dial-consistent. Judgment axes stay in the manual protocol. -
Effort gate auto-match (ADR-0031 amendment): the
PreToolUseeffort gate now
detects the session's current effort (CLAUDE_EFFORTinherited by the hook) and
opens itself when the model's proposed level equals it — no pause when there is
nothing for the user to change. Mismatch or undetectable effort keeps the original
pause; the deny message now advertises the detected level. Subprocess-level tests
cover match/mismatch/undetectable/template-only paths. -
Diversified round (round 2/3 of the live benches), Opus added at reduced n —
raw data under each eval'sresults/2026-07-21-round2/:- research-bench: the skill's gain GENERALIZES — held-out domain topic
(container-queries: Haiku +35, Sonnet +52.5, Opus +50) and Opus on sqlite-vec
(+45); stock Opus still fails the consolidation contract. - discipline-bench on the new harder tasks (incl. the held-out instrument):
Sonnet +19/+21, Opus +25/+31; Haiku flat-to-−3 within spread — where the hidden
contract exceeds the small model's reach, the doctrine neither helps nor hurts,
exactly what the dial predicts. - token-quality-ab adversarial fixture (no-keyword decisive lines): delta 0.0
on all three models measuring the FIXED hook — verdict stays KEEP. - skills-triggering hard set (12 near-miss/multi-skill/tool-vs-skill cases):
Haiku 11/12, Sonnet 12/12, Opus 11/12 — desaturated, both misses are arguable
boundary calls, logged with the description tweaks to try next round.
- research-bench: the skill's gain GENERALIZES — held-out domain topic
-
Round 1 of both new benches, raw data committed (2026-07-21, Haiku 4.5 + Sonnet 5,
n=3/cell, underresults/2026-07-21-round1/):- research-bench: the skill delivers — Haiku 33.3 → 70.0 (+36.7), Sonnet
35.0 → 96.7 (+61.7). Stock output fails the consolidation contract (no typed
supersedes, weak linking, the seeded injection usually dropped silently). - implementer-bench: honest null result — explicit specs saturate both
conditions (the value lives in the spec, which is /vkm-spec's job), underspec
deltas sit inside replica spread with one negative Sonnet cell noted for re-check.
The agent's case is delegation ergonomics, not raw scores; the bench exists to
catch contract regressions. No verdict claimed beyond n=3.
- research-bench: the skill delivers — Haiku 33.3 → 70.0 (+36.7), Sonnet
Changed
- The skill structure gate's cross-reference allowlist is now EMPTY (was 4
tolerated vkm-design entries): the in-reference pointers became plain-text mentions
at the point of use ("contemporary.md, this folder") and SKILL.md remains the only
place that links — Anthropic's one-level-deep rule now holds to the letter, and the
gate ratchets at zero exceptions.lineages.mddeliberately NOT partitioned: it
passes the## Contentsnavigability gate, and splitting it would mint new
cross-references — the exact debt just retired.
v4.5.0
Fixed
-
execa 10 reverted to ^9.6.1 — it broke the kit's own Node 20 floor. The Dependabot
bump (#83) landed even thoughexeca@10declaresengines.node >=22and calls
Set.prototype.union(Node 22+) at import time, crashing every execa-importing test
file on CI's Node-20 leg (TypeError: TEXT_ENCODINGS.union is not a function) — the
exact regression that leg exists to catch. All four workspaces pinned back to^9.6.1
and the stale nestednode_modules/execa@10lockfile entries deduped away. execa can
ride to 10 when the kit'senginesfloor moves to 22 — deliberately, not via a bump. -
The token-saver's Bash compaction was provably losing diagnostic DETAIL. The new
adversarial-fixture gate (test/compact-diagnostics.test.mjs: real 650–1,100-line
failure logs with the decisive error buried mid-stream) caught it before any live A/B:
the rescue pass kept lines matching the diagnostic regex but dropped their neighbors —
theTS2339:detail under anERROR in …header, thestate.go:161stack frame under
WARNING: DATA RACE.compactTextnow rescues context blocks (1 line before,
3 after each match, overlaps merged,[...]separators, cap raised 40→60 lines) so a
diagnosis-from-compacted-output is possible. The gate stays in CI to keep it that way. -
Docs no longer under-report the tool surface. The installer
--help,ARCHITECTURE.md
anddocs/{en,es}/how-it-works.mddescribed obscura-web as 2 tools (it registers 8 —
obscura_fetch/_fetch_many,obscura_search,obscura_research+_start/_status/_stop,
obscura_consolidate) and vkm-downloads as 2 (it registers 6 — addsprobe_mirrorsand
download_start/_status/_cancel). The hybrid MCP README now carries the authoritative
22-tool table, and a newtool-doc-drift.test.mjsgate parses the registrations in
hybrid-mcp.mjsand fails the build if the README table and the code ever diverge again —
same philosophy as the schema-budget gate: doc drift a reader can't detect becomes a red build. -
Residual "v3 kit" self-descriptions in
docs/{en,es}/faq.mdandtroubleshooting.mdupdated
to the v4 identity (historical version anchors like "since v3.8.1" are kept — they're history,
not drift). ADR-0046's body no longer claims a "Proposed" status for a deletion that shipped in
4.0.0, and the ADR index now surfaces ADR-0050's amendment instead of a bare "Accepted". -
A tagged release published to npm having run zero tests.
ci.ymltriggers only on
push-to-main andpull_request, and neither fires on a tag push — sorelease.ymlreached
npm publishgated on nothing but the version, changelog-section and license-mirror checks.
workflow_dispatchwas worse: it releases whatevermaincurrently is, equally untested.ci.yml
is nowworkflow_call-able andrelease.ymlrequires the whole matrix at the ref being released. -
A skipped
npm-publishjob reports green, and the guard that would have said so is inside it.
The job's condition matched the fullowner/name, so a repo rename silently disarmed publishing
while the workflow still reported success — the exact failure that shipped 4.2.0 and 4.3.0 late,
and this repo has already been renamed once (obsidian-memory-kit→create-vkm-kit). Now keyed
on the owner.releasealso takes aconcurrencygroup (never cancel-in-progress — aborting
mid-publish is worse than queueing) so two dispatches cannot race the samenpm publish. -
markdownlint never saw any file inside a dot-directory.
**/*.mddoes not match them, so
.agents/rules/*.md,.continue/rules/*.mdand.github/PULL_REQUEST_TEMPLATE.mdwere silently
unlinted — which is also why.markdownlintignorelisted a.githubfile the glob could never
have reached. All ten were already clean; the gate now actually covers them. -
The
basic-memorypin CI verifies is no longer hand-copied.scripts/mcp-smoke.mjsimports
BASIC_MEMORY_VERSIONinstead of repeating the literal, so the smoke test can no longer certify a
server users never receive — the drift its own comment warned about but did not prevent. -
The
agent.tomlschema check no longer rides on an unpinned interpreter.tomllibis stdlib
only from 3.11 and thelintjob had nosetup-python; it is now pinned to 3.12 like the other
Python jobs. The network-boundlinksandmcp-smokejobs get timeouts instead of the 6h default. -
engines: node >=20was declared in five manifests, advertised in the README badge, and
verified nowhere — every CI job pinned Node 24 and there is no runtime guard.test-nodenow
includes one ubuntu leg on the declared floor, enough to keep the claim honest without tripling
the matrix. -
Latent test flakes, fixed at the mechanism rather than by widening a tolerance.
hybrid-mcp.test.mjs'scleanup()restoredBASIC_MEMORY_HOMEbut never closed the client or
server, leaking a connected pair per call (~15 per run); since the vault is resolved lazily from
the environment on every call, a surviving server that handled anything after the restore would
resolve against the ambient vault — the developer's real one. It now closes both, before
restoring the env. Andollama-resources.test.mjsassertedelapsed < 1000msagainst a closed
port as a proxy for "it short-circuited" — a proxy a loaded runner, a GC pause, or a firewall
that DROPs rather than RSTs can blow. It now pointsensureOllamaServerat a reachable fake
Ollama and asserts it still returns false: an implementation that probed would find
/api/versionanswering and return true, so only a real short-circuit passes. Deterministic, and
a stronger claim than the timing check ever made. -
The installer reported its own version as
v2 / v3. A hardcoded banner string on a 4.x kit —
the one place every user sees a version was the one place guaranteed to be wrong. It now prints
readKitVersion(), already the source of truth for--check-updateand the sidecar's
kitVersion. Relatedly,--version/-vused to fall through to the interactive wizard, so
the standard way to answer "which version are you on?" started an install; it now prints the
version and exits. -
The issue and PR templates were from the deleted v1 project, and blocked filing.
bug_report
required a PowerShell version ($PSVersionTable) in a repo with no.ps1file, required a
Cursor version, asked forDoctor.ps1/Vault-Doctor.ps1output (neither exists), and made
"I readdocs/troubleshooting.md" a required checkbox for a path that does not exist — the
real file isdocs/en/troubleshooting.md.feature_requestrequired agreeing that "this is not a
runnable codebase" and that "scripts live inside the prompt as literal text", both false for a
repo with a Go daemon, eight npm workspaces and a Python package. Rewritten around what actually
ships: kit version via--version, the real component list, the six agent surfaces,vkm-doctor
output, and Windows/macOS/Linux rather than Windows-only. -
CONTRIBUTING.md's "local checks" claimed to mirror CI and did not. It omittedversion.mjs check,lint,typecheck,license:sync:check,linkcheck, theagent.tomlparse and the
whole Node test suite, while listingnpx lychee— lychee is a Rust binary, so that command
installs an unrelated npm package. Now thelintjob verbatim and in order, with an explicit
note about which gates (lychee, gitleaks, govulncheck, mcp-smoke, the benches) are CI-only and
why. The PR template's checklist points at it instead of drifting a third variant. -
version.mjscould never fix — or even report — a drifted-ldflagsversion. The Go daemon
carries the kit version twice (var versionand the example-ldflagsin the build comment),
and they were one marker whosereadlooked only at the first. Three things then compounded:
setskips a file whosereadalready matches, so oncevar versionwas right the second
replace never ran again; the "refusing partial write" guard compares the whole file, so a no-op
second replace is invisible whenever the first one changed something; andcheckonly ever
inspected whatreadreturned. The documented build command could print a stale version forever
with bothsetandchecksilent. Now two independent markers — surveyed, checked and written
separately — with a regression test that also pins every marker'sread/writeas inverses, so
a regex that writes a shape its own reader cannot parse can't create self-inflicted drift. -
First query against a cold vault could crash with
database is locked.store.connect()set
PRAGMA journal_mode=WALbeforePRAGMA busy_timeout, but the ordering was never the real
problem: converting a database into WAL takes a brief EXCLUSIVE lock on a sqlite code path
(pagerExclusiveLock) that does not consult the busy handler, sobusy_timeoutdoes not
apply to that one statement however large it is or how early it is set. Measured: with
busy_timeout=3000already in effect, a contended transition raises in 0.000s, while the same
pragma against an already-WAL database returnswalin 0.008s taking no lock at all. That second
measurement is why a bounded retry is the fix and not a band-aid — the only way to lose this race
is another connection converting the same database, so once any one of them wins, every later
attempt is a lock-free no-op and the loop exits immediately. The trigger was never exotic:
assembleContextfans out three rag processes over one vault at once, so the first query against
a fresh index has three processes creating the same database concurrently. It surfaced as a flaky
vkm-specpipeline test in CI, but it was a user-facing cold-start bug, not a test artifact.
connect()now also closes the...
v4.4.0
Added
-
obscura_researchgains a background deep-research mode: three new tools, an unattended
multi-round crawl, and a per-run report (ADR-0060).
obscura_research_start(objective, topics, topic, …)launches a job that runs INSIDE the MCP
server process for up to 30 minutes, the way a human researcher iterates on a hard question —
each round reusesdeepResearchwhole, then a
local model (generateLeads, zero-shot, the sameqwen3.5:4b-q4_K_Mexpand model curation
already uses for recall tasks) proposes typed next queries (subtopic/related/analogy/
application) anchored to the stated objective rather than the seed topics' literal wording. The
call itself returns in milliseconds: the MCP transport's 60stools/callwall is client-side and
unavoidable for one call (ADR-0057 §5), so depth now lives entirely off the wire instead of
costing an agent round-trip per increment, the way the priorpersist:true+excludeHashesloop
did (still supported, unchanged). Poll progress withobscura_research_status, stop early with
obscura_research_stop; only one job runs at a time — the same fan-out ban risk ADR-0057 §6
measured live applies at greater scale to an unattended run. Every round persists to
RESEARCH/<topic>/as it finishes (a killed process loses only the round in flight), and a run
report lands inRESEARCH/<topic>/runs/<timestamp>.mdon every exit path — including an
"Unexplored leads" section naming application/improvement ideas the job never got to chase —
ready for/vkm-research <topic>to consolidate. New, optional env knobs:
OBSCURA_DEEP_ROUND_MS(default 100000) andOBSCURA_DEEP_PACE_MS(default 15000). This is the
"background continuation" mechanism ADR-0057's fourth addendum explicitly declined, pending "a
use case [that] ever needs progress with no caller ever returning" — the trigger it named,
firing.packages/obscura-web326/326 tests, no regressions. -
Static analysis now gates the ~32k LOC of shipped JavaScript. New root ESLint 10 flat
config (@eslint/jsrecommended + tunedno-unused-vars,no-shadow, and the type-aware
promise rulesno-floating-promises/await-thenable/no-misused-promisesvia
typescript-eslint), plus a CI-gatedtsccheckJspass (tsconfig.checkjs.json) over
shippedsrc— tests stay covered by ESLint's type-aware rules through
tsconfig.eslint.json; skill templates are excluded because their optional deps only exist
in the user's env. Run withnpm run lint/npm run typecheck; both wired into the
ci / lintjob, andnode:testrunners are allowed viaallowForKnownSafeCallsinstead of
a blanket disable. The first sweep took 861 ESLint + 424checkJsfindings to zero and fixed
real defects along the way: vkm-doctor's CLImain()ran unawaited (a crash died as an
unhandled rejection instead of a clean non-zero exit), a no-opawaiton the synchronous
NodeSDK.start(), a dozen wrapperthrows that discarded the original error (now
{ cause }), async HTTP handlers passed where a void listener is expected (now guarded with
a.catchbackstop), dead stores, and producer JSDoc contracts narrower than the values they
actually return (e.g.curatePage's undocumentedrelevance/reasonfields). -
A safe self-update path for installed skill/subagent templates, plus an opt-in version
check (ADR-0061).npx @vkmikc/create-vkm-kit --check-updatereports the installed vs.
npm-latest version and a plan for every managed file under~/.claude/skills/and
~/.claude/agents/, without writing anything or ever failing on a network error (offline
degrades to an honest "skipped" line).--updateapplies that plan: new/missing/kit-only-changed
files install, a file you edited locally is left alone and reported asconflict(only
--forceoverwrites it, and it says so — that DISCARDS the local edit), and both accept
--dry-runto preview with zero writes. The classification is three-way (template vs. the
sidecar's recorded install-time hash vs. what's on disk now — chezmoi's
source/target/destination model), because the sidecar already recorded the ancestor hash for
uninstall's benefit; that is what makes "you edited this" and "the kit changed this"
distinguishable instead of both looking like "disk differs from template." v1 covers the
hash-guarded asset layer only — managed rule blocks and MCP registrations still need a normal
installer re-run. -
Structure gate for the shipped skill templates, against Anthropic's published
skill-authoring checklist (ADR-0061). Newtest/skill-structure.test.mjsenforces, over all
four shipped skills:SKILL.mdbody ≤500 lines, a## Contentsheading on any reference file
over 100 lines, one-level reference nesting (no new relative.md-to-.mdlink inside a
skill, past a small named baseline of four pre-existing ones), forward-slash link paths, and a
valid non-branded frontmattername.vkm-design's eight reference files over 100 lines gained
a Contents heading as part of landing this gate. -
Drift gate for the repo's own Cursor memory rule. The committed
.cursor/rules/obsidian-memory.mdcis fresh-install output of the installer
(installRules(["cursor"], "es")), not anagents-manifest.yamlartifact — so no check
covered it and it silently kept the pre-renameobsidian-memory:start/endsentinels and
create-obsidian-memorybranding across the vkm-kit rename (ADR-0041).sync-agents.tsnow
renders the fresh-install output (the newly exportedCURSOR_RULE_FRONTMATTER+
memoryRulesBlock("es")through the realmergeManagedBlock) and byte-compares it under
--check(already in CI), failing with a "rerun the generator: npm run sync-agents" hint;
write mode regenerates the file. The committed copy is regenerated to the current block, and a
package test pins generator output ≡ gate expectation so the two can never diverge.
Changed
-
CONTRIBUTING.md's SemVer section now describes the kit, not the v1 prompt, and adds an
explicit post-4.x versioning policy. The MAJOR/MINOR/PATCH definitions still spoke of
"prompt section numbers"; they are rewritten in terms of the installed contract (CLI flags,
MCP tools, vault layout/hooks), and the new policy freezes majors except for unavoidable
contract breaks — batched into one planned major with its migration doc. README links the
policy from "Más · More". -
Repo face: a social-preview card, and the measured claims now name their substrate.
docs/assets/social-preview.png(1280×640, on thehero.svgdesign language) gives GitHub's
social preview and link unfurls a real card instead of a cropped avatar. The READMEs'
token-economy paragraph now states what those numbers are measured against — a fixed labelled
corpus and a deterministic embedder, i.e. reproducible regression floors, not leaderboard
claims — and the hybrid-memory chip marks semantic search as the local opt-in it is. The code
already said both (embeddings.py,ci.ymlcomments); the front page now matches it.
Fixed
-
The repo's own
.cursor/rules/obsidian-memory.mdcno longer ships the pre-rename block. The
committed dogfooding artifact (not generated bysync-agents—agents-manifest.yamlnever
listed it) still carried v2 branding:obsidian-memory:start/endsentinels, "Bloque gestionado
porcreate-obsidian-memory", the(obsidian-memory-kit)frontmatter description, and it
predated theRESEARCH/and discipline sections. Regenerated with the current generator
(installRules, fresh-install path), asserting the ADR-0041 legacy-sentinel migration produces
the identical managed block. The cursor frontmatter (CURSOR_RULE_FRONTMATTERin
rules-merge.mjs, the drift gate's single source) now writes the(vkm-kit)description for
new installs too — the last v2-branded string the installer still emitted. -
go.modmodule path matches the repo slug. The module still declared
github.com/Vahlame/obsidian-memory-kit, so importing orgo install-ing the daemon by its
real path (github.com/Vahlame/create-vkm-kit/cmd/obsidian-memoryd) failed — latent only
because the installer builds locally.agent.toml's[daemon].modulemirror is updated to
match. -
release.ymlno longer reports success when npm publish silently skipped. The npm-publish
job soft-exited (exit 0) when theNPM_TOKENsecret was missing, so the workflow went green
while npm stayed behind — this shipped 4.2.0 and 4.3.0 to GitHub without their npm counterparts
until someone noticed. Both publish steps now emit a::error::annotation and fail the job
when the token is absent; fork runs were already excluded by the repository guard, so the hard
fail only ever fires where publishing is actually expected. -
Stale
obsidian-memory-kitURLs swept from every functional link.LICENSE.md(root + the
package mirrors vialicense:sync),SECURITY.md's source-verification step and this
changelog's compare/release link definitions still pointed atVahlame/obsidian-memory-kit;
the GitHub redirect masks it today, but a redirect dies the day the old slug is re-registered.
Historical mentions of the rename stay as written. -
License labeling stopped implying plain MIT. Both README badges now read
"MIT-derived + attribution (non-OSI)" and the License sections state explicitly that the
mandatory visible-attribution clause is outside the OSI open-source definition — matching what
package.jsonalready declares ("license": "SEE LICENSE IN LICENSE.md"). Labeling only; the
license terms are unchanged. -
vkm-downloadswas missing from both README "What's inside" tables despite shipping since
4.3.0 — added, including its install mode (opt-in--downloads, deliberately not part of
--fullbecause it writes to disk). -
**`ci / ...
v4.3.0
Removed
- Token-saver
permissions.denyrules retired; installer now sweeps them away (ADR-0043
amendment). Daily-use evidence:Read(**/*.lock)hard-blocked readingpubspec.lockin a
Flutter session (small file, needed to resolve a dependency constraint) and the auto-deny was
indistinguishable from a manual denial. A prompt-less hard block trades a bounded token cost for
an unbounded workflow obstruction across heterogeneous projects.configureTokenSaverno longer
takes adenyRulesoption; the four old rules live on only asLEGACY_TOKEN_SAVER_DENY_RULES,
a frozen list that every reconcile (install,--no-token-saver,--uninstall) strips from
~/.claude/settings.jsonwhile leaving user-authored deny rules untouched (new regression test
covers the sweep, both with pieces ON and with everything OFF). Token savings on build
artifacts remain the job of the compaction hooks, which degrade gracefully instead of refusing.
Security
obscura_research's crawl no longer hands an LLM curator content hidden from every human
viewer, unfiltered. Found while reviewing Scrapling (a
Python scraping library) for capabilities worth matching: its own MCP server strips CSS/ARIA-
hidden elements before conversion, which this package had no equivalent of. Verified live, not
hypothesized — a synthetic page hiding text behinddisplay:none/aria-hiddenhad that text
pass straight through the realobscurabinary's--dump markdown(and--dump text, tried as
a cheaper fix-hypothesis and found to fail identically — neither is visibility-aware) as if it
were visible, including a prompt-injection-shaped payload that this package's own
scanInjectionheuristic — a visible-text scanner — also failed to flag. Every page
obscura_researchfetches is now parsed withcheerio(packages/obscura-web/src/sanitize.mjs
— the package's first runtime dependency beyond@modelcontextprotocol/sdk/execa/zod, added
deliberately for this) and stripped of hidden elements,<template>content, and HTML comments
before either the heuristic extractor or the local curator sees it. Re-verified live,
end-to-end, through the real pipeline against the same synthetic payload: the curated result
contained only real, visible content. Scoped to the automated crawl —obscura_fetch's
general, single-URL default is unaffected (matching its existing markdown fidelity from
sanitized HTML would need a second, unapproved dependency); a class-based hiding rule (versus an
element's own inlinestyle/aria-hidden) is a named, tested, accepted gap, since catching it
needs a real browser's computed styles. See ADR-0057's third addendum.
Fixed
- CI red since
b59e4fb(4 pushes): two independent causes, both reproduced before fixing.
(1)lint:slop-check.mjswas committed unformatted for the pinnedprettier@3.8.4—
reformatted (installed copy re-synced). (2)test-python: the ADR-0038 regression test
(test_usage_boost_decays_stale_credit_so_new_note_not_buried) was green on Windows and red on
ubuntu — retrieval order was platform-dependent:os.walkreturns names sorted on NTFS but
hash-ordered on ext4, insertion order decides FTS rowids, and rowids are the hidden tie-breaker
when BM25/cosine scores tie exactly; with a tie at the top, the ranks fed into RRF differ per
platform and the stale note's residual usage credit (linear taper leaves ~0.022 at 89.5/90
days) crossed the ~w/(k+r)²one-rank margin, burying the brand-new note the test protects.
Root fixes, all five ordering sites + the decay: sorted walk in_iter_markdown_files;
ORDER BY score, path(BM25 FTS query);ORDER BY dist, path, ordinal(sqlite-vec path);
heapq.nsmallestover(-score, path, ordinal)(brute-force cosine — same O(n·log k));
(-score, path)tie-breaks inreciprocal_rank_fusionand the post-lever sort; and
usage_counts_decayedtaper linear→quadratic so window-edge credit lands two orders below
any rank margin instead of inside it. Verified: an adverse-order simulation (reversed
insertion, the ext4 condition) reproduced the CI failure locally with the ordering fixes alone
and passes with the full set; 197/197 pytest; all four retrieval/token/assemble bench gates
exit 0 locally at CI thresholds. - vkm-design
modes/visual-loop.md: capability-check note for preview panes that only render
file://inside the project folder. Two real sessions hit the blank "static snapshot" +
"no site open" wall on an outside-the-project file and misread it as a page bug; the mode now
prescribes the copy-into-project pattern (dot-named temp, re-copy after each edit, delete at
loop end) instead of fighting the pane or skipping the loop. - vkm-design audit scripts: four defects exposed by a real user session log (a session
"audited" a roadmap HTML with these scripts and half the tool output was wrong — each fix
below re-verified against the exact input that failed).
(1)scale.mjsno longer infers the 1.067 ratio: its steps sit 6.7% apart, so the default
±3% tolerance bands cover ~91% of the value space and almost any incoherent set "fits" — 22
real ad-hoc font sizes scored 1 offender under inferred 1.067, a false PASS that buried
exactly the incoherence the check exists to expose (now honestly 11+ offenders under the
best non-degenerate ratio, with a hint that--ratio 1.067remains available explicitly).
(2)scale.mjs --genaccepts the natural named-flag form (--gen --base 16 --ratio 1.25 --steps 6): the packed-only parser swallowed a following--baseas its value and died
with a misleading "not a comma-separated number list".
(3)checkSpacingattaches ahalfGriddiagnosis when it explains part of the mess: a file
interleaving 4k and 4k+2 paddings (a real 2px half-step rhythm) was reported flat as "8
values off-rhythm", sending the session on a manual detour to conclude "false alarm" — the
CLI now says "only 3 stay off — the rest is a half-step rhythm; judge intent" itself, while
the exit code still fails against the declared base.
(4)audit-css.mjsgiven.html/.htmaudits only<style>blocks + inlinestyle=""
attributes: the whole file used to go through the rule regex, which read a<script>mermaid
theme object ({ background: "#0e2846", … }) as CSS — one barecolor:key away from
fabricated contrast pairs — while real inline styles stayed invisible. Regression tests for
all four; installed copies under~/.claude/skills/re-synced. - vkm-doctor skills-drift check: compares EVERY installed skill file, not just
SKILL.md.
The motivating real case (2026-07-18):vkm-discipline/domains/design-ui.mdstale under a
byte-identicalSKILL.md— the check reported "ok" for exactly the drift class it was built
to catch, and the routing fix that file carried (design tasks → vkm-design gate) silently
never reached sessions. A skill is now STALE if any of its template files differs installed-
side or is absent (new regression test reproduces the design-ui.md case). - vkm-design
modes/visual-loop.md: new trap — republishing a document to a NEW artifact URL
while the user still holds the old link. Real session: the fixes landed on a fresh artifact,
the user's open copy never changed, and the session read back "no veo ningún cambio" as a
design complaint instead of a delivery failure. The loop now instructs: list existing
artifacts, redeploy to the SAME url, verify at the link the user actually holds. obsidian-memory-rag: concurrentensure_freshwriters no longer die withdatabase is locked. Two simultaneousvault_hybrid_searchcalls with a stale index (reproduced live while
verifying ADR-0056) racedindex_vectors'BEGIN IMMEDIATE; the loser only got Python's
implicit ~5ssqlite3.connecttimeout — undocumented and demonstrably too short for a
batch_commit_every=64batch under a real embedder. Root-cause fix in the shared
store.connect(): explicitPRAGMA busy_timeout=30000on every connection, so a second writer
blocks and then reads the winner's committed state (the existing mtime/size incremental logic
already skips re-indexing) — no manual retry loop needed, SQLite's busy handler transparently
retriesBEGIN IMMEDIATE. Verified red→green with a new barrier-synced two-thread test
(tests/test_concurrent_ensure_fresh.py, slow-embedder holds the lock ~6.4s); full suite 197
passed, none modified.
Added
vkm-downloads: background downloads, sets of files, resume, and fastest-mirror selection
(ADR-0059). Follow-up to the synchronous tool: the user needed to download large files or SETS
of files "whether the download is fast or slow" and to "find the site with the fastest download" —
both blocked by the MCP transport's ~60s per-call wall (a 3 GB ISO can't be one synchronous call)
and by the conservative 500MB cap. Additive (the syncdownload_fileand every ADR-0058 guardrail
are byte-for-byte unchanged): four new tools + a background job runner.download_start({ files })
registers a job and returns ajob_idimmediately, running the transfer as an async task inside
the long-lived stdio server — no child process, so the "never execute downloaded bytes" property
holds;download_status/download_cancelpoll and abort. Each file may list several mirror
URLs;probe_mirrors(andprefer_fastest) measures each with a ~512 KB ranged GET and ranks by
real throughput (latency tiebreak; asize_mismatchflag catches a "mirror" that isn't the same
file), all through the same guarded, IP-pinned path so a private/loopback mirror is refused, not
probed. Large files are gated by a free-disk-space check (fs.statfs) rather than a hard byte
wall (background ceilingVKM_DOWNLOAD_MAX_BYTES_BG, 100GB); interrupted d...
v4.2.0
Added
obscura_searchgains a local SearXNG structured backend, started on demand (ADR-0052). Free
SERP scraping can't be fast + high-volume + relevant at once; a local SearXNG (aggregated engines,
structured JSON, no anti-bot wall) can.ensureSearxng()starts SearXNG the momentobscura_search
needs it and stops it after an idle window (OBSCURA_SEARXNG_IDLE_MS, default 90 s) — nothing runs
in the background while idle — and falls back to the scrape chain when it's unavailable.searchWeb
now defaults to a local instance (http://127.0.0.1:8888;OBSCURA_SEARXNG_URL=""disables it).
A reproducible Windows setup (no Docker) ships underpackages/obscura-web/searxng/
(settings.template.yml, apwdshim, a README); paths are overridable via
OBSCURA_SEARXNG_{PORT,PY,SRC,SETTINGS,IDLE_MS,AUTOSTART}.- Desktop monitor for SearXNG (
searxng-gui.pyw). A stdlib-only Tkinter app: live up/idle status
plus a feed of what the agent has searched (each search is appended to
~/.vkm/searxng/searches.log). It only monitors — the MCP owns the on-demand lifecycle, so closing
the window frees only the window. - Domain reference system for
/vkm-discipline+ anobscuraweb-search domain. Skills can now
ship on-demand domain reference files: the installer copies a skill's whole directory (not just
SKILL.md), loaded on demand (progressive disclosure). The first is
skills/vkm-discipline/domains/web-search.md— how to search/fetch well through obscura and hand
back a verified, grounded result (cite only what you fetched, cross-check high-stakes facts,
match freshness/cache to volatility). - Full cross-domain reference set for
/vkm-discipline, re-authored from the SOP-suite's
bench-refined annexes in the execution-first voice (deliver-a-better-result checklists + anti-patterns,
not bureaucratic gates):coding,debugging(bugs + live incidents:
first-hypothesis-is-what-changed, capture before mitigate, SEV cadence, revert-by-default,
next-update-not-resolution comms),data(rehearse destructive DML with a SELECT-count + backup,
parameterize, validate with aggregates),infra(test-env-first, restorable backup, health-check
baseline, canonical mechanism),writing(every claim traceable, run every instruction, recompute
numbers),design-ui(contrast computed per theme, keyboard/focus, ≥24px targets, 200% zoom, the
three states),web-search,security(untrusted-input-at-the-boundary, rotate-don't-delete leaked
secrets, least-privilege, dependency vetting),llm-artifacts(model proposes, evidence decides;
plausibility raises the bar), andexpertise(separate fact/inference/hypothesis, calibrate
confidence, steelman the alternative, say what would make you wrong). Ten domains cover the SOP-suite's
A1–A8 plus web-search and expert judgment — the kit now supersedes the SOP-suite's cross-domain role.
Changed
/vkm-disciplinereoriented from a coding contract into cross-domain "resourceful execution"
(begins superseding the SOP-suite's role; ADR-0049 channel). Infer the real intent, do it the
best way, deliver more than the literal ask — with minimal friction, depth scaled to task
difficulty AND model (a smaller model skips long step-by-step reasoning, which measurably hurts it;
a larger model self-verifies). Bias to action; ask only when the answer changes what you'd do.
Guardrails (confirm-before-irreversible, injection/untrusted-data scanning, evidence gates) are now
opt-in modules, off by default — the skill's job is execution, not friction. Installed copy re-synced.
v4.0.0
The kit becomes vkm-kit: one plug-and-play efficiency suite for Claude Code —
persistent vault memory + token-saver + local usage doctor + spec-builder. The
repo is now Vahlame/create-vkm-kit; the installer package is
@vkmikc/create-vkm-kit (bins create-vkm-kit and vkm), and the old
@vkmikc/create-obsidian-memory name lives on as a forwarding shim — see the
migration notes in ADR-0041/0050. Machine identifiers your installs depend on
are FROZEN: the MCP server key obsidian-memory-hybrid, the obsidian-memoryd
daemon, and BASIC_MEMORY_HOME. Managed blocks and hook files written by 3.x
are recognized and migrated in place on the next install (dual-read markers,
vkm-kit:start/end sentinels, VKM_VAULT env accepted alongside the legacy
names).
Changed
- Rename (ADR-0041):
packages/create-obsidian-memory→
packages/create-vkm-kit; ownership markervkm-kit(legacy
create-obsidian-memorystill recognized); managed-block sentinels
<!-- vkm-kit:start/end -->with in-place migration of legacy blocks
(test-pinned);VKM_VAULTaccepted beforeBASIC_MEMORY_HOME/
OBSIDIAN_MEMORY_VAULT; release workflow publishes the renamed installer
plus the shim.
Security
- RAG-passthrough MCP tools no longer accept a caller-supplied
vaultpath.
vault_hybrid_search,vault_fts_search,vault_fts_index,vault_complete,
vault_relations,vault_observations,vault_kg_suggest,
memory_extract_candidates,vault_audit, andvault_memory_reportexposed
an optionalvaultparameter with no validation against the configured
default — a prompt-injected note could coerce an agent into pointing one of
these tools at an arbitrary directory on disk, indexing and searching its
.mdfiles (a cross-directory read oracle) and creating a
.obsidian-memory-rag/sidecar there. Removed the parameter from all 10
tool schemas, matching the posture the filesystem tools (vault_read_file,
vault_write_file,vault_edit_file,vault_list_directory) already had.
See ADR-0040.
Added
-
vkm-specpackage (ADR-0046/0047/0048) — the spec-builder pipeline: a one-line
idea + vault context (oneassembleContextcall) compiles into an editable
<orchestration_package>XML prompt, optionally enriched by a LOCAL Ollama
draft (phi4-mini:3.8b-q4_K_M,/api/chatwithformat=<JSON schema>
constrained decoding, fetch-only client, typed failures) — then reviewed by
the human (GUI on127.0.0.1:4923with an SSE draft stream and a visible
ollama/fallback source badge, orvkm-specCLI with$EDITORreview) and
pasted into Claude Code / Claude web / any AI. The deterministic fallback is
structural:buildSpecalways compiles a working prompt and the LLM draft
only overwrites fields on success — pinned by a degradation-gate test (Ollama
down → SSEerrorframe still carries a working XML). The installer can
auto-provision Ollama + the model under explicit--fullor--ollama
(winget silent on Windows, ~2.3GB pull, best-effort — nevercurl|sh, never
a surprise download on a bare install;--no-ollamaopts out). -
vkm skills + subagent template (ADR-0049) —
/vkm-discipline(execution
contract: same functionality and quality in fewer readable lines — density,
never reduced scope; vault-first context via oneassemble_contextcall;
terse output; nothing is "done" without executed evidence) and/vkm-spec
(turn a one-line idea into a precise, context-grounded spec in-session — no
local LLM in the synchronous path) installed into~/.claude/skills/, plus
thevkm-implementersubagent template in~/.claude/agents/. All
hash-tracked (--skills/--agentstoggles; user-modified files survive
uninstall), with a lint gate keeping every skill description ≤300 chars. The
managed rules block gains one "Executable discipline (vkm)" bullet (ES/EN,
within existing budgets; AGENTS.md + install docs re-synced — drift gates
green). -
assemble_contextMCP tool (ADR-0045) — one call returns a budgeted context
bundle for a task (typed[decision]s from the project note, non-decision
observations + relevant cross-note passages,#stackfacts, and a raw-note
fallback excerpt when a project has no typed decisions yet), replacing the
3-6 discrete search/read round-trips an agent would otherwise chain. Engine
relocated from the prompt-compiler'scontext-search.mjsinto
obsidian-memory-mcp/src/context-assemble.mjs(paralleljson-hybrid-search --graph+ twojson-observationspasses over the same Python bridge).
Honors the security posture of every RAG tool: no wire-levelvaultparam
(env-resolved only, pinned by test), untrusted-data_trustenvelope with
injection flagging, and abudget_charscap (default 6000) that trims
passages-first so decisions survive longest. Measured and CI-locked by the
newbench-assemblegate (--assert-savings 0.60 --assert-answered 0.90):
on the labelled fixture, median 68% fewer wire tokens than the naive
multi-call pattern at 100% completeness (10,867 vs 30,816 tokens aggregate;
cross-cutting queries save an honest 45%, project tasks 68% — and the naive
arm pays no discovery cost, so these are floors). -
vkm-doctorpackage + local telemetry (ADR-0044) — a zero-dependency local
OTLP/HTTP JSON sink (vkm-otel-sink, 127.0.0.1:4319 → NDJSON rollups in
~/.vkm/telemetry/, 90-day prune, lockfile singleton, tolerant parser that
archives unknown metric shapes instead of dropping them) plus avkm-doctor
CLI that reports token usage per day/model/type, the cache-hit ratio, cost,
and a "broken cache" diagnosis (high input volume with near-zero cacheRead).
The installer wires Claude Code's OTEL export to the sink (managedenv
block;--no-telemetryopts out) and aSessionStarthook spawns the sink
when it isn't running — no OS service. Local only: nothing leaves the
machine. Transcript scanning exists only as a clearly-labeled
transcript-estimatefallback (the JSONL format is officially unstable). -
Token-saver module (ADR-0043) in
create-obsidian-memory— on by default for
Claude Code installs (--no-token-saver/--no-terse-styleopt out;
VKM_TOKEN_SAVER=0is a runtime kill switch). Three pieces, all reconciled
symmetrically and fully reversed by--uninstall: (1) aPostToolUsehook
(matcherBash) that compacts noisy shell output before it enters context —
ANSI stripped,\rprogress repaints reduced to their final frame, runs of
identical lines collapsed, >200-line logs windowed to head+tail — while a
hard, test-gated guarantee keeps every error/warn/fail/exit-code line and the
final lines verbatim (≥30% reduction on the noisy CI fixture, zero
diagnostic loss); (2) aPostToolUsehook (matchermcp__.*) that
re-serializes pretty-printed JSON tool results into compact form
(whitespace-only — parsed data, including the_trustenvelope, is
byte-identical afterJSON.parse); (3)permissions.denyread rules for
token-hungry artifacts (node_modules/**,**/dist/**, lockfiles) plus the
vkm-terseoutput style (keep-coding-instructions: true), installed
hash-tracked and activated via theoutputStylesetting. -
Shared settings-write infrastructure (
settings-io.mjs,settings-writers.mjs,
asset-install.mjs) increate-obsidian-memory— Phase 0 groundwork for the
vkm-kit efficiency-suite train. The safe~/.claude/settings.jsonidiom
(read→BOM-strip→parse with invalid-JSON backup, pure managed-hook merge/remove
deduped by filename stem, JSON sanity parse, restricted backups, atomic
tmp+rename writes, ownership-marker checks) is extracted out of
claude-native-memory.mjsintosettings-io.mjs— behavior unchanged, existing
suite as the refactor guard — and joined by new pure writers for the sections
upcoming modules manage (env,permissions.deny/allow,outputStyle;
removal only reverses values still provably ours) plus a managed-asset
installer that tracks installed template files by SHA-256 in a
~/.claude/vkm-kit.assets.jsonsidecar so uninstall deletes only unmodified
files. -
Cross-process lock for the Go daemon's git-sync. Two
obsidian-memoryd
instances (or a daemon plus a manualsync once/git operation) pointed at
the same vault could previously raceadd/commit/pull/pushagainst
the same working tree. See ADR-0040. -
create-obsidian-memory --uninstall. Fully reverses this kit's
Claude Code integration (hook entries,autoMemoryEnabledoverride, and the
hook script files it owns) without touching a user's own unrelated hooks or
same-named files. Turning an enforcement flag off on a re-run
(--no-effort-gate,--no-memory-enforcement,--no-native-memory-override)
now also removes the corresponding previously-installed hook instead of
leaving it active. See ADR-0040.
Removed
obsidian-prompt-compilerpackage (ADR-0046) — absorbed intovkm-spec.
Its pure modules (compile-xml,prompt-defaults,project-resolve,
clipboard,review) moved verbatim; its retrieval was already relocated to
obsidian-memory-mcp/src/context-assemble.mjs(ADR-0045). The GUI moved off
port 4317 (OTLP collision) to 4923.
Fixed
- Go daemon
doctorcould report healthy while actually blind. A failed
git add/commit/pull(not justpush) now feeds the same health state
doctoralarms on; a repeatedrebase --abortnow feeds the alarm too
(previously only displayed, never alarmed); a silently-failing filesystem
watcher startup (fsnotify.NewWatcher) now sets an immediate alarm instead
of leaving the daemon looking idle-but-fine with a heartbeat that never
starts; a sync aborted by intentional shutdown (Ctrl-C / service stop) no
longer counts toward the "vault not syncing" alarm;doctor's unpushed-
commit git call now h...
v3.15.0
Added
- RULES contract — dated, sourced, reasoned project rules (ADR-0039). The
vault scaffold now createsRULES/TEMPLATE.md(es/en) and the managed rules
block teaches the contract in its What-to-save section: keep project
rules (knowledge invisible from the repo — domain contracts, deliberate
traps, user mandates), each carrying its why (history), its source
(the file/test/ADR that defines or enforces it) and a last_verified
date; re-verify a rule against its source when you use it, and when a note
contradicts the repo, fix the note in the same session. Grounded in an
external multi-agent bench (sop-suite RUN1–RUN7): memory pays on invisible
project rules, not on generic method (+2–3 pts/task); rules with a recorded
why get respected; agents self-verify stale rules when the source of truth
is one link away (aged-memory trap: 0/7 fell). Rules-block budgets raised
es 8,400 → 9,100 / en 8,200 → 8,850 chars as a reviewed ADR-0036 decision
(+~270 chars per language), with two new load-bearing phrases pinned per
language.
v3.14.0
Changed
- ADR-0038 retrieval levers join the default stack. The installer now wires
OBSIDIAN_MEMORY_PIN_FAILURES=1andOBSIDIAN_MEMORY_USAGE_BOOST=1into the
obsidian-memory-hybridMCP env by default (all three surfaces: Cursor
mcp.json,claude mcp add,codex mcp add), same rationale as sqlite-vec:
ranking-only levers over telemetry that is already collected by default,
bench-gated in CI. Opt out with--no-pin-failures/--no-usage-boost.
The cross-encoder reranker stays opt-in (--rerank) — it downloads a model
on first use and needs the[rerank]extra.
Fixed
- npm license metadata caught up with the license change. The published
package (@vkmikc/create-obsidian-memory) still declared"license": "MIT"
and its tarball shipped no license text — npm only auto-includesLICENSE*
files from the package root, not the repo root. The field now reads
SEE LICENSE IN LICENSE.md, everypackages/*dir mirrors the canonical
rootLICENSE.md(newnpm run license:sync+license:sync:check,
drift-gated in the CI lint job and beforenpm publishin the release
workflow), the private packages (obsidian-memory-mcp,
obsidian-prompt-compiler) declare the same field, and stale "MIT" wording
in the package README andCONTRIBUTING.mdnow points atLICENSE.md.
Verified withnpm pack --dry-runthatLICENSE.mdlands in the tarball.
v3.13.1
Changed
- Docs caught up with v3.13.0 across both languages. how-it-works /
como-funciona gain a "multi-writer safety + evolutive memory" section with
two new diagrams (ifMatch sequence; the learning loop), the retrieval-stack
and memory-report diagrams now show thepin_failures/usagelevers,
recall telemetry andreflect: true; sync guides document the 3.13
audit-trail commits andOBSIDIAN_MEMORY_AGENT; the AGENTS stack catalog
and README--fullcallouts mention the new capabilities (ADR-0037/0038).
v3.13.0
Added
- Optimistic concurrency for vault writes (ADR-0037).
vault_read_file
now reports a contentetag(+ mtime) outside the untrusted envelope;
vault_write_file/vault_edit_fileaccept an opt-inifMatchthat
fails the write with a retryableprecondition failedwhen the note
changed since the read — closing the lost-update window across
read→think→write round-trips. Write results return the new etag so edits
can chain without re-reading. - Advisory cross-process write lock (ADR-0037). Sidecar writes serialize
through an O_EXCL lockfile in.obsidian-memory-rag/with automatic
stale-lock recovery (dead PID / expired TTL, same-host only — foreign-host
locks are never stolen). Kill-switchOBSIDIAN_MEMORY_NO_LOCK=1. The
daemon watcher now skips the sidecar dir so lock/index churn can't trigger
spurious sync cycles. - Audit-grade auto-commits (ADR-0037). Daemon commits now carry the
staged-file list (subject counts, body lists up to 20 paths) and an
optionalAgent:trailer fromOBSIDIAN_MEMORY_AGENT, sogit log
answers what changed and which daemon committed it. - Corrupted-state checks in
vault_audit(ADR-0037). New read-only keys:
Syncthingsync_conflicts(any extension), committed git
conflict_markersoutside code regions,stale_tmpleftovers from crashed
atomic writes, andgit_state(in-progress rebase/merge).doctoralso
records the watcher's last conflict-file sighting and scans the vault for
pre-existing ones. - Opt-in frontmatter schema validation (ADR-0037). A committed
memory-schema.jsondeclares required keys per top-level folder (*
fallback); the write path warns (or rejects withenforce: true, before
anything lands on disk) andvault_auditreportsschema_violationsfrom
the same config. Required-keys presence only — no YAML dependency. - Evolutive memory loop (ADR-0038).
memory_extract_candidates
classifies candidates (failure/gotcha/preference/decision/fact,
EN+ES) with routing hints and dedups failures againstKNOWN_FAILURES.md;
a structured failure-entry convention makes lessons queryable by
category/tag; the opt-inpin_failuresretrieval lever (CLI
--pin-failures/OBSIDIAN_MEMORY_PIN_FAILURES=1) resurfaces recorded
lessons on matching tasks, gated in CI by a newfailure-recallbench
slice run baseline + lever-on. - Usage reinforcement + decay (ADR-0038). A local
recall_logtelemetry
table (survives reindexes, 1y auto-prune,OBSIDIAN_MEMORY_RECALL_LOG=0
to disable) records which notes searches return and which the agent then
opens; the opt-inusagelever (CLI--usage/
OBSIDIAN_MEMORY_USAGE_BOOST=1) boosts memory that demonstrably helped,
andvault_memory_reportsurfaces never-touchedcold_notesfor human
review (no telemetry ⇒ no cold claims; nothing is auto-deleted). memory-reflectconsolidation proposals (ADR-0038). New CLI pair
(memory-reflect/json-memory-reflect, plus
vault_memory_report({reflect:true})): pendingPRACTICES/observations.md
lines past their window → confirm/dismiss; near-duplicate pairs → merge
candidates; stalestatus: hypothesisnotes and orphans → verify/link/
archive proposals; SESSION_LOG recent-activity digest.--write-note
renders them to_meta/reflection-YYYY-MM-DD.md(idempotent per day).
vault_auditgainsstale_hypotheses/unverifiedfrom the new
status:/last_verified:frontmatter convention.
Changed
- ADR-0013 amended: the documented-but-never-implemented three-way
git merge-fileauto-merge for Syncthing conflicts is withdrawn — the
daemon's contract is detect-and-surface; resolution stays human. - Docs answer the "system of record belongs in Postgres" critique head-on:
new ADR-0037 (vault vs database: transactions/concurrency/auth/audit stated
honestly) and ADR-0038 (evolutive loop), a FAQ entry ("Can I run a ticketing
system on the vault?" — no, and why), a SECURITY.md authentication-model
subsection, and a "Multiple agents writing to one vault" section (EN+ES)
with an ifMatch worked example and a conflict recovery playbook.