memory-eternal 0.10.22 → 0.10.24
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +5 -3
- package/README.md +5 -3
- package/bin/dsh-memory.mjs +42 -0
- package/lib/api.js +45 -0
- package/lib/client.js +15 -15
- package/lib/refine.js +177 -1
- package/package.json +1 -1
- package/scripts/release.mjs +21 -1
- package/web/app.js +18 -18
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://anthropic.com/claude-code/plugin.schema.json",
|
|
3
3
|
"name": "memory-eternal",
|
|
4
|
-
"version": "0.10.
|
|
4
|
+
"version": "0.10.24",
|
|
5
5
|
"description": "记忆核心(Memory Eternal):对话结束后自动沉淀知识卡到本地记忆库(~/.dsh/memory-vault),支持检索 / 召回 / 审核中心。",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "EternalNight996",
|
package/README.en.md
CHANGED
|
@@ -361,14 +361,16 @@ curl http://127.0.0.1:7999/memory-eternal/api/web-info # is the standalone we
|
|
|
361
361
|
|
|
362
362
|
| Version | Date | Highlights |
|
|
363
363
|
|---|---|---|
|
|
364
|
-
| **v0.10.
|
|
365
|
-
| **v0.10.
|
|
364
|
+
| **v0.10.24** | 2026-10-13 | **"Same goal, different approach" now clusters too (third and final slice of issue #36)**. The two previous versions covered "merge duplicates" and "judge conflicts", but the last piece — one purpose, several workflows/codebases — is **undetectable by body similarity** ("compress with 7z", "distribute as tar.gz", "slim the npm package" read very differently yet share a goal). Approach: **the model first writes one line per card stating what problem it solves, then we cluster by goal** — goals are cached in `refine-goals.json` (each card extracted once; a second scan makes zero calls) and clustered with union-find; hits are grouped as "one goal, N cards" with two actions: **keep all** (records pairwise rulings so it stops asking) or **merge this group** (via the existing /merge; the new card enters the audit queue). *Read-only throughout — the only write is the goal cache.* Entry points: the panel (organize → 🎯 same goal, different approach) and APIs `/refine-goals`, `/refine-goals-keep`; CLI `dsh-memory refine --goals --keep --path A --path B` (the clustering itself needs a model, run it in the panel). `npm test` now runs **332 checks** |
|
|
365
|
+
| **v0.10.23** | 2026-10-13 | **Existing "injected junk cards" can be cleaned up in one click (second slice of issue #36)**. The previous version stopped such cards from being **created** (skill catalogs / `<system-reminder>` arriving as user messages were captured as knowledge after their tags were stripped), but the **backlog remained**: a scan of a real vault immediately found **32** cards unrelated to any conversation (catalog lines like `- \`code-review\`: Review the changes…`, fragments like `用户system-reminder-you-are-teammate-c…`). Adds `findInjectedCards()` (a **proposal only**, it changes nothing; criteria: injection tags present / catalog lines ≥30% of the body / under 200 characters left after stripping / a fragmentary title) and `purgeInjected()` (**goes to the recycle bin, recoverable**, reporting failures per card). Entry points: the panel (organize → 🧹 injected junk cards: scan, recycle one, recycle all) and the CLI `dsh-memory refine --junk [--purge --path P] [--json]`; API `/refine-junk`, `/refine-junk-purge`. `npm test` now runs **331 checks** |
|
|
366
366
|
|
|
367
367
|
<details>
|
|
368
|
-
<summary>Older release notes (v0.10.
|
|
368
|
+
<summary>Older release notes (v0.10.22 and earlier · 49 releases, click to expand)</summary>
|
|
369
369
|
|
|
370
370
|
| Version | Date | Highlights |
|
|
371
371
|
|---|---|---|
|
|
372
|
+
| **v0.10.22** | 2026-10-13 | **"Conflict scan": similar cards may be merged, conflicts must be judged by a human (first slice of issue #36)**. Until now `/optimize` merged **blindly** on similarity (≥0.55) — yet two semantically close cards with mutually exclusive conclusions score just as high (0.93 measured), so a blind merge quietly "reconciles" the conflict, and `mergeCards` **permanently deletes** the sources. New `lib/refine.js` plus a "organize → 🧭 Conflict scan" panel section: ① **wide recall** (≥0.30, upper bound lifted to 1.0, deliberately including the near-duplicate band — the most dangerous one for blind merging); ② the model only classifies each pair as "duplicate / complement / conflict / unrelated" and states the conflict in one line plus who to keep; ③ **the human decides**: keep A / keep B / keep both / merge; the **scan never writes**, and `keepA|keepB` only moves the yielding card to the **recycle bin** (recoverable); ④ rulings are recorded in `refine-decisions.json` so the same pair is not raised again; ⑤ **safety interlock**: pairs ruled conflicting are **excluded from one-click optimize** entirely. Also fixes one source of redundancy: skill-catalog injections (`- \`skill-name\`: …`) are no longer captured as junk cards (real examples already existed in the vault). API: `/refine`, `/refine-scan`, `/refine-apply`; CLI: `dsh-memory refine` (lists candidates without calling a model; `--apply` records a ruling). `npm test` now runs **329 checks** |
|
|
373
|
+
| **v0.10.21** | 2026-10-13 | **"DB unavailable" goes from silent to visible (issue #33, scoped by review)**. ① **Health check at open**: the old code ran `PRAGMA journal_mode=WAL` and assumed success (that pragma does not throw on failure — it just returns the actual mode), so a read-only file or unwritable directory let the plugin run on silently; now the real journal mode is **read back** and a **write probe** runs (create then drop a table, in the main DB rather than temp), the verdict lands in `dbStatus()`, and an unwritable DB immediately logs the reason plus where to look. ② **`/overview` degrades instead of 500**: when the DB or directory is unavailable it no longer returns 500 (probes, the panel and scripts read 500 as "this port is broken / not our service", which historically started the restart storm and port drift); it now returns 200 with `degraded:true`, `dbError` and `db:{journalMode,writable,error}` while keeping the `vaultDir` / `version` identity fields — so "this is us, but the DB is broken" can finally be expressed separately. ③ **New `dsh-memory doctor`**: DB health self-check (real journal mode / write probe / backup list / recovery guidance) with `--json` for scripting; when the DB is unwritable or suspected corrupt it prints **guidance that never touches data** (stop writers → copy the scene aside → list backups → swap → re-verify). **Deliberately not done** (review conclusion, now guarded by tests so nobody adds it back casually): `busy_timeout` (`DatabaseSync` is a synchronous API, so waiting on a lock blocks the host event loop) and automatic WAL→DELETE downgrade (it **permanently** changes the DB mode, and a rollback journal has worse concurrency, which could worsen lock contention). `npm test` now runs **322 checks** |
|
|
372
374
|
| **v0.10.20** | 2026-10-12 | **Fixing the three-layer "DB unavailable → the watchdog kills its own web → the port drifts to 8006" scene (issues #31 / #33, measured by AkiMei0721)**. ① **A non-200 `/overview` is not a dead port**: `/overview` touches the database, so a read-only / WAL-corrupted DB makes it 500, and the watchdog declared the port offline → `spawnWeb()` (which first SIGTERMs its own previous child) → **it killed and respawned its own healthy web, drifting the port from 7999 all the way to 8006 (six instances)**; a new pure `decideTickAction()` treats **a reachable TCP port as alive** (`api-degraded-but-listening`, resetting the restart counter), and the readiness check for a new instance likewise accepts "listening but `/overview` non-200". ② **The version probe now asks `/version-check`** (no database access; falls back to `/overview`) — the root symptom was "the port is clearly alive yet no version comes back", so verify reported "(no response)" and `status` even misjudged it as "a pre-v0.10.4 build". ③ **No more crash in restricted sandboxes**: the default `stdio:'pipe'` makes `spawn()` **throw EPERM synchronously** (the old code only guarded the `'error'` event, which never ran), so `dsh-memory watchdog --reap` crashed and process enumeration stayed permanently empty; `spawn` is now wrapped in `try/catch` and stdout goes through a **temp file**, touching no named pipes. ④ The stall check also gained a "minimum uptime" (30 minutes): a freshly restarted process necessarily has zero stops, so it must not report "turn-stop events are broken" (a false alarm was measured 1.5 minutes after a restart). Adds `tests/issue-fixes-8.test.mjs` (4 checks) plus a too-early case in `tests/stall.test.mjs`. `npm test` now runs **318 checks** |
|
|
373
375
|
| **v0.10.19** | 2026-10-12 | **`UNSUPPORTED_REASONING_EFFORT` no longer kills the whole distillation (issue #34)**. The scene (zafir-zhong and others): many **gateways** claim reasoning-effort support in their capability list and only reject `reasoningEffort: 'off'` when it is actually sent (measured: workbuddy/hy3, FallbacksChain/Auto, new-api-resp/gpt-6-luna), so the entire candidate is marked failed and the pipeline moves to the next provider — auto-capture stops dead for those users (that FAIL shows up in the diagnostics of #37 / #38 / #40 too). The existing design only covered "the provider **says** it does not support it → do not send the field" (#15); the runtime half was missing. Fix: an in-process memo `markEffortUnsupported()\/isEffortUnsupported()` — on the first rejection the call **retries once without the field** (same candidate, same provider), remembers that `provider/model`, and from then on the first request for it carries no effort at all; the memo is **keyed by provider/model**, so other providers are untouched. Guards added in `tests/capture-route.test.mjs` (sent → rejected → retried without and got a card → later only one request → other providers unaffected). `npm test` now runs **313 checks** |
|
|
374
376
|
| **v0.10.18** | 2026-10-12 | **"Save config" was being rejected unconditionally (issue #35)**. The scene (Dzifxazy): a fresh install, change nothing, hit save → **HTTP 400 "field must not be empty: secretHint"**, the whole submission rejected, so none of the numbers or toggles the user did change were stored; the wording also pointed troubleshooting at "some numeric box left blank". Root cause: the host-side pre-check treated **any empty string** as "numeric field not filled" and only exempted a hard-coded list (vaultDir / captureProvider / captureModel / recallEmbedding / activeVault) — while `secretHint`'s default **is** an empty string (`z.string().default('')`) and was not on that list; since the panel submits the whole form, every save hit it. Fix: new `emptyNumericFields(patch, Config.dict)` decides which keys were cleared by looking at the schema (`type === 'number'`), and the hard-coded list is gone (future nullable string fields need nobody to remember an exemption). Adds `tests/config-validate.test.mjs` (2 checks: empty strings only count for number fields — including a brand-new string field that appears on no list as a regression nail, plus a source guard against reintroducing the list). `npm test` now runs **312 checks** |
|
package/README.md
CHANGED
|
@@ -393,14 +393,16 @@ curl http://127.0.0.1:7999/memory-eternal/api/web-info # 独立 web 是否活
|
|
|
393
393
|
|
|
394
394
|
| 版本 | 日期 | 关键改动 |
|
|
395
395
|
|---|---|---|
|
|
396
|
-
| **v0.10.
|
|
397
|
-
| **v0.10.
|
|
396
|
+
| **v0.10.24** | 2026-10-13 | **「同目标、不同做法」也能归到一起了(issue #36 第三块,闭环)**。前两版解决了「冗余合并」与「矛盾判别」,但最后一块 —— 同一目的、不同工作流/代码 —— 按正文相似度**根本抓不到**(「用 7z 压缩」「用 tar.gz 分发」「npm 包瘦身」字面差很远、目标却一样)。做法:**先让模型给每张卡抽一句「这张卡解决什么问题」,再按目标聚类** —— 目标抽取结果缓存在 `refine-goals.json`(同一张卡只抽一次,第二次扫描 0 次调用),聚类用并查集;命中后按「同一目标 N 张卡」成组给出,动作二选一:**都保留**(记两两决策,下次不再提示)或**合并这一组**(走现有 /merge,新卡进审核队列)。*全程只读,唯一写入是目标缓存*。入口:面板「整理 → 🎯 同目标不同做法」+ API `/refine-goals`、`/refine-goals-keep`;CLI `dsh-memory refine --goals --keep --path A --path B`(聚类本身需要模型,在面板跑)。`npm test` 共 **332 项** |
|
|
397
|
+
| **v0.10.23** | 2026-10-13 | **存量「注入垃圾卡」可一键清理(issue #36 第二刀)**。上一版修掉了**新生成**的注入垃圾卡(技能目录 / `<system-reminder>` 被当 user 消息 → 剥标签后仍被当知识沉淀),但**存量还在**:在真实库里一跑就找出 **32 张**「和对话毫无关系」的卡(`- \`code-review\`: Review the changes…` 这种目录行、`用户system-reminder-you-are-teammate-c…` 这种碎片)。新增 `findInjectedCards()`(只**提议**、不动数据;判据:含注入标签 / 技能目录条目占比 ≥30% / 剥离注入后只剩 <200 字 / 标题是碎片)与 `purgeInjected()`(**走回收站、可还原**,逐个报告失败)。入口:面板「整理 → 🧹 注入垃圾卡」(扫描存量 / 单张回收 / 全部回收)+ CLI `dsh-memory refine --junk [--purge --path P] [--json]`;API `/refine-junk`、`/refine-junk-purge`。`npm test` 共 **331 项** |
|
|
398
398
|
|
|
399
399
|
<details>
|
|
400
|
-
<summary>更早的更新日志(v0.10.
|
|
400
|
+
<summary>更早的更新日志(v0.10.22 及以前 · 共 49 版,点开查看)</summary>
|
|
401
401
|
|
|
402
402
|
| 版本 | 日期 | 关键改动 |
|
|
403
403
|
|---|---|---|
|
|
404
|
+
| **v0.10.22** | 2026-10-13 | **「矛盾扫描」:相似可以合并,矛盾必须人判断(issue #36 第一刀)**。现状是 `/optimize` 只按相似度(≥0.55)**盲合并** —— 而两张语义相近、结论互斥的卡相似度同样很高(实测 0.93),盲合并会把冲突悄悄「和解」掉,且 `mergeCards` 会**永久删除**源卡。新增 `lib/refine.js` + 客户端「整理 → 🧭 矛盾扫描」:① **宽召回**(≥0.30,上限放到 1.0,特意把近重复那一段纳入 —— 那正是盲合并最危险的区间);② 模型只判「重复 / 补充 / 矛盾 / 无关」并给一句冲突点 + 建议保留谁;③ **人拍板**:保留 A / 保留 B / 都保留 / 合并;**扫描绝不写库**,`keepA|keepB` 只把让位那张送**回收站**(可还原);④ 决策记进 `refine-decisions.json`,同一对不再重复打扰;⑤ **安全联锁**:被判为矛盾的对**绝不参与一键优化**的盲合并。另修一处冗余来源:技能目录(`- \`skill-name\`: …`)这类注入文本不再被抓成垃圾卡(实测库里已有这种卡)。API:`/refine`、`/refine-scan`、`/refine-apply`;CLI:`dsh-memory refine`(列候选不调模型,`--apply` 落裁决)。`npm test` 共 **329 项** |
|
|
405
|
+
| **v0.10.21** | 2026-10-13 | **「库不可用」从「静默运行」变成「看得清」(issue #33,按评审裁剪范围)**。① **打开即体检**:老实现执行完 `PRAGMA journal_mode=WAL` 就当成功(这条 PRAGMA 失败时**不抛错**,只把真实模式当一行返回),文件只读 / 目录不可写时插件会一路静默跑下去;现在打开后**读回真实 journal 模式 + 真写一次探针**(建表→删表,写主库而非 temp 库),结论记进 `dbStatus()`,不可写时立即把原因与排查方向打到日志。② **`/overview` 降级返回**:库/目录不可用时不再吐 500(500 会被探针、面板、脚本读成「这个端口坏了 / 不是我们的服务」,历史上正是重启风暴与端口漂移的入口),改成 200 + `degraded:true` + `dbError` + `db:{journalMode,writable,error}`,并保住 `vaultDir` / `version` 身份字段 —— 「这是我们、但库有问题」从此可以被分开表达。③ **新增 `dsh-memory doctor`**:库健康自检(真实 journal 模式 / 写探针 / 备份清单 / 恢复引导),带 `--json` 可脚本化;不可写或疑似损坏时给出**只引导、不动数据**的步骤(先停写者 → 复制现场留证 → 查备份 → 替换 → 复核)。**刻意不做**(评审结论,已写成测试守卫防止顺手加回):`busy_timeout`(`DatabaseSync` 是同步 API,撞锁会阻塞宿主事件循环)与 WAL→DELETE 自动降级(会**永久**改变库模式,且 rollback journal 并发更差、可能反过来加剧锁竞争)。`npm test` 共 **322 项** |
|
|
404
406
|
| **v0.10.20** | 2026-10-12 | **「库不可用 → 看门狗自杀自拉 → 端口漂到 8006」三层现场一起修(issue #31 / #33,AkiMei0721 实测)**。① **`/overview` 非 200 ≠ 端口死了**:`/overview` 会碰数据库,库只读 / WAL 损坏时它返回 500,而看门狗据此判「离线」→ 走 `spawnWeb()`(开头先 SIGTERM 掉自己的上一个 child)→ **自杀自拉、端口从 7999 一路漂到 8006(6 个实例)**;新增 `decideTickAction()` 纯决策:只要 **TCP 还能连**就视为存活(`api-degraded-but-listening`,并重置重启计数),新实例的就绪判定同样接受「已监听但 `/overview` 非 200」。② **版本探测改走 `/version-check`**(不碰库;拿不到才回落 `/overview`)—— 病根是「端口上明明活着、版本却拿不到」,于是 verify 报「(无响应)」、`status` 甚至误判成「v0.10.4 之前的旧版」。③ **受限沙箱里不再崩**:默认 `stdio:'pipe'` 会在 `spawn()` 时**同步抛 EPERM**(老实现只在 `'error'` 事件里兜底,根本没机会执行)→ `dsh-memory watchdog --reap` 直接崩、进程枚举永远空名单;现在 `try/catch` 包住 spawn,并**用临时文件接 stdout**、完全不碰命名管道。④ 顺带给 stall 判据加「最短运行时长(30 分钟)」:刚落进程必然 0 收尾,不该据此报「收尾事件失效」(实测重启 1.5 分钟后就误报过)。新增 `tests/issue-fixes-8.test.mjs`(4 项)+ `tests/stall.test.mjs` 的 too-early 用例。`npm test` 共 **318 项** |
|
|
405
407
|
| **v0.10.19** | 2026-10-12 | **`UNSUPPORTED_REASONING_EFFORT` 不再让整条蒸馏熔断(issue #34)**。现场(zafir-zhong 等):很多**网关**在能力自述里说支持 reasoning effort,真把 `reasoningEffort: 'off'` 发出去才回 `UNSUPPORTED_REASONING_EFFORT`(实测:workbuddy/hy3、FallbacksChain/Auto、new-api-resp/gpt-6-luna),于是整个候选被判失败、换下一个 provider —— 用户的自动沉淀直接停摆(#37 / #38 / #40 的诊断信息里都能看到这条 FAIL)。既有设计只覆盖「provider **自述**不支持 → 不传该字段」(#15),缺了运行时这一半。修法:新增进程内记忆 `markEffortUnsupported()\/isEffortUnsupported()` —— 首次被拒时**不带该字段重试一次**(同一候选、同一 provider),并记住这个 `provider/model`,此后同一进程内它第一次请求就不再带 effort;记忆**按 provider/model 分开**,不误伤别的 provider。回归守卫加在 `tests/capture-route.test.mjs`(第一次带 → 被拒 → 第二次不带并拿到卡 → 后续只发一次 → 别的 provider 不受影响)。`npm test` 共 **313 项** |
|
|
406
408
|
| **v0.10.18** | 2026-10-12 | **「保存配置」被无条件拒绝的修复(issue #35)**。现场(Dzifxazy):全新安装、什么都不动、直接点保存 → **HTTP 400「字段不能为空:secretHint」**,整单被拒,用户改的数字与开关**一个都存不进去**;文案还会把排查方向误导到「某个数字格子没填」。根因:宿主侧的前置校验把**任何空字符串**都当成「数字没填」,只豁免一份硬编码名单(vaultDir / captureProvider / captureModel / recallEmbedding / activeVault)—— 而 `secretHint` 的默认值**就是**空串(`z.string().default('')`)又不在名单里;面板提交的是整张表单,于是每次保存必然命中。修法:新增 `emptyNumericFields(patch, Config.dict)`,**按 schema 的 `type === 'number'` 判定**「哪些键被清空」,彻底删掉硬编码名单(以后新增可空字符串字段不需要谁记得去补)。新增 `tests/config-validate.test.mjs`(2 项:空串只对 number 字段报「没填」——里面放了一个「不在任何名单里」的新字符串字段做回归钉子;源码守卫不许退回名单)。`npm test` 共 **312 项** |
|
package/bin/dsh-memory.mjs
CHANGED
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// dsh-memory sweep <dir> 挖掘 Claude Code 会话 JSONL
|
|
13
13
|
// dsh-memory doctor [--vault DIR] [--json] 库健康自检(journal 模式 / 写探针 / 备份清单 / 恢复引导)
|
|
14
14
|
// dsh-memory refine [--vault DIR] [--json] 矛盾扫描候选(不调模型);--apply --a A --b B --action keepA|keepB|merge|keepBoth 落裁决
|
|
15
|
+
// dsh-memory refine --junk [--purge --path P] [--json] 注入垃圾卡(技能目录等注入文本沉淀成的卡):列清单 / 送回收站
|
|
16
|
+
// dsh-memory refine --goals [--keep --path A --path B] 同目标不同做法:聚类需模型(面板里跑);--keep 落"都保留"
|
|
15
17
|
//
|
|
16
18
|
// 环境变量:
|
|
17
19
|
// MEMORY_VAULT_DIR vault 目录(默认 ~/.dsh/memory-vault)
|
|
@@ -170,6 +172,46 @@ async function main() {
|
|
|
170
172
|
const { defaultVaultDir } = await import('../lib/capture-run.js')
|
|
171
173
|
const { refineCandidates, refineApply, readDecisions } = await import('../lib/refine.js')
|
|
172
174
|
const root = argOf('--vault') ? path.resolve(argOf('--vault')) : defaultVaultDir()
|
|
175
|
+
if (has('--goals')) {
|
|
176
|
+
// 「同目标、不同做法」聚类:需要模型(CLI 里通常拿不到 llm)→ 提示去面板;--keep 可落"都保留"
|
|
177
|
+
const { goalScan, markClusterKept } = await import('../lib/refine.js')
|
|
178
|
+
const paths = []
|
|
179
|
+
for (let i = 0; i < argv.length; i++) if (argv[i] === '--path' && argv[i + 1]) paths.push(argv[i + 1])
|
|
180
|
+
if (has('--keep')) {
|
|
181
|
+
const out = await markClusterKept(root, { paths })
|
|
182
|
+
if (has('--json')) { console.log(JSON.stringify(out, null, 2)); if (!out.ok) process.exitCode = 1; return }
|
|
183
|
+
if (out.ok) console.log('已记录「都保留」:' + out.recorded + ' 对')
|
|
184
|
+
else { console.error('失败:' + out.error); process.exitCode = 1 }
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
const out = await goalScan(root, { llm: null, limit: Number(argOf('--limit')) || 30 })
|
|
188
|
+
if (has('--json')) { console.log(JSON.stringify(out, null, 2)); if (!out.ok) process.exitCode = 1; return }
|
|
189
|
+
console.error('带模型的目标聚类请在 DSH 面板跑:记忆 → 整理 → 🎯 同目标不同做法')
|
|
190
|
+
console.error('(CLI 可用 --goals --keep --path A --path B 把一组标成"都保留")')
|
|
191
|
+
process.exitCode = 1
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
if (has('--junk')) {
|
|
195
|
+
// 存量「注入垃圾卡」:--junk 列清单;--junk --purge --path <p>(可重复)送回收站
|
|
196
|
+
const { findInjectedCards, purgeInjected } = await import('../lib/refine.js')
|
|
197
|
+
if (has('--purge')) {
|
|
198
|
+
const paths = []
|
|
199
|
+
for (let i = 0; i < argv.length; i++) if (argv[i] === '--path' && argv[i + 1]) paths.push(argv[i + 1])
|
|
200
|
+
const out = await purgeInjected(root, { paths })
|
|
201
|
+
if (has('--json')) { console.log(JSON.stringify(out, null, 2)); if (!out.ok) process.exitCode = 1; return }
|
|
202
|
+
console.log('已送回收站 ' + out.removed.length + ' 张(可从回收中心还原)' + (out.failed.length ? ',失败 ' + out.failed.length + ' 张' : ''))
|
|
203
|
+
for (const x of out.failed) console.error(' ✗ ' + x.path + ':' + x.error)
|
|
204
|
+
if (!out.ok) process.exitCode = 1
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
const items = await findInjectedCards(root)
|
|
208
|
+
if (has('--json')) { console.log(JSON.stringify({ ok: true, count: items.length, items }, null, 2)); return }
|
|
209
|
+
console.log('注入垃圾卡(提议回收,未动数据):' + items.length + ' 张')
|
|
210
|
+
for (const it of items) console.log(' ' + it.path + ' —— ' + it.reasons.join(';'))
|
|
211
|
+
console.log('')
|
|
212
|
+
console.log('回收(进回收站,可还原):dsh-memory refine --junk --purge --path <路径> [--path <路径>…]')
|
|
213
|
+
return
|
|
214
|
+
}
|
|
173
215
|
if (has('--apply')) {
|
|
174
216
|
const out = await refineApply(root, {
|
|
175
217
|
a: argOf('--a'), b: argOf('--b'), action: argOf('--action'),
|
package/lib/api.js
CHANGED
|
@@ -603,6 +603,51 @@ export function createApi(deps = {}) {
|
|
|
603
603
|
json(res, out.ok ? 200 : 400, out)
|
|
604
604
|
return
|
|
605
605
|
}
|
|
606
|
+
case '/refine-goals': {
|
|
607
|
+
// 「同目标、不同做法」聚类(#36 第三块):先让模型给每张卡抽一句「解决什么问题」(带缓存),
|
|
608
|
+
// 再按**目标**聚类 —— 正文相似度抓不到这种关系(7z 压缩 / tar 分发 / npm 瘦身 字面差很远)。
|
|
609
|
+
if (req.method !== 'POST') { json(res, 405, { ok: false, error: '需 POST' }); return }
|
|
610
|
+
const { goalScan } = await import('./refine.js')
|
|
611
|
+
let opts = {}
|
|
612
|
+
try { let raw = ''; for await (const chunk of req) raw += chunk; opts = JSON.parse(raw || '{}') } catch { opts = {} }
|
|
613
|
+
const out = await goalScan(vaultRoot, {
|
|
614
|
+
llm: getLlm(),
|
|
615
|
+
limit: Number(opts.limit) || 30,
|
|
616
|
+
threshold: Number(opts.threshold) || 0.55,
|
|
617
|
+
provider: opts.provider,
|
|
618
|
+
model: opts.model,
|
|
619
|
+
})
|
|
620
|
+
json(res, out.ok ? 200 : 400, out)
|
|
621
|
+
return
|
|
622
|
+
}
|
|
623
|
+
case '/refine-goals-keep': {
|
|
624
|
+
// 把一组「同目标」标成都保留(记两两决策,避免下次重复提示)
|
|
625
|
+
if (req.method !== 'POST') { json(res, 405, { ok: false, error: '需 POST' }); return }
|
|
626
|
+
const { markClusterKept } = await import('./refine.js')
|
|
627
|
+
let payload = {}
|
|
628
|
+
try { let raw = ''; for await (const chunk of req) raw += chunk; payload = JSON.parse(raw || '{}') } catch { return json(res, 400, { ok: false, error: 'JSON 解析失败' }) }
|
|
629
|
+
const out = await markClusterKept(vaultRoot, payload)
|
|
630
|
+
json(res, out.ok ? 200 : 400, out)
|
|
631
|
+
return
|
|
632
|
+
}
|
|
633
|
+
case '/refine-junk': {
|
|
634
|
+
// 注入垃圾卡的**提议清单**(只读):正文其实是被注入的运行时文本(技能目录 / system-reminder…)。
|
|
635
|
+
// 新版本已不再生成这类卡,这里负责把**存量**找出来,由人决定是否回收。
|
|
636
|
+
const { findInjectedCards } = await import('./refine.js')
|
|
637
|
+
const items = await findInjectedCards(vaultRoot)
|
|
638
|
+
json(res, 200, { ok: true, count: items.length, items })
|
|
639
|
+
return
|
|
640
|
+
}
|
|
641
|
+
case '/refine-junk-purge': {
|
|
642
|
+
// POST {paths:[...]}:送**回收站**(可还原),不是永久删除
|
|
643
|
+
if (req.method !== 'POST') { json(res, 405, { ok: false, error: '需 POST' }); return }
|
|
644
|
+
const { purgeInjected } = await import('./refine.js')
|
|
645
|
+
let payload = {}
|
|
646
|
+
try { let raw = ''; for await (const chunk of req) raw += chunk; payload = JSON.parse(raw || '{}') } catch { return json(res, 400, { ok: false, error: 'JSON 解析失败' }) }
|
|
647
|
+
const out = await purgeInjected(vaultRoot, payload)
|
|
648
|
+
json(res, out.ok ? 200 : 400, out)
|
|
649
|
+
return
|
|
650
|
+
}
|
|
606
651
|
case '/setup-run': {
|
|
607
652
|
// 「补全 MCP」:真正执行 runSetup(写外部 agent 配置)。共享层(独立 web 也可用)
|
|
608
653
|
if (req.method !== 'POST') return json(res, 405, { ok: false, error: '需 POST' })
|