@modusensus/dsh-mneme 0.10.1 → 0.11.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/README.md +10 -3
- package/bin/dsh-mneme-choro.mjs +254 -0
- package/docs/CONFIGURATION.md +8 -1
- package/lib/api-standalone.js +17 -21
- package/lib/api.js +247 -11
- package/lib/bootstrap-http.js +105 -0
- package/lib/bootstrap.js +5 -1
- package/lib/choro-command.js +104 -0
- package/lib/client.js +1028 -102
- package/lib/config-keys.js +144 -0
- package/lib/config.js +28 -0
- package/lib/index.js +50 -4
- package/lib/inject.js +15 -3
- package/lib/lang.js +54 -0
- package/lib/quality-filter.js +48 -12
- package/lib/service.js +3 -0
- package/lib/settings.js +8 -1
- package/lib/store.js +502 -0
- package/lib/tools.js +127 -1
- package/lib/wiki/inject.js +164 -0
- package/lib/wiki/partition.js +200 -0
- package/lib/wiki/plan.js +214 -0
- package/lib/wiki/queries.js +220 -0
- package/lib/wiki/render.js +187 -0
- package/lib/wiki/scan.js +371 -0
- package/lib/wiki/t0.js +463 -0
- package/lib/wiki/t1.js +496 -0
- package/lib/write-admission.js +5 -3
- package/package.json +4 -2
- package/src/api-standalone.js +17 -21
- package/src/api.js +247 -11
- package/src/bootstrap-http.js +105 -0
- package/src/bootstrap.js +5 -1
- package/src/choro-command.js +104 -0
- package/src/config-keys.js +144 -0
- package/src/config.js +28 -0
- package/src/index.js +50 -4
- package/src/inject.js +15 -3
- package/src/lang.js +54 -0
- package/src/quality-filter.js +48 -12
- package/src/service.js +3 -0
- package/src/settings.js +8 -1
- package/src/store.js +502 -0
- package/src/tools.js +127 -1
- package/src/wiki/inject.js +164 -0
- package/src/wiki/partition.js +200 -0
- package/src/wiki/plan.js +214 -0
- package/src/wiki/queries.js +220 -0
- package/src/wiki/render.js +187 -0
- package/src/wiki/scan.js +371 -0
- package/src/wiki/t0.js +463 -0
- package/src/wiki/t1.js +496 -0
- package/src/write-admission.js +5 -3
- package/test/api-routes-gaps.test.js +131 -1
- package/test/api.test.js +7 -1
- package/test/bootstrap.test.js +8 -0
- package/test/choro-command.test.js +218 -0
- package/test/client.test.js +655 -19
- package/test/config-keys-routes.test.js +233 -0
- package/test/contrast.test.js +14 -7
- package/test/inject.test.js +51 -0
- package/test/llm-audit.test.js +29 -1
- package/test/quality-backfill.test.js +290 -0
- package/test/quality-filter.test.js +113 -4
- package/test/service.test.js +1 -1
- package/test/wiki-api.test.js +153 -0
- package/test/wiki-inject.test.js +265 -0
- package/test/wiki-partition.test.js +112 -0
- package/test/wiki-plan.test.js +80 -0
- package/test/wiki-queries.test.js +187 -0
- package/test/wiki-scan.test.js +208 -0
- package/test/wiki-store.test.js +190 -0
- package/test/wiki-t0.test.js +380 -0
- package/test/wiki-t1.test.js +235 -0
- package/test/wiki-tools.test.js +121 -0
package/test/api.test.js
CHANGED
|
@@ -760,7 +760,13 @@ test("GET /api/dsh-mneme/features returns empty overrides and effective config d
|
|
|
760
760
|
// 输入侧预算,两个 int 键都走 INT_RANGES 表)= 110。
|
|
761
761
|
// issue #529 再新增 sleepFailureBackoff(sleep 连续失败退避;顶层布尔键,
|
|
762
762
|
// 不需要 NESTED_FLAG_PATHS 条目)= 111。
|
|
763
|
-
|
|
763
|
+
// chorography M1 新增 chorographyEnabled(仓库地志只读工具 + 面板页的暴露闸;
|
|
764
|
+
// 顶层扁平键,不需要 NESTED_FLAG_PATHS 条目)= 112。
|
|
765
|
+
// chorography M3 新增 chorographyInject(开局注入档位;枚举键走 ENUMS 表,
|
|
766
|
+
// 顶层扁平原值 "light",不需要 NESTED_FLAG_PATHS 条目)= 113。
|
|
767
|
+
assert.equal(Object.keys(data.effective).length, 113);
|
|
768
|
+
assert.equal(data.effective.chorographyEnabled, false);
|
|
769
|
+
assert.equal(data.effective.chorographyInject, "light");
|
|
764
770
|
assert.equal(data.effective.dreamSkipInvalid, true);
|
|
765
771
|
assert.equal(data.effective.allowCrossTypeMerge, false);
|
|
766
772
|
assert.equal(data.effective.dreamMergeGuard, true);
|
package/test/bootstrap.test.js
CHANGED
|
@@ -175,6 +175,14 @@ test("bootstrap validates its inputs with coded errors", async () => {
|
|
|
175
175
|
bootstrapFromDirectory({ service, dir: filePath }),
|
|
176
176
|
(e) => e instanceof BootstrapError && e.code === "not-a-directory"
|
|
177
177
|
);
|
|
178
|
+
// 带首尾空格的路径不许被静默 trim 成真目录(#386 第 6 条评审):协议层
|
|
179
|
+
// readBootstrapDir 承诺「回传原串、交给 fs 报错」,这一层若偷偷改写,用户填
|
|
180
|
+
// " /repo " 会被读成 "/repo",而报告上照样写着成功——读的是另一个目录,
|
|
181
|
+
// 他却一无所知。所以这里必须仍然是 dir-not-found。
|
|
182
|
+
await assert.rejects(
|
|
183
|
+
bootstrapFromDirectory({ service, dir: ` ${root} ` }),
|
|
184
|
+
(e) => e instanceof BootstrapError && e.code === "dir-not-found"
|
|
185
|
+
);
|
|
178
186
|
} finally {
|
|
179
187
|
rmSync(root, { recursive: true, force: true });
|
|
180
188
|
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
// /choro 的回归锁(M3)。锁三件事:
|
|
2
|
+
// 1. **只读**——它一个字节都不写(重建、富化、切档位都不在这条命令里,spec §2 D6);
|
|
3
|
+
// 2. **三态过期**——一致 / 不一致 / 读不到 HEAD 必须分开报,不许把「读不到」说成
|
|
4
|
+
// 「没过期」(那是这条命令唯一的实质判断);
|
|
5
|
+
// 3. **回执永不抛进宿主**——handler 走 CommandResult 契约,异常写进文本。
|
|
6
|
+
import test from "node:test";
|
|
7
|
+
import assert from "node:assert/strict";
|
|
8
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "node:fs";
|
|
9
|
+
import { tmpdir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { runChoroT0 } from "../src/wiki/t0.js";
|
|
13
|
+
import { createStore } from "../src/store.js";
|
|
14
|
+
import { createChoroQueries } from "../src/wiki/queries.js";
|
|
15
|
+
import { createChoroHandler, registerChoroCommand, CHORO_COMMAND } from "../src/choro-command.js";
|
|
16
|
+
|
|
17
|
+
const FIXED_NOW = () => "2026-11-05T00:00:00.000Z";
|
|
18
|
+
|
|
19
|
+
function makeFixture() {
|
|
20
|
+
const root = mkdtempSync(join(tmpdir(), "choro-cmd-"));
|
|
21
|
+
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "cfix", scripts: { test: "node --test" } }, null, 2) + "\n", "utf8");
|
|
22
|
+
mkdirSync(join(root, "src"), { recursive: true });
|
|
23
|
+
writeFileSync(join(root, "src", "a.js"), 'import { b } from "./b.js";\nexport const a = () => b();\n', "utf8");
|
|
24
|
+
writeFileSync(join(root, "src", "b.js"), "export const b = () => 1;\n", "utf8");
|
|
25
|
+
execFileSync("git", ["init", "-b", "main"], { cwd: root, stdio: "pipe" });
|
|
26
|
+
execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: root, stdio: "pipe" });
|
|
27
|
+
execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-m", "init", "--no-gpg-sign"], { cwd: root, stdio: "pipe" });
|
|
28
|
+
return root;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function rmTempBestEffort(dir) {
|
|
32
|
+
for (let i = 0; i < 5; i++) {
|
|
33
|
+
try {
|
|
34
|
+
rmSync(dir, { recursive: true, force: true });
|
|
35
|
+
return;
|
|
36
|
+
} catch {
|
|
37
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 200);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
console.warn("[choro-command.test] 临时目录清理未完成:" + dir);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** git 提交一次,用于把 HEAD 推离地志基线(造「可能已过期」)。 */
|
|
44
|
+
function commitSomething(root, name) {
|
|
45
|
+
writeFileSync(join(root, name), "// drift\n", "utf8");
|
|
46
|
+
execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "add", "-A"], { cwd: root, stdio: "pipe" });
|
|
47
|
+
execFileSync("git", ["-c", "user.email=t@t", "-c", "user.name=t", "commit", "-m", "drift", "--no-gpg-sign"], { cwd: root, stdio: "pipe" });
|
|
48
|
+
return execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function setup(config = {}) {
|
|
52
|
+
const root = makeFixture();
|
|
53
|
+
const store = createStore(":memory:");
|
|
54
|
+
runChoroT0({ repoRoot: root, store, apply: true, now: FIXED_NOW });
|
|
55
|
+
const choro = createChoroQueries({ store });
|
|
56
|
+
return { root, store, choro, config: { chorographyEnabled: true, ...config } };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** 造 invocation:handler 只读 agent.session.header.cwd。 */
|
|
60
|
+
function invocation(root) {
|
|
61
|
+
return { agent: { session: { header: { cwd: root } } } };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
test("/choro:总闸关时报「没开」并给出打开路径,不是空回执", () => {
|
|
65
|
+
const { root, store, choro } = setup({ chorographyEnabled: false });
|
|
66
|
+
try {
|
|
67
|
+
const res = createChoroHandler({ choro, config: { chorographyEnabled: false }, language: () => "zh" })(invocation(root));
|
|
68
|
+
assert.equal(res.kind, "error");
|
|
69
|
+
assert.ok(res.text.includes("没开"));
|
|
70
|
+
} finally {
|
|
71
|
+
store.close();
|
|
72
|
+
rmTempBestEffort(root);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("/choro:拿不到 cwd / 仓库没登记,两种失败分开说", () => {
|
|
77
|
+
const { root, store, choro, config } = setup();
|
|
78
|
+
try {
|
|
79
|
+
const handler = createChoroHandler({ choro, config, language: () => "zh" });
|
|
80
|
+
const noCwd = handler({ agent: { session: { header: {} } } });
|
|
81
|
+
assert.equal(noCwd.kind, "error");
|
|
82
|
+
assert.ok(noCwd.text.includes("工作目录"));
|
|
83
|
+
const noRepo = handler(invocation(join(root, "no-such-dir")));
|
|
84
|
+
assert.equal(noRepo.kind, "error");
|
|
85
|
+
assert.ok(noRepo.text.includes("dsh-mneme-choro"), "要给出生成命令,否则用户不知道下一步");
|
|
86
|
+
} finally {
|
|
87
|
+
store.close();
|
|
88
|
+
rmTempBestEffort(root);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("/choro:快乐路径带提纲、状态与用法,且基线一致时明说没过期", () => {
|
|
93
|
+
const { root, store, choro, config } = setup();
|
|
94
|
+
try {
|
|
95
|
+
const res = createChoroHandler({ choro, config, language: () => "zh" })(invocation(root));
|
|
96
|
+
assert.equal(res.kind, "success");
|
|
97
|
+
assert.ok(res.text.includes("仓库地志 · cfix"));
|
|
98
|
+
assert.ok(res.text.includes(".chorography/modules/src.md"), "full 档列全部模块的页路径");
|
|
99
|
+
assert.ok(res.text.includes("基线一致"), "T0 刚跑完,HEAD 就是基线");
|
|
100
|
+
assert.ok(res.text.includes("choro_search"), "用法提示要指出「正文怎么读」");
|
|
101
|
+
assert.equal(res.text.includes("## 文件清单"), false, "回执同样只给标题级,不贴正文");
|
|
102
|
+
} finally {
|
|
103
|
+
store.close();
|
|
104
|
+
rmTempBestEffort(root);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test("/choro:HEAD 推离基线 → 明说可能已过期,且明说「不会自己重跑」", () => {
|
|
109
|
+
const { root, store, choro, config } = setup();
|
|
110
|
+
try {
|
|
111
|
+
const head = commitSomething(root, "drift.txt");
|
|
112
|
+
const res = createChoroHandler({ choro, config, language: () => "zh" })(invocation(root));
|
|
113
|
+
assert.equal(res.kind, "success");
|
|
114
|
+
assert.ok(res.text.includes("可能已过期"), res.text);
|
|
115
|
+
assert.ok(res.text.includes(head.slice(0, 7)), "要给出当前 HEAD 的短 sha,人才能自己比对");
|
|
116
|
+
assert.ok(res.text.includes("不会自己重跑"), "只提示不自动更新是 spec §2 D6 的明说语义");
|
|
117
|
+
} finally {
|
|
118
|
+
store.close();
|
|
119
|
+
rmTempBestEffort(root);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("/choro:读不到 HEAD 时报「判断不了」,不许冒充「没过期」", () => {
|
|
124
|
+
const { store } = setup();
|
|
125
|
+
const fake = {
|
|
126
|
+
repoByCwd: () => ({ id: 1, name: "ghost", root_path: join(tmpdir(), "definitely-not-a-repo-" + Date.now()), updated_at: FIXED_NOW(), git_baseline: "deadbeef" }),
|
|
127
|
+
outline: () => ({ repo: { id: 1, name: "ghost" }, generatedAt: FIXED_NOW(), modules: [{ slug: "a", title: "A", fileCount: 1, parentSlug: null, status: "skeleton", relPath: ".chorography/modules/a.md" }] })
|
|
128
|
+
};
|
|
129
|
+
try {
|
|
130
|
+
const res = createChoroHandler({ choro: fake, config: { chorographyEnabled: true }, language: () => "zh" })({ agent: { session: { header: { cwd: "/x" } } } });
|
|
131
|
+
assert.equal(res.kind, "success");
|
|
132
|
+
assert.ok(res.text.includes("没法判断是否过期"), res.text);
|
|
133
|
+
assert.equal(res.text.includes("基线一致"), false, "读不到 HEAD 绝不能说成一致");
|
|
134
|
+
} finally {
|
|
135
|
+
store.close();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("/choro:模块为空时给明确空态而不是半个回执", () => {
|
|
140
|
+
const empty = {
|
|
141
|
+
repoByCwd: () => ({ id: 1, name: "emptyfix", root_path: "/x", updated_at: FIXED_NOW(), git_baseline: "abc" }),
|
|
142
|
+
outline: () => ({ repo: { id: 1, name: "emptyfix" }, generatedAt: FIXED_NOW(), modules: [] })
|
|
143
|
+
};
|
|
144
|
+
const res = createChoroHandler({ choro: empty, config: { chorographyEnabled: true }, language: () => "zh" })({ agent: { session: { header: { cwd: "/x" } } } });
|
|
145
|
+
assert.equal(res.kind, "success");
|
|
146
|
+
assert.ok(res.text.includes("还没扫出任何模块"));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("/choro:读侧抛错时不把异常扔进宿主,原因写进回执", () => {
|
|
150
|
+
const boom = { repoByCwd: () => { throw new Error("store closed"); }, outline: () => null };
|
|
151
|
+
const res = createChoroHandler({ choro: boom, config: { chorographyEnabled: true }, language: () => "zh" })({ agent: { session: { header: { cwd: "/x" } } } });
|
|
152
|
+
assert.equal(res.kind, "error");
|
|
153
|
+
assert.ok(res.text.includes("store closed"));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
test("/choro:注册闸——没开 / 宿主没有命令表都返回 null(不抛)", () => {
|
|
157
|
+
const registered = [];
|
|
158
|
+
const ctx = { commands: { register: (def) => { registered.push(def); return () => {}; } } };
|
|
159
|
+
assert.equal(registerChoroCommand({ ctx, choro: null, config: {}, enabled: false, language: () => "zh" }), null);
|
|
160
|
+
assert.equal(registerChoroCommand({ ctx: {}, choro: null, config: {}, enabled: true, language: () => "zh" }), null);
|
|
161
|
+
const dispose = registerChoroCommand({ ctx, choro: null, config: { chorographyEnabled: true }, enabled: true, language: () => "zh" });
|
|
162
|
+
assert.equal(typeof dispose, "function");
|
|
163
|
+
assert.equal(registered.length, 1);
|
|
164
|
+
assert.equal(registered[0].name, CHORO_COMMAND);
|
|
165
|
+
assert.equal(typeof registered[0].handler, "function");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
test("/choro:英文语言取英文文案(双语是成对的,不许只写中文)", () => {
|
|
169
|
+
const { root, store, choro } = setup();
|
|
170
|
+
try {
|
|
171
|
+
const res = createChoroHandler({ choro, config: { chorographyEnabled: true }, language: () => "en" })(invocation(root));
|
|
172
|
+
assert.equal(res.kind, "success");
|
|
173
|
+
assert.ok(res.text.includes("Repo atlas"), res.text);
|
|
174
|
+
assert.ok(res.text.includes("not stale"), res.text);
|
|
175
|
+
} finally {
|
|
176
|
+
store.close();
|
|
177
|
+
rmTempBestEffort(root);
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("/choro:提纲超字符上限时,截断语报的是「还剩几个」而不是总数", () => {
|
|
182
|
+
// 回执贴爆 UI 同样是失败,所以 full 档也守 6000 字符上限。但截断语必须报差额:
|
|
183
|
+
// 报总数(38 个)而实际只漏了 3 个,读者会以为大部分模块都看不到。
|
|
184
|
+
const many = Array.from({ length: 80 }, (_, i) => ({
|
|
185
|
+
slug: "mod-" + i,
|
|
186
|
+
title: "模块" + i + "-".repeat(120),
|
|
187
|
+
fileCount: i,
|
|
188
|
+
parentSlug: null,
|
|
189
|
+
status: "skeleton",
|
|
190
|
+
relPath: ".chorography/modules/mod-" + i + ".md"
|
|
191
|
+
}));
|
|
192
|
+
const fake = {
|
|
193
|
+
repoByCwd: () => ({ id: 1, name: "bigfix", root_path: join(tmpdir(), "no-such-repo-" + Date.now()), updated_at: FIXED_NOW(), git_baseline: "x" }),
|
|
194
|
+
outline: () => ({ repo: { id: 1, name: "bigfix" }, generatedAt: FIXED_NOW(), modules: many })
|
|
195
|
+
};
|
|
196
|
+
const res = createChoroHandler({ choro: fake, config: { chorographyEnabled: true }, language: () => "zh" })({ agent: { session: { header: { cwd: "/x" } } } });
|
|
197
|
+
assert.equal(res.kind, "success");
|
|
198
|
+
const m = /另有 (\d+) 个模块/.exec(res.text);
|
|
199
|
+
assert.ok(m, "超上限必须明说还剩几个:" + res.text.slice(-200));
|
|
200
|
+
const rest = Number(m[1]);
|
|
201
|
+
assert.ok(rest > 0 && rest < 80, "必须报差额而不是总数 80,实际报 " + rest);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("/choro:轻量档(lightMode)压掉的是注入,不是这条命令", () => {
|
|
205
|
+
// resolveInjectTier 在 lightMode 下也返回 off。若拿它当命令闸,lightMode 的用户
|
|
206
|
+
// 敲 /choro 会听到「没开,去设置里打开」——而设置里明明是开着的。档位管注入,
|
|
207
|
+
// 命令只管子系统开没开。
|
|
208
|
+
const { root, store, choro } = setup();
|
|
209
|
+
try {
|
|
210
|
+
const res = createChoroHandler({ choro, config: { chorographyEnabled: true, lightMode: true, chorographyInject: "full" }, language: () => "zh" })(invocation(root));
|
|
211
|
+
assert.equal(res.kind, "success", "轻量档下命令照常可用");
|
|
212
|
+
assert.ok(res.text.includes("仓库地志 · cfix"));
|
|
213
|
+
assert.equal(res.text.includes("没开"), false, "子系统开着时不许说没开");
|
|
214
|
+
} finally {
|
|
215
|
+
store.close();
|
|
216
|
+
rmTempBestEffort(root);
|
|
217
|
+
}
|
|
218
|
+
});
|