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 +61 -0
- package/dist/bin/map-import.js +14 -0
- package/dist/bin/stats.js +5 -4
- package/dist/db/migrate.js +42 -5
- package/dist/mcp/server.js +1 -1
- package/package.json +1 -1
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.
|
package/dist/bin/map-import.js
CHANGED
|
@@ -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
|
|
5
|
-
// npx linksee-memory
|
|
6
|
-
// npx linksee-memory
|
|
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,
|
package/dist/db/migrate.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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);
|
package/dist/mcp/server.js
CHANGED
|
@@ -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\
|
|
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.
|
|
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",
|