Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-3E63DD?style=flat-square" alt="license"></a>
<a href="https://github.com/slow-stack/mneme/actions"><img src="https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml?style=flat-square&label=CI" alt="CI"></a>
<a href="https://nodejs.org"><img src="https://img.shields.io/badge/node-22%2B-3E63DD?style=flat-square&logo=nodedotjs&logoColor=white" alt="node"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1437%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://github.com/slow-stack/mneme"><img src="https://img.shields.io/badge/tests-1441%20passed-3E63DD?style=flat-square" alt="tests"></a>
<a href="https://codecov.io/gh/slow-stack/mneme"><img src="https://img.shields.io/codecov/c/github/slow-stack/mneme/main?style=flat-square" alt="coverage"></a>
<a href="https://github.com/awesome-dsh-plugin/awesome-dsh-plugin"><img src="https://awesome-dsh-plugin.com/badge.svg" alt="Awesome"></a>
</p>
Expand Down Expand Up @@ -183,7 +183,7 @@ dsh web

```bash
cd dsh-mneme && npm install
npm test # 1431 个测试
npm test # 1441 个测试
npm run stress # 三轴线压测
npm run sync # src → lib 同步
```
Expand Down Expand Up @@ -366,7 +366,7 @@ The plugin ships a zero-dependency stdio MCP server (standalone npm package **`m

```bash
cd dsh-mneme && npm install
npm test # 1431 tests
npm test # 1441 tests
npm run stress # three-axis stress test
npm run sync # src → lib sync
```
Expand Down
6 changes: 3 additions & 3 deletions dsh-mneme/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
[![npm version](https://img.shields.io/npm/v/@modusensus/dsh-mneme?color=blue&label=npm)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
[![license](https://img.shields.io/badge/license-MIT-green)](LICENSE)
[![Awesome](https://awesome-dsh-plugin.com/badge.svg)](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
[![tests](https://img.shields.io/badge/tests-1437%20passed-success)](https://github.com/slow-stack/mneme)
[![tests](https://img.shields.io/badge/tests-1441%20passed-success)](https://github.com/slow-stack/mneme)
[![CI](https://img.shields.io/github/actions/workflow/status/slow-stack/mneme/ci.yml)](https://github.com/slow-stack/mneme/actions)
[![node](https://img.shields.io/badge/node-22%2B-blue)](https://nodejs.org)
[![npm downloads](https://img.shields.io/npm/d18m/@modusensus/dsh-mneme.svg?color=blue&label=downloads)](https://www.npmjs.com/package/@modusensus/dsh-mneme)
Expand Down Expand Up @@ -584,7 +584,7 @@ src/
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
└── index.js # 插件接线
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
test/ # 1431 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
test/ # 1441 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js / benchmark-embed.js / benchmark-rerank.js 基准 · sync-test-badge.mjs 测试徽章 · build-runtime-manifest.mjs 运行时清单
```

Expand All @@ -593,7 +593,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
```bash
cd dsh-mneme
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
npm test # 运行 1431 个测试
npm test # 运行 1441 个测试
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
```
Expand Down
16 changes: 9 additions & 7 deletions dsh-mneme/scripts/sync-test-badge.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
// 1. 跑一遍全量测试(npm test),取套件总数(`ℹ tests N` 汇总行);
// 2. 把双 README 的 tests 徽章(img.shields.io/badge/tests-N%20passed)与
// 开发命令注释(# N 个测试 / # 运行 N 个测试 / # N 个 node:test 测试 /
// # N tests)刷成该数字;
// # N tests)刷成该数字——形状集合收在 scripts/test-count-sync.mjs,与发版
// 自动路径 scripts/release-prep.mjs 共用(两条路径各存一份曾导致 v0.8.11
// 徽章与注释不同数);
// 3. 有变化就写回文件(由调用方决定是否 commit/push),无变化静默退出。
//
// 测试失败(fail>0 或进程非零退出)时以非零退出且不改任何文件——徽章永远
Expand All @@ -16,6 +18,7 @@ import { execSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { applyTestCount } from "./test-count-sync.mjs";

const scriptDir = dirname(fileURLToPath(import.meta.url));
const repoRoot = join(scriptDir, "..", "..");
Expand Down Expand Up @@ -46,18 +49,17 @@ if (!count) {
process.exit(1);
}

// 3) 刷双 README(徽章 + 两行开发命令注释)
// 3) 刷双 README(徽章 + 四种开发命令注释形状)
// 形状集合与 release-prep.mjs(发版自动路径)共用,收在 scripts/test-count-sync.mjs —
// 两条路径各自维护一份规则正是 v0.8.11 注释漂移的根因。
const targets = [
join(repoRoot, "README.md"),
join(repoRoot, "dsh-mneme", "README.md")
];
let changed = 0;
for (const file of targets) {
let text = readFileSync(file, "utf8");
const next = text
.replace(/tests-\d+%20passed-/g, `tests-${count}%20passed-`)
.replace(/(#\s*(?:运行\s*)?)\d+( 个(?:\s*node:test)?\s*测试)/g, `$1${count}$2`)
.replace(/(#\s*)\d+( tests)/g, `$1${count}$2`);
const text = readFileSync(file, "utf8");
const next = applyTestCount(text, count);
if (next !== text) {
writeFileSync(file, next);
changed += 1;
Expand Down
44 changes: 44 additions & 0 deletions dsh-mneme/scripts/test-count-sync.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// 测试数在双 README 里以多种形状重复出现:shields.io 徽章 URL,以及四种 `# N …测试`
// 注释(`# N 个测试` / `# 运行 N 个测试` / `# N 个 node:test 测试` / `# N tests`)。
// 所有出现处必须永远报同一个数。
//
// 这些规则原先在两条写入路径里各存一份,且形状集合不同:
// - scripts/release-prep.mjs(Release Prep 工作流,自动,每次发版跑)
// - dsh-mneme/scripts/sync-test-badge.mjs(npm run badge:sync,手动)
// release-prep 当时只 replace 徽章 URL,于是 v0.8.11 发版时徽章自动跳到 1437,
// 四条注释留在 1431 —— 自动路径反而成了漂移的来源。规则收在这里共用,
// 由 test/test-count-sync.test.js 锁住形状集合与「双 README 同数」这条不变量。
//
// 徽章正则刻意不带尾随 `-`:`tests-N%20passed` 既能命中 `…passed-3E63DD`(root)
// 也能命中 `…passed-success`(包 README),将来出现无后缀写法也不会静默漏改。

const BADGE = /tests-\d+%20passed/g;
const COMMENT_CJK = /(#\s*(?:运行\s*)?)\d+( 个(?:\s*node:test)?\s*测试)/g;
const COMMENT_EN = /(#\s*)\d+( tests)/g;

/** 把一段文本里所有测试数出现处刷成 count(含徽章与四种注释形状)。 */
export function applyTestCount(text, count) {
return text
.replace(BADGE, `tests-${count}%20passed`)
.replace(COMMENT_CJK, `$1${count}$2`)
.replace(COMMENT_EN, `$1${count}$2`);
}

/**
* 只读地抽出文本里所有测试数字面量及其形状,供「处处一致」断言使用。
* 历史版本表里的「745 测试全绿」不带 `#` 前缀,刻意不在两种口径内——那是各版本
* 当时的真实记录,不该被后续发版改写。
*/
export function readTestCounts(text) {
const found = [];
for (const m of text.matchAll(/tests-(\d+)%20passed/g)) {
found.push({ shape: "badge", value: Number(m[1]) });
}
for (const m of text.matchAll(/#\s*(?:运行\s*)?(\d+)\s*个(?:\s*node:test)?\s*测试/g)) {
found.push({ shape: "comment-cjk", value: Number(m[1]) });
}
for (const m of text.matchAll(/#\s*(\d+)\s+tests/g)) {
found.push({ shape: "comment-en", value: Number(m[1]) });
}
return found;
}
60 changes: 60 additions & 0 deletions dsh-mneme/test/test-count-sync.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { applyTestCount, readTestCounts } from "../scripts/test-count-sync.mjs";

const HERE = dirname(fileURLToPath(import.meta.url));
const PKG_ROOT = join(HERE, "..");
const REPO_ROOT = join(PKG_ROOT, "..");
const READMES = [join(REPO_ROOT, "README.md"), join(PKG_ROOT, "README.md")];

// 双 README 是发布门面,同一个数字在里面重复出现三到五次(一个 shields.io 徽章 +
// 每条开发命令注释一处,root 是双语所以中英各一条)。它们必须永远同数——否则
// 「这个项目有多少测试」取决于读者恰好看了哪一行。
//
// v0.8.11 实际漂移过:徽章 1437、四条注释留在 1431。本用例就是那次漂移的回归锁,
// 跑在真实 README 上(不是 fixture),所以它能在发版前拦住同类漂移。
test("双 README 的测试数处处一致(徽章与所有注释同数)", () => {
for (const f of READMES) {
const counts = readTestCounts(readFileSync(f, "utf8"));
const shapes = new Set(counts.map((c) => c.shape));
assert.ok(counts.length >= 2, `${f} 抽到的测试数出现处太少:${JSON.stringify(counts)}`);
assert.ok(shapes.has("badge"), `${f} 没抽到徽章:${JSON.stringify(counts)}`);
const values = [...new Set(counts.map((c) => c.value))];
assert.equal(values.length, 1, `${f} 测试数不一致:${JSON.stringify(counts)}`);
}
});

// v0.8.11 的真实漂移样本(徽章已 1437、注释仍 1431)。锁住「徽章 + 四种注释形状」
// 全覆盖:少任何一种,就会有一处静默落后——release-prep.mjs 原先只覆盖徽章 URL,
// 自动发版路径因此反倒成了漂移来源。
test("applyTestCount 覆盖徽章与全部四种注释形状(真实漂移样本)", () => {
const drifted = [
'<img src="https://img.shields.io/badge/tests-1431%20passed-3E63DD?style=flat-square" alt="tests">',
"[![tests](https://img.shields.io/badge/tests-1431%20passed-success)](https://github.com/slow-stack/mneme)",
"npm test # 1431 个测试",
"npm test # 运行 1431 个测试",
"test/ # 1431 个 node:test 测试(审计与三轴线压测不变量)",
"npm test # 1431 tests"
].join("\n");
const fixed = applyTestCount(drifted, 1437);
assert.ok(!/\b1431\b/.test(fixed), "仍有 1431 未被替换:\n" + fixed);
const counts = readTestCounts(fixed);
assert.equal(counts.length, 6, "六处出现都该被认出来:" + JSON.stringify(counts));
assert.deepEqual([...new Set(counts.map((c) => c.value))], [1437]);
});

test("applyTestCount 幂等:同一个数重复施加不再产生变化", () => {
const once = applyTestCount("tests-9%20passed-3E63DD\n# 9 个测试\n# 9 tests", 12);
assert.equal(applyTestCount(once, 12), once);
});

// 历史版本表里的「745 测试全绿」「790 通过」是各版本当时的真实记录(sync-test-badge
// 的注释也写明了刻意不改它们)。替换规则只认徽章 URL 与 `#` 前缀注释两种形状,
// 这条用例锁住那个边界,免得将来放宽正则把历史改写掉。
test("applyTestCount 不碰历史版本表里的「N 测试全绿」", () => {
const history = "| **v0.8.0** | 745 测试全绿 |\n| **v0.7.9** | 790 通过 |\n| v0.2.7 | 263 |";
assert.equal(applyTestCount(history, 1437), history);
});
10 changes: 7 additions & 3 deletions scripts/release-prep.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"use strict";

import fs from "node:fs";
import { applyTestCount } from "../dsh-mneme/scripts/test-count-sync.mjs";

const version = (process.argv[2] || "").replace(/^v/, "");
const testCount = process.argv[3];
Expand All @@ -31,14 +32,17 @@ for (const f of [PKG, LOCK]) {
console.log(`✓ ${f} → ${version}`);
}

// ── 2. 测试徽章同步(两个 README)──
// ── 2. 测试数同步(两个 README:徽章 + 4 种 `# N …测试` 注释形状)──
// 规则收在 dsh-mneme/scripts/test-count-sync.mjs,与手动路径 sync-test-badge.mjs
// 共用。原先两条路径各存一份、形状集合还不同——本脚本只 replace 徽章 URL,于是
// v0.8.11 发版时徽章跳到 1437 而四条注释留在 1431,自动路径反倒成了漂移来源。
if (testCount && /^\d+$/.test(testCount)) {
for (const f of READMES) {
const t = fs.readFileSync(f, "utf8");
const n = t.replace(/tests-\d+%20passed/g, `tests-${testCount}%20passed`);
const n = applyTestCount(t, testCount);
if (n !== t) {
fs.writeFileSync(f, n);
console.log(`✓ ${f} 测试徽章 → ${testCount}`);
console.log(`✓ ${f} 测试数 → ${testCount}`);
}
}
}
Expand Down
Loading