@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 &gt; resolveStoreDirectory > primary agent maps to <store>/agent/
[0.040ms]
isolation: agent &gt; resolveStoreDirectory > direct subagent maps to <store>/subagent/<key>/
[0.040ms]
isolation: agent &gt; resolveStoreDirectory > nested subagent (two levels) maps to <store>/subagent/<parent>.<child>/
[0.030ms]
isolation: agent &gt; resolveStoreDirectory > deeply nested subagent (three levels) flattens with dots
[0.020ms]
isolation: agent &gt; resolveStoreDirectory > trailing slash on store is normalized away
[0.020ms]
isolation: project &gt; resolveStoreDirectory > primary agent maps to <store>/project/ regardless of agent path
[0.020ms]
isolation: project &gt; resolveStoreDirectory > subagent under project isolation also maps to <store>/project/
[0.020ms]
isolation: project &gt; resolveStoreDirectory > nested subagent under project isolation also maps to <store>/project/
[0.020ms]
validation &gt; resolveStoreDirectory > agent path that does not start with 'primary' throws
[0.080ms]
validation &gt; resolveStoreDirectory > agent path with malformed segment delimiters throws
[0.050ms]
validation &gt; resolveStoreDirectory > agent path containing 'subagents' without a following key throws
[0.040ms]
validation &gt; resolveStoreDirectory > empty store throws
[0.040ms]
validation &gt; resolveStoreDirectory > invalid isolation value throws
[0.040ms]
config:/ resolution &gt; resolveStoreUri > config:/memory → <config_root>/memory/<project_id>
[0.040ms]
config:/ resolution &gt; resolveStoreUri > config:/memory/special-place → <config_root>/memory/special-place/<project_id>
[0.020ms]
config:/ resolution &gt; resolveStoreUri > config:/ with trailing slash is normalized
[0.020ms]
project:/ resolution &gt; resolveStoreUri > project:/memory → <project_root>/memory (no project_id appended)
[0.020ms]
project:/ resolution &gt; resolveStoreUri > project:/sub/dir → <project_root>/sub/dir
[0.020ms]
project:/ resolution &gt; resolveStoreUri > project:/ with trailing slash is normalized
[0.110ms]
path traversal rejection &gt; resolveStoreUri > config:/../escape is rejected
[0.090ms]
path traversal rejection &gt; resolveStoreUri > project:/../escape is rejected
[0.040ms]
path traversal rejection &gt; resolveStoreUri > config:/foo/../bar is rejected (mid-path traversal)
[0.030ms]
path traversal rejection &gt; resolveStoreUri > project:/memory/../../etc is rejected
[0.030ms]
scheme validation &gt; resolveStoreUri > naked path (no scheme) is rejected
[0.040ms]
scheme validation &gt; resolveStoreUri > relative path is rejected
[0.020ms]
scheme validation &gt; resolveStoreUri > file:// scheme is rejected
[0.020ms]
scheme validation &gt; resolveStoreUri > https:// scheme is rejected
[0.030ms]
scheme validation &gt; resolveStoreUri > empty string is rejected
[0.030ms]
context validation &gt; resolveStoreUri > empty project_id is rejected for config:/ resolution
[0.030ms]
context validation &gt; resolveStoreUri > empty project_root is rejected for project:/ resolution
[0.030ms]
context validation &gt; resolveStoreUri > empty config_root is rejected for config:/ resolution
[0.040ms]
context validation &gt; resolveStoreUri > project_root is not consulted when resolving config:/ URIs
[0.030ms]
context validation &gt; resolveStoreUri > config_root is not consulted when resolving project:/ URIs
[0.020ms]