@kira905/dsh-memory-toolkit 0.0.0-stage → 0.1.1
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/CHANGELOG.md +42 -0
- package/LICENSE +661 -0
- package/README.en.md +110 -0
- package/README.md +102 -2
- package/dsh-memory-local/FORK-BASE.json +24 -0
- package/dsh-memory-local/README.md +113 -0
- package/dsh-memory-local/build.mjs +101 -0
- package/dsh-memory-local/cordis.patch.yml +20 -0
- package/dsh-memory-local/deploy.mjs +306 -0
- package/dsh-memory-local/entity-status.mjs +75 -0
- package/dsh-memory-local/lib/bridge.mjs +108 -0
- package/dsh-memory-local/lib/host-adapter.js +1171 -0
- package/dsh-memory-local/lib/index.d.ts +128 -0
- package/dsh-memory-local/lib/index.js +944 -0
- package/dsh-memory-local/lib/injector.mjs +400 -0
- package/dsh-memory-local/lib/memory-read.mjs +116 -0
- package/dsh-memory-local/lib/module-gate.mjs +68 -0
- package/dsh-memory-local/lib/pointer.mjs +152 -0
- package/dsh-memory-local/lib/prod-gate.mjs +121 -0
- package/dsh-memory-local/lib/recall-log.mjs +118 -0
- package/dsh-memory-local/lib/recall.mjs +192 -0
- package/dsh-memory-local/lib/schema-migrate.mjs +365 -0
- package/dsh-memory-local/lib/user-message.mjs +32 -0
- package/dsh-memory-local/lib/vectors-read.mjs +110 -0
- package/dsh-memory-local/package.json +51 -0
- package/dsh-memory-local/restore.mjs +259 -0
- package/dsh-memory-local/rollback-injector-fix.mjs +166 -0
- package/dsh-memory-local/src/bridge.mjs +108 -0
- package/dsh-memory-local/src/host-adapter.js +1171 -0
- package/dsh-memory-local/src/index.d.ts +128 -0
- package/dsh-memory-local/src/index.js +944 -0
- package/dsh-memory-local/src/injector.mjs +400 -0
- package/dsh-memory-local/src/memory-read.mjs +116 -0
- package/dsh-memory-local/src/module-gate.mjs +68 -0
- package/dsh-memory-local/src/pointer.mjs +152 -0
- package/dsh-memory-local/src/recall-log.mjs +118 -0
- package/dsh-memory-local/src/recall.mjs +192 -0
- package/dsh-memory-local/src/schema-migrate.mjs +365 -0
- package/dsh-memory-local/src/user-message.mjs +32 -0
- package/dsh-memory-local/src/vectors-read.mjs +110 -0
- package/dsh-memory-local/test/fixtures-s2b.mjs +216 -0
- package/dsh-memory-local/test/probe-entity.mjs +170 -0
- package/dsh-memory-local/test/regression.mjs +270 -0
- package/dsh-memory-local/test/s2b.test.mjs +568 -0
- package/dsh-memory-local/test/schema-migrate.test.mjs +334 -0
- package/dsh-memory-local/test/test-adapter-converge.mjs +127 -0
- package/dsh-memory-local/test/visible-injections.test.mjs +239 -0
- package/dsh-memory-local/verify-after-restart.mjs +181 -0
- package/lib/deploy-guard.mjs +391 -0
- package/lib/guard-http.mjs +93 -0
- package/lib/pitfall-form-map.mjs +130 -0
- package/lib/pitfall-score.mjs +185 -0
- package/lib/session-file-name.mjs +160 -0
- package/lib/zstd-frames.mjs +78 -0
- package/memory-db-tools/README.md +68 -0
- package/memory-db-tools/_test/test-memory-scope.mjs +302 -0
- package/memory-db-tools/audit-analyze.mjs +352 -0
- package/memory-db-tools/audit-dump-ids.mjs +245 -0
- package/memory-db-tools/audit-export.mjs +270 -0
- package/memory-db-tools/audit-hit-probe.mjs +227 -0
- package/memory-db-tools/audit-ledger-probe.mjs +236 -0
- package/memory-db-tools/audit-persona-overlap.mjs +277 -0
- package/memory-db-tools/audit-struct-probe.mjs +213 -0
- package/memory-db-tools/audit-verify-unchanged.mjs +276 -0
- package/memory-db-tools/check-memory-db.mjs +8 -0
- package/memory-db-tools/dump-memory-db.mjs +12 -0
- package/memory-db-tools/lib/memory-scope.mjs +446 -0
- package/memory-db-tools/list-memory-summary.mjs +477 -0
- package/memory-db-tools/memory-identity-check.mjs +101 -0
- package/memory-db-tools/memory-schema-migrate.mjs +290 -0
- package/memory-db-tools/memory-scope-fixture.mjs +197 -0
- package/memory-db-tools/read-memories.mjs +26 -0
- package/memory-db-tools/read-memory-db.mjs +20 -0
- package/memory-db-tools/refine-memories.mjs +247 -0
- package/memory-db-tools/show-mem-times.mjs +22 -0
- package/memory-engine/README.md +70 -0
- package/memory-engine/bench/bench.mjs +221 -0
- package/memory-engine/cli/distill-rollback.mjs +86 -0
- package/memory-engine/cli/distill.mjs +156 -0
- package/memory-engine/cli/doc-index-rebuild.mjs +155 -0
- package/memory-engine/cli/doc-index.mjs +48 -0
- package/memory-engine/cli/domain-tags.mjs +251 -0
- package/memory-engine/cli/pitfall-hook.mjs +91 -0
- package/memory-engine/cli/recall.mjs +132 -0
- package/memory-engine/cli/selfcheck.mjs +57 -0
- package/memory-engine/cli/serve-start.mjs +247 -0
- package/memory-engine/cli/sweep.mjs +58 -0
- package/memory-engine/cli/sync.mjs +69 -0
- package/memory-engine/lib/cli-util.mjs +197 -0
- package/memory-engine/lib/config.mjs +135 -0
- package/memory-engine/lib/dirty-ledger.mjs +174 -0
- package/memory-engine/lib/distill-rollback.mjs +178 -0
- package/memory-engine/lib/distill.mjs +898 -0
- package/memory-engine/lib/doc-index-store.mjs +325 -0
- package/memory-engine/lib/doc-index.mjs +529 -0
- package/memory-engine/lib/doc-scan.mjs +271 -0
- package/memory-engine/lib/doc-summarize.mjs +305 -0
- package/memory-engine/lib/domain-tags.mjs +429 -0
- package/memory-engine/lib/embedder.mjs +242 -0
- package/memory-engine/lib/hashes.mjs +12 -0
- package/memory-engine/lib/memory-source.mjs +127 -0
- package/memory-engine/lib/pitfall-source.mjs +170 -0
- package/memory-engine/lib/policy-source.mjs +371 -0
- package/memory-engine/lib/recall.mjs +331 -0
- package/memory-engine/lib/selfcheck.mjs +131 -0
- package/memory-engine/lib/sweep.mjs +159 -0
- package/memory-engine/lib/sync.mjs +180 -0
- package/memory-engine/lib/tick-hook.mjs +218 -0
- package/memory-engine/lib/vectors-store.mjs +292 -0
- package/memory-engine/package-lock.json +1086 -0
- package/memory-engine/package.json +22 -0
- package/memory-engine/serve.mjs +171 -0
- package/memory-engine/test/01-store.test.mjs +127 -0
- package/memory-engine/test/02-sync.test.mjs +196 -0
- package/memory-engine/test/03-sweep.test.mjs +145 -0
- package/memory-engine/test/04-recall.test.mjs +214 -0
- package/memory-engine/test/05-selfcheck.test.mjs +175 -0
- package/memory-engine/test/06-doc-index.test.mjs +928 -0
- package/memory-engine/test/07-domain-tags.test.mjs +298 -0
- package/memory-engine/test/08-distill.test.mjs +686 -0
- package/memory-engine/test/09-pitfall-source.test.mjs +365 -0
- package/memory-engine/test/10-tick-hook.test.mjs +252 -0
- package/memory-engine/test/11-policy-source.test.mjs +224 -0
- package/memory-engine/test/_tickhook-worker.mjs +56 -0
- package/memory-engine/test/fixtures.mjs +205 -0
- package/memory-engine/test/harness.mjs +73 -0
- package/memory-engine/test/run.mjs +35 -0
- package/memory-engine/tools/bridge-smoke.mjs +80 -0
- package/memory-engine/tools/fetch-model.mjs +128 -0
- package/memory-engine/tools/smoke-recall.mjs +76 -0
- package/package.json +53 -4
- package/persona-memory-audit.mjs +217 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// audit-hit-probe.mjs — 季度记忆库体检:机器特征词命中探针(给「漏标设备」判据兜底)
|
|
3
|
+
// 固化自早期临时脚本(2026-09-25 体检工具链固化)
|
|
4
|
+
// · 去硬编码:原来写死 15 个 id → 改为 --ids 传参;数据源 --in(默认系统临时目录)
|
|
5
|
+
// · 去硬编码:用户名/盘符一律泛匹配(跨机通用)。
|
|
6
|
+
// · 加 --help / --self-test / --json · 收尾 process.exitCode
|
|
7
|
+
// 用途:对给定 id 逐条列出「命中了哪些机器特征词」,用于自证 list-nodev 的漏标判据站得住。
|
|
8
|
+
// 只读:只读 memories.json,不写任何文件、不碰 memory.db。
|
|
9
|
+
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import os from "node:os";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
export const SELF = fileURLToPath(import.meta.url);
|
|
16
|
+
export const TOOL = path.basename(SELF);
|
|
17
|
+
|
|
18
|
+
export function resolveDshHome() {
|
|
19
|
+
if (process.env.DSH_HOME) return process.env.DSH_HOME;
|
|
20
|
+
return path.join(process.env.USERPROFILE || os.homedir(), ".dsh");
|
|
21
|
+
}
|
|
22
|
+
export const DSH_HOME = resolveDshHome();
|
|
23
|
+
export const DEFAULT_WORK_DIR = path.join(os.tmpdir(), "dsh-mem-audit");
|
|
24
|
+
export const DEFAULT_IN = path.join(DEFAULT_WORK_DIR, "memories.json");
|
|
25
|
+
|
|
26
|
+
export function isMainEntry() {
|
|
27
|
+
const a = process.argv[1];
|
|
28
|
+
if (!a) return false;
|
|
29
|
+
try {
|
|
30
|
+
return fs.realpathSync(a) === fs.realpathSync(SELF);
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseArgs(argv) {
|
|
37
|
+
const out = { _: [] };
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const a = argv[i];
|
|
40
|
+
if (a === "--") {
|
|
41
|
+
out._.push(...argv.slice(i + 1));
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (a.startsWith("--")) {
|
|
45
|
+
const eq = a.indexOf("=");
|
|
46
|
+
if (eq > 0) out[a.slice(2, eq)] = a.slice(eq + 1);
|
|
47
|
+
else {
|
|
48
|
+
const k = a.slice(2);
|
|
49
|
+
const next = argv[i + 1];
|
|
50
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
51
|
+
out[k] = next;
|
|
52
|
+
i++;
|
|
53
|
+
} else out[k] = true;
|
|
54
|
+
}
|
|
55
|
+
} else out._.push(a);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function makeAsserter() {
|
|
61
|
+
let pass = 0,
|
|
62
|
+
total = 0;
|
|
63
|
+
const fails = [];
|
|
64
|
+
return {
|
|
65
|
+
ok(cond, label) {
|
|
66
|
+
total++;
|
|
67
|
+
if (cond) pass++;
|
|
68
|
+
else fails.push(label);
|
|
69
|
+
},
|
|
70
|
+
eq(actual, expected, label) {
|
|
71
|
+
this.ok(actual === expected, `${label}(期望 ${JSON.stringify(expected)},实际 ${JSON.stringify(actual)})`);
|
|
72
|
+
},
|
|
73
|
+
report() {
|
|
74
|
+
if (fails.length) {
|
|
75
|
+
console.log(`[self-test] FAIL ${pass}/${total}`);
|
|
76
|
+
for (const f of fails) console.log(` - ${f}`);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
console.log(`[self-test] PASS ${pass}/${total}`);
|
|
80
|
+
return 0;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function parseIds(raw) {
|
|
86
|
+
const parts = Array.isArray(raw) ? raw : String(raw ?? "").split(/[,\s]+/);
|
|
87
|
+
return parts
|
|
88
|
+
.flatMap((s) => String(s).split(/[,\s]+/))
|
|
89
|
+
.map((s) => s.trim())
|
|
90
|
+
.filter(Boolean)
|
|
91
|
+
.map(Number)
|
|
92
|
+
.filter(Number.isInteger);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ---------- 机器特征词表(跨机通用,用户名泛匹配) ----------
|
|
96
|
+
export const MACHINE_PATTERNS = [
|
|
97
|
+
["盘符绝对路径", /[A-Za-z]:[\\/]/g],
|
|
98
|
+
["用户目录", /[\\/]Users[\\/][^\\/]+/g],
|
|
99
|
+
["临时/测试根", /(?:dsh[-]offsync|DSH-test|_tmp)/g],
|
|
100
|
+
["ComfyUI", /ComfyUI/g],
|
|
101
|
+
["3080/3090/3092", /30[89][0-9]/g],
|
|
102
|
+
[".dsh", /\.dsh/g],
|
|
103
|
+
["guard/守护鱼", /guard|守护鱼/g],
|
|
104
|
+
["dsh-", /dsh-/g],
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
// ---------- 核心逻辑 ----------
|
|
108
|
+
export function hitReport(mems, ids) {
|
|
109
|
+
const out = [];
|
|
110
|
+
for (const id of ids) {
|
|
111
|
+
const m = mems.find((x) => x.id === id);
|
|
112
|
+
if (!m) {
|
|
113
|
+
out.push({ id, missing: true, hits: [], head: "" });
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
const hits = MACHINE_PATTERNS.filter(([, re]) => {
|
|
117
|
+
re.lastIndex = 0;
|
|
118
|
+
return re.test(m.text);
|
|
119
|
+
}).map(([n]) => n);
|
|
120
|
+
out.push({ id, missing: false, hits, head: m.text.split("\n")[0].slice(0, 46) });
|
|
121
|
+
}
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const USAGE = `audit-hit-probe.mjs — 季度体检:给定 id 看命中了哪些机器特征词
|
|
126
|
+
|
|
127
|
+
用法:
|
|
128
|
+
node ${TOOL} --ids 40,46,67 [--in <memories.json>] [--json]
|
|
129
|
+
node ${TOOL} --help
|
|
130
|
+
node ${TOOL} --self-test
|
|
131
|
+
|
|
132
|
+
参数:
|
|
133
|
+
--ids <a,b,c> 要检查的记忆 id(逗号或空格分隔;必填)
|
|
134
|
+
--in <file> 记忆导出 JSON,默认 ${DEFAULT_IN}(由 audit-export.mjs 产出)
|
|
135
|
+
--json 以 JSON 输出
|
|
136
|
+
|
|
137
|
+
退出码:
|
|
138
|
+
0 成功
|
|
139
|
+
1 --self-test 断言失败
|
|
140
|
+
2 用法错误(未给 id)
|
|
141
|
+
3 输入文件不存在
|
|
142
|
+
4 运行错误(JSON 解析失败等)
|
|
143
|
+
|
|
144
|
+
只读:仅读 memories.json,不写任何文件、不触碰 memory.db。
|
|
145
|
+
`;
|
|
146
|
+
|
|
147
|
+
export function main(argv = process.argv.slice(2)) {
|
|
148
|
+
const a = parseArgs(argv);
|
|
149
|
+
if (a.help) {
|
|
150
|
+
console.log(USAGE);
|
|
151
|
+
return 0;
|
|
152
|
+
}
|
|
153
|
+
if (typeof a["self-test"] !== "undefined") return selfTest();
|
|
154
|
+
|
|
155
|
+
const ids = parseIds(a.ids ?? a._);
|
|
156
|
+
if (!ids.length) {
|
|
157
|
+
console.error(`[${TOOL}] 未提供 id。用法见 --help(示例:node ${TOOL} --ids 40,46)`);
|
|
158
|
+
return 2;
|
|
159
|
+
}
|
|
160
|
+
const inFile = typeof a.in === "string" ? path.resolve(a.in) : DEFAULT_IN;
|
|
161
|
+
if (!fs.existsSync(inFile)) {
|
|
162
|
+
console.error(`[${TOOL}] 输入不存在:${inFile}(先跑 audit-export.mjs,或用 --in 指定)`);
|
|
163
|
+
return 3;
|
|
164
|
+
}
|
|
165
|
+
const mems = JSON.parse(fs.readFileSync(inFile, "utf8"));
|
|
166
|
+
const report = hitReport(mems, ids);
|
|
167
|
+
if (a.json) {
|
|
168
|
+
console.log(JSON.stringify({ in: inFile, report }, null, 1));
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
for (const r of report) {
|
|
172
|
+
if (r.missing) console.log(`#${r.id} [未找到]`);
|
|
173
|
+
else console.log(`#${r.id} [${r.hits.join(", ")}] ${r.head}`);
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// ---------- self-test(无副作用:合成 JSON 落临时目录) ----------
|
|
179
|
+
export function selfTest() {
|
|
180
|
+
const t = makeAsserter();
|
|
181
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "audit-hit-selftest-"));
|
|
182
|
+
const fix = path.join(tmp, "memories.json");
|
|
183
|
+
try {
|
|
184
|
+
const DRV = path.parse(process.cwd()).root;
|
|
185
|
+
const mems = [
|
|
186
|
+
{ id: 1, text: "纯文本,不含任何机器特征", pinned: false },
|
|
187
|
+
{ id: 2, text: `在 ${DRV}work 下干活,用户 ${DRV}Users${path.sep}alice`, pinned: false },
|
|
188
|
+
{ id: 3, text: "提到 guard 守护鱼与 .dsh 目录和 3080 端口", pinned: false },
|
|
189
|
+
];
|
|
190
|
+
fs.writeFileSync(fix, JSON.stringify(mems), "utf8");
|
|
191
|
+
const before = fs.readFileSync(fix, "utf8");
|
|
192
|
+
const data = JSON.parse(fs.readFileSync(fix, "utf8"));
|
|
193
|
+
|
|
194
|
+
const r = hitReport(data, [1, 2, 3, 99]);
|
|
195
|
+
t.eq(r.length, 4, "返回条数(含未找到)");
|
|
196
|
+
t.eq(r[0].hits.length, 0, "#1 无命中");
|
|
197
|
+
t.ok(r[1].hits.includes("盘符绝对路径"), "#2 命中盘符绝对路径");
|
|
198
|
+
t.ok(r[1].hits.includes("用户目录"), "#2 命中用户目录(用户名泛匹配)");
|
|
199
|
+
t.ok(r[1].hits.includes("临时/测试根") === false, "#2 不含测试根词");
|
|
200
|
+
t.ok(r[2].hits.includes("guard/守护鱼"), "#3 命中 guard/守护鱼");
|
|
201
|
+
t.ok(r[2].hits.includes(".dsh"), "#3 命中 .dsh");
|
|
202
|
+
t.ok(r[2].hits.includes("3080/3090/3092"), "#3 命中端口号");
|
|
203
|
+
t.eq(r[3].missing, true, "#99 未找到标记");
|
|
204
|
+
t.eq(parseIds("1,2").length, 2, "parseIds 逗号分隔");
|
|
205
|
+
|
|
206
|
+
// 自证只读
|
|
207
|
+
t.eq(fs.readFileSync(fix, "utf8"), before, "输入 JSON 前后字节一致(未写输入)");
|
|
208
|
+
const src = fs.readFileSync(SELF, "utf8");
|
|
209
|
+
const body = src.split("// ---------- self-test")[0];
|
|
210
|
+
t.ok(!/fs\.writeFileSync/.test(body), "主体逻辑段不含 fs.writeFileSync");
|
|
211
|
+
t.ok(!/new DatabaseSync\(/.test(body), "主体逻辑段不开放任何数据库(纯 JSON 只读)");
|
|
212
|
+
} catch (e) {
|
|
213
|
+
t.ok(false, `self-test 抛错:${e?.message ?? e}`);
|
|
214
|
+
} finally {
|
|
215
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
return t.report();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (isMainEntry()) {
|
|
221
|
+
try {
|
|
222
|
+
process.exitCode = main();
|
|
223
|
+
} catch (e) {
|
|
224
|
+
console.error(`[${TOOL}] 运行错误:${e?.stack ?? e}`);
|
|
225
|
+
process.exitCode = 4;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// audit-ledger-probe.mjs — 季度记忆库体检:看板账本只读探针
|
|
3
|
+
// 固化自早期临时脚本(2026-09-25 体检工具链固化)
|
|
4
|
+
// · 去硬编码:账本路径从 DSH_HOME 动态解析 + `--ledger` 传参。
|
|
5
|
+
// · 去硬编码:关键词 /卡会话|CW-[1-7]/ → --keyword <正则> 传参(默认不筛,只出状态统计)
|
|
6
|
+
// · 加 --help / --self-test / --json · 收尾 process.exitCode
|
|
7
|
+
// 用途:体检时顺带看一眼看板账本 —— 按关键词筛相关卡(如「体检」「记忆」),并出状态分布。
|
|
8
|
+
// 只读:只读账本 JSON 文件,不写任何文件、不走 Host API(要改卡请走 task-board 技能)。
|
|
9
|
+
// ⚠️ 看板账本是权威账本,本工具**只读**;任何建卡/移卡/删卡一律走 task-board 技能与 Host API。
|
|
10
|
+
|
|
11
|
+
import fs from "node:fs";
|
|
12
|
+
import os from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { fileURLToPath } from "node:url";
|
|
15
|
+
|
|
16
|
+
export const SELF = fileURLToPath(import.meta.url);
|
|
17
|
+
export const TOOL = path.basename(SELF);
|
|
18
|
+
|
|
19
|
+
export function resolveDshHome() {
|
|
20
|
+
if (process.env.DSH_HOME) return process.env.DSH_HOME;
|
|
21
|
+
return path.join(process.env.USERPROFILE || os.homedir(), ".dsh");
|
|
22
|
+
}
|
|
23
|
+
export const DSH_HOME = resolveDshHome();
|
|
24
|
+
export const DEFAULT_LEDGER = path.join(DSH_HOME, "task-board", "ledger-v2.json");
|
|
25
|
+
|
|
26
|
+
export function isMainEntry() {
|
|
27
|
+
const a = process.argv[1];
|
|
28
|
+
if (!a) return false;
|
|
29
|
+
try {
|
|
30
|
+
return fs.realpathSync(a) === fs.realpathSync(SELF);
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function parseArgs(argv) {
|
|
37
|
+
const out = { _: [] };
|
|
38
|
+
for (let i = 0; i < argv.length; i++) {
|
|
39
|
+
const a = argv[i];
|
|
40
|
+
if (a === "--") {
|
|
41
|
+
out._.push(...argv.slice(i + 1));
|
|
42
|
+
break;
|
|
43
|
+
}
|
|
44
|
+
if (a.startsWith("--")) {
|
|
45
|
+
const eq = a.indexOf("=");
|
|
46
|
+
if (eq > 0) out[a.slice(2, eq)] = a.slice(eq + 1);
|
|
47
|
+
else {
|
|
48
|
+
const k = a.slice(2);
|
|
49
|
+
const next = argv[i + 1];
|
|
50
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
51
|
+
out[k] = next;
|
|
52
|
+
i++;
|
|
53
|
+
} else out[k] = true;
|
|
54
|
+
}
|
|
55
|
+
} else out._.push(a);
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function makeAsserter() {
|
|
61
|
+
let pass = 0,
|
|
62
|
+
total = 0;
|
|
63
|
+
const fails = [];
|
|
64
|
+
return {
|
|
65
|
+
ok(cond, label) {
|
|
66
|
+
total++;
|
|
67
|
+
if (cond) pass++;
|
|
68
|
+
else fails.push(label);
|
|
69
|
+
},
|
|
70
|
+
eq(actual, expected, label) {
|
|
71
|
+
this.ok(actual === expected, `${label}(期望 ${JSON.stringify(expected)},实际 ${JSON.stringify(actual)})`);
|
|
72
|
+
},
|
|
73
|
+
report() {
|
|
74
|
+
if (fails.length) {
|
|
75
|
+
console.log(`[self-test] FAIL ${pass}/${total}`);
|
|
76
|
+
for (const f of fails) console.log(` - ${f}`);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
console.log(`[self-test] PASS ${pass}/${total}`);
|
|
80
|
+
return 0;
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ---------- 核心逻辑 ----------
|
|
86
|
+
export function probeLedger(ledger, keyword, limit = 50) {
|
|
87
|
+
const cards = ledger.cards ?? ledger.tasks ?? [];
|
|
88
|
+
const re = keyword ? new RegExp(keyword) : null;
|
|
89
|
+
const hits = cards.filter((c) => (re ? re.test(`${c.title ?? ""} ${c.description ?? ""} ${c.prompt ?? ""}`) : false));
|
|
90
|
+
const statusCounts = {};
|
|
91
|
+
for (const c of cards) statusCounts[c.status] = (statusCounts[c.status] ?? 0) + 1;
|
|
92
|
+
return {
|
|
93
|
+
total: cards.length,
|
|
94
|
+
revision: ledger.revision ?? null,
|
|
95
|
+
keyword: keyword ?? null,
|
|
96
|
+
hits: hits.slice(0, limit).map((c) => ({
|
|
97
|
+
status: c.status ?? null,
|
|
98
|
+
line: c.line ?? null,
|
|
99
|
+
seq: c.seq ?? null,
|
|
100
|
+
id: c.id ? String(c.id).slice(0, 8) : null,
|
|
101
|
+
exec: (c.executions ?? []).length,
|
|
102
|
+
updatedAt: c.updatedAt ?? null,
|
|
103
|
+
title: c.title ?? "",
|
|
104
|
+
})),
|
|
105
|
+
hitCount: hits.length,
|
|
106
|
+
statusCounts,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const USAGE = `audit-ledger-probe.mjs — 季度体检:看板账本只读探针(关键词筛卡 + 状态分布)
|
|
111
|
+
|
|
112
|
+
用法:
|
|
113
|
+
node ${TOOL} [--ledger <path>] [--keyword <正则>] [--limit <n>] [--json]
|
|
114
|
+
node ${TOOL} --help
|
|
115
|
+
node ${TOOL} --self-test
|
|
116
|
+
|
|
117
|
+
参数:
|
|
118
|
+
--ledger <path> 账本 JSON,默认 ${DEFAULT_LEDGER}
|
|
119
|
+
--keyword <正则> 在 title/description/prompt 上筛选(如 --keyword "体检|记忆");省略则只出状态统计
|
|
120
|
+
--limit <n> 最多列出多少张命中卡,默认 50
|
|
121
|
+
--json 以 JSON 输出
|
|
122
|
+
|
|
123
|
+
退出码:
|
|
124
|
+
0 成功
|
|
125
|
+
1 --self-test 断言失败
|
|
126
|
+
2 用法错误(正则非法)
|
|
127
|
+
3 账本文件不存在
|
|
128
|
+
4 运行错误(JSON 解析失败等)
|
|
129
|
+
|
|
130
|
+
只读:只读账本文件;不改卡、不写文件、不走 Host API(改卡请用 task-board 技能)。
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
export function main(argv = process.argv.slice(2)) {
|
|
134
|
+
const a = parseArgs(argv);
|
|
135
|
+
if (a.help) {
|
|
136
|
+
console.log(USAGE);
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
if (typeof a["self-test"] !== "undefined") return selfTest();
|
|
140
|
+
|
|
141
|
+
const ledgerPath = typeof a.ledger === "string" ? path.resolve(a.ledger) : DEFAULT_LEDGER;
|
|
142
|
+
if (!fs.existsSync(ledgerPath)) {
|
|
143
|
+
console.error(`[${TOOL}] 账本不存在:${ledgerPath}(可用 --ledger 指定)`);
|
|
144
|
+
return 3;
|
|
145
|
+
}
|
|
146
|
+
const keyword = typeof a.keyword === "string" ? a.keyword : null;
|
|
147
|
+
if (keyword) {
|
|
148
|
+
try {
|
|
149
|
+
new RegExp(keyword);
|
|
150
|
+
} catch (e) {
|
|
151
|
+
console.error(`[${TOOL}] --keyword 不是合法正则:${e?.message ?? e}`);
|
|
152
|
+
return 2;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const limit = typeof a.limit === "string" ? Number(a.limit) : 50;
|
|
156
|
+
const ledger = JSON.parse(fs.readFileSync(ledgerPath, "utf8"));
|
|
157
|
+
const r = probeLedger(ledger, keyword, Number.isFinite(limit) && limit > 0 ? limit : 50);
|
|
158
|
+
|
|
159
|
+
if (a.json) {
|
|
160
|
+
console.log(JSON.stringify({ ledger: ledgerPath, ...r }, null, 1));
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
console.log(`LEDGER=${ledgerPath}`);
|
|
164
|
+
console.log("cards total:", r.total, "| revision:", r.revision ?? "?");
|
|
165
|
+
if (keyword) {
|
|
166
|
+
console.log(`--- 命中 --keyword "${keyword}":${r.hitCount} 张(列出前 ${r.hits.length})---`);
|
|
167
|
+
for (const c of r.hits) {
|
|
168
|
+
console.log(
|
|
169
|
+
`[${c.status}] line=${c.line ?? "-"} seq=${c.seq ?? "-"} id=${c.id ?? "-"} exec=${c.exec} updated=${c.updatedAt ?? "-"} :: ${c.title}`,
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
console.log("--- 未给 --keyword,只出状态统计(加 --keyword 可筛卡)---");
|
|
174
|
+
}
|
|
175
|
+
console.log("--- 状态统计 ---");
|
|
176
|
+
console.log(JSON.stringify(r.statusCounts));
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ---------- self-test(无副作用:合成账本落临时目录) ----------
|
|
181
|
+
export function selfTest() {
|
|
182
|
+
const t = makeAsserter();
|
|
183
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "audit-ledger-selftest-"));
|
|
184
|
+
const fix = path.join(tmp, "ledger-v2.json");
|
|
185
|
+
try {
|
|
186
|
+
const ledger = {
|
|
187
|
+
revision: 7,
|
|
188
|
+
cards: [
|
|
189
|
+
{ id: "aaaaaaaa-1111", title: "记忆库体检预约卡", description: "跑体检工具链", status: "todo", seq: 1, executions: [] },
|
|
190
|
+
{ id: "bbbbbbbb-2222", title: "无关的卡", description: "别的活", status: "done", seq: 2, executions: [{ sessionId: "s1" }] },
|
|
191
|
+
{ id: "cccccccc-3333", title: "CW-3 卡会话工作区", description: "卡会话独立目录", status: "review", seq: 3, executions: [] },
|
|
192
|
+
],
|
|
193
|
+
};
|
|
194
|
+
fs.writeFileSync(fix, JSON.stringify(ledger), "utf8");
|
|
195
|
+
const before = fs.readFileSync(fix, "utf8");
|
|
196
|
+
const data = JSON.parse(fs.readFileSync(fix, "utf8"));
|
|
197
|
+
|
|
198
|
+
const all = probeLedger(data, null);
|
|
199
|
+
t.eq(all.total, 3, "卡总数");
|
|
200
|
+
t.eq(all.revision, 7, "revision 透出");
|
|
201
|
+
t.eq(all.hitCount, 0, "无关键词时不筛卡");
|
|
202
|
+
t.eq(all.statusCounts.todo, 1, "状态统计 todo");
|
|
203
|
+
t.eq(all.statusCounts.done, 1, "状态统计 done");
|
|
204
|
+
t.eq(all.statusCounts.review, 1, "状态统计 review");
|
|
205
|
+
|
|
206
|
+
const hit = probeLedger(data, "体检|CW-[1-7]");
|
|
207
|
+
t.eq(hit.hitCount, 2, "关键词命中数");
|
|
208
|
+
t.eq(hit.hits[0].id, "aaaaaaaa", "id 截断为 8 位");
|
|
209
|
+
t.eq(hit.hits[1].exec, 0, "executions 计数");
|
|
210
|
+
const capped = probeLedger(data, ".", 1);
|
|
211
|
+
t.eq(capped.hitCount, 3, "hitCount 不受 limit 截断");
|
|
212
|
+
t.eq(capped.hits.length, 1, "limit 截断列表长度");
|
|
213
|
+
|
|
214
|
+
// 自证只读
|
|
215
|
+
t.eq(fs.readFileSync(fix, "utf8"), before, "账本文件前后字节一致(未写账本)");
|
|
216
|
+
const src = fs.readFileSync(SELF, "utf8");
|
|
217
|
+
const body = src.split("// ---------- self-test")[0];
|
|
218
|
+
t.ok(!/fs\.writeFileSync/.test(body), "主体逻辑段不含 fs.writeFileSync");
|
|
219
|
+
t.ok(!/new DatabaseSync\(/.test(body), "主体逻辑段不开放任何数据库(纯 JSON 只读)");
|
|
220
|
+
t.ok(!/fetch\(|http:\/\/|https:\/\//.test(body), "主体逻辑段不含网络调用(不触 Host API)");
|
|
221
|
+
} catch (e) {
|
|
222
|
+
t.ok(false, `self-test 抛错:${e?.message ?? e}`);
|
|
223
|
+
} finally {
|
|
224
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
225
|
+
}
|
|
226
|
+
return t.report();
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (isMainEntry()) {
|
|
230
|
+
try {
|
|
231
|
+
process.exitCode = main();
|
|
232
|
+
} catch (e) {
|
|
233
|
+
console.error(`[${TOOL}] 运行错误:${e?.stack ?? e}`);
|
|
234
|
+
process.exitCode = 4;
|
|
235
|
+
}
|
|
236
|
+
}
|