claude-mem-lite 6.3.0 → 6.5.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/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +22 -2
- package/README.zh-CN.md +19 -2
- package/hook-context.mjs +17 -2
- package/hook-shared.mjs +48 -3
- package/hook-update.mjs +6 -1
- package/hook.mjs +35 -1
- package/install.mjs +431 -40
- package/lib/data-paths.mjs +33 -0
- package/lib/db-backup.mjs +42 -17
- package/lib/db-unusable.mjs +178 -0
- package/lib/plugin-key.mjs +4 -1
- package/lib/record-once.mjs +71 -0
- package/lib/save-observation.mjs +16 -5
- package/lib/schema-skew.mjs +21 -30
- package/lib/timeline-core.mjs +54 -15
- package/mem-cli.mjs +33 -32
- package/npm-shrinkwrap.json +2 -2
- package/package.json +5 -1
- package/schema.mjs +15 -22
- package/scripts/hook-launcher.mjs +25 -4
- package/server.mjs +59 -1
- package/source-files.mjs +7 -0
- package/tool-schemas.mjs +74 -47
|
@@ -4,14 +4,14 @@
|
|
|
4
4
|
"name": "sdsrss"
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
|
-
"description": "Plugins by sdsrss"
|
|
8
|
-
"homepage": "https://github.com/sdsrss/claude-mem-lite"
|
|
7
|
+
"description": "Plugins by sdsrss"
|
|
9
8
|
},
|
|
10
9
|
"plugins": [
|
|
11
10
|
{
|
|
12
11
|
"name": "claude-mem-lite",
|
|
13
|
-
"version": "6.
|
|
12
|
+
"version": "6.5.0",
|
|
14
13
|
"source": "./",
|
|
14
|
+
"homepage": "https://github.com/sdsrss/claude-mem-lite",
|
|
15
15
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark)."
|
|
16
16
|
}
|
|
17
17
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-mem-lite",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.5.0",
|
|
4
4
|
"description": "Persistent long-term memory for Claude Code via MCP — captures coding decisions, bugfixes, and context across sessions. Hybrid FTS5 + TF-IDF search with episode batching. Single SQLite DB, no external services. A lighter, lower-cost alternative to claude-mem (episode batching + a smaller model; cost savings are an internal estimate, not a measured benchmark).",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "sdsrss"
|
package/README.md
CHANGED
|
@@ -598,7 +598,12 @@ Your data directory (`~/.claude-mem-lite/`) is untouched by install/rollback; sc
|
|
|
598
598
|
|
|
599
599
|
### doctor
|
|
600
600
|
|
|
601
|
-
Checks Node.js version, dependencies, server/hook files, database integrity, FTS5 indexes,
|
|
601
|
+
Checks Node.js version, dependencies, server/hook files, database integrity, FTS5 indexes,
|
|
602
|
+
stale processes, MCP registration, and whether the marketplace clone can still be updated.
|
|
603
|
+
|
|
604
|
+
It runs `claude mcp list` to answer the registration question, and that **health-checks every
|
|
605
|
+
MCP server you have configured** — i.e. briefly launches each one, including remote endpoints.
|
|
606
|
+
`status` deliberately does not: on a plugin install it answers from the manifest instead.
|
|
602
607
|
|
|
603
608
|
### status
|
|
604
609
|
|
|
@@ -617,9 +622,11 @@ claude-mem-lite repair
|
|
|
617
622
|
**If `repair` itself fails** (the bin is older than v2.84.0, or the bin is also broken), run this one-liner — it pulls a fresh tarball into a temp dir and runs *that* tarball's `install.mjs`, bypassing every file on your disk:
|
|
618
623
|
|
|
619
624
|
```bash
|
|
620
|
-
T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/
|
|
625
|
+
T=$(mktemp -d) && U=$(curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/releases/latest | grep -o '"tarball_url"[^,]*' | cut -d'"' -f4) && curl -sL "$U" | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install
|
|
621
626
|
```
|
|
622
627
|
|
|
628
|
+
It resolves the latest **release** tag first. A shell one-liner cannot verify the release signature the way `repair` does, so running it is a trust decision you are making explicitly — that is why it is the last resort and not the first suggestion.
|
|
629
|
+
|
|
623
630
|
After it finishes, `~/.claude-mem-lite/` is back in sync with the latest release and `claude-mem-lite repair` is available for next time.
|
|
624
631
|
|
|
625
632
|
## Uninstall
|
|
@@ -643,6 +650,19 @@ Data in `~/.claude-mem-lite/` is preserved by default. Delete manually if needed
|
|
|
643
650
|
rm -rf ~/.claude-mem-lite/
|
|
644
651
|
```
|
|
645
652
|
|
|
653
|
+
**`/plugin uninstall` does not delete the plugin cache.** Claude Code materializes each
|
|
654
|
+
version under `~/.claude/plugins/cache/`, with its own `node_modules`. While the plugin is
|
|
655
|
+
installed these get pruned to the newest three (SessionStart does it, and so does the update
|
|
656
|
+
path), so the directory is bounded — measured at 241 MB — not unbounded. But `/plugin
|
|
657
|
+
uninstall` removes the manifest and there is no uninstall hook a plugin can attach to, so the
|
|
658
|
+
hooks stop firing and whatever is left is never reclaimed. `claude-mem-lite uninstall` does
|
|
659
|
+
reclaim it, but after `/plugin uninstall` that command may no longer be on your PATH. Either
|
|
660
|
+
run it **first**, or delete the directory yourself:
|
|
661
|
+
|
|
662
|
+
```bash
|
|
663
|
+
rm -rf ~/.claude/plugins/cache/sdsrss/claude-mem-lite
|
|
664
|
+
```
|
|
665
|
+
|
|
646
666
|
### Mixed-install residue (read this if you've used multiple install methods)
|
|
647
667
|
|
|
648
668
|
`/plugin uninstall` only removes the plugin manifest — it **does not touch `~/.claude/settings.json`**. If you've ever run `claude-mem-lite install` (npx or git-clone path), hook entries pointing at `~/.claude-mem-lite/hook.mjs` were written into your user-global settings, and they keep firing after `/plugin uninstall`. If `~/.claude-mem-lite/hook.mjs` still exists they double-fire alongside the plugin; if you also ran `rm -rf ~/.claude-mem-lite/` they error every session.
|
package/README.zh-CN.md
CHANGED
|
@@ -478,7 +478,11 @@ npx claude-mem-lite doctor # 诊断问题
|
|
|
478
478
|
|
|
479
479
|
### doctor
|
|
480
480
|
|
|
481
|
-
检查 Node.js 版本、依赖、服务器/钩子文件、数据库完整性、FTS5
|
|
481
|
+
检查 Node.js 版本、依赖、服务器/钩子文件、数据库完整性、FTS5 索引、残留进程、MCP 注册状态,
|
|
482
|
+
以及 marketplace clone 是否还能被更新。
|
|
483
|
+
|
|
484
|
+
它用 `claude mcp list` 回答注册状态那一问,而这会**对你配置的每个 MCP server 做健康检查**——
|
|
485
|
+
也就是逐个短暂启动,包括远程 endpoint。`status` 刻意不这么做:插件形态下它从 manifest 回答。
|
|
482
486
|
|
|
483
487
|
### status
|
|
484
488
|
|
|
@@ -497,9 +501,11 @@ claude-mem-lite repair
|
|
|
497
501
|
**如果 `repair` 自己也跑不起来**(bin 比 v2.84.0 旧,或 bin 也坏了),用这条单行命令——它把最新 tarball 拉到临时目录、跑 *那份* tarball 里的 `install.mjs`,完全不依赖你磁盘上的任何文件:
|
|
498
502
|
|
|
499
503
|
```bash
|
|
500
|
-
T=$(mktemp -d) && curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/
|
|
504
|
+
T=$(mktemp -d) && U=$(curl -sL https://api.github.com/repos/sdsrss/claude-mem-lite/releases/latest | grep -o '"tarball_url"[^,]*' | cut -d'"' -f4) && curl -sL "$U" | tar xz -C "$T" --strip-components=1 && node "$T/install.mjs" install
|
|
501
505
|
```
|
|
502
506
|
|
|
507
|
+
它会先解析出最新 **release** 的 tag。shell 单行命令无法像 `repair` 那样校验 release 签名,所以跑它等于你自己做了一次信任决定——这也是它排在最后、而不是被优先推荐的原因。
|
|
508
|
+
|
|
503
509
|
跑完之后,`~/.claude-mem-lite/` 就和最新 release 对齐,`claude-mem-lite repair` 下次再遇到类似问题也能直接用了。
|
|
504
510
|
|
|
505
511
|
## 卸载
|
|
@@ -523,6 +529,17 @@ npx claude-mem-lite uninstall --purge
|
|
|
523
529
|
rm -rf ~/.claude-mem-lite/
|
|
524
530
|
```
|
|
525
531
|
|
|
532
|
+
**`/plugin uninstall` 不会删 plugin cache。** Claude Code 会把每个版本展开到
|
|
533
|
+
`~/.claude/plugins/cache/` 下,各带一份 `node_modules`。插件还装着的时候,这些会被裁剪到最新
|
|
534
|
+
三个版本(SessionStart 会做,更新路径也会做),所以这个目录是有上限的——实测 241 MB——而不是无限
|
|
535
|
+
增长。但 `/plugin uninstall` 只移除 manifest,插件又没有可挂的卸载生命周期钩子,于是钩子停止触发,
|
|
536
|
+
剩下的东西再也没人回收。`claude-mem-lite uninstall` 能回收它,但 `/plugin uninstall` 之后这个命令
|
|
537
|
+
可能已经不在 PATH 上了。所以要么**先**跑它,要么自己删:
|
|
538
|
+
|
|
539
|
+
```bash
|
|
540
|
+
rm -rf ~/.claude/plugins/cache/sdsrss/claude-mem-lite
|
|
541
|
+
```
|
|
542
|
+
|
|
526
543
|
### 混装残留(用过多种安装方式的话务必看一下)
|
|
527
544
|
|
|
528
545
|
`/plugin uninstall` 只删 plugin manifest,**不会动 `~/.claude/settings.json`**。如果你曾经跑过 `claude-mem-lite install`(npx 或 git-clone 路径),指向 `~/.claude-mem-lite/hook.mjs` 的 hook 条目就被写进了你的 user-global settings;`/plugin uninstall` 之后它们还在每会话触发。如果 `~/.claude-mem-lite/hook.mjs` 还在 → 与 plugin 双触发;如果你又 `rm -rf ~/.claude-mem-lite/` → 每次会话报错。
|
package/hook-context.mjs
CHANGED
|
@@ -585,7 +585,12 @@ export function buildSessionContextLines(
|
|
|
585
585
|
} else if (!latestSummary && !effectiveQuiet()) {
|
|
586
586
|
// Fallback: no summary AND no key observations — show recent activity.
|
|
587
587
|
// Skipped under QUIET_HOOKS since the Recent table already carries titles.
|
|
588
|
-
|
|
588
|
+
// Slice FIRST, then sort: the slice is the selection (top 3 by value density) and must
|
|
589
|
+
// stay that way; only the order they are printed in is corrected, same as the Recent
|
|
590
|
+
// table below. Sorting before the slice would silently change WHICH three are injected.
|
|
591
|
+
const recentObs = (observations.length >= 3 ? observations : fallbackObs)
|
|
592
|
+
.slice(0, 3)
|
|
593
|
+
.sort((a, b) => Date.parse(b.created_at) - Date.parse(a.created_at) || b.id - a.id);
|
|
589
594
|
if (recentObs.length > 0) {
|
|
590
595
|
summaryLines.push('### Recent Activity');
|
|
591
596
|
for (const o of recentObs) {
|
|
@@ -695,8 +700,18 @@ export function buildSessionContextLines(
|
|
|
695
700
|
}
|
|
696
701
|
|
|
697
702
|
// 6. Recent observations table
|
|
703
|
+
//
|
|
704
|
+
// SELECTION order (greedy knapsack, value density) is not DISPLAY order. This block used
|
|
705
|
+
// to render the picks in the order the knapsack happened to take them, under a heading
|
|
706
|
+
// that says "Recent" next to a Time column — so row 1 was not the newest row, and both a
|
|
707
|
+
// human and the model read it as if it were. Sorting here is display-only: `obsToShow` is
|
|
708
|
+
// already chosen, so the token budget and the row set are untouched (pinned by a case in
|
|
709
|
+
// tests/hook-context.test.mjs). Tiebroken on id for the same reason D#9 gives — an
|
|
710
|
+
// untiebroken tie flips direction, and two saves in one millisecond are common.
|
|
698
711
|
const obsLines = [];
|
|
699
|
-
const obsToShow = observations.length >= 3 ? observations : fallbackObs
|
|
712
|
+
const obsToShow = [...(observations.length >= 3 ? observations : fallbackObs)].sort(
|
|
713
|
+
(a, b) => Date.parse(b.created_at) - Date.parse(a.created_at) || b.id - a.id,
|
|
714
|
+
);
|
|
700
715
|
if (obsToShow.length > 0) {
|
|
701
716
|
const today = now.toISOString().slice(0, 10);
|
|
702
717
|
obsLines.push(`### Recent (${today})`);
|
package/hook-shared.mjs
CHANGED
|
@@ -28,6 +28,8 @@ import {
|
|
|
28
28
|
shouldRecordSkew,
|
|
29
29
|
SKEW_MARKER_PREFIX,
|
|
30
30
|
} from './lib/schema-skew.mjs';
|
|
31
|
+
import { isDbUnusableError, DB_UNUSABLE_MARKER_PREFIX } from './lib/db-unusable.mjs';
|
|
32
|
+
import { shouldRecordOnce } from './lib/record-once.mjs';
|
|
31
33
|
// Audit 2026-09-05 P1-2 (carried from 2026-09-02 P2-9): `callLLM`, the quiet/adoption
|
|
32
34
|
// predicates and the handoff constants moved into `lib/` because two lib modules
|
|
33
35
|
// imported them from here and dragged this file's whole import graph — haiku-client,
|
|
@@ -244,6 +246,9 @@ export const GC_PROJECT_MARKER_PREFIXES = Object.freeze([
|
|
|
244
246
|
// v5.0.0; a prefix for files nothing writes any more is dead weight in a hot-path loop.
|
|
245
247
|
'last-mark-compressible-', // per-project auto-compress 24h gate
|
|
246
248
|
SKEW_MARKER_PREFIX, // per-project schema-skew log dedup; regenerated on the next skewed open
|
|
249
|
+
// Same shape, same reason, and it was missed here first time round — the note above is
|
|
250
|
+
// about exactly this defect, two entries up.
|
|
251
|
+
DB_UNUSABLE_MARKER_PREFIX, // per-project unopenable-DB log dedup; regenerated on the next failing open
|
|
247
252
|
]);
|
|
248
253
|
|
|
249
254
|
// Records of a completed side effect — never age out. `ep-`/`ep-flush-`/
|
|
@@ -405,12 +410,28 @@ export function lastSchemaSkew() {
|
|
|
405
410
|
return lastSkew;
|
|
406
411
|
}
|
|
407
412
|
|
|
413
|
+
// Same idea, other unhealable family: the file exists and SQLite will not open it. Held as a
|
|
414
|
+
// boolean rather than the error, because the only thing SessionStart needs is "which notice",
|
|
415
|
+
// and keeping an Error alive here would tempt a caller into rendering a stack trace at a user.
|
|
416
|
+
let lastUnusable = false;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* True when the most recent openDb() returned null because the database file is not a usable
|
|
420
|
+
* database. Cleared by any successful open, so a repair mid-session stops the notice.
|
|
421
|
+
*
|
|
422
|
+
* @returns {boolean}
|
|
423
|
+
*/
|
|
424
|
+
export function lastDbUnusable() {
|
|
425
|
+
return lastUnusable;
|
|
426
|
+
}
|
|
427
|
+
|
|
408
428
|
export function openDb() {
|
|
409
429
|
try {
|
|
410
430
|
// WAL-corruption self-heal (was server.mjs-only): without it, hooks stayed
|
|
411
431
|
// silently dead (null DB) on a corrupt WAL until the next MCP server start.
|
|
412
432
|
const db = ensureDbWithWalRecovery();
|
|
413
433
|
lastSkew = null;
|
|
434
|
+
lastUnusable = false;
|
|
414
435
|
return db;
|
|
415
436
|
} catch (e) {
|
|
416
437
|
// Forward-incompat is its own family: it cannot be healed by anything this process can
|
|
@@ -423,10 +444,11 @@ export function openDb() {
|
|
|
423
444
|
//
|
|
424
445
|
// shouldRecordSkew is TOTAL by contract. Nothing in this catch may throw: the first cut
|
|
425
446
|
// called getSessionId() here, which MINTS and writes a session id, so an unwritable
|
|
426
|
-
// runtime dir turned openDb() itself into a thrower. All
|
|
447
|
+
// runtime dir turned openDb() itself into a thrower. All 12 openDb() call sites in hook.mjs are written to
|
|
427
448
|
// no-op on null and none of them expects an exception.
|
|
428
449
|
if (isSchemaSkewError(e)) {
|
|
429
450
|
lastSkew = schemaSkewFromError(e) || { dbVersion: null, binaryVersion: null };
|
|
451
|
+
lastUnusable = false;
|
|
430
452
|
// Guarded even though inferProject() reads env and cwd: "the only statement in this
|
|
431
453
|
// catch cannot throw" was true of the original one-line body and stopped being true
|
|
432
454
|
// the moment anything was added. An unscoped marker is a worse dedup, not a crash.
|
|
@@ -441,8 +463,31 @@ export function openDb() {
|
|
|
441
463
|
}
|
|
442
464
|
return null;
|
|
443
465
|
}
|
|
444
|
-
//
|
|
445
|
-
//
|
|
466
|
+
// The OTHER unhealable family, and it was the silent one. A file that is not a database
|
|
467
|
+
// repeats on every fire exactly like a skew does, and until now took the generic branch
|
|
468
|
+
// below: one full stack trace per SessionStart fire (measured: 20 fires → 20 records, ~860 B
|
|
469
|
+
// each), no dedup, and no user-visible word anywhere in the session. Same treatment as skew
|
|
470
|
+
// — record once per project per hour, and hand SessionStart a flag to speak with.
|
|
471
|
+
//
|
|
472
|
+
// Nothing in this branch may throw: `isDbUnusableError` is a regex over a string and
|
|
473
|
+
// `shouldRecordOnce` is total by contract, which is exactly the property the first cut of
|
|
474
|
+
// the skew dedup lost by calling a function that WRITES.
|
|
475
|
+
if (isDbUnusableError(e)) {
|
|
476
|
+
lastUnusable = true;
|
|
477
|
+
lastSkew = null; // the two flags are a set: whichever family fired last is the true one
|
|
478
|
+
let project = '';
|
|
479
|
+
try {
|
|
480
|
+
project = inferProject();
|
|
481
|
+
} catch {
|
|
482
|
+
/* total: the marker degrades to one shared file */
|
|
483
|
+
}
|
|
484
|
+
if (shouldRecordOnce(RUNTIME_DIR, DB_UNUSABLE_MARKER_PREFIX, project, 'unusable')) {
|
|
485
|
+
recordHookError('hook-shared:db-open', e, RUNTIME_DIR);
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
// Still null, still no throw — a hook must never crash the host session, and all 12
|
|
490
|
+
// openDb() call sites in hook.mjs are written to no-op on null. But "returned null"
|
|
446
491
|
// used to be the ONLY trace: nothing reached runtime/hook-errors/, so `stats`
|
|
447
492
|
// reported 0 and doctor printed "no recent silent hook breakage" while every
|
|
448
493
|
// capture path was dead (audit B1, 2026-08-14 — the same blindness that hid the
|
package/hook-update.mjs
CHANGED
|
@@ -20,7 +20,12 @@ import {
|
|
|
20
20
|
import { join, dirname, resolve } from 'node:path';
|
|
21
21
|
import { pathToFileURL } from 'node:url';
|
|
22
22
|
import { tmpdir, homedir } from 'node:os';
|
|
23
|
-
|
|
23
|
+
// lib/data-paths.mjs, NOT schema.mjs: this module is what install.mjs::repair() imports to
|
|
24
|
+
// reach the signature-verified release path, and schema.mjs statically imports
|
|
25
|
+
// better-sqlite3 — so importing two path constants from there made the verified repair
|
|
26
|
+
// unreachable on exactly the broken-install state it exists to repair (2026-09-08).
|
|
27
|
+
// tests/repair-path-no-native-dep.test.mjs fails on any package edge reachable from here.
|
|
28
|
+
import { DB_DIR, CODE_DIR } from './lib/data-paths.mjs';
|
|
24
29
|
import { debugCatch, debugLog } from './utils.mjs';
|
|
25
30
|
import { NATIVE_BINDING_SOURCE_BUILD_CMD } from './lib/binding-probe.mjs';
|
|
26
31
|
// Local manifest is fallback only — the active manifest is loaded from the
|
package/hook.mjs
CHANGED
|
@@ -62,7 +62,7 @@ import {
|
|
|
62
62
|
} from './hook-episode.mjs';
|
|
63
63
|
// CODE_DIR, not DB_DIR: the schema-skew notice asks which CODE homes exist, and those are
|
|
64
64
|
// always homedir-rooted even when CLAUDE_MEM_DIR relocates the data.
|
|
65
|
-
import { DB_DIR, CODE_DIR } from './schema.mjs';
|
|
65
|
+
import { DB_DIR, DB_PATH, CODE_DIR } from './schema.mjs';
|
|
66
66
|
import { cleanupClaudeMdLegacyBlock, buildSessionContextLines } from './hook-context.mjs';
|
|
67
67
|
import { entry as preCompactEntry } from './hook-precompact.mjs';
|
|
68
68
|
import {
|
|
@@ -85,6 +85,7 @@ import {
|
|
|
85
85
|
sweepOrphanEpisodeFiles,
|
|
86
86
|
sweepStaleProjectMarkers,
|
|
87
87
|
lastSchemaSkew,
|
|
88
|
+
lastDbUnusable,
|
|
88
89
|
} from './hook-shared.mjs';
|
|
89
90
|
import { handleLLMEpisode, handleLLMSummary, saveEpisodeImmediate } from './hook-llm.mjs';
|
|
90
91
|
import { readFastSummarySource, insertFastSummary, FAST_SUMMARY_LIMITS } from './lib/fast-summary.mjs';
|
|
@@ -2354,6 +2355,33 @@ async function emitSchemaSkewNotice() {
|
|
|
2354
2355
|
}
|
|
2355
2356
|
}
|
|
2356
2357
|
|
|
2358
|
+
/**
|
|
2359
|
+
* The corruption twin of emitSchemaSkewNotice. Same channels, same reason they are BOTH used:
|
|
2360
|
+
* queueHookContext reaches the model, queueHookSystemMessage reaches the human, and a notice
|
|
2361
|
+
* whose whole job is handing the user a command must not depend on the assistant volunteering
|
|
2362
|
+
* it (lib/hook-stdout.mjs names v3.70.0 for exactly that mistake).
|
|
2363
|
+
*
|
|
2364
|
+
* Dynamically imported like its twin: a cold path must not cost the healthy SessionStart a
|
|
2365
|
+
* directory scan for backup snapshots.
|
|
2366
|
+
*/
|
|
2367
|
+
async function emitDbUnusableNotice() {
|
|
2368
|
+
try {
|
|
2369
|
+
if (!lastDbUnusable()) return;
|
|
2370
|
+
const mod = await import('./lib/db-unusable.mjs');
|
|
2371
|
+
// TWO DIFFERENT STRINGS, unlike the skew twin, and the asymmetry is the point: the
|
|
2372
|
+
// human channel carries the repair command, the model channel carries only the fact.
|
|
2373
|
+
// See formatDbUnusableModelNotice — this family's remedy overwrites the database.
|
|
2374
|
+
queueHookSystemMessage(
|
|
2375
|
+
mod.formatDbUnusableNotice({ dbPath: DB_PATH, remedy: mod.dbUnusableRemedy(DB_PATH) }),
|
|
2376
|
+
);
|
|
2377
|
+
queueHookContext('SessionStart', mod.formatDbUnusableModelNotice());
|
|
2378
|
+
} catch (e) {
|
|
2379
|
+
// A hook must never crash the host session, and a notice that cannot render is better
|
|
2380
|
+
// handled by staying quiet than by taking SessionStart down with it.
|
|
2381
|
+
debugCatch(e, 'session-start-db-unusable');
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2357
2385
|
async function handleSessionStart() {
|
|
2358
2386
|
// GC stale per-session cooldown files. Cheap (<5ms typical) and idempotent;
|
|
2359
2387
|
// moved here from pre-tool-recall.js's hot path.
|
|
@@ -2542,7 +2570,13 @@ async function handleSessionStart() {
|
|
|
2542
2570
|
// only other signal it produces is a `-32000 Connection closed` from the MCP host, which
|
|
2543
2571
|
// names nothing. Measured 2026-09-08: a whole day of it, >=648 log lines, zero words to
|
|
2544
2572
|
// the user. This is the surface the user actually reads.
|
|
2573
|
+
//
|
|
2574
|
+
// A database file that is not a database is the same shape and got the same treatment:
|
|
2575
|
+
// unhealable without the user, disables every path, and every OTHER surface (CLI exit 1,
|
|
2576
|
+
// `status`, `doctor` with an exact repair command) already reports it — leaving the
|
|
2577
|
+
// in-session one as the only silent one.
|
|
2545
2578
|
await emitSchemaSkewNotice();
|
|
2579
|
+
await emitDbUnusableNotice();
|
|
2546
2580
|
return;
|
|
2547
2581
|
}
|
|
2548
2582
|
|