linksee-memory 0.11.1 → 0.11.3

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
@@ -419,12 +419,49 @@ Add to `~/.gemini/settings.json`:
419
419
 
420
420
  </details>
421
421
 
422
+ <details>
423
+ <summary><strong>Claude Desktop</strong></summary>
424
+
425
+ Add the same stdio command to `claude_desktop_config.json`:
426
+
427
+ ```json
428
+ {
429
+ "mcpServers": {
430
+ "linksee": {
431
+ "command": "npx",
432
+ "args": ["-y", "linksee-memory"]
433
+ }
434
+ }
435
+ }
436
+ ```
437
+
438
+ Config file: macOS `~/Library/Application Support/Claude/`, Windows `%APPDATA%\Claude\`. Restart Claude Desktop.
439
+
440
+ </details>
441
+
422
442
  All editors share the same `~/.linksee-memory/memory.db`. A decision made in Claude Code is recalled in Cursor. A caveat recorded in Windsurf prevents the same mistake in Codex.
423
443
 
424
444
  ### Database location
425
445
 
426
446
  Default: `~/.linksee-memory/memory.db`. Override with `LINKSEE_MEMORY_DIR` env var.
427
447
 
448
+ ### Uninstall
449
+
450
+ ```bash
451
+ # 1. Remove the MCP server registration
452
+ claude mcp remove linksee
453
+
454
+ # 2. Remove the hooks from settings.json (edit the file, delete the linksee entries):
455
+ # ~/.claude/settings.json → the Stop hook running "npx -y linksee-memory sync"
456
+ # <project>/.claude/settings.json → the SessionStart/PreToolUse hooks running "npx -y linksee-memory guard"
457
+
458
+ # 3. Remove the installed skill and all local memory (optional)
459
+ rm -rf ~/.claude/skills/linksee-memory
460
+ rm -rf ~/.linksee-memory # deletes all stored memory — nothing is kept anywhere else
461
+ ```
462
+
463
+ Nothing ever leaves your machine, so step 3 fully erases everything Linksee stored.
464
+
428
465
  ## What's new in v0.9
429
466
 
430
467
  | Feature | Detail |
@@ -576,6 +613,20 @@ Claude Code ships a built-in memory feature at `~/.claude/projects/<path>/memory
576
613
 
577
614
  Use both.
578
615
 
616
+ ## Security & privacy
617
+
618
+ linksee-memory runs locally and is built to read — and send — as little as possible.
619
+
620
+ - **Local-first.** Memory is one SQLite file at `~/.linksee-memory/memory.db`. No account, no cloud, no API key.
621
+ - **Telemetry is opt-in and OFF by default.** Nothing is sent unless you set `LINKSEE_TELEMETRY=basic`. Even then it never sends your source code, file contents, prompts, conversation, entity/project names, or the memory DB — only anonymous counters ([details](#telemetry-opt-in-off-by-default)).
622
+ - **No automatic repo crawling.** linksee reads: memory you explicitly save, your `map.yaml`, the specific files a map reality-check points at, the local SQLite DB, and — when the Stop hook fires — your Claude Code session transcript (locally, to capture what happened). It does **not** crawl your repo, read `.env`/secrets/`node_modules`, or touch your home directory on its own.
623
+ - **Clean MCP transport.** The server writes only JSON-RPC to stdout; all logs go to stderr.
624
+ - **Hooks are documented and removable.** `setup` adds a Stop hook (session capture) and an optional guard hook. They make no network calls by default, are time-bounded, fail-open (a hook error never breaks your session), and are listed under [Uninstall](#uninstall).
625
+ - **No shell-injection surface.** Subcommands run via `spawn` with array args and `shell: false`, from a fixed allowlist; `map.yaml` is parsed with the safe `yaml` parser (no arbitrary tag execution).
626
+ - **Supply chain.** MIT, published from a single owner. `npx -y linksee-memory` runs the published package — pin a version in CI if you need reproducibility.
627
+
628
+ Found a security issue? See [SECURITY.md](SECURITY.md).
629
+
579
630
  ## Telemetry (opt-in, off by default)
580
631
 
581
632
  linksee-memory ships with **opt-in** anonymous telemetry that helps us understand which MCP servers and workflows actually work in the wild. **Nothing is sent unless you explicitly enable it.** No conversation content, no file content, no entity names, no project paths — ever.
@@ -787,6 +838,16 @@ After install, in a new Claude session ask: *"Can you remember that I prefer Typ
787
838
 
788
839
  ## Changelog
789
840
 
841
+ ### v0.11.3 — Robustness + MCP hygiene (2026-06-16)
842
+
843
+ - **Corrupt-database recovery:** if `~/.linksee-memory/memory.db` is unreadable, linksee preserves it as `memory.db.corrupt-<timestamp>` and starts a fresh one (with a clear message) instead of crashing with a raw SQLite error. Old memories stay recoverable in the backup.
844
+ - **`recall` tool description** no longer suggests editing your system prompt — cleaner MCP citizenship.
845
+
846
+ ### v0.11.2 — More cold-start hardening (2026-06-16)
847
+
848
+ - **`stats` works on a fresh database** instead of crashing with `no such table` — it ensures the schema exists first (it may be the first command a new user runs).
849
+ - **`map --help`** prints usage instead of trying to import a map.
850
+
790
851
  ### v0.11.1 — Cold-start fixes (2026-06-16)
791
852
 
792
853
  - **Run any CLI through the package name:** `npx -y linksee-memory setup` (and `map`, `sync`, `guard`, `stats`, `import`, `install-skill`). A fresh user couldn't reach the standalone bins (`linksee-memory-setup`, …) via `npx` — npx resolves package names, not sibling bin names — so the one-command install 404'd. The main bin now dispatches subcommands; the standalone bins remain as aliases.
@@ -39,6 +39,20 @@ for (let i = 0; i < argv.length; i++) {
39
39
  const sub = positionals[0] ?? 'import';
40
40
  const arg1 = positionals[1]; // the node-id / file / topic for explain|blast|affects|where
41
41
  const mapPath = flagValue(argv, 'file', join(process.cwd(), 'map.yaml'));
42
+ if (argv.includes('--help') || argv.includes('-h') || sub === 'help') {
43
+ process.stdout.write(`linksee-memory map — navigate your product map (map.yaml) and catch drift\n\n` +
44
+ `Usage: npx -y linksee-memory map <command> [args]\n\n` +
45
+ ` where [<file>] where am I? (no arg = infer from recent edits) → node + what it affects\n` +
46
+ ` explain <node> why this status? — declared vs reality + file:line evidence + fix\n` +
47
+ ` affects <node> what to change together if you touch this node\n` +
48
+ ` status whole-project health % + what needs attention\n` +
49
+ ` next the prioritized next fix candidate(s)\n` +
50
+ ` reconcile re-check the map against the real code/files\n` +
51
+ ` inspect --json machine-readable dump (CI / tooling)\n` +
52
+ ` blueprint stage x node board (colors = live verdict)\n\n` +
53
+ `Flags: --file <map.yaml> --root <repo> --lang ja\n`);
54
+ process.exit(0);
55
+ }
42
56
  const db = openDb();
43
57
  runMigrations(db);
44
58
  // Graceful exit when there's no map yet (the cold-user state) — never a raw stack trace.
package/dist/bin/stats.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  // linksee-memory-stats — summary of the local memory DB.
3
3
  // Usage:
4
- // npx linksee-memory-stats
5
- // npx linksee-memory-stats --json
6
- // npx linksee-memory-stats --per-entity 10
4
+ // npx -y linksee-memory stats
5
+ // npx -y linksee-memory stats --json
6
+ // npx -y linksee-memory stats --per-entity 10
7
7
  //
8
8
  // Safe to run anytime (read-only).
9
9
  import { statSync } from 'node:fs';
10
- import { openDb, getDbPath } from '../db/migrate.js';
10
+ import { openDb, runMigrations, getDbPath } from '../db/migrate.js';
11
11
  function parseArgs() {
12
12
  const argv = process.argv.slice(2);
13
13
  const a = { json: false, perEntity: 5, help: false };
@@ -68,6 +68,7 @@ function main() {
68
68
  }
69
69
  catch { /* no db yet */ }
70
70
  const db = openDb();
71
+ runMigrations(db); // ensure schema exists — `stats` may be the first command a fresh user runs
71
72
  const counts = {
72
73
  entities: db.prepare('SELECT COUNT(*) as c FROM entities').get().c,
73
74
  memories: db.prepare('SELECT COUNT(*) as c FROM memories').get().c,
@@ -1,6 +1,6 @@
1
1
  import Database from 'better-sqlite3';
2
2
  import { readFileSync } from 'node:fs';
3
- import { mkdirSync } from 'node:fs';
3
+ import { mkdirSync, renameSync, existsSync } from 'node:fs';
4
4
  import { dirname, join } from 'node:path';
5
5
  import { homedir } from 'node:os';
6
6
  import { fileURLToPath } from 'node:url';
@@ -10,12 +10,49 @@ const DB_PATH = join(DEFAULT_DB_DIR, 'memory.db');
10
10
  export function getDbPath() {
11
11
  return DB_PATH;
12
12
  }
13
+ function openAt(path) {
14
+ const db = new Database(path);
15
+ try {
16
+ db.pragma('journal_mode = WAL'); // first real read of the file header — throws if it isn't a DB
17
+ db.pragma('foreign_keys = ON');
18
+ return db;
19
+ }
20
+ catch (e) {
21
+ try {
22
+ db.close();
23
+ }
24
+ catch { /* ignore */ } // release the handle so a corrupt file can be renamed (Windows locks it otherwise)
25
+ throw e;
26
+ }
27
+ }
13
28
  export function openDb() {
14
29
  mkdirSync(DEFAULT_DB_DIR, { recursive: true });
15
- const db = new Database(DB_PATH);
16
- db.pragma('journal_mode = WAL');
17
- db.pragma('foreign_keys = ON');
18
- return db;
30
+ try {
31
+ return openAt(DB_PATH);
32
+ }
33
+ catch (err) {
34
+ const msg = err instanceof Error ? err.message : String(err);
35
+ // A corrupt / non-database file throws on the first pragma. Don't crash with a raw
36
+ // stack trace: preserve the bad file (so it can be recovered) and start a fresh DB.
37
+ if (/not a database|file is encrypted|malformed|disk image/i.test(msg) && existsSync(DB_PATH)) {
38
+ const backup = `${DB_PATH}.corrupt-${Date.now()}`;
39
+ try {
40
+ renameSync(DB_PATH, backup);
41
+ }
42
+ catch { /* best effort */ }
43
+ for (const ext of ['-wal', '-shm']) {
44
+ try {
45
+ if (existsSync(DB_PATH + ext))
46
+ renameSync(DB_PATH + ext, backup + ext);
47
+ }
48
+ catch { /* ignore */ }
49
+ }
50
+ process.stderr.write(`[linksee-memory] the memory database was unreadable (${msg}). ` +
51
+ `Moved it to ${backup} and started a fresh one — your old memories are preserved there for recovery.\n`);
52
+ return openAt(DB_PATH);
53
+ }
54
+ throw err; // not a corruption we recognize — surface it
55
+ }
19
56
  }
20
57
  export function runMigrations(db) {
21
58
  const __filename = fileURLToPath(import.meta.url);
@@ -153,7 +153,7 @@ const TOOLS = [
153
153
  },
154
154
  {
155
155
  name: 'recall',
156
- description: 'Your persistent memory across all AI tools. CALL THIS BEFORE STARTING ANY TASK to check for past caveats (pain records), decisions, and learnings — prevents repeating mistakes across sessions.\n\nTypical usage: recall({ query: "keywords" }) for search, recall({ path: "file.ts" }) for file history, recall() for overview.\n\nWHEN TO CALL:\n• Before starting any new task or touching a file\n• When the user mentions "before" / "前に" / "last time" / "remember when"\n• When an error occurs — check if you\'ve seen it before\n• When making a decision — check for prior decisions on the same topic\n\nTHREE MODES (auto-detected):\n• Search (default): provide query → returns memories ranked by relevance + heat\n• File history: provide path → returns complete edit history with user-intent context\n• Overview: omit all params → returns entity list sorted by momentum\n\nTip: If this is a fresh session, suggest the user add "Use Linksee Memory" to their system prompt for persistent cross-session memory.\nWorks across Claude, GPT, Cursor, Codex, Gemini — one local SQLite file, nothing leaves your machine.',
156
+ description: 'Your persistent memory across all AI tools. CALL THIS BEFORE STARTING ANY TASK to check for past caveats (pain records), decisions, and learnings — prevents repeating mistakes across sessions.\n\nTypical usage: recall({ query: "keywords" }) for search, recall({ path: "file.ts" }) for file history, recall() for overview.\n\nWHEN TO CALL:\n• Before starting any new task or touching a file\n• When the user mentions "before" / "前に" / "last time" / "remember when"\n• When an error occurs — check if you\'ve seen it before\n• When making a decision — check for prior decisions on the same topic\n\nTHREE MODES (auto-detected):\n• Search (default): provide query → returns memories ranked by relevance + heat\n• File history: provide path → returns complete edit history with user-intent context\n• Overview: omit all params → returns entity list sorted by momentum\n\nWorks across Claude, GPT, Cursor, Codex, Gemini — one local SQLite file, nothing leaves your machine.',
157
157
  inputSchema: {
158
158
  type: 'object',
159
159
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "linksee-memory",
3
- "version": "0.11.1",
3
+ "version": "0.11.3",
4
4
  "mcpName": "io.github.michielinksee/linksee-memory",
5
5
  "description": "Local-first agent memory MCP — cross-agent brain with drift detection, 6-layer structured memory + token-saving file diff cache",
6
6
  "type": "module",