@modusensus/dsh-mneme 0.10.1 → 0.12.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.
Files changed (97) hide show
  1. package/README.md +17 -6
  2. package/bin/dsh-mneme-choro.mjs +254 -0
  3. package/docs/CONFIGURATION.md +10 -1
  4. package/lib/api-standalone.js +17 -21
  5. package/lib/api.js +414 -12
  6. package/lib/bootstrap-http.js +105 -0
  7. package/lib/bootstrap.js +5 -1
  8. package/lib/choro-command.js +104 -0
  9. package/lib/client.js +1637 -110
  10. package/lib/config-keys.js +144 -0
  11. package/lib/config.js +60 -1
  12. package/lib/document.js +9 -3
  13. package/lib/dream/clustering.js +6 -1
  14. package/lib/index.js +87 -5
  15. package/lib/inject.js +18 -4
  16. package/lib/lang.js +82 -0
  17. package/lib/profile-facts.js +356 -0
  18. package/lib/quality-filter.js +48 -12
  19. package/lib/service.js +112 -4
  20. package/lib/settings.js +13 -1
  21. package/lib/store.js +978 -27
  22. package/lib/tools.js +244 -1
  23. package/lib/user-settings.js +63 -7
  24. package/lib/wiki/inject.js +164 -0
  25. package/lib/wiki/partition.js +200 -0
  26. package/lib/wiki/plan.js +214 -0
  27. package/lib/wiki/queries.js +360 -0
  28. package/lib/wiki/render.js +187 -0
  29. package/lib/wiki/scan.js +371 -0
  30. package/lib/wiki/t0.js +463 -0
  31. package/lib/wiki/t1.js +496 -0
  32. package/lib/write-admission.js +5 -3
  33. package/package.json +4 -2
  34. package/scripts/sync-test-badge.mjs +1 -1
  35. package/scripts/test-count-sync.mjs +9 -5
  36. package/src/api-standalone.js +17 -21
  37. package/src/api.js +414 -12
  38. package/src/bootstrap-http.js +105 -0
  39. package/src/bootstrap.js +5 -1
  40. package/src/choro-command.js +104 -0
  41. package/src/config-keys.js +144 -0
  42. package/src/config.js +60 -1
  43. package/src/document.js +9 -3
  44. package/src/dream/clustering.js +6 -1
  45. package/src/index.js +87 -5
  46. package/src/inject.js +18 -4
  47. package/src/lang.js +82 -0
  48. package/src/profile-facts.js +356 -0
  49. package/src/quality-filter.js +48 -12
  50. package/src/service.js +112 -4
  51. package/src/settings.js +13 -1
  52. package/src/store.js +978 -27
  53. package/src/tools.js +244 -1
  54. package/src/user-settings.js +63 -7
  55. package/src/wiki/inject.js +164 -0
  56. package/src/wiki/partition.js +200 -0
  57. package/src/wiki/plan.js +214 -0
  58. package/src/wiki/queries.js +360 -0
  59. package/src/wiki/render.js +187 -0
  60. package/src/wiki/scan.js +371 -0
  61. package/src/wiki/t0.js +463 -0
  62. package/src/wiki/t1.js +496 -0
  63. package/src/write-admission.js +5 -3
  64. package/test/api-routes-gaps.test.js +131 -1
  65. package/test/api.test.js +9 -1
  66. package/test/bootstrap.test.js +8 -0
  67. package/test/choro-command.test.js +218 -0
  68. package/test/client.test.js +802 -21
  69. package/test/config-keys-routes.test.js +233 -0
  70. package/test/config.test.js +79 -0
  71. package/test/contrast.test.js +14 -7
  72. package/test/document.test.js +32 -0
  73. package/test/embedding-cache-f32.test.js +276 -0
  74. package/test/inject-pin-explicit.test.js +300 -0
  75. package/test/inject-pin-vector-crowd.test.js +225 -0
  76. package/test/inject.test.js +51 -0
  77. package/test/llm-audit.test.js +29 -1
  78. package/test/persona-settings.test.js +31 -0
  79. package/test/profile-facts.test.js +492 -0
  80. package/test/quality-backfill.test.js +290 -0
  81. package/test/quality-filter.test.js +113 -4
  82. package/test/service.test.js +1 -1
  83. package/test/session-gate.test.js +1 -1
  84. package/test/test-count-sync.test.js +14 -3
  85. package/test/tool-state-ignore.test.js +65 -0
  86. package/test/tools-dto-schema-matrix.test.js +23 -2
  87. package/test/tools.test.js +144 -7
  88. package/test/wiki-api.test.js +273 -0
  89. package/test/wiki-inject.test.js +265 -0
  90. package/test/wiki-partition.test.js +112 -0
  91. package/test/wiki-plan.test.js +80 -0
  92. package/test/wiki-queries.test.js +270 -0
  93. package/test/wiki-scan.test.js +208 -0
  94. package/test/wiki-store.test.js +190 -0
  95. package/test/wiki-t0.test.js +380 -0
  96. package/test/wiki-t1.test.js +235 -0
  97. package/test/wiki-tools.test.js +176 -0
@@ -0,0 +1,176 @@
1
+ // #579:从宿主的工具执行入口验证,绕过 schema 校验直调 execute 会漏掉原故障。
2
+ import test from "node:test";
3
+ import assert from "node:assert/strict";
4
+ import { mkdtempSync, rmSync, writeFileSync, mkdirSync, unlinkSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+ import { execFileSync } from "node:child_process";
8
+ import { Context } from "@deepseek-ai/cordis";
9
+ import SystemPrompt from "@deepseek-ai/dsh-system-prompt";
10
+ import ToolRuntime from "@deepseek-ai/dsh-tools";
11
+ import { createStore } from "../src/store.js";
12
+ import { createService } from "../src/service.js";
13
+ import { createTools } from "../src/tools.js";
14
+ import { createChoroQueries } from "../src/wiki/queries.js";
15
+ import { runChoroT0 } from "../src/wiki/t0.js";
16
+
17
+ const choroArgs = {
18
+ choro_module_map: {},
19
+ choro_read_page: { slug: "src" },
20
+ choro_search: { query: "b.js" }
21
+ };
22
+
23
+ async function setup(t, config = {}, withChoro = false) {
24
+ const store = createStore(":memory:");
25
+ t.after(() => store.close());
26
+ const ctx = new Context();
27
+ const prompt = await ctx.plugin(SystemPrompt);
28
+ const runtime = await ctx.plugin(ToolRuntime);
29
+ t.after(() => { runtime.dispose(); prompt.dispose(); });
30
+ const tools = ctx.get("tools");
31
+ const service = createService({ store, mirror: null, config: {} });
32
+ createTools({ tools }, service, config, null, null, withChoro ? createChoroQueries({ store }) : null);
33
+ const call = (name, args = {}) => tools.execute({
34
+ name, callId: name, arguments: args, signal: new AbortController().signal
35
+ });
36
+ return { tools, store, call };
37
+ }
38
+
39
+ function makeRepo(t, store, name = "toolfix") {
40
+ const root = mkdtempSync(join(tmpdir(), "choro-tools-"));
41
+ t.after(() => rmTempBestEffort(root));
42
+ writeFileSync(join(root, "package.json"), JSON.stringify({ name, scripts: { test: "node --test" } }, null, 2) + "\n", "utf8");
43
+ mkdirSync(join(root, "src"), { recursive: true });
44
+ writeFileSync(join(root, "src", "a.js"), 'import { b } from "./b.js";\nexport const a = () => b();\n', "utf8");
45
+ writeFileSync(join(root, "src", "b.js"), "export const b = () => 1;\n", "utf8");
46
+ execFileSync("git", ["init", "-b", "main"], { cwd: root, stdio: "pipe" });
47
+ execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: root, stdio: "pipe" });
48
+ execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-m", "init", "--no-gpg-sign"], { cwd: root, stdio: "pipe" });
49
+ runChoroT0({ repoRoot: root, store, apply: true, now: () => "T" });
50
+ return root;
51
+ }
52
+
53
+ function rmTempBestEffort(dir) {
54
+ try {
55
+ rmSync(dir, { recursive: true, force: true });
56
+ } catch {
57
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
58
+ try { rmSync(dir, { recursive: true, force: true }); } catch { /* 留给系统清理 */ }
59
+ }
60
+ }
61
+
62
+ function expectToolError(result, pattern) {
63
+ const message = result.content[0]?.text ?? "";
64
+ assert.equal(result.isError, true, message);
65
+ assert.match(message, pattern);
66
+ }
67
+
68
+ test("默认配置下三个 choro 工具不注册(模型看不到就不会调)", async (t) => {
69
+ const { tools } = await setup(t, {}, true);
70
+ for (const name of Object.keys(choroArgs)) {
71
+ assert.ok(!tools.get(name), `${name} 默认缺席`);
72
+ }
73
+ });
74
+
75
+ test("开关开但 queries 缺装配 → 同样缺席(可区分性归路由层)", async (t) => {
76
+ const { tools } = await setup(t, { chorographyEnabled: true });
77
+ for (const name of Object.keys(choroArgs)) assert.ok(!tools.get(name));
78
+ });
79
+
80
+ test("开启后三个只读工具的成功结果通过宿主校验", async (t) => {
81
+ const { call, store } = await setup(t, { chorographyEnabled: true }, true);
82
+ makeRepo(t, store);
83
+ const map = await call("choro_module_map");
84
+ assert.equal(map.isError, false);
85
+ assert.equal(map.value.repo, "toolfix");
86
+ assert.ok(map.value.modules.length >= 2);
87
+ const page = await call("choro_read_page", { slug: "src" });
88
+ assert.equal(page.isError, false);
89
+ assert.equal(page.value.slug, "src");
90
+ assert.match(page.value.content, /## 文件清单/);
91
+ const search = await call("choro_search", { query: "b.js" });
92
+ assert.equal(search.isError, false);
93
+ assert.ok(search.value.hits.length >= 1);
94
+ assert.equal(search.value.unreadablePages, undefined);
95
+ });
96
+
97
+ test("未登记或找不到指定仓库时,三个工具都返回可操作的错误", async (t) => {
98
+ const { call, store } = await setup(t, { chorographyEnabled: true }, true);
99
+ for (const [name, args] of Object.entries(choroArgs)) {
100
+ expectToolError(await call(name, args), /No repository is registered.*dsh-mneme-choro/);
101
+ }
102
+ makeRepo(t, store);
103
+ const [{ id }] = createChoroQueries({ store }).listRepos();
104
+ const unknownId = String(id + 1);
105
+ for (const [name, args] of Object.entries(choroArgs)) {
106
+ expectToolError(await call(name, { ...args, repo: unknownId }), /Repository "\d+" is not registered/);
107
+ }
108
+ });
109
+
110
+ test("多仓歧义提示可选 id,按 id 重试可读到对应仓库", async (t) => {
111
+ const { call, store } = await setup(t, { chorographyEnabled: true }, true);
112
+ makeRepo(t, store);
113
+ makeRepo(t, store, "other");
114
+ const repos = createChoroQueries({ store }).listRepos();
115
+ for (const [name, args] of Object.entries(choroArgs)) {
116
+ const result = await call(name, args);
117
+ expectToolError(result, /More than one repository is registered/);
118
+ for (const { id, name } of repos) assert.ok(result.content[0].text.includes(`${id} (${name})`));
119
+ }
120
+ const selected = await call("choro_module_map", { repo: String(repos[1].id) });
121
+ assert.equal(selected.isError, false);
122
+ assert.equal(selected.value.repo, "other");
123
+ });
124
+
125
+ test("读页错误区分未知 slug、缺失文件和不可读文件", async (t) => {
126
+ const { call, store } = await setup(t, { chorographyEnabled: true }, true);
127
+ const root = makeRepo(t, store);
128
+ expectToolError(await call("choro_read_page", { slug: "nope" }), /Page "nope" was not found.*choro_module_map/);
129
+ const path = join(root, ".chorography", "modules", "src.md");
130
+ unlinkSync(path);
131
+ expectToolError(await call("choro_read_page", { slug: "src" }), /Page "src" is missing from disk.*dsh-mneme-choro/);
132
+ mkdirSync(path);
133
+ const unreadable = await call("choro_read_page", { slug: "src" });
134
+ expectToolError(unreadable, /Page "src" could not be read.*permissions/);
135
+ assert.ok(!unreadable.content[0].text.includes(root), "工具错误不暴露仓库绝对路径");
136
+ });
137
+
138
+ test("共享库中本机不可达的仓库仍有模块图,读页和检索报告文件访问失败", async (t) => {
139
+ const { call, store } = await setup(t, { chorographyEnabled: true }, true);
140
+ const root = makeRepo(t, store);
141
+ const [{ id }] = createChoroQueries({ store }).listRepos();
142
+ store.db.prepare("UPDATE choro_repos SET root_path = ? WHERE id = ?")
143
+ .run(join(root, "unavailable-checkout"), id);
144
+ const repo = String(id);
145
+
146
+ const map = await call("choro_module_map", { repo });
147
+ assert.equal(map.isError, false);
148
+ assert.equal(map.value.repo, "toolfix");
149
+ assert.ok(map.value.modules.some(({ slug }) => slug === "src"));
150
+ expectToolError(await call("choro_read_page", { repo, slug: "src" }), /missing from disk.*local checkout/);
151
+ expectToolError(await call("choro_search", { repo, query: "b.js" }), /Search was incomplete.*src.*local checkout/);
152
+ });
153
+
154
+ test("检索报告不可读的页,保留已读到的命中", async (t) => {
155
+ const { call, store } = await setup(t, { chorographyEnabled: true }, true);
156
+ const root = makeRepo(t, store);
157
+ const rootPage = join(root, ".chorography", "modules", "root.md");
158
+ unlinkSync(rootPage);
159
+ const partial = await call("choro_search", { query: "b.js" });
160
+ assert.equal(partial.isError, false);
161
+ assert.ok(partial.value.hits.length >= 1);
162
+ assert.deepEqual(partial.value.unreadablePages, ["root"]);
163
+ assert.match(partial.content[0].text, /Search was incomplete.*root/);
164
+
165
+ const empty = await call("choro_search", { query: "not present", module: "src" });
166
+ assert.equal(empty.isError, false);
167
+ assert.deepEqual(empty.value, { hits: [] });
168
+ assert.equal(empty.content[0].text, "0 hits.");
169
+
170
+ const incomplete = await call("choro_search", { query: "not present" });
171
+ expectToolError(incomplete, /Search was incomplete.*root/);
172
+ const srcPage = join(root, ".chorography", "modules", "src.md");
173
+ unlinkSync(srcPage);
174
+ mkdirSync(srcPage);
175
+ expectToolError(await call("choro_search", { query: "b.js" }), /Search was incomplete.*root.*src/);
176
+ });