@kenz1117/dsh-engram 0.7.6 → 0.7.8
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.en.md +55 -6
- package/README.md +49 -6
- package/lib/client.js +1 -1
- package/lib/client.js.map +1 -1
- package/lib/index.js +67 -17
- package/package.json +1 -1
package/lib/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, statSync } from "node:fs";
|
|
2
|
+
import { createWriteStream, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
4
|
import { BlockAssembler, createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -16,6 +16,7 @@ import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
|
16
16
|
*/
|
|
17
17
|
/** 合法配置键集合(未知键 loud 失败)。 */
|
|
18
18
|
const CONFIG_KEYS = /* @__PURE__ */ new Set([
|
|
19
|
+
"legacyMigration",
|
|
19
20
|
"dbDir",
|
|
20
21
|
"injectProfile",
|
|
21
22
|
"profileTopN",
|
|
@@ -47,6 +48,7 @@ const INGEST_MODES = /* @__PURE__ */ new Set([
|
|
|
47
48
|
/** Schemastery 校验面(cordis.yml 读取时校验)。 */
|
|
48
49
|
const Config = z.object({
|
|
49
50
|
dbDir: z.string(),
|
|
51
|
+
legacyMigration: z.string(),
|
|
50
52
|
injectProfile: z.boolean(),
|
|
51
53
|
profileTopN: z.number().step(1).min(1).max(64),
|
|
52
54
|
modelCacheDir: z.string(),
|
|
@@ -77,6 +79,7 @@ const Config = z.object({
|
|
|
77
79
|
*/
|
|
78
80
|
function resolveConfig(config = {}) {
|
|
79
81
|
for (const key of Object.keys(config)) if (!CONFIG_KEYS.has(key)) throw new Error(`dsh-engram: unknown config key "${key}"`);
|
|
82
|
+
if (config.legacyMigration !== void 0 && config.legacyMigration !== "eager" && config.legacyMigration !== "conservative") throw new Error("dsh-engram: legacyMigration must be one of eager|conservative");
|
|
80
83
|
if (config.ingest !== void 0 && !INGEST_MODES.has(config.ingest)) throw new Error(`dsh-engram: ingest must be one of off|light|eager, got "${String(config.ingest)}"`);
|
|
81
84
|
if (config.profileTopN !== void 0 && (!Number.isInteger(config.profileTopN) || config.profileTopN < 1 || config.profileTopN > 64)) throw new Error("dsh-engram: profileTopN must be an integer in [1, 64]");
|
|
82
85
|
const hasProvider = config.provider !== void 0;
|
|
@@ -93,6 +96,7 @@ function resolveConfig(config = {}) {
|
|
|
93
96
|
const dbDir = config.dbDir ?? join(homedir(), ".dsh", "engram");
|
|
94
97
|
return {
|
|
95
98
|
dbDir,
|
|
99
|
+
legacyMigration: config.legacyMigration ?? "eager",
|
|
96
100
|
injectProfile: config.injectProfile ?? true,
|
|
97
101
|
profileTopN: config.profileTopN ?? 8,
|
|
98
102
|
modelCacheDir: config.modelCacheDir ?? join(dbDir, "models"),
|
|
@@ -2511,17 +2515,17 @@ function registerEngramRoutes(ctx, deps) {
|
|
|
2511
2515
|
//#region src/project/identity.ts
|
|
2512
2516
|
/**
|
|
2513
2517
|
* 项目标识:git origin URL 归一化 → sha256 短哈希分库名;无 git 或无 origin 时
|
|
2514
|
-
*
|
|
2518
|
+
* 回退完整 cwd 哈希。纯文件读(.git/config、worktree 的 gitdir/commondir
|
|
2515
2519
|
* 指针),不起子进程。启动时负责旧库文件向新标识的 rename 迁移。
|
|
2516
2520
|
* @module @kenz1117/dsh-engram/project/identity
|
|
2517
2521
|
*/
|
|
2518
|
-
/** v0.4 旧命名:cwd 的 hex 编码前 24 位(只覆盖 cwd 前 12
|
|
2522
|
+
/** v0.4 旧命名:cwd 的 hex 编码前 24 位(只覆盖 cwd 前 12 UTF-8 字节,同前缀目录会撞库)。保留仅供迁移识别。 */
|
|
2519
2523
|
function legacyProjectDbName(cwd) {
|
|
2520
2524
|
return `project-${Buffer.from(cwd).toString("hex").slice(0, 24)}.db`;
|
|
2521
2525
|
}
|
|
2522
2526
|
/**
|
|
2523
2527
|
* 无 git 时的 cwd 命名:cwd 全量 sha256 前 24 位。
|
|
2524
|
-
* 旧算法只取 cwd 前 12
|
|
2528
|
+
* 旧算法只取 cwd 前 12 UTF-8 字节,`C:\Users\Adm…` 这类同前缀目录会共用同一个库;
|
|
2525
2529
|
* 新算法对每个目录唯一,启动时把旧库 rename 迁移到新名(见 migrateProjectDb)。
|
|
2526
2530
|
*/
|
|
2527
2531
|
function cwdProjectDbName(cwd) {
|
|
@@ -2611,37 +2615,83 @@ function resolveProjectIdentity(cwd) {
|
|
|
2611
2615
|
const origin = configPath === void 0 ? void 0 : readOriginUrl(configPath);
|
|
2612
2616
|
const normalized = origin === void 0 ? void 0 : normalizeOriginUrl(origin);
|
|
2613
2617
|
if (normalized === void 0) return {
|
|
2618
|
+
cwd,
|
|
2614
2619
|
dbName: cwdProjectDbName(cwd),
|
|
2615
2620
|
source: "cwd",
|
|
2616
2621
|
legacyDbName
|
|
2617
2622
|
};
|
|
2618
2623
|
return {
|
|
2624
|
+
cwd,
|
|
2619
2625
|
dbName: `project-${createHash("sha256").update(normalized).digest("hex").slice(0, 24)}.db`,
|
|
2620
2626
|
source: "origin",
|
|
2621
2627
|
legacyDbName
|
|
2622
2628
|
};
|
|
2623
2629
|
}
|
|
2630
|
+
function readMigrationPointer(dbDir, identity) {
|
|
2631
|
+
const path = join(dbDir, `${identity.legacyDbName}.migrated-to`);
|
|
2632
|
+
let text;
|
|
2633
|
+
try {
|
|
2634
|
+
text = readFileSync(path, "utf8");
|
|
2635
|
+
} catch (error) {
|
|
2636
|
+
if (error.code === "ENOENT") return void 0;
|
|
2637
|
+
throw error;
|
|
2638
|
+
}
|
|
2639
|
+
const value = JSON.parse(text);
|
|
2640
|
+
if (typeof value !== "object" || value === null || !("migratedTo" in value) || typeof value.migratedTo !== "string" || !/^project-[0-9a-f]{24}\.db$/.test(value.migratedTo) || !("claimedByCwd" in value) || typeof value.claimedByCwd !== "string" || !("claimedAt" in value) || typeof value.claimedAt !== "string" || !Number.isFinite(Date.parse(value.claimedAt))) throw new Error(`dsh-engram: invalid migration pointer ${path}; inspect it manually`);
|
|
2641
|
+
return value;
|
|
2642
|
+
}
|
|
2624
2643
|
/**
|
|
2625
|
-
*
|
|
2626
|
-
*
|
|
2627
|
-
*
|
|
2628
|
-
*
|
|
2629
|
-
* 注意旧 cwd 命名只覆盖前 12 字符:同一前缀下的多个目录可能共用过一个旧库,
|
|
2630
|
-
* 迁移只能把库判给先打开它的那个目录,调用方应对 kept-both 与本次迁移都告警。
|
|
2644
|
+
* Eager keeps the existing rename policy, recording its ambiguous ownership in JSON.
|
|
2645
|
+
* Conservative leaves legacy bytes untouched; the caller may open an empty new store.
|
|
2646
|
+
* Coexisting databases are never merged or overwritten. This is not a cross-process lock
|
|
2647
|
+
* or an atomic transaction across the rename and sidecar write: stop other hosts first.
|
|
2631
2648
|
*/
|
|
2632
|
-
function migrateProjectDb(dbDir, identity) {
|
|
2649
|
+
function migrateProjectDb(dbDir, identity, policy = "eager") {
|
|
2633
2650
|
if (identity.dbName === identity.legacyDbName) return "none";
|
|
2634
2651
|
const next = join(dbDir, identity.dbName);
|
|
2635
2652
|
const legacy = join(dbDir, identity.legacyDbName);
|
|
2636
2653
|
const hasNext = existsSync(next);
|
|
2637
2654
|
const hasLegacy = existsSync(legacy);
|
|
2638
2655
|
if (hasNext && hasLegacy) return "kept-both";
|
|
2656
|
+
const pointer = readMigrationPointer(dbDir, identity);
|
|
2657
|
+
if (pointer !== void 0) {
|
|
2658
|
+
if (hasLegacy || pointer.migratedTo !== identity.dbName) return "already-migrated";
|
|
2659
|
+
return "none";
|
|
2660
|
+
}
|
|
2639
2661
|
if (!hasNext && hasLegacy) {
|
|
2662
|
+
if (policy === "conservative") return "deferred";
|
|
2640
2663
|
renameSync(legacy, next);
|
|
2664
|
+
try {
|
|
2665
|
+
writeFileSync(`${legacy}.migrated-to`, JSON.stringify({
|
|
2666
|
+
migratedTo: identity.dbName,
|
|
2667
|
+
claimedByCwd: identity.cwd,
|
|
2668
|
+
claimedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2669
|
+
}) + "\n", {
|
|
2670
|
+
flag: "wx",
|
|
2671
|
+
mode: 384
|
|
2672
|
+
});
|
|
2673
|
+
} catch (error) {
|
|
2674
|
+
throw new Error(`dsh-engram: renamed ${identity.legacyDbName} to ${identity.dbName}, but could not record migration; inspect both paths before retrying`, { cause: error });
|
|
2675
|
+
}
|
|
2641
2676
|
return "renamed";
|
|
2642
2677
|
}
|
|
2643
2678
|
return "none";
|
|
2644
2679
|
}
|
|
2680
|
+
/** Shared guidance for plugin boot and session-cwd migration checks. */
|
|
2681
|
+
function migrationWarning(dbDir, identity, outcome) {
|
|
2682
|
+
const names = `旧库 ${identity.legacyDbName};新库 ${identity.dbName}`;
|
|
2683
|
+
const manual = "人工处理前请停止相关宿主并备份所有数据库及 sidecar;新库可能已写入记忆,请勿直接覆盖,先用 engram_review / engram_export 审核并人工重新归属";
|
|
2684
|
+
switch (outcome) {
|
|
2685
|
+
case "none": return;
|
|
2686
|
+
case "renamed": return `[dsh-engram] 项目记忆库已迁移(${names}),已记录 .migrated-to;旧库可能包含同前缀目录的记忆。${manual}`;
|
|
2687
|
+
case "kept-both": return `[dsh-engram] 新旧项目记忆库并存,未合并或移动(${names})。${manual}`;
|
|
2688
|
+
case "deferred": return `[dsh-engram] conservative 模式暂缓旧库迁移(${names});旧库保持不变,将打开新的空库。${manual}`;
|
|
2689
|
+
case "already-migrated": {
|
|
2690
|
+
const pointer = readMigrationPointer(dbDir, identity);
|
|
2691
|
+
return `[dsh-engram] 旧库已有迁移记录(${names}):${JSON.stringify(pointer)};先前迁移的库可能含有当前目录的记忆。${manual}`;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2645
2695
|
//#endregion
|
|
2646
2696
|
//#region src/project/registry.ts
|
|
2647
2697
|
/**
|
|
@@ -5993,9 +6043,9 @@ function apply(ctx, config = {}) {
|
|
|
5993
6043
|
mode: 448
|
|
5994
6044
|
});
|
|
5995
6045
|
const identity = resolveProjectIdentity(process.cwd());
|
|
5996
|
-
const migration = migrateProjectDb(resolved.dbDir, identity);
|
|
5997
|
-
|
|
5998
|
-
|
|
6046
|
+
const migration = migrateProjectDb(resolved.dbDir, identity, resolved.legacyMigration);
|
|
6047
|
+
const warning = migrationWarning(resolved.dbDir, identity, migration);
|
|
6048
|
+
if (warning !== void 0) console.warn(warning);
|
|
5999
6049
|
const rankBoost = {
|
|
6000
6050
|
recencyWeight: resolved.rankRecencyWeight,
|
|
6001
6051
|
proofWeight: resolved.rankProofWeight,
|
|
@@ -6039,9 +6089,9 @@ function apply(ctx, config = {}) {
|
|
|
6039
6089
|
const cached = cwdDbNames.get(cwd);
|
|
6040
6090
|
if (cached !== void 0) return cached;
|
|
6041
6091
|
const projectIdentity = resolveProjectIdentity(cwd);
|
|
6042
|
-
const migration = migrateProjectDb(resolved.dbDir, projectIdentity);
|
|
6043
|
-
|
|
6044
|
-
|
|
6092
|
+
const migration = migrateProjectDb(resolved.dbDir, projectIdentity, resolved.legacyMigration);
|
|
6093
|
+
const warning = migrationWarning(resolved.dbDir, projectIdentity, migration);
|
|
6094
|
+
if (warning !== void 0) console.warn(warning);
|
|
6045
6095
|
cwdDbNames.set(cwd, projectIdentity.dbName);
|
|
6046
6096
|
return projectIdentity.dbName;
|
|
6047
6097
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenz1117/dsh-engram",
|
|
3
3
|
"description": "Cross-session long-term memory for DeepSeek Harness (memory palace · AGI Architecture Exploration on dsh-market): dual-scope SQLite memory graph, hybrid retrieval, provenance audit, and a knowledge flywheel.",
|
|
4
|
-
"version": "0.7.
|
|
4
|
+
"version": "0.7.8",
|
|
5
5
|
"icon": "icon.svg",
|
|
6
6
|
"logo": "icon-256.svg",
|
|
7
7
|
"publishConfig": {
|