@kaged/plugin-memory-markdown

Reference project plugin for agent memory — markdown-based entry storage with keyword scoring, recency boost, tag filtering, and auto-facts aggregation

12
source files
10
test files
~3.9k
lines
✓ 186 pass
tests
pass
typecheck
clean
lint

Test results 186

mergeConfig > applies defaults when no overrides are provided [0.120ms]
mergeConfig > merges project and system config and clones tags [0.110ms]
buildEntryFile > produces a file with --- delimited YAML frontmatter and the body [0.280ms]
buildEntryFile > emits scalar fields in the frontmatter [0.060ms]
buildEntryFile > emits tags as a YAML array [0.110ms]
buildEntryFile > omits optional fields when not provided [0.060ms]
buildEntryFile > body ending without trailing newline still produces a file ending with newline [0.040ms]
parseEntryFile > round-trips a full entry [0.420ms]
parseEntryFile > round-trips a minimal entry (no optional fields) [0.100ms]
parseEntryFile > preserves snake_case keys (does not camelCase them) [0.080ms]
parseEntryFile > preserves multi-line body content [0.060ms]
parseEntryFile > throws on file with no frontmatter delimiters [0.110ms]
parseEntryFile > throws on file with malformed YAML in frontmatter [0.150ms]
parseEntryFile > throws on missing required field [0.080ms]
parseEntryFile > throws on invalid kind value [0.070ms]
parseEntryFile > accepts each valid kind [0.150ms]
handleRetain > writes entry and returns stored result with valid snowflake ID [3.18ms]
handleRetain > merges config.tags with params.tags [2.03ms]
handleRetain > uses only config.tags when params.tags absent [2.05ms]
handleRetain > resolves config:/ store URI correctly [1.23ms]
handleRetain > resolves subagent directory under agent isolation [1.14ms]
handleRetain > resolves project isolation to shared directory [1.06ms]
handleRecall > returns scored results for a query [5.54ms]
handleRecall > filters by tags [4.25ms]
handleRecall > respects max_results [3.25ms]
handleRecall > returns empty for fresh store [0.270ms]
handleForget > deletes an existing memory entry [1.56ms]
handleForget > returns not_found for missing IDs [0.130ms]
handleListTags > collects unique tags in alphabetical lowercase order [2.63ms]
handleOnSessionStart > ensures store ready and returns inject when entries exist [2.78ms]
handleOnSessionStart > returns empty object when no entries exist [1.18ms]
handleOnSessionStart > creates README.md at store root [1.18ms]
handleOnSessionStart > respects inject_on_session_start_max_entries [4.43ms]
handleOnSessionStart > excludes transcript-kind entries from injection [3.66ms]
handleOnSessionIdle > writes transcript to transcripts/ directory [1.54ms]
handleOnSessionIdle > is a no-op when retain_on_session_idle is false [0.140ms]
handleOnSessionIdle > upserts same session — second call overwrites [1.90ms]
handleOnCompact > preserves compacted messages on disk and returns inject [3.56ms]
handleOnCompact > writes compact-chunk transcript file [2.25ms]
handleOnCompact > returns no inject when store is empty [1.40ms]
DEFAULT_CONFIG > matches spec defaults [0.090ms]
generateMemoryId > returns a 16-character hex string [0.050ms]
generateMemoryId > generates unique IDs on rapid successive calls [0.820ms]
generateMemoryId > IDs generated in sequence sort lexicographically by creation order [0.190ms]
generateMemoryId > IDs generated across a delay still sort by creation order [5.28ms]
isValidMemoryId > accepts a freshly generated ID [0.020ms]
isValidMemoryId > accepts a literal 16-hex string [0.030ms]
isValidMemoryId > rejects too-short strings [0.010ms]
isValidMemoryId > rejects too-long strings [0.010ms]
isValidMemoryId > rejects uppercase hex [0.010ms]
isValidMemoryId > rejects non-hex characters [0.010ms]
isValidMemoryId > rejects empty string [0.010ms]
writeEntry > creates the directory and writes a .md file with a valid snowflake ID [1.26ms]
writeEntry > written file round-trips through parseEntryFile [1.24ms]
writeEntry > omits optional fields when not provided [1.74ms]
writeEntry > omits tags when array is empty [1.21ms]
writeEntry > rejects content > 100 KB with ContentTooLargeError [0.370ms]
writeEntry > short fact content appends to facts.md [1.09ms]
writeEntry > long content does NOT append to facts.md [1.23ms]
writeEntry > multi-line content does NOT append to facts.md [0.900ms]
writeEntry > markdown-structured content does NOT append to facts.md [1.17ms]
writeEntry > facts.md accumulates across multiple writes [1.96ms]
writeEntry > each call generates a unique ID [2.89ms]
readEntries > returns entries from written files [2.17ms]
readEntries > returns empty array for non-existent directory [0.190ms]
readEntries > skips facts.md [0.430ms]
readEntries > skips README.md [0.240ms]
readEntries > skips corrupt files without throwing [0.490ms]
recall > returns scored results sorted by score desc [1.83ms]
recall > filters by tags when provided [0.710ms]
recall > tag filter is case-insensitive [0.610ms]
recall > respects max_results [0.640ms]
recall > returns empty results for non-existent directory [0.170ms]
recall > result shape matches spec (content not body, null for missing context) [0.690ms]
recall > ties broken by created_at descending [0.620ms]
writeTranscript > writes a transcript file under transcripts/ [1.08ms]
writeTranscript > upsert semantics: second write overwrites the same file [1.79ms]
writeTranscript > compactId produces a different filename [1.04ms]
writeTranscript > includes tags and context when provided [1.15ms]
ensureStoreReady > creates directory and README.md [2.28ms]
ensureStoreReady > preserves existing README (operator customization) [0.950ms]
ensureStoreReady > includes store description when provided [0.960ms]
ensureStoreReady > is idempotent — calling twice does not error [1.68ms]
getRecentRetains > returns only retain-kind entries, sorted by created_at desc [1.79ms]
getRecentRetains > respects maxEntries limit [0.850ms]
getRecentRetains > returns empty for non-existent directory [0.160ms]
formatInjection > formats entries as bulleted list with tag prefix [0.090ms]
formatInjection > entries without tags have no tag prefix [0.050ms]
formatInjection > returns null for empty array [0.030ms]
formatInjection > collapses multi-line body to single line [0.040ms]
formatInjection > uses custom header and optional tag hint [0.060ms]
deleteEntry > removes an existing memory file [1.11ms]
deleteEntry > returns not_found for missing files [0.100ms]
collectTags > returns lowercased unique tags sorted alphabetically [2.34ms]
collectTags > returns empty when directory is missing [0.180ms]
renderReadmeContent > starts with the kaged agent memory heading [0.050ms]
renderReadmeContent > includes the project id in the opening sentence [0.030ms]
renderReadmeContent > documents the per-agent storage convention [0.080ms]
renderReadmeContent > documents what subdirectories contain [0.030ms]
renderReadmeContent > includes the gitignore-or-commit guidance section [0.060ms]
renderReadmeContent > includes the safe-to-edit / not-safe-to-edit section [0.050ms]
renderReadmeContent > output ends with a single trailing newline [0.030ms]
renderReadmeContent > output is deterministic for the same input [0.030ms]
renderReadmeContent > different project_ids produce different output [0.040ms]
renderReadmeContent > empty project_id throws [0.060ms]
tokenizeQuery > splits on whitespace and lowercases [0.050ms]
tokenizeQuery > removes stopwords [0.030ms]
tokenizeQuery > returns empty array on whitespace-only input [0.020ms]
tokenizeQuery > strips punctuation [0.020ms]
tokenizeQuery > collapses repeated tokens [0.020ms]
keyword score > scoreRecall > each query term matched in body scores 1 [0.100ms]
keyword score > scoreRecall > query terms matched in context score [0.050ms]
keyword score > scoreRecall > query terms matched in tags also score keyword points [0.030ms]
keyword score > scoreRecall > no matching terms scores 0 (with no recency boost) [0.060ms]
recency boost > scoreRecall > entry within last 24h gets +2 [0.100ms]
recency boost > scoreRecall > entry within last 7 days (but >24h) gets +1 [0.070ms]
recency boost > scoreRecall > entry older than 7 days gets +0 recency [0.060ms]
recency boost > scoreRecall > 24h boundary is inclusive (exactly 24h ago is still +2) [0.120ms]
tag exact-match boost > scoreRecall > query term matching a tag exactly gets +3 on top of keyword score [0.050ms]
tag exact-match boost > scoreRecall > query terms not matching a tag get no tag boost [0.050ms]
tag exact-match boost > scoreRecall > tag match is case-insensitive (tag stored lowercase, query upper) [0.050ms]
composite scoring > scoreRecall > body match + recency + tag boost compose [0.040ms]
composite scoring > scoreRecall > empty query returns recency boost only [0.060ms]
composite scoring > scoreRecall > entry with no tags still scores keyword + recency [0.040ms]
composite scoring > scoreRecall > uses configurable scoring weights [0.040ms]
idempotency > scoreRecall > calling scoreRecall twice with the same inputs returns the same score [0.040ms]
initialize handshake > responds with name, version, api_version, methods [0.790ms]
initialize handshake > initialized notification is accepted silently [0.140ms]
ping > responds with status ok [0.130ms]
shutdown > responds with status ok [0.100ms]
memory-markdown.retain > stores entry and returns stored result [1.79ms]
memory-markdown.retain > rejects missing content with -32602 [0.290ms]
memory-markdown.retain > rejects content > 100KB with plugin error [0.390ms]
memory-markdown.retain > passes context and tags correctly [1.03ms]
memory-markdown.recall > returns recall results [1.48ms]
memory-markdown.recall > rejects missing query with -32602 [0.150ms]
memory-markdown.recall > returns empty for fresh store [0.310ms]
memory-markdown.recall > filters by tags [2.52ms]
memory-markdown.forget > deletes an existing entry [1.37ms]
memory-markdown.forget > rejects missing id with -32602 [0.130ms]
memory-markdown.list_tags > returns unique lowercased tags [2.44ms]
kaged.hook.on_session_start > returns inject when entries exist [2.41ms]
kaged.hook.on_session_start > returns empty object when no entries [1.18ms]
kaged.hook.on_session_idle > writes transcript and returns null [1.14ms]
kaged.hook.on_session_idle > is a no-op when retain_on_session_idle is false [0.190ms]
kaged.hook.post_compact > preserves compacted messages and returns inject [2.93ms]
unknown method > returns -32601 method not found [0.170ms]
unknown hook > returns -32601 for unrecognized hook name [0.120ms]
malformed input > parse error on invalid JSON [0.120ms]
malformed input > invalid request on non-object [0.090ms]
malformed input > invalid request on missing jsonrpc field [0.100ms]
malformed input > empty line is silently ignored [0.090ms]
context extraction > uses defaults when _context is missing [2.15ms]
isolation: agent > resolveStoreDirectory > primary agent maps to <store>/agent/ [0.040ms]
isolation: agent > resolveStoreDirectory > direct subagent maps to <store>/subagent/<key>/ [0.040ms]
isolation: agent > resolveStoreDirectory > nested subagent (two levels) maps to <store>/subagent/<parent>.<child>/ [0.030ms]
isolation: agent > resolveStoreDirectory > deeply nested subagent (three levels) flattens with dots [0.020ms]
isolation: agent > resolveStoreDirectory > trailing slash on store is normalized away [0.020ms]
isolation: project > resolveStoreDirectory > primary agent maps to <store>/project/ regardless of agent path [0.020ms]
isolation: project > resolveStoreDirectory > subagent under project isolation also maps to <store>/project/ [0.020ms]
isolation: project > resolveStoreDirectory > nested subagent under project isolation also maps to <store>/project/ [0.020ms]
validation > resolveStoreDirectory > agent path that does not start with 'primary' throws [0.080ms]
validation > resolveStoreDirectory > agent path with malformed segment delimiters throws [0.050ms]
validation > resolveStoreDirectory > agent path containing 'subagents' without a following key throws [0.040ms]
validation > resolveStoreDirectory > empty store throws [0.040ms]
validation > resolveStoreDirectory > invalid isolation value throws [0.040ms]
config:/ resolution > resolveStoreUri > config:/memory → <config_root>/memory/<project_id> [0.040ms]
config:/ resolution > resolveStoreUri > config:/memory/special-place → <config_root>/memory/special-place/<project_id> [0.020ms]
config:/ resolution > resolveStoreUri > config:/ with trailing slash is normalized [0.020ms]
project:/ resolution > resolveStoreUri > project:/memory → <project_root>/memory (no project_id appended) [0.020ms]
project:/ resolution > resolveStoreUri > project:/sub/dir → <project_root>/sub/dir [0.020ms]
project:/ resolution > resolveStoreUri > project:/ with trailing slash is normalized [0.110ms]
path traversal rejection > resolveStoreUri > config:/../escape is rejected [0.090ms]
path traversal rejection > resolveStoreUri > project:/../escape is rejected [0.040ms]
path traversal rejection > resolveStoreUri > config:/foo/../bar is rejected (mid-path traversal) [0.030ms]
path traversal rejection > resolveStoreUri > project:/memory/../../etc is rejected [0.030ms]
scheme validation > resolveStoreUri > naked path (no scheme) is rejected [0.040ms]
scheme validation > resolveStoreUri > relative path is rejected [0.020ms]
scheme validation > resolveStoreUri > file:// scheme is rejected [0.020ms]
scheme validation > resolveStoreUri > https:// scheme is rejected [0.030ms]
scheme validation > resolveStoreUri > empty string is rejected [0.030ms]
context validation > resolveStoreUri > empty project_id is rejected for config:/ resolution [0.030ms]
context validation > resolveStoreUri > empty project_root is rejected for project:/ resolution [0.030ms]
context validation > resolveStoreUri > empty config_root is rejected for config:/ resolution [0.040ms]
context validation > resolveStoreUri > project_root is not consulted when resolving config:/ URIs [0.030ms]
context validation > resolveStoreUri > config_root is not consulted when resolving project:/ URIs [0.020ms]

Mentioned in

Type Document
adr ADR-0023: Project-plugin lifecycle hooks, per-agent declaration, isolation as a core principle
adr ADR-0024: Context compaction is kaged-owned, layered, observable, and operator-tunable
spec Spec: HTTP + WebSocket API
spec Spec: Local config
spec Spec: Plugin Host
spec Spec: Project DSL
spec Release pipeline