Repository navigation
v1.35.0 #67
v1.35.0
#67
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Changed —
requireExistingDirhelper de-duplicates the CLI existence-check boilerplate (arch-review #369 slice)CLI-internal refactor, no behaviour change, no public API (PATCH-class). 18 command handlers in
}…`, opts.json); }src/cli/mk.tsopened with the sameif (!fs.existsSync(<dir>)) { exitWithError(\Memory directory not found: ${block (13 with theRun "mk init" first.hint, 6 without — deliberately left out of the #360 helper to avoid message drift). Introduced a CLI-internal **requireExistingDir(dir, json, { hint })** helper and collapsed all 18 to one call each, preserving each site's message **byte-identically** (hintdefault on;{ hint: false }for the 6 no-suffix sites). Themk doctorsite is intentionally **not** routed through it — it exits **2** with custom JSON/console output, a different contract. Behaviour proven unchanged by the existing CLI subprocess e2e tests (which assert the exactMemory directory not found` message + exit code on a missing dir); full suite green at 2013. One concrete slice of the #369 mk.ts-thinning umbrella, which stays open (per-command option→engine translation + inline cron templating remain).Added —
RECALL_DEBUGrecall ranking observability (arch-review #371)PATCH-class, opt-in diagnostic, no public API (no
ContextBundlefield, stderr only). Recall produced a final ranking with no way to see why atom X outranked atom Y. SettingRECALL_DEBUG=1now emits a per-atom score breakdown to stderr, sorted by final score desc: task path printsfts/specificity/length/coverage/semantic/recency/type_weight/conf_factor/graph_boost/final(slotted into the existingscoreTaskAtomsloop); the no-task constitution path printsstatus/status_priority/recency/updated_at. OFF by default with zero overhead — a single env read per recall; no row allocation or string building when disabled (verified by a no-leak test). Deliberately stderr-only (not aContextBundlefield) to keep it off the public surface; a programmatic field can be a later MINOR if a consumer needs it. +3 tests (test/recall-observability.test.ts);public-api-surfacesnapshot unchanged.Fixed —
events.ndjsonowner-only after bootstrap/merge +store-permissionsaudits the NDJSON sidecars (arch-review #389)PATCH-class (no public API). Two parts:
events.ndjsonwas rewritten world/group-readable aftermk bootstrapandmk merge.appendEvent/compactLogwrite the event log0o600(#138), butbootstrapEvents(src/bootstrap.ts) andmergeStores(src/merge.ts) rewrote the whole log viawriteFileAtomic(logPath, ndjson)without a mode → it landed at the umask default (0o644), exposing SECRET-derived atom snapshots/evidence until the nextappendEvent. Both now pass0o600. (Surfaced by the code review of part 2.)store-permissionsaudited.memory-index.db+ SECRET atom files but not the NDJSON sidecarsevents.ndjson(#138) andtriples.ndjson(#370/#380).probe()now checks both when present;fix()repairs them to0o600(it already iterates violations, andformatViolationrenders non-atom paths by basename — a drift prints e.g.triples.ndjson has mode 640, expected 600).+4 POSIX-guarded tests (
doctor-store-checksprobe incl. abootstrapEventsregression that the un-fixed write path would have reddened;doctor-fix-checksrepair). Full suite green at 2010.Tests — MCP client↔server transport E2E + test-layer taxonomy (arch-review #391)
Test + docs, no source change → no version bump. Until now no test exercised a real MCP transport: handlers were called in-process, but the
StdioServerTransport+server.connect()bootstrap and the JSON-RPC tool/resource schema marshalling over the wire were untested. Newtest/mcp-transport-e2e.test.tswires a real SDKClientto theMcpServerover anInMemoryTransportlinked pair (same construction path assrc/mcp/server.ts:registerTools/registerResources→connect) and asserts end-to-end:listToolsreturns the registered tools with marshalled input schemas;callTool mk_rememberround-trips and the atom lands on disk;listResources/readResource('memory://decisions')return view content incl. the PLACEHOLDER (EISDIR) path; and an isolated-mode call routes the write into the agent store. The server bootstrap surfaced no bug. File-level 20s timeout (vi.setConfig) absorbs connect-handshake latency under full-suite parallel load. Also added a test-layer taxonomy totest/README.md(unit / engine-integration / CLI-subprocess-e2e / MCP-transport-e2e) with the #390 coverage caveat. +5 tests (2001 → 2006).Added — coverage tooling:
@vitest/coverage-v8+npm run test:coverage(arch-review #390)Infra/tooling, no source change → no version bump. The repo had no coverage measurement, so every figure in the architecture review was an estimate. Added
@vitest/coverage-v8(dev dep) and atest:coveragescript backed by a dedicatedvitest.coverage.config.ts(v8 provider;text/json-summary/htmlreporters;coverage/gitignored). A non-gatingcoverageCI job runs it report-only (parallel totest, so no added wall-clock on the gating path); a blocking threshold is a deliberate later decision once the baseline is trusted.The coverage config excludes the 17 subprocess CLI e2e test files (they spawn
node dist/cli/mk.js— the child isn't instrumented, so they add zero in-process coverage and time out under instrumentation/load). Consequence:src/cli/**andsrc/mcp/server.tsunder-report here (exercised end-to-end by the excluded suite, just invisibly to v8) — a measurement artifact documented inCONTRIBUTING.md+ the config header, not a real gap.Real in-process baseline (2026-06-17): Lines 72.28% (5186/7174), Statements 71.6%, Functions 78.57%, Branches 61.15% (3012/4925) — replacing #358's estimates. Engine modules (
src/scoring/*,src/index/*, recall, etc.) are well-covered; the 0%-line entries are allsrc/cli/*command modules (the documented subprocess artifact). Also bumped thewander-frontier-cap1200-atom stress test's explicit timeout 15s→30s so it clears the bar under instrumentation.Changed — split
index-db.tsinto internalsrc/index/*modules behind a facade (arch-review #368)Internal refactor, no public API change (PATCH-class).
src/index-db.ts(1297 lines bundling connection lifecycle, FTS, embeddings, relations, and reindex/indexing) is now a thin re-export facade over internal modules undersrc/index/:connection.ts(owns the single process-global SQLite connection cache + schema +openIndex/closeIndex/closeAllIndexes/indexExists),indexing.ts(atoms-table write/read:reindex/indexAtom/removeFromIndex/queryIndex/indexStats/getAllAtomIds+ the internalbodyHash/extractTitlehelpers),embeddings.ts(KNN vector storage/retrieval),fts.ts(atom + episode full-text search + term-frequency/corpus helpers), andrelations.ts(typed graph edges). Every other module obtains its handle fromconnection.ts, so there is exactly one connection cache — the correctness-critical constraint. Notriples.tsmodule: triple write/read already live insrc/triples.ts/src/triples-sidecar.tsand the reindex reconcile stays inline inindexing.reindex. All ~44 importers and the public barrel (src/index.ts) keep importing from./index-db.jsunchanged;public-api-surfacesnapshot unchanged; full suite green at 2001 (no test-count change). Heaviest of the arch-review items; landed after the #379 reconcile fix so the union-reconcile was fixed in the old shape before the split.Tests — CLI error/branch + extract/export-obsidian/mcp-resources coverage (arch-review #358)
Test-only, no source or behaviour change. Closes the honest coverage gaps the architecture review flagged:
src/cli/extract.ts(~4% — its validation branches all fire before the LLM spawn),src/cli/export-obsidian.ts(~32% —transformAtomhad only the CodeQL YAML-escape tests), andsrc/mcp/resources.ts(~44% —registerResources+ thesafeReadViewPLACEHOLDER fallback were uncovered). Newtest/cli-extract-errors.test.ts(subprocess; log-not-found, memory-dir-not-found, invalid--max-atoms/--skip-lines/--max-input-chars, plain +--jsoncontracts), newtest/export-obsidian-cli.test.ts(transformAtomclassification/tags/relations/wikilink-resolution branches +export-obsidiansubprocess e2e incl.--include-archived/--json), and extendedtest/mcp-resources.test.ts(explicit PLACEHOLDER via an EISDIR read error + aregisterResourcesmock-server wiring test). Review pass also added theresolveAtomIdCase-2 slug-match branch test and tightened two pre-existing weaktypeof-only assertions. +21 tests (1980 → 2001).Changed —
resolveContextDirhelper de-duplicates CLI dir+agent boilerplate (arch-review #360)CLI-internal refactor, no behaviour change, no public API change (PATCH-class). 16 command handlers in
src/cli/mk.tsopened with the identicalresolveDir(opts.dir, getAgent())boilerplate, re-threading the--agentisolation lookup at every site. Introduced a small CLI-internalresolveContextDir(opts)helper (a literal wrapper aroundresolveDir(opts.dir, getAgent())) and collapsed all 16 sites toresolveContextDir(opts).--diris a per-command option defaulting to./memory, soopts.diris always a string. The separateif (!fs.existsSync(memoryDir)) exitWithError(...)pattern (which varies per command in message/JSON-flag) is left untouched to avoid behaviour drift. Behaviour is byte-for-byte identical; proven by the existing CLI integration suite. No new test (trivial literal wrapper);public-api-surfacesnapshot unchanged.Changed —
entity_triplesare now durable on disk via atriples.ndjsonsidecar (arch-review #370)PATCH-class (no public API change). Previously LLM-extracted entity triples lived only in the SQLite index (
entity_triples); deleting.memory-index.dblost them permanently — the one documented exception to "files are truth, index is a derived cache." Triples now have a durable on-disk home: a newtriples.ndjsonsidecar at the store root (parallel toevents.ndjson,src/triples-sidecar.ts).insertTriplesmirrors every row into it, andmk reindexrebuildsentity_triplesfrom it — so deleting the index and reindexing now restores all triples. Reindex also reconciles the sidecar to the rebuilt table, which backfills pre-#370 stores (runmk reindexonce after upgrading to create the sidecar) and prunes triples for atoms that no longer exist. Theentity_triplesexception is removed fromdocs/invariants.mdand theindex-db.tsheader. New store filetriples.ndjson; sidecar helpers are internal (not exported from the package barrel). +6 tests (test/triples-sidecar.test.ts). Post-merge review hardening (#378 follow-up): the sidecar is now written owner-only (0o600) andfsynced for parity withevents.ndjson/atom files (#138) —writeTriplesSidecarreusesstore.writeFileAtomic,appendTriplesSidecaropens +fsyncs +chmods likeappendEvent— so triple content (possibly extracted from SECRET atoms) is not world/group-readable and durably reaches disk; the stale "entity_triplesis the exception / lost on index deletion" cross-references left behind inREADME.md,CONTRIBUTING.md,docs/migration.md, both agent quickrefs,docs/host-integration-doctrine.md,scripts/activate-memory.ts, and the.privacyCLAUDE.md override were corrected to the sidecar model, and the stale inline comment inindex-db.ts reindex()was fixed. +2 permission regression tests. Reconcile-correctness hardening (#379 follow-up): the reconcile is now a union over still-existing atoms instead of all-or-nothing. Previously recovery was gated on the wholeentity_triplestable being empty, so a table that was a non-empty subset of the durable sidecar (partial DB row loss/corruption, or an atom file transiently missing during the snapshot restore) skipped recovery andwriteTriplesSidecartruncated the sidecar to the smaller table set — silent loss from the durable layer. Reindex now unions the rebuilt table with the sidecar's rows for existing atoms, deduped by(atom_id, subject, predicate, object)keeping the earliestcreated_at, and rewrites both stores (delete + reinsert) to that deduped union — so the sidecar is truly authoritative, and latent duplicates (append-only sidecar + noUNIQUEonentity_triples) collapse. Safe because there is no intentional per-atom triple deletion (only FKON DELETE CASCADE, already handled by the existing-atom filter). +2 regression tests (table-subset recovery; duplicate collapse).Changed — moved
markExecutedto the engine layer (arch-review #376, sibling of #359)Internal refactor, no public API change (PATCH-class). Completes the #359 layering cleanup: the public barrel re-exported
markExecuted(#309) + itsExecuteOptions/ExecuteResulttypes from./cli/execute.js, butmarkExecutedis engine logic (marks an atom executed-once; called bymk executeand re-exported as public SDK API). It now lives in a new engine modulesrc/execute.ts(importing from concrete modules —./store.js,./index-db.js,./retain.js,./event-log.js,./format.js,./atom-lookup.js— not the barrel);src/cli/execute.tsis now a thin Commander wrapper importing from../execute.js;src/index.tsre-exports from./execute.js. Public API names are identical (markExecuted/ExecuteOptions/ExecuteResult), so thepublic-api-surfacesnapshot is unchanged and thesdk-referenceimport { markExecuted } from 'memory-kernel'example stays correct.grep "from './cli/" src/*.tsnow returns nothing (incl. the barrel). The barrel touch trips docs-hygiene Layer-2 with no user-facing surface change, so the PR uses the sanctioneddocs-hygiene-overridelabel.test/execute.test.ts'smarkExecutedimport repointed to the engine path (red→green).Changed — moved
supersedeAtoms/findAtomFileto the engine layer (arch-review #359)Internal refactor, no public API change (PATCH-class). Fixes the engine→CLI layering inversion the architecture review flagged:
src/conflict-detect.tsandsrc/seed.ts(engine) importedsupersedeAtomsfromsrc/cli/supersede.js.supersedeAtoms(+ itsSupersedeOptions/SupersedeResulttypes) now lives insrc/supersede.tsandfindAtomFile— whichsupersedeAtomsdepends on — now lives insrc/atom-lookup.ts(both engine modules importing from concrete modules, not the barrel).src/cli/supersede.tsis now a thin Commander wrapper;cli/relate.tsandcli/execute.tsimportfindAtomFilefrom the engine path.grep "from './cli/" src/*.tsno longer matches any non-barrel engine module. (The one remaining sibling — the public barrel re-exportingmarkExecutedfromcli/execute.js— is fixed in #376 above.) No behavior change;public-api-surfacesnapshot unchanged.Added —
mk grounding: advisory confidence-vs-usage reconciliation, read-only (#245)MINOR-class (new public API). New read-only
mk groundingcommand and pure engine that reconcile each atom's stated confidence (theprior) against a usagegrounding_scorederived purely from the event log, then bin every atom into a 2×2prior × groundingquadrant. No atom files are ever written — this is the advisory half of #245; the destructive confidence write-back (issue step 4) is deferred and gated onhuman_editprovenance events (#247), so confidence is never silently rewritten from a noisy usage signal.grounding_score∈ [0.01, 1] — a prior-independent posterior over use:clamp((0.5·recency + 0.5·frequency) · 0.6^n_conflict, 0.01, 1), whererecency = 0for a never-read atom (else2^(−days_since_last_read / H)with a per-type half-lifeH— fact 30d, belief 180d, preference 60d, decision 90d, procedure 365d, …),frequency = 1 − 2^(−n_access / 5), and each detected conflict multiplies by 0.6. A never-read atom floors at 0.01 regardless of age — grounding is about use, not birth, soage_daysis reported as an input but is not a score term.--prior-threshold 0.6,--grounding-threshold 0.5;≥is "high"): well-grounded (prior matches use — inert), review (high prior / low grounding — stated confidently, not validated by use; actionable), promote (low prior / high grounding — written cautiously, grounded by use; actionable once read across ≥2 sessions), noise (low / low; actionable once the atom is ≥5 sessions old). Onlyactive, non-conflictatoms are graded by default (--include-allgrades everything).mk grounding -d <dir> [--json] [--prior-threshold <n>] [--grounding-threshold <n>] [--actionable-only] [--include-all]. Human output groups by quadrant and prints an advisory footer naming the deferral;--jsonemits the full engine summary plusreports(honoring--actionable-only) and a machine-readableshownrow-count so a consumer never mistakessummary.totalfor the number of rows present.computeGrounding(atoms, events, opts)+classifyQuadrant(...), the tuning constants (DEFAULT_HALF_LIVES,DEFAULT_HALF_LIFE,DEFAULT_ACCESS_HALF_SATURATION,DEFAULT_CONFLICT_DECAY,DEFAULT_PRIOR_THRESHOLD,DEFAULT_GROUNDING_THRESHOLD,DEFAULT_PROMOTE_MIN_SESSIONS,DEFAULT_NOISE_SESSIONS), and types (GroundingResult/GroundingReport/GroundingOptions/GroundingInputs/GroundingQuadrant/ClassifyContext/QuadrantVerdict). The engine validates its scalar knobs (throwsRangeErroron an out-of-[0,1] weight rather than silently producing NaN the clamp would mask) and is shared so the deferred Phase-2 write-back (#247-gated) reuses the exact scoring.mk groundingrun and assert zero changes, plus a dedicated test that deletes any setup-built SQLite index and confirms grounding never rebuilds one (it reads the NDJSON event log directly, never opening the index). +23 tests (test/grounding.test.ts). README +docs/sdk-reference.md+docs/contracts.mdupdated.Added —
mk extractoversized-input guard with a distinguishable failure signal (#355)MINOR-class (new public API).
mk extractnow pre-flights the assembled prompt size (extraction system prompt + the full log) against a budget before spawning the LLM. Over-budget input fails fast and recognizably — exit code 2 and, under--json,{"error":<human message>,"reason":"input_too_large","exit_code":2,"input_chars":…,"limit":…}— instead of the opaqueclaude -p exited with code 1that previously stopped fleet session-end atom creation silently (a multi-MB transcript stalled extraction ~2 days). A cron/host wrapper can branch on the distinct exit code / the stablereason: "input_too_large"token and retry, rather than treating it as a generic crash. (erroris the same human-readable string every command's--jsonuses;reasonis the machine discriminator.)mk extract --max-input-chars <n>(defaultDEFAULT_MAX_INPUT_CHARS= 500 000) and--truncate(keep the newest content and drop the oldest to fit: a visible marker is prepended, omissions reported).ExtractInputTooLargeError→ exit code 2 (vs the generic 1 fromexitWithError); plain output prints⚠ input truncated: sent … of … chars (… omitted from the beginning)when truncation fired.planExtractInput(content, systemPromptChars, opts)(unit-testable without an LLM), typed errorExtractInputTooLargeError(carries.inputChars/.limit), constantDEFAULT_MAX_INPUT_CHARS, and typeExtractInputPlan.ExtractOptionsgainsmaxInputChars?/truncate?;ExtractResultgainstruncation?: { original_chars, sent_chars, omitted_chars }(present only when truncation fired).--max-input-charshigh enough for its input, or--truncate.--skip-lines, which trims head preamble), converges by dropping ≥1024 leading chars per non-fitting iteration, and is surrogate-pair-safe at the seam. If even an empty body overflows the budget (e.g. a system prompt larger than the limit), it throws rather than send a degenerate request. Full chunking (multi-pass over the whole transcript) is deliberately deferred — this lands the guard, not a chunker; byte-level / streaming intake is the separate follow-up (#361).test/extract-input-guard.test.ts): pure-planner cases (fits / throws / default limit / truncate-keeps-tail / surrogate-seam-safe / truncate-still-throws) and integration cases (pre-flight throws before anyfetch;--truncateproceeds and reports; normal input attaches notruncation). README +docs/sdk-reference.md+docs/contracts.md+docs/troubleshooting.md(new §6) updated.Changed — single source of truth for the recall reservation rule (arch-review #357)
Internal refactor, no public barrel change (PATCH-class). The rule "type-reservations default OFF for task-focused recall, ON for the no-task constitution pipeline, with an explicit
--reservations/--no-reservationschoice always winning" was inline ingetTypeReservations(src/recall.ts) and untested in isolation. Extracted to a pureshouldUseReservations(hasTask, explicit?)helper (exported fromsrc/recall.tsfor unit-testing but deliberately not re-exported from the package barrel, so thepublic-api-surfacesnapshot is unchanged);getTypeReservationsnow routes through it (explicit = no_reservations === undefined ? undefined : !no_reservations). Behaviour is byte-for-byte preserved across all fourno_reservations×taskcases. Audit note: the CLI (src/cli/mk.ts) does not re-implement the task rule — it only inverts the Commander flag into the engine'sno_reservationsfield and delegates — so the drift the review flagged is resolved by making the engine decision explicit and tested. +4 helper unit tests (test/recall-should-use-reservations.test.ts).Changed — decomposed
recall()body into named in-file stages (arch-review Phase B, #367)Internal refactor, no public API change (PATCH-class). Following Phase A's pure-scoring extraction, the ~357-line
recall()body is now a thin orchestrator over two named in-file, non-exported stage functions:selectCandidatePool(...)(the #214 anchor-set logic — union of FTS-matched + semantic-matched IDs, optional 1-hop graph expansion, andrecall_statusmatch/no_match/fts_unavailabledetermination; returns{ filtered, recallStatus }) andscoreTaskAtoms(...)(the task-path scoring loop — FTS + semantic + specificity + length + coverage combination, single-hop graph boost, and the comparator sort; returns thefinal_scoremap, sorts in place). The no-task sort,applyTokenBudgetdelegation, and episode handling are left as-is per the issue. Behaviour-preserving: byte-for-byte identical logic moved into functions; no new modules or exports;public-api-surfacesnapshot unchanged; full suite green at 1974 tests (no test count change). Second step of the recall decomposition tracked under thearch-reviewlabel.Changed — extracted pure recall scoring into
src/scoring/*(arch-review Phase A)Internal refactor, no public API change (PATCH-class). The IO-free ranking primitives that were inlined in
recall.tsare now their own unit-testable modules:src/scoring/temporal.ts(temporalDecay),src/scoring/diversity.ts(computeTextSimilarity,applyMMR+ the internal trigram helper),src/scoring/length.ts(computeLengthFactors), andsrc/scoring/graph-boost.ts(applyGraphBoost).recall.tsimports them for internal use and re-exportstemporalDecay/computeTextSimilarity/applyMMR/computeLengthFactors, so the public barrel (src/index.ts) and the../src/recall.jstest import path are byte-identical (thepublic-api-surfacesnapshot is unchanged).recall.tsdrops from 1155 → 931 lines. The IO-coupled scorers (computeCoverageBoosts,computeSpecificityScores) stay inrecall.tsfor now. +13 tests (test/scoring-modules.test.ts). First step of the recall decomposition tracked under thearch-reviewlabel.Added — nightly KNOWLEDGE auto-observe in the cron wrapper (#256)
The runtime half of the #244 canonical-
KNOWLEDGE/-dir feature.mk init --cron's generated wrapper gains a Step 0 that scans the store'sKNOWLEDGE/tree for.mddocs changed since the last run,mk observe … --mode documents each, thenreflect(Step 1) turns the observations into atoms — so research/design docs dropped intoKNOWLEDGE/become memory automatically on the nightly cycle. SkipsKNOWLEDGE/draft/(work-in-progress) and the scaffoldedKNOWLEDGE/README.md; idempotent via a.knowledge-manifest(per-doc<mtime>\t<path>) so unchanged docs aren't re-observed (no duplicate atoms); fail-soft (a failedobservelogs a WARN, carries forward the old mtime to retry next run, never aborts the sync — the #303 contract). Portable bash (3.2-safe;stat -c‖stat -f,awk -F'\t'lookup). The manifest is auto-gitignored (per-host runtime state — local mtimes + absolute paths — so the wrapper'sgit add -Adoesn't churn/conflict it across hosts; review note). +2 tests (test/cron-template.test.ts) incl. an end-to-end smoke (observes a doc, skips draft/+README, idempotent on re-run, re-observes on mtime bump, manifest gitignored). Wrapper-template change (regenerate viamk init --cron --update) — PATCH-class.This discussion was created from the release v1.35.0.
All reactions