memory-eternal 0.10.22 → 0.10.23
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 +3 -2
- package/README.md +3 -2
- package/bin/dsh-memory.mjs +22 -0
- package/lib/api.js +18 -0
- package/lib/client.js +15 -15
- package/lib/refine.js +51 -1
- package/package.json +1 -1
- package/scripts/release.mjs +21 -1
- package/web/app.js +17 -17
package/lib/refine.js
CHANGED
|
@@ -14,7 +14,7 @@ import fs from 'node:fs'
|
|
|
14
14
|
import path from 'node:path'
|
|
15
15
|
import { getDb } from './db.js'
|
|
16
16
|
import { deleteCard, mergeCards, textSimilarity } from './vault.js'
|
|
17
|
-
import { callLlmJson, routeCandidates } from './capture.js'
|
|
17
|
+
import { callLlmJson, routeCandidates, stripRuntimeNoise, looksTruncated } from './capture.js'
|
|
18
18
|
|
|
19
19
|
const DECISIONS_FILE = 'refine-decisions.json'
|
|
20
20
|
const RELATIONS = new Set(['duplicate', 'complement', 'conflict', 'unrelated'])
|
|
@@ -130,6 +130,56 @@ export async function refineScan(root, { llm, limit = 12, provider, model, minSi
|
|
|
130
130
|
return { ok: true, candidates: cands.length, useModel: route.provider + '/' + route.model, findings, skipped }
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
/** 技能目录条目行的形态(与 capture.js 的噪声闸门同一口径)。 */
|
|
134
|
+
const CATALOG_LINE_RE = /^\s*[-*]\s*\x60[a-z0-9][a-z0-9-]{2,40}\x60:\s/
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* 找出「注入垃圾卡」:正文其实是被注入的运行时文本(技能目录、system-reminder、环境快照…),
|
|
138
|
+
* 剥掉噪声后几乎不剩人话。
|
|
139
|
+
*
|
|
140
|
+
* 为什么需要(实测):库里已经有一批「- \`code-review\`: Review the changes…」这种**和对话毫无关系**的卡
|
|
141
|
+
* —— DSH 的 available-skills 目录被当 user 消息注入,旧噪声闸门没挡住,于是被当成知识沉淀下来。
|
|
142
|
+
* 新版本已不再生成,但**存量还在**。这里只做**提议**(返回清单,不动数据),由人决定回收。
|
|
143
|
+
*/
|
|
144
|
+
export async function findInjectedCards(root, { limit = 500 } = {}) {
|
|
145
|
+
const rows = rowsOf(root).slice(0, Math.max(1, Number(limit) || 500))
|
|
146
|
+
const out = []
|
|
147
|
+
for (const r of rows) {
|
|
148
|
+
const body = String(r.body || '')
|
|
149
|
+
if (!body.trim()) continue
|
|
150
|
+
const cleaned = stripRuntimeNoise(body)
|
|
151
|
+
const ratio = body.length ? cleaned.length / body.length : 0
|
|
152
|
+
const allLines = body.split('\n').filter((l) => l.trim())
|
|
153
|
+
const catalogLines = allLines.filter((l) => CATALOG_LINE_RE.test(l)).length
|
|
154
|
+
const reasons = []
|
|
155
|
+
if (/<available_skills>|<system-reminder>/i.test(body)) reasons.push('含运行时注入标签')
|
|
156
|
+
if (catalogLines >= 3 && catalogLines / Math.max(1, allLines.length) >= 0.3) reasons.push('技能目录条目 ' + catalogLines + ' 行')
|
|
157
|
+
if (ratio < 0.35 && cleaned.length < 200) reasons.push('剥离注入后只剩 ' + cleaned.length + ' 字')
|
|
158
|
+
if (looksTruncated(String(r.title || ''))) reasons.push('标题是碎片')
|
|
159
|
+
if (reasons.length) out.push({ path: r.path, title: r.title, kind: r.kind, reasons, ratio: Math.round(ratio * 100) / 100, cleanedLen: cleaned.length })
|
|
160
|
+
}
|
|
161
|
+
return out
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* 回收这些卡 —— **走回收站(可还原)**,不是永久删除;逐个报告失败项。
|
|
166
|
+
* @param {{paths?:string[]}} opts
|
|
167
|
+
*/
|
|
168
|
+
export async function purgeInjected(root, { paths = [] } = {}) {
|
|
169
|
+
const list = (Array.isArray(paths) ? paths : []).map((x) => String(x || '').trim()).filter(Boolean)
|
|
170
|
+
if (!list.length) return { ok: false, error: '没有给出要回收的 path' }
|
|
171
|
+
const removed = []
|
|
172
|
+
const failed = []
|
|
173
|
+
for (const p of list) {
|
|
174
|
+
try {
|
|
175
|
+
const r = await deleteCard(root, p) // 默认非永久 → 进回收站
|
|
176
|
+
if (r && r.ok === false) failed.push({ path: p, error: r.error || '删除失败' })
|
|
177
|
+
else removed.push(p)
|
|
178
|
+
} catch (e) { failed.push({ path: p, error: String((e && e.message) || e) }) }
|
|
179
|
+
}
|
|
180
|
+
return { ok: failed.length === 0, removed, failed, recoverable: true }
|
|
181
|
+
}
|
|
182
|
+
|
|
133
183
|
/**
|
|
134
184
|
* 执行人的决定。**只做可恢复动作**:
|
|
135
185
|
* keepA / keepB → 另一张进**回收站**(deleteCard 默认非永久,可从回收中心还原)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-eternal",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.23",
|
|
4
4
|
"description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
package/scripts/release.mjs
CHANGED
|
@@ -62,7 +62,8 @@ const npmHasVersion = async (v) => {
|
|
|
62
62
|
return r.status === 200
|
|
63
63
|
} catch { return false }
|
|
64
64
|
}
|
|
65
|
-
|
|
65
|
+
// 实测:这个 registry 的 version 端点偶尔要 2 分钟才可见(0.10.22 就因为 60s 超时中断过一次发布)→ 给足 3 分钟
|
|
66
|
+
const waitNpmVersion = async (v, { tries = 36, delayMs = 5000 } = {}) => {
|
|
66
67
|
for (let i = 0; i < tries; i++) {
|
|
67
68
|
if (await npmHasVersion(v)) return true
|
|
68
69
|
await new Promise((r) => setTimeout(r, delayMs))
|
|
@@ -196,6 +197,25 @@ for (const remote of remotes) {
|
|
|
196
197
|
if (!b.ok) problems.push(`${remote} tag ${tag} 未推送成功(${b.out.slice(0, 120)})→ 重试:git push ${remote} ${tag}`)
|
|
197
198
|
}
|
|
198
199
|
|
|
200
|
+
// 推完再**实证**每个远端的 tag 指向:Gitee / GitHub 的 Release API 会在 main 还没推上去时
|
|
201
|
+
// 把 tag 建在"当时的 main"上(实测踩过三次:gitee v0.10.19/20/21、origin v0.10.22 —— 都是
|
|
202
|
+
// "Release 创建成功"但其实指向旧提交)。这里逐个核对 peeled 提交,不一致就强制修正并如实报告。
|
|
203
|
+
const headSha = run('git', ['rev-parse', 'HEAD']).out
|
|
204
|
+
for (const remote of remotes) {
|
|
205
|
+
if (DRY) continue
|
|
206
|
+
const ls = run('git', ['ls-remote', remote, `refs/tags/${tag}*`], { allowFail: true })
|
|
207
|
+
if (!ls.ok) { problems.push(`${remote} tag ${tag} 无法核对(${ls.out.slice(0, 120)})`); continue }
|
|
208
|
+
const peeled = (ls.out.split(/\r?\n/).find((l) => l.includes('^{}')) || '').split(/\s+/)[0] || ''
|
|
209
|
+
if (!peeled) { problems.push(`${remote} 上找不到 tag ${tag} → 重试:git push ${remote} ${tag}`); continue }
|
|
210
|
+
if (peeled !== headSha) {
|
|
211
|
+
const fix = run('git', ['push', '--force', remote, tag], { allowFail: true })
|
|
212
|
+
say(`${remote}: tag ${tag} 原指向 ${peeled.slice(0, 7)}(≠ HEAD ${headSha.slice(0, 7)})→ ${fix.ok ? '已强制修正' : '修正失败:' + fix.out.slice(0, 120)}`)
|
|
213
|
+
if (!fix.ok) problems.push(`${remote} tag ${tag} 指向错误且修正失败 → 重试:git push --force ${remote} ${tag}`)
|
|
214
|
+
} else {
|
|
215
|
+
say(`${remote}: tag ${tag} → ${headSha.slice(0, 7)} ✓`)
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
199
219
|
// ---- 5. GitHub Release ---------------------------------------------------
|
|
200
220
|
step(5, 'GitHub Release')
|
|
201
221
|
if (DRY) say(`[dry-run] 将执行:gh release create ${tag} --title … --notes-file …`)
|