linksee-memory 0.7.1 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  # linksee-memory
2
2
 
3
- > **Your agent forgets everything when a session ends. Linksee Memory is the fix.**
3
+ > **Your agent forgets everything when a session ends. Worse it silently drifts from what you decided last week.**
4
4
  >
5
- > Local-first cross-LLM memory MCP — one SQLite file that **Claude Code, Cursor, Windsurf, OpenAI Codex, and Gemini CLI** all read from. Not just "what happened" but **WHY** it happened: 6-layer structured memory with precision recall that surfaces the right context at the right moment.
5
+ > Local-first cross-LLM memory MCP — one SQLite file that **Claude Code, Cursor, Windsurf, OpenAI Codex, and Gemini CLI** all read from. Not just "what happened" but **WHY** it happened: 6-layer structured memory with precision recall, **plus drift detection that catches when reality diverges from your decisions**.
6
+ >
7
+ > Memory is the entry point. Drift detection is the real value.
6
8
  >
7
9
  > `npx linksee-memory-setup` — one command, done.
8
10
 
@@ -79,7 +81,7 @@ That single `caveat` memory is what separates "flat fact storage" from "the agen
79
81
  Returns match_reasons explaining each hit
80
82
  ```
81
83
 
82
- Every memory is tagged with **exactly one layer**. `caveat`-layer entries are protected from auto-forgetting. Cold low-importance memories get compressed into `learning` entries via `consolidate()`.
84
+ Every memory is tagged with **exactly one layer**. `caveat`-layer entries are protected from auto-forgetting. Cold low-importance memories are auto-consolidated into `learning` entries on server startup.
83
85
 
84
86
  ---
85
87
 
@@ -87,21 +89,69 @@ Every memory is tagged with **exactly one layer**. `caveat`-layer entries are pr
87
89
 
88
90
  Most "agent memory" services (Mem0, Letta, Zep) save a flat list of facts. Then the agent looks at "edited file X 30 times" and has no idea why. **linksee-memory keeps the WHY.**
89
91
 
90
- It is a Model Context Protocol (MCP) server that gives any AI agent four superpowers:
92
+ It is a Model Context Protocol (MCP) server with **7 tools** that gives any AI agent structured memory + drift detection:
91
93
 
92
94
  | | Mem0 / Letta / Zep | Claude Code auto-memory | linksee-memory |
93
95
  |---|---|---|---|
94
96
  | Cross-agent | △ (cloud) | ❌ Claude only | ✅ single SQLite file |
95
97
  | 6-layer WHY structure | ❌ flat | ❌ flat markdown | ✅ goal / context / emotion / impl / caveat / learning |
98
+ | **Drift detection** | ❌ | ❌ | ✅ intent ↔ reality divergence tracking |
96
99
  | File diff cache | ❌ | ❌ | ✅ AST-aware, 50-99% token savings on re-reads |
97
100
  | Active forgetting | △ | ❌ | ✅ Ebbinghaus curve, caveat layer protected |
98
101
  | Local-first / private | ❌ | ✅ | ✅ |
99
102
 
100
- ## Three pillars
103
+ ## Four pillars
101
104
 
102
105
  1. **Token savings** via `read_smart` — sha256 + AST/heading/indent chunking. Re-reads return only diffs. **Measured 86% saved on a typical TS file edit, 99% saved on unchanged re-reads.**
103
106
  2. **Cross-agent portability** — single SQLite file at `~/.linksee-memory/memory.db`. Same brain for Claude Code, Cursor, Windsurf, OpenAI Codex, Gemini CLI.
104
107
  3. **WHY-first structured memory** — six explicit layers (`goal` / `context` / `emotion` / `implementation` / `caveat` / `learning`). Solves "flat fact memory is useless without goals".
108
+ 4. **Drift detection** — declare decisions as anchors, then the engine automatically detects when committed reality diverges from stated intent. Think "Datadog for product decisions" — unaccounted divergences surface as drift, intentional evolution (recorded as supersede/fix) stays quiet.
109
+
110
+ ## 🔍 Drift Detection — "Intent Datadog"
111
+
112
+ Most teams make decisions, then forget them. The agent from last week decided "we'll use FTS5 instead of vector search" — but this week a new session installs `pgvector` without knowing why that was rejected. **That's drift.** Not a bug. Not malice. Just forgotten context.
113
+
114
+ Linksee Memory's drift detection catches this:
115
+
116
+ ```
117
+ You: What's drifting right now?
118
+ Agent: [calls drift_status]
119
+
120
+ 28 anchors: ⚪ 1 held · 🔵 27 aligned
121
+
122
+ Needs attention:
123
+ ⚪ HELD — "Focus on 4 areas: Recipe layer, agent-native API,
124
+ Japanese market, Agent Insights"
125
+ ↻ Reopens 2026-07-04
126
+
127
+ Everything else is aligned — no unaccounted divergence.
128
+ ```
129
+
130
+ ### How it works
131
+
132
+ 1. **Declare** decisions as anchors: `declare_anchor({ kind: "decision", statement: "We use FTS5, not vector search", violation_signal: ["pgvector", "embedding"] })`
133
+ 2. **The engine detects** when committed code reality diverges from these anchors
134
+ 3. **State derivation** classifies each anchor:
135
+ - 🔴 **Drift** — reality diverges with no recorded resolution
136
+ - 🟡 **Review** — a soft signal awaits your decision
137
+ - ⚪ **Held** — you acknowledged the gap, parked it with a review date
138
+ - 🔵 **Aligned** — reality matches intent, or a recorded resolution explains the change
139
+ 4. **Resolve** with `fix`, `supersede`, `acknowledge`, or `dismiss`
140
+
141
+ The **make-or-break rule**: a divergence accounted for by a recorded resolution (supersede/fix/acknowledge) is NOT drift. Only unaccounted gaps are flagged. This means intentional evolution stays quiet while silent abandonment gets caught.
142
+
143
+ ### 4-species taxonomy
144
+
145
+ Anchors are classified into four species with different display formats:
146
+
147
+ | Species | Icon | Display Format | Example |
148
+ |---|---|---|---|
149
+ | Hypothesis | 🧪 | Decision Card (journal format) | "We'll launch English-first on HN" |
150
+ | Constraint | 🔒 | Rule (pass/fail checklist) | "All writes go through remember()" |
151
+ | Commitment | 🔁 | Heartbeat (alive/dead) | "Ship a new version every week" |
152
+ | Source of Truth | 📍 | Reference (stable anchor) | "MCP server runs on stdio, single SQLite" |
153
+
154
+ ---
105
155
 
106
156
  ## Quick Start — One Command
107
157
 
@@ -127,7 +177,7 @@ Restart Claude Code, then just chat normally. Add **"Use Linksee"** to any promp
127
177
  claude mcp add -s user linksee -- npx -y linksee-memory
128
178
  ```
129
179
 
130
- Tools appear as `mcp__linksee__remember`, `mcp__linksee__recall`, `mcp__linksee__recall_file`, `mcp__linksee__read_smart`, `mcp__linksee__forget`, `mcp__linksee__consolidate`.
180
+ Tools appear as `mcp__linksee__remember`, `mcp__linksee__recall`, `mcp__linksee__read_smart`.
131
181
 
132
182
  **Install the skill (auto-invocation):**
133
183
 
@@ -248,28 +298,60 @@ All editors share the same `~/.linksee-memory/memory.db`. A decision made in Cla
248
298
 
249
299
  Default: `~/.linksee-memory/memory.db`. Override with `LINKSEE_MEMORY_DIR` env var.
250
300
 
251
- ## What's new in v0.4
301
+ ## What's new in v0.8
302
+
303
+ | Feature | Detail |
304
+ |---|---|
305
+ | **4 drift detection tools** | `drift_status`, `check_decision`, `declare_anchor`, `resolve_drift` — agents can now query and act on intent ↔ reality divergence. The biggest gap in agent memory (decisions are forgotten across sessions) is now closed. |
306
+ | **Truth engine** | State derivation logic (drift/review/held/aligned) now lives in the MCP engine, not just the dashboard. Any MCP client can query drift status. |
307
+ | **4-species taxonomy** | Anchors classified as hypothesis/constraint/commitment/source_of_truth with species-appropriate display formats. |
308
+ | **Resolution priority** | When multiple resolutions exist for an anchor, the most recent one wins (prevents stale acknowledge from shadowing a newer fix). |
309
+
310
+ <details>
311
+ <summary>What's new in v0.7</summary>
312
+
313
+ | Feature | Detail |
314
+ |---|---|
315
+ | **3-tool unified surface** | 8 tools → 3: `remember` (create + update + delete), `recall` (search + file history + overview), `read_smart` (token-saving reads). Fewer tools = better cross-LLM consistency. Follows Context7's proven pattern. |
316
+ | **Auto-consolidate** | Consolidation runs automatically on server startup (non-blocking, 7-day threshold). No manual `consolidate()` calls needed. |
317
+ | **Deprecation guidance** | Old tool names (`forget`, `recall_file`, etc.) return specific migration examples instead of silent failures. |
318
+ | **"Use Linksee Memory" trigger** | Add "Use Linksee Memory" to any prompt to force memory recall — same adoption pattern as Context7. |
319
+ | **Claude Code Plugin** | `claude plugin add -- linksee-memory` — ships MCP server + auto-invocation skill in one install. |
320
+
321
+ </details>
322
+
323
+ <details>
324
+ <summary>What's new in v0.4</summary>
252
325
 
253
326
  | Feature | Detail |
254
327
  |---|---|
255
328
  | **One-command setup** | `npx linksee-memory-setup` — registers MCP server, installs skill, configures auto-capture hook. One command instead of three. |
256
329
  | **Structured memory v2** | 3-axis classification (altitude × type × state) for every memory. Auto-extraction from sessions produces machine-scannable JSON, not raw chat dumps. |
257
330
  | **Precision recall guide** | SKILL.md now teaches agents HOW to write effective queries, WHEN to recall vs skip, and WHEN to proactively surface caveats before risky actions. |
258
- | **"Use Linksee" trigger** | Add "Use Linksee" to any prompt to force memory recall same adoption pattern as Context7. |
259
- | **Five MCP Blocks** | Tools + Resources + Prompts + Sampling + Roots + Elicitation. Most MCP servers expose only Tools; linksee-memory implements all six primitives. |
331
+ | **Five MCP Blocks** | Tools + Resources + Prompts + Sampling + Roots + Elicitation. Most MCP servers expose only Tools; linksee-memory implements all five primitives. |
332
+
333
+ </details>
334
+
335
+ ## 7 Tools (v0.8)
336
+
337
+ ### Memory tools
338
+
339
+ | Tool | What it does |
340
+ |---|---|
341
+ | `remember` | **Save / update / delete** memories. Auto-classifies into 6 layers. Modes: create (default), update (`memory_id` + fields), delete (`forget: true` + `memory_id`). |
342
+ | `recall` | **Search / file history / overview.** Modes: search (`query`), file history (`path`), entity overview (no params). FTS5 + heat × momentum ranking with `match_reasons`. |
343
+ | `read_smart` | **Token-saving file reader** with AST diff caching. First read = full content. Re-read unchanged = ~50 tokens. Re-read modified = changed chunks only. |
260
344
 
261
- ## Tools
345
+ ### Drift tools (v0.8.0)
262
346
 
263
- | Tool | Purpose |
347
+ | Tool | What it does |
264
348
  |---|---|
265
- | `remember` | Store memory in 1 of 6 layers for an entity. Rejects pasted assistant output / CI logs unless `force=true`. Set `importance=1.0` to pin (survives auto-forget). |
266
- | `recall` | FTS5 + heat × momentum × importance composite ranking with `match_reasons` explaining WHY each row matched. Supports pagination (`offset`/`has_more`), `band` filter, layer aliases (`decisions`/`warnings`/`how`/...), and `mark_accessed=false` for passive previews. |
267
- | `recall_file` | Complete edit history of a file across all sessions, with per-edit user-intent context. **v0.3.0** `scope_to_roots` flag filters by client roots. |
268
- | `update_memory` | **v0.1.0** Atomic edit of an existing memory. Preserves `memory_id` (session_file_edits links stay intact). Prefer over forget+remember. |
269
- | `list_entities` | **v0.1.0** List what the memory knows about — cheapest "what do I know?" primitive. Filter by `kind`/`min_memories`; returns layer breakdown per entity. |
270
- | `read_smart` | Diff-only file read. Returns full content on first read, ~50 tokens on unchanged re-reads, only changed chunks on real edits. |
271
- | `forget` | Explicit delete OR auto-sweep based on `forgettingRisk`. Pinned (`importance>=1.0`) and caveat-layer memories are always preserved. **v0.3.0** `interactive` flag asks the user via Elicitation before deleting a specific memory_id. |
272
- | `consolidate` | Sleep-mode compression: cluster cold low-importance memories → protected learning-layer summary. Supports `dry_run` preview. **v0.3.0** `use_llm` flag asks the client LLM (Sampling) to rewrite cluster summaries into prose. |
349
+ | `drift_status` | **"What's drifting right now?"** Returns the truth map with 4-species classification (hypothesis/constraint/commitment/source_of_truth) and per-node state (🔴 drift / 🟡 review / held / 🔵 aligned). |
350
+ | `check_decision` | **Deep-dive into a specific decision.** Returns the full context: what was decided, why, what reality says, pending candidates, and drift edges. |
351
+ | `declare_anchor` | **Record a decision as a truth-map anchor.** The drift detector checks these against committed reality. Supports v9 fields (domain, confidence, lifecycle, review_after). |
352
+ | `resolve_drift` | **Close the loop.** Record a resolution: `fix` (reality now matches), `supersede` (intent evolved), `acknowledge` (parking with review date), or `dismiss` (false positive). |
353
+
354
+ Previous versions exposed 3 tools — v0.8.0 added 4 drift tools that let agents query and act on product-level intent reality divergence. The memory tools are unchanged.
273
355
 
274
356
  ### CLI utilities
275
357
 
@@ -313,7 +395,7 @@ A single SQLite file (`better-sqlite3` + FTS5 trigram tokenizer for JP/EN) conta
313
395
  - **Layer 4** — `events` (time-series log for heat / momentum computation)
314
396
  - **Layer 5** — `file_snapshots` + `session_file_edits` (diff cache + conversation↔file linkage)
315
397
 
316
- The conversation↔file linkage is the key. Every file edit captured by the Stop hook is stored alongside the **user message that drove the edit**. So `recall_file("server.ts")` returns "this file was edited 30 times across 3 days, and here are the actual user instructions that motivated each change".
398
+ The conversation↔file linkage is the key. Every file edit captured by the Stop hook is stored alongside the **user message that drove the edit**. So `recall({ path: "server.ts" })` returns "this file was edited 30 times across 3 days, and here are the actual user instructions that motivated each change".
317
399
 
318
400
  ## Why the design choices
319
401
 
@@ -324,17 +406,22 @@ The conversation↔file linkage is the key. Every file edit captured by the Stop
324
406
 
325
407
  ## Roadmap
326
408
 
327
- - ✅ Core 8 MCP tools + Five Blocks (Tools + Resources + Prompts + Sampling + Roots + Elicitation)
409
+ - ✅ 3-tool unified surface (remember / recall / read_smart) v0.7.0
410
+ - ✅ Auto-consolidate on server startup — v0.7.0
411
+ - ✅ Claude Code Plugin (`claude plugin add -- linksee-memory`)
412
+ - ✅ Five MCP Blocks (Tools + Resources + Prompts + Sampling + Roots + Elicitation)
328
413
  - ✅ Stop-hook auto-capture for Claude Code
329
414
  - ✅ JP/EN trigram FTS5
330
415
  - ✅ One-command setup (`npx linksee-memory-setup`)
331
416
  - ✅ Structured memory v2 (3-axis classification: altitude × type × state)
332
- - ✅ Precision recall guide + proactive caveat surfacing
333
417
  - ✅ Cross-LLM: Claude Code, Cursor, Windsurf, OpenAI Codex, Gemini CLI
334
- - 🚧 Landing page + SEO
418
+ - Landing page ([linksee-site.vercel.app](https://linksee-site.vercel.app))
419
+ - ✅ Drift detection engine + 4 MCP drift tools — v0.8.0
420
+ - ✅ 4-species truth map (hypothesis/constraint/commitment/source_of_truth) — v0.8.0
421
+ - ✅ Dashboard with Decision Register visualization
422
+ - 🔮 Obsidian plugin (read truth map in your vault)
335
423
  - 🔮 Vector search via `sqlite-vec` (already in deps, embedding backend pending)
336
424
  - 🔮 Cross-device cloud sync (Pro tier)
337
- - 🔮 Optional anonymized telemetry → MCP-quality intelligence layer
338
425
 
339
426
  ## Comparison with Claude Code auto-memory
340
427
 
@@ -454,21 +541,29 @@ recall({ query: "...", entity_name: "my-project", layer: "caveat" })
454
541
  rm -rf ~/.linksee-memory # nuke everything; next run creates a fresh DB
455
542
  ```
456
543
 
457
- Or delete individual memories via the `forget` tool with a specific `memory_id`.
544
+ Or delete individual memories via `remember({ forget: true, memory_id: <id> })`.
458
545
  </details>
459
546
 
460
547
  <details>
461
548
  <summary><b>DB is getting large (>100 MB). How do I trim it?</b></summary>
462
549
 
463
- Run consolidate it clusters old cold memories into compressed learning-layer summaries:
464
- ```
465
- consolidate({ scope: "all", min_age_days: 7 })
466
- ```
467
- Caveat and active-goal layers are always preserved. Consider scheduling a weekly run via cron / Task Scheduler.
550
+ Consolidation runs automatically on server startup (7-day threshold). It clusters old cold memories into compressed learning-layer summaries. Caveat and active-goal layers are always preserved.
551
+
552
+ If you want to force a manual consolidation, restart the MCP server — auto-consolidate triggers on every startup.
468
553
  </details>
469
554
 
470
555
  ## FAQ
471
556
 
557
+ <details>
558
+ <summary><strong>What is drift detection and why do I need it?</strong></summary>
559
+
560
+ Drift = when your code reality silently diverges from what you decided. Example: Last week you decided "FTS5, not vector search" but this week a new agent session installs pgvector without knowing the history.
561
+
562
+ Linksee Memory tracks this by letting you declare decisions as "anchors" and then automatically checking committed code against them. The make-or-break rule: **intentional evolution (recorded as fix/supersede) stays quiet, while unaccounted gaps get flagged.** It's like Datadog but for product decisions instead of server metrics.
563
+
564
+ You don't need to use drift detection to benefit from linksee-memory — the 3 memory tools (remember/recall/read_smart) work independently. Drift tools are an additional layer for teams and solo devs managing multiple projects.
565
+ </details>
566
+
472
567
  <details>
473
568
  <summary><strong>How is this different from Mem0 / Letta / Zep?</strong></summary>
474
569
 
@@ -484,7 +579,7 @@ Three axes:
484
579
  Claude Code's auto-memory is Claude-only (doesn't help if you switch to Cursor, OpenAI Codex, or Gemini CLI) and stores flat markdown with no structure. linksee-memory is the same local-first principle but:
485
580
  - Works across Claude Code, Cursor, OpenAI Codex, Gemini CLI (shared SQLite)
486
581
  - Structured 6-layer format makes recall explainable
487
- - Provides explicit forget/consolidate primitives rather than the agent guessing
582
+ - Auto-consolidation compresses cold memories on startup; caveats are permanently protected
488
583
  </details>
489
584
 
490
585
  <details>
@@ -509,7 +604,7 @@ The default is no sync — the SQLite file lives at `~/.linksee-memory/memory.db
509
604
 
510
605
  Two mechanisms:
511
606
  1. **Ebbinghaus forgetting**: cold low-importance memories decay naturally, eligible for auto-forget sweeps. `caveat` layer and memories with `importance ≥ 0.9` are always protected.
512
- 2. **`consolidate()`**: compresses clusters of cold low-importance memories by entity into a single `learning`-layer summary, then deletes the originals. Run via `linksee-memory-consolidate` CLI (or schedule weekly).
607
+ 2. **Auto-consolidation**: runs on every server startup (7-day threshold). Compresses clusters of cold low-importance memories by entity into a single `learning`-layer summary, then deletes the originals. No manual scheduling needed.
513
608
 
514
609
  In practice a solo developer hits ~100MB after 6 months of heavy use. A year-old DB I tested with 80K memories still recalls in <10ms.
515
610
  </details>
@@ -523,7 +618,7 @@ Yes — any MCP-compatible client works:
523
618
  - **Cursor**: add to MCP settings in Cursor → Settings → Features → Model Context Protocol
524
619
  - **OpenAI Codex**: `codex mcp add linksee -- npx -y linksee-memory` (or `~/.codex/config.toml` with `[mcp_servers.linksee]` block)
525
620
  - **Gemini CLI**: add to `~/.gemini/settings.json` mcpServers section
526
- - **ChatGPT (web/mobile app)**: stdio MCP not supported by the consumer app — requires Remote MCP server over HTTPS. `linksee-memory-remote` planned for v0.4.
621
+ - **ChatGPT (web/mobile app)**: stdio MCP not supported by the consumer app — requires Remote MCP server over HTTPS (not yet available).
527
622
  - **Custom agent**: the MCP stdio protocol is documented at modelcontextprotocol.io
528
623
  </details>
529
624
 
@@ -536,7 +631,7 @@ Yes — any MCP-compatible client works:
536
631
  <details>
537
632
  <summary><strong>How do I verify it's actually working?</strong></summary>
538
633
 
539
- After install, in a new Claude session ask: *"Can you remember that I prefer TypeScript over JavaScript?"* Claude should confirm it called `mcp__linksee__remember` and stored this. Then in a **different session** ask: *"What languages do I prefer?"* It should recall via `mcp__linksee__recall` and return the preference with `match_reasons` showing why.
634
+ After install, in a new Claude session ask: *"Can you remember that I prefer TypeScript over JavaScript? Use Linksee Memory."* Claude should confirm it called `mcp__linksee__remember` and stored this. Then in a **different session** ask: *"What languages do I prefer? Use Linksee Memory."* It should recall via `mcp__linksee__recall` and return the preference with `match_reasons` showing why.
540
635
  </details>
541
636
 
542
637
  ## Support
@@ -548,6 +643,70 @@ After install, in a new Claude session ask: *"Can you remember that I prefer Typ
548
643
 
549
644
  ## Changelog
550
645
 
646
+ ### v0.8.0 — Drift Detection MCP Tools (2026-06-08)
647
+
648
+ **3 tools → 7 tools.** The biggest update since launch — agents can now detect, query, and resolve intent ↔ reality drift.
649
+
650
+ **New tools:**
651
+ - **`drift_status`** — returns the truth map with 4-species classification and per-node drift state
652
+ - **`check_decision`** — deep-dive into a single anchor: state, edges, pending candidates
653
+ - **`declare_anchor`** — record a decision/constraint/prohibition as a truth-map node (with v9 ProjectCoreNode fields)
654
+ - **`resolve_drift`** — close the feedback loop: fix / supersede / acknowledge / dismiss
655
+
656
+ **New engine module:**
657
+ - **`truth-engine.ts`** — state derivation logic migrated from the dashboard into the MCP engine. Any MCP client can now query drift status without a dashboard.
658
+ - **Resolution priority fix**: when multiple resolutions reference the same anchor, the most recent one wins (by `resolved_at` timestamp). Prevents a stale acknowledge from shadowing a newer fix.
659
+ - **4-species classification**: nodes classified by `decision_mode` into hypothesis / constraint / commitment / source_of_truth with display format guidance.
660
+
661
+ No breaking changes to existing memory tools. All 3 memory tools (remember, recall, read_smart) are unchanged.
662
+
663
+ ### v0.7.2 — Recall ergonomics + auto-edge detection + classifier precision (2026-05-30)
664
+
665
+ Quality pass on v0.7.0 / v0.7.1 — sharper day-to-day agent UX and cleaner data for the dashboard:
666
+
667
+ - **`recall` token discipline**: drops the redundant `content_raw` from the response (parsed `content` was already there — it was a 2× duplicate), and actually enforces `max_tokens` by greedy assembly that measures real serialized size (was a flat ~100 tok/memory estimate). Adds `approx_tokens` to the response so the agent can see its budget usage. The same query that previously returned ~15,800 tokens for a 1200 budget now stays inside it.
668
+ - **`recall` precision**: near-duplicate memories — same entity + near-identical core text, e.g. the same message captured under both `goal` and `learning` — collapse to one in the result set. Composite weights adapt to query specificity: multi-term queries weight relevance higher so off-topic-but-pinned memories don't crowd narrow recalls.
669
+ - **Capture dedup (write side)**: `session-extractor` now produces AT MOST one memory per user turn, with priority `goal[first_intent] > caveat > decision > context`. A first-intent message containing decision words (e.g. "決めた" / "これで進めよう") is no longer double-saved as both `goal` and `learning`.
670
+ - **`memory_edges` auto-detection**: the previously-empty `memory_edges` table is now populated during the sleep-mode consolidation sweep. `detectMemoryEdges()` links a later DECISION memory to the most-recent earlier same-topic decision within an entity (chain, not clique) so the dashboard can render Pivot Chains. The default relation is `extends` — a same-topic later decision builds on, but does NOT deactivate, the earlier one. Explicit reversal markers (やめる / revert / instead of) produce `contradicts`; explicit replacement markers (の代わり / replaces / deprecate) produce `supersedes`. Prevents silent deactivation of still-valid decisions.
671
+ - **`inferType` / `inferState` precision**: chitchat acknowledgements ("そうだね" / "ありがとう"), pasted terminal/git/email content, and meta-noise no longer classify as `decision` — they return `note` / `open` before pattern matching. The learning-layer default → `decision` is gated by this guard. Real decisions (採用 / 決めた, even after an acknowledgement opener) survive.
672
+
673
+ No schema migration, no breaking API changes. Existing rows keep their stored content; the classifier improvements apply to new captures going forward.
674
+
675
+ ### v0.7.1 — Review fixes (2026-05-29)
676
+
677
+ Based on Opus 4.7 design review of v0.7.0:
678
+
679
+ - **P0 — Required params guidance**: `remember` tool description now includes "REQUIRED PARAMS BY MODE" section so LLMs know exactly which fields are needed for create vs update vs delete.
680
+ - **P0 — Migration guidance**: Deprecated tool names (`forget`, `recall_file`, etc.) now return specific migration examples instead of generic errors.
681
+ - **P1 — recall path+query merge**: When both `path` and `query` are provided to `recall`, results from file history and memory search are merged into a single response.
682
+ - **P2 — Auto-consolidate safety**: Table existence check via `sqlite_master` before querying `consolidations` table, preventing errors on fresh databases.
683
+
684
+ ### v0.7.0 — 3-Tool Unified Surface (2026-05-29)
685
+
686
+ **8 tools → 3 tools.** Following Context7's proven pattern of fewer tools = better cross-LLM consistency.
687
+
688
+ **Breaking change**: The following tools are removed from the MCP surface. Calling them returns a migration guide:
689
+
690
+ | Old tool | New equivalent |
691
+ |---|---|
692
+ | `forget` | `remember({ forget: true, memory_id: <id> })` |
693
+ | `update_memory` | `remember({ memory_id: <id>, content: "..." })` |
694
+ | `recall_file` | `recall({ path: "server.ts" })` |
695
+ | `list_entities` | `recall({})` (no params = entity overview) |
696
+ | `consolidate` | Auto-runs on server startup (7-day threshold) |
697
+
698
+ **New unified tools:**
699
+ - **`remember`** — create + update + delete in one tool. Mode is inferred from params.
700
+ - **`recall`** — search + file history + overview in one tool. Mode is inferred from params.
701
+ - **`read_smart`** — unchanged.
702
+
703
+ **Other changes:**
704
+ - Auto-consolidate on server startup (non-blocking `setTimeout`, 7-day threshold, `sqlite_master` safety check)
705
+ - Claude Code Plugin bundle (`claude plugin add -- linksee-memory`)
706
+ - Deprecation errors include specific migration examples
707
+
708
+ All internal handler functions are preserved — this is a surface change, not a logic rewrite.
709
+
551
710
  ### v0.2.0 — English-first launch readiness (2026-04-20)
552
711
 
553
712
  Prepares the package for a broader (primarily English-speaking) audience on Reddit, Hacker News, and Anthropic Discord. No breaking API changes.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+ // linksee-memory-declare — declare / list / retire drift anchors (v8).
3
+ //
4
+ // The explicit write path for drift observability. Anchors declared here are clean
5
+ // by construction (declare-don't-mine): a human typed them. The bulk seeding script
6
+ // (curate the existing candidate pool) reuses curateAnchorFromMemory() from the lib.
7
+ //
8
+ // Usage:
9
+ // linksee-memory-declare list [--status active|retired] [--kind prohibition|decision|constraint]
10
+ // linksee-memory-declare add --kind <k> --statement "<text>" [--rationale "<text>"]
11
+ // [--affects "glob1,glob2"] [--terms "t1,t2"] [--violation "v1,v2"] [--tier human|explicit]
12
+ // linksee-memory-declare retire --id <n>
13
+ import { openDb, runMigrations } from '../db/migrate.js';
14
+ import { declareAnchor, listAnchors, retireAnchor, setNodeFields, getCurrentTruth, getAlertPolicy, setAlertPolicy, } from '../lib/drift-anchors.js';
15
+ function parseFlags(argv) {
16
+ const out = {};
17
+ for (let i = 0; i < argv.length; i++) {
18
+ const a = argv[i];
19
+ if (a.startsWith('--')) {
20
+ const key = a.slice(2);
21
+ const next = argv[i + 1];
22
+ if (next === undefined || next.startsWith('--')) {
23
+ out[key] = 'true';
24
+ }
25
+ else {
26
+ out[key] = next;
27
+ i++;
28
+ }
29
+ }
30
+ }
31
+ return out;
32
+ }
33
+ function splitList(v) {
34
+ if (!v)
35
+ return [];
36
+ return v
37
+ .split(',')
38
+ .map((s) => s.trim())
39
+ .filter(Boolean);
40
+ }
41
+ function print(obj) {
42
+ process.stdout.write(JSON.stringify(obj, null, 2) + '\n');
43
+ }
44
+ function main() {
45
+ const [, , sub, ...rest] = process.argv;
46
+ const flags = parseFlags(rest);
47
+ const db = openDb();
48
+ runMigrations(db); // ensure drift tables exist when run standalone
49
+ try {
50
+ switch (sub) {
51
+ case 'add': {
52
+ const anchor = declareAnchor(db, {
53
+ kind: flags.kind,
54
+ statement: flags.statement ?? '',
55
+ rationale: flags.rationale,
56
+ affects: splitList(flags.affects),
57
+ detect_terms: splitList(flags.terms),
58
+ violation_signal: splitList(flags.violation),
59
+ tier: flags.tier || undefined,
60
+ });
61
+ // v9 ProjectCoreNode fields (minimal input — only what's given):
62
+ // --node-type --domain --mode --confidence --cadence --stale-days --applies --not-applies
63
+ const nf = {};
64
+ if (flags['node-type'])
65
+ nf.node_type = flags['node-type'];
66
+ if (flags.domain)
67
+ nf.domain = flags.domain;
68
+ if (flags.mode)
69
+ nf.decision_mode = flags.mode;
70
+ if (flags.confidence)
71
+ nf.confidence = Number(flags.confidence);
72
+ if (flags.cadence || flags['stale-days']) {
73
+ const cp = { enabled: true };
74
+ if (flags.cadence)
75
+ cp.cadence_days = Number(flags.cadence);
76
+ if (flags['stale-days'])
77
+ cp.stale_threshold_days = Number(flags['stale-days']);
78
+ nf.card_policy = cp;
79
+ }
80
+ if (flags.applies || flags['not-applies']) {
81
+ const vs = {};
82
+ if (flags.applies)
83
+ vs.applies_to = splitList(flags.applies);
84
+ if (flags['not-applies'])
85
+ vs.does_not_apply_to = splitList(flags['not-applies']);
86
+ nf.validity_scope = vs;
87
+ }
88
+ if (Object.keys(nf).length)
89
+ setNodeFields(db, anchor.id, nf);
90
+ print({ ok: true, declared: anchor, node_fields: Object.keys(nf).length ? nf : null });
91
+ break;
92
+ }
93
+ case 'retire': {
94
+ const id = Number(flags.id);
95
+ if (!Number.isFinite(id))
96
+ throw new Error('--id <n> required');
97
+ const ok = retireAnchor(db, id);
98
+ print({ ok, retired: ok ? id : null });
99
+ break;
100
+ }
101
+ case 'list':
102
+ case undefined: {
103
+ const anchors = listAnchors(db, {
104
+ status: flags.status,
105
+ kind: flags.kind,
106
+ });
107
+ print({ ok: true, count: anchors.length, anchors });
108
+ break;
109
+ }
110
+ case 'truth': {
111
+ // ⑧ read_smart-style scoped read: active Current-Truth slice only.
112
+ const nodes = getCurrentTruth(db, { domain: flags.domain, decision_mode: flags.mode });
113
+ print({ ok: true, scope: { domain: flags.domain ?? 'all', decision_mode: flags.mode ?? 'all' }, count: nodes.length, current_truth: nodes });
114
+ break;
115
+ }
116
+ case 'policy': {
117
+ const p = {};
118
+ if (flags['max-cards-per-day'])
119
+ p.max_cards_per_day = Number(flags['max-cards-per-day']);
120
+ if (flags['max-soft-per-week'])
121
+ p.max_soft_cards_per_week = Number(flags['max-soft-per-week']);
122
+ if (flags['min-soft-confidence'])
123
+ p.min_confidence_for_soft_card = Number(flags['min-soft-confidence']);
124
+ if (flags['two-sided'])
125
+ p.require_two_sided_evidence = flags['two-sided'] !== 'false';
126
+ const policy = Object.keys(p).length ? setAlertPolicy(db, p) : getAlertPolicy(db);
127
+ print({ ok: true, alert_policy: policy });
128
+ break;
129
+ }
130
+ default:
131
+ throw new Error(`unknown subcommand "${sub}" — use: add | list | retire | truth | policy`);
132
+ }
133
+ }
134
+ catch (err) {
135
+ print({ ok: false, error: err?.message ?? String(err) });
136
+ db.close();
137
+ process.exit(1);
138
+ }
139
+ db.close();
140
+ }
141
+ if (import.meta.url === `file://${process.argv[1]}` ||
142
+ process.argv[1]?.endsWith('declare-anchor.ts') ||
143
+ process.argv[1]?.endsWith('declare-anchor.js')) {
144
+ main();
145
+ }
146
+ //# sourceMappingURL=declare-anchor.js.map
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ // linksee-memory-detect — run the drift detector (照合: declared intent vs. actual reality).
3
+ //
4
+ // The manual-trigger entry point for drift detection (the cadence question). Mirrors
5
+ // declare-anchor.ts; purely additive — touches only drift_edges (the feature's own table) and
6
+ // ONLY when --persist is passed. SAFE BY DEFAULT: a bare run is a dry-run (reads only, no
7
+ // writes), so you can preview drift before committing it to the view. Pass --persist to write
8
+ // the edges that /drift renders. No embedding layer: matching is lexical/glob/trigram-FTS only.
9
+ //
10
+ // Usage:
11
+ // linksee-memory-detect # dry-run — preview drift, writes NOTHING (default)
12
+ // linksee-memory-detect --persist # write contradicts/absent edges into drift_edges
13
+ // linksee-memory-detect --stale-days 30 # override absence staleness gate (default 14)
14
+ // linksee-memory-detect --threshold 0.5 # override emit threshold (default 0.5)
15
+ import { openDb, runMigrations } from '../db/migrate.js';
16
+ import { detectDrift, detectFileViolations } from '../lib/drift-detection.js';
17
+ function parseFlags(argv) {
18
+ const out = {};
19
+ for (let i = 0; i < argv.length; i++) {
20
+ const a = argv[i];
21
+ if (a.startsWith('--')) {
22
+ const key = a.slice(2);
23
+ const next = argv[i + 1];
24
+ if (next === undefined || next.startsWith('--')) {
25
+ out[key] = 'true';
26
+ }
27
+ else {
28
+ out[key] = next;
29
+ i++;
30
+ }
31
+ }
32
+ }
33
+ return out;
34
+ }
35
+ function print(obj) {
36
+ process.stdout.write(JSON.stringify(obj, null, 2) + '\n');
37
+ }
38
+ function main() {
39
+ const [, , ...rest] = process.argv;
40
+ const flags = parseFlags(rest);
41
+ const persist = flags.persist === 'true';
42
+ const staleDays = flags['stale-days'] !== undefined ? Number(flags['stale-days']) : undefined;
43
+ const emitThreshold = flags.threshold !== undefined ? Number(flags.threshold) : undefined;
44
+ if (staleDays !== undefined && !Number.isFinite(staleDays))
45
+ throw new Error('--stale-days <n> must be a number');
46
+ if (emitThreshold !== undefined && !Number.isFinite(emitThreshold))
47
+ throw new Error('--threshold <n> must be a number');
48
+ const db = openDb();
49
+ runMigrations(db); // ensure drift tables exist when run standalone
50
+ try {
51
+ const res = detectDrift(db, { dryRun: !persist, staleDays, emitThreshold });
52
+ const fres = detectFileViolations(db, { dryRun: !persist, emitThreshold });
53
+ print({
54
+ ok: true,
55
+ mode: persist ? 'PERSISTED' : 'DRY RUN (no writes — pass --persist to write)',
56
+ persisted: res.persisted,
57
+ anchorsScanned: res.anchorsScanned,
58
+ editsScanned: res.editsScanned,
59
+ // v1 — edit-snippet scan (what a captured edit's snippet contained)
60
+ editSnippetScan: {
61
+ contradicts: res.contradicts,
62
+ absent: res.absent,
63
+ edgesEmitted: res.edgesEmitted,
64
+ byAnchorHits: res.byAnchor.filter((b) => b.contradicts > 0 || b.absent > 0),
65
+ samples: res.samples.slice(0, 6),
66
+ },
67
+ // v2 — current-file scan (live file:line against violation_signal)
68
+ currentFileScan: {
69
+ filesScanned: fres.filesScanned,
70
+ anchorsCapped: fres.anchorsCapped,
71
+ contradicts: fres.contradicts,
72
+ edgesEmitted: fres.edgesEmitted,
73
+ byAnchorHits: fres.byAnchor,
74
+ samples: fres.samples.slice(0, 14),
75
+ },
76
+ totalEdgesEmitted: res.edgesEmitted + fres.edgesEmitted,
77
+ });
78
+ }
79
+ catch (err) {
80
+ print({ ok: false, error: err?.message ?? String(err) });
81
+ db.close();
82
+ process.exit(1);
83
+ }
84
+ db.close();
85
+ }
86
+ if (import.meta.url === `file://${process.argv[1]}` ||
87
+ process.argv[1]?.endsWith('detect-drift.ts') ||
88
+ process.argv[1]?.endsWith('detect-drift.js')) {
89
+ main();
90
+ }
91
+ //# sourceMappingURL=detect-drift.js.map