@yottameta/yotta-memory 0.17.3 → 0.17.4

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 CHANGED
@@ -1,3 +1,14 @@
1
+ ## v0.17.4 (2026-09-26)
2
+
3
+ 新增 `rename`:给单条记忆改名,用来消除「平铺 / 分层同序号」冲突。
4
+
5
+ - 用法:`yotta-memory rename <记忆 id> <新文件名> [--dry-run] [--reason <原因>] [--unsafe]`;新文件名需形如 `YYYY-MM-DD-NNNN.md`。
6
+ - 身份键 = 类型 + owner + 文件名:目标名会**跨平铺 / 分层**检查同身份占用,占用即拒绝;目标文件已存在也拒绝覆盖(fail-closed)。
7
+ - 写入闸门复用 `destructiveGuardCore`(doctor + 独立备份 + 事务快照);改名后重建索引并写 `action: rename` 审计(含目标路径与原因)。
8
+ - 权限:走 `checkOwnerWritable`,其它 owner 的私密记忆默认拒绝,需 `--unsafe` 显式授权。
9
+ - `--dry-run` 只预览、零写入。命令仅 CLI 提供(写操作不进 MCP 工具面,工具数不变)。
10
+ - 回归:新增 `test/rename.test.js` 5 项(冲突消除 + 跨布局占用拒绝 + 非法名 / 已存在拒绝 + owner 门 + dry-run 零写入)。
11
+
1
12
  ## v0.17.3 (2026-09-26)
2
13
 
3
14
  帮助可读性微调:顶层 `--help` 的每个选项去掉逐项重复的「做什么:」前缀,直接写说明正文;
package/README.md CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  > 📖 The user-facing operations manual lives in [USER_GUIDE.md](USER_GUIDE.md).
25
25
 
26
+ > 🆕 **v0.17.4 (rename)**: `yotta-memory rename <id> <YYYY-MM-DD-NNNN.md>` renames a single memory entry — the minimal fix for flat/layered same-sequence collisions. Identity is type + owner + file name, checked across both layouts; an occupied target or an existing file is refused (fail-closed). Runs through the destructive guard (doctor + independent backup + snapshot), rebuilds the index and writes a `rename` audit entry; `--dry-run` previews with zero writes. CLI only — the MCP tool list is unchanged.
26
27
  > 🆕 **v0.17.3 (help readability)**: the top-level `--help` no longer repeats a "做什么:" label on every command and option — each entry starts straight with its description, while "什么时候用:" and "注意:" stay as-is. Help text only: commands, arguments and exit codes are unchanged.
27
28
  > 🆕 **v0.17.2 (owner gate for private maintenance)**: `explain` now refuses entries owned by another agent instead of printing their subject/statement, and `archive` / `maintain` skip private entries the caller does not own (declare your identity with `--agent <id>`; only an explicit user `--unsafe` overrides). Installer hardening: symlinked targets are refused and no whole-directory delete is performed. Previous v0.17.1: a mismatched identity-confirmation ID in `view` now shows an immediate error instead of returning silently; cancel stays silent and the server-side confirmation and destructive-operation gates are unchanged.
28
29
  > 🆕 **v0.17.0 (scale: year/month layout + doctor scale check + lazy index + bench)**: new entries are written into year/month folders — public `facts/<yyyy>/<mm>/`, private `private/<owner>/<type>/<yyyy>/<mm>/`. Flat files from v0.16 and earlier stay in place and remain readable; nothing is migrated automatically. Archiving keeps the year/month path, so same-named files from different months no longer overwrite each other. `doctor` gained a scale section (entry count / largest directory / index size / index cold start) with thresholds set through `config set scale_warn_entries` / `scale_warn_files_per_dir` / `scale_warn_index_bytes` / `scale_warn_cold_start_ms`; exceeding a threshold warns only and never locks destructive writes. Large stores can read only the shards of a given year: `recall --year <yyyy>` / `context --year <yyyy>` (repeatable; omitting it loads everything exactly as before). New `bench` command: deterministic auto sampling or `--evalset <file>` (evalset v1), reporting Recall@k / MRR / nDCG@k / HitRate with bootstrap 95% CI plus store and evalset fingerprints; `--gate <metric>=<value>` fits CI, and the same store + evalset + parameters always produce the same output. `bench` is read-only: it never rebuilds the index, never touches access counters and never calls an external embedding plugin. Previous v0.16.7: interactive `yotta-memory migrate --recovery-key-out` prompt, `YOTTA_MEMORY_PASS` for automation, and a `view` memory-home fingerprint before reusing a running server.
package/README.zh-CN.md CHANGED
@@ -23,6 +23,7 @@
23
23
 
24
24
  > 📖 面向用户的操作手册见 [USER_GUIDE.md](USER_GUIDE.md)。
25
25
 
26
+ > 🆕 **v0.17.4(rename 改名)**:`yotta-memory rename <记忆 id> <YYYY-MM-DD-NNNN.md>` 给单条记忆改名,用于消除「平铺 / 分层同序号」冲突。身份键 = 类型 + owner + 文件名,跨两种布局检查;目标名被占用或文件已存在一律拒绝(fail-closed)。走破坏性闸门(doctor + 独立备份 + 事务快照),改名后重建索引并写 `rename` 审计;`--dry-run` 只预览零写入。仅 CLI 提供,MCP 工具面不变。
26
27
  > 🆕 **v0.17.3(帮助可读性)**:顶层 `--help` 不再逐项重复「做什么:」前缀——每条命令与选项直接写说明正文,「什么时候用:」与「注意:」保留不变。仅帮助文案变化,命令、参数与退出码零改动。
27
28
  > 🆕 **v0.17.2(私密维护的 owner 门)**:`explain` 对其它 owner 的私密条目改为直接拒绝,不再打印 subject / statement;`archive` / `maintain` 跳过不属于当前身份的私密条目(用 `--agent <id>` 声明身份,只有用户显式 `--unsafe` 才越界)。安装器加固:拒绝对符号链接目标写入,不做整目录删除。上一版 v0.17.1:`view` 删除 AI 身份时确认 ID 输入错误会立即提示「ID 不匹配,请输入完整 agent ID」,不再静默返回;用户取消仍保持静默,后端确认校验与破坏性操作闸门不变。
28
29
  > 🆕 **v0.17.0(规模:文件分层 + doctor 规模体检 + 索引按需加载 + bench 基准评测)**:新写入按年/月分层——公共 `facts/<年>/<月>/`、私密 `private/<owner>/<type>/<年>/<月>/`;v0.16 及更早的平铺文件留在原位继续可读,不做自动迁移。归档保留分层,不同月份的同名文件不会互相覆盖。`doctor` 新增规模段(记忆条数 / 单目录最大文件数 / 索引总体积 / 索引冷启动耗时),阈值用 `config set scale_warn_entries` / `scale_warn_files_per_dir` / `scale_warn_index_bytes` / `scale_warn_cold_start_ms` 调整,超阈值只告警,不锁定破坏性写入。大库检索可以按年份只读对应分片:`recall --year <yyyy>` / `context --year <yyyy>`(可重复传多次,不传即全量、行为与旧版一致)。新增 `bench` 可复算基准评测:默认按库内条目确定性抽样,`--evalset <文件>` 也可指定评测集;输出 Recall@k / MRR / nDCG@k / HitRate + 95% 置信区间、库指纹与评测集指纹,`--gate <指标>=<数值>` 可接 CI,同库同评测集同参数必得同结果;全程只读,不重建索引、不写访问计数、不调用外部 embedding。上一版 v0.16.7:明文库转加密推荐交互式 `yotta-memory migrate --recovery-key-out "$env:USERPROFILE\yotta-memory-recovery.key"`,自动化使用 `YOTTA_MEMORY_PASS`;`view` 复用端口前校验 memory_home 指纹。
package/SKILL.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: yotta-memory
3
3
  description: 元忆 —— 有权限边界的文件式智能体记忆。文件式、零依赖、可 diff/可回滚:让任何 AI 智能体活过会话,开工 recall 恢复上下文、重要信息 remember 落盘、收工归档。类型体系 FACT(公共共享)/ PREF / BOUND / COMMIT(私密隔离)。触发:记住、别忘了、记一笔、记忆、remember、recall、跨会话、上次说到、续测、交接、归档、记忆盘、共享记忆、局域网记忆、画像、开工上下文、长期理解摘要、近期走廊、会话闭环、记忆守则、profile、context、越用越懂、语义检索、反馈、维护、蒸馏、feedback、maintain、distill、explain、自我学习、自我进化、自我提升、查看平台分页、recall 候选预过滤、任务相关记忆、--focus、--embedding、压缩遗忘、consolidate、周期摘要、自动合并、分类型衰减、回滚、备份、backup、防误删、doctor、事务快照、基线探针、恢复演练、记忆库安全扫描、scan
4
- version: 0.17.3
4
+ version: 0.17.4
5
5
  license: MIT
6
6
  ---
7
7
 
@@ -25,7 +25,7 @@ const net = require('net');
25
25
  const child_process = require('child_process');
26
26
  const { AsyncLocalStorage } = require('async_hooks');
27
27
 
28
- const VERSION = '0.17.3';
28
+ const VERSION = '0.17.4';
29
29
  const CLI_VALUE_OPTS = new Set(['--type', '--limit', '--days', '--out', '--owner', '--agent', '--agent-id', '--agent-key', '--agent-key-file', '--plugin-data', '--threshold', '--scope', '--host', '--port', '--dir', '--name', '--user', '--relationship', '--source', '--weight', '--budget', '--password', '--new-password', '--recovery-key', '--recovery-key-out', '--reason', '--merge', '--model', '--subject', '--embedding', '--focus', '--embedding-timeout', '--min-age', '--min-idle', '--max-utility', '--min-group', '--period', '--to', '--id', '--time', '--tools', '--mcp-config', '--skill-dir', '--year', '--evalset', '--k', '--seed', '--bootstrap', '--gate', '--against', '--template', '--path']);
30
30
  const CLI_FLAG_OPTS = new Set(['--project', '--all', '--unsafe', '--no-auth', '--stdio', '--onstart', '--from-current', '--restart', '--force', '--attach', '--allow-same-volume', '--verify', '--no-hint', '--encrypt', '--no-encrypt', '--password-stdin', '--json', '--manual', '--skip-schedule', '--useful', '--useless', '--undo', '--dry-run', '--apply', '--purge', '--dedup', '--batches', '--explain', '--semantic', '--runtime', '--ablate', '--timing', '--baseline', '--probe', '--quarantine', '--restore', '--yes', '--keep-memories', '--keep-identity']);
31
31
 
@@ -70,6 +70,11 @@ const HELP_MODEL = [
70
70
  helpOption('--reason', '<原因>', '记录删除原因', '需要留下为什么删除的审计线索时', ''),
71
71
  helpOption('--unsafe', '', '跳过越界删除保护', '只有获得明确授权处理隔离库时', '会放宽删除边界,误删风险很高'),
72
72
  ] },
73
+ { name: 'rename', usage: 'rename <记忆 id> <新文件名> [选项]', what: '给单条记忆改名(内容与身份不变)', when: '平铺 / 分层出现同序号冲突、或想整理文件名时', options: [
74
+ helpOption('--dry-run', '', '只预览,不改库', '想先确认改到哪个文件名时', '预演只读,不改任何文件'),
75
+ helpOption('--reason', '<原因>', '记录改名原因', '需要留下为什么改名的审计线索时', ''),
76
+ helpOption('--unsafe', '', '跳过越界写入保护', '只有获得明确授权处理隔离库时', '会放宽写入边界,谨慎使用'),
77
+ ] },
73
78
  { name: 'archive', usage: 'archive [选项]', what: '把低价值或过期记忆归档', when: '库变大,想让旧记忆退出主检索但保留可回溯时', options: [
74
79
  helpOption('--days', '<天数>', '按未使用天数判断归档', '想调整「多久没用才归档」时', ''),
75
80
  helpOption('--threshold', '<数值>', '按效用分阈值判断归档', '想调整「用得少不少」时', ''),
@@ -4456,6 +4461,77 @@ function appendTrashAudit(root, record) {
4456
4461
  fs.mkdirSync(dir, { recursive: true });
4457
4462
  fs.appendFileSync(path.join(dir, 'audit-' + today() + '.jsonl'), JSON.stringify(record) + '\n', 'utf8');
4458
4463
  }
4464
+
4465
+ // v0.17.4:给单条记忆改名(消除「平铺 / 分层同序号」冲突的最小手段)。
4466
+ // 身份键 = 类型 + owner + 文件名:目标名必须避开同身份占用,跨平铺 / 分层一起查;
4467
+ // 走 destructiveGuardCore(doctor + 独立备份 + 事务快照),改名后重建索引并写审计。
4468
+ function renameCore(fileRef, newName, opts) {
4469
+ opts = opts || {};
4470
+ const selfAgent = resolveIdentity(opts).id;
4471
+ const target = resolveMemoryTarget(fileRef);
4472
+ if (!target) return { error: true, text: '未找到记忆文件: ' + fileRef };
4473
+ const deny = checkOwnerWritable(target.root, target.rel, selfAgent, opts.unsafe);
4474
+ if (deny) return { error: true, text: deny };
4475
+ const name = String(newName || '').trim();
4476
+ if (!/^\d{4}-\d{2}-\d{2}-\d{4}\.md$/.test(name)) {
4477
+ return { error: true, text: '新文件名需形如 YYYY-MM-DD-NNNN.md(例:2026-09-25-0354.md)' };
4478
+ }
4479
+ const oldBase = path.basename(target.fp);
4480
+ if (name === oldBase) return { error: true, text: '新文件名与原名相同,无需改名。' };
4481
+ const owner = ownerFromPrivatePath(target.root, target.fp);
4482
+ let meta;
4483
+ try { meta = parseFrontmatter(readMemoryText(target.root, target.fp, owner)).meta; }
4484
+ catch (e) { return { error: true, text: '读取失败: ' + e.message }; }
4485
+ const type = (meta.type || 'FACT').toUpperCase();
4486
+ const conflicts = [];
4487
+ for (const fp of collectEntryFiles(target.root)) {
4488
+ if (fp === target.fp) continue;
4489
+ if (path.basename(fp) !== name) continue;
4490
+ const otherOwner = ownerFromPrivatePath(target.root, fp);
4491
+ if (otherOwner !== owner) continue;
4492
+ let otherMeta;
4493
+ try { otherMeta = parseFrontmatter(readMemoryText(target.root, fp, otherOwner)).meta; }
4494
+ catch (e) { conflicts.push(relOf(target.root, fp)); continue; }
4495
+ if ((otherMeta.type || 'FACT').toUpperCase() === type) conflicts.push(relOf(target.root, fp));
4496
+ }
4497
+ if (conflicts.length) {
4498
+ return { error: true, text: '目标文件名已被同 owner 同类型记忆占用,拒绝改名(跨平铺 / 分层检查): ' + conflicts.join('、') };
4499
+ }
4500
+ const dest = path.join(path.dirname(target.fp), name);
4501
+ if (fs.existsSync(dest)) return { error: true, text: '目标文件已存在,拒绝覆盖: ' + relOf(target.root, dest) };
4502
+ const destRel = relOf(target.root, dest);
4503
+ if (opts.dryRun) {
4504
+ return { error: false, text: '预览(未改动): ' + target.rel + ' → ' + destRel };
4505
+ }
4506
+ const guard = destructiveGuardCore({
4507
+ root: target.root,
4508
+ action: 'rename',
4509
+ snapshotDir: opts.snapshotDir,
4510
+ backupDir: opts.backupDir,
4511
+ allowSameVolumeForTest: opts.allowSameVolumeForTest,
4512
+ sameVolumeFn: opts.sameVolumeFn,
4513
+ now: opts.now,
4514
+ });
4515
+ if (guard.error) return { error: true, text: guard.text };
4516
+ try {
4517
+ fs.renameSync(target.fp, dest);
4518
+ } catch (e) {
4519
+ return { error: true, text: '改名失败,原文件未动: ' + e.message };
4520
+ }
4521
+ try {
4522
+ removeIndexEntry(target.root, target.rel);
4523
+ buildIndex(target.root);
4524
+ } catch (e) { /* 索引失败不回滚文件改名;下一次 reindex 会重建 */ }
4525
+ appendAudit(target.root, 'audit', {
4526
+ ts: new Date().toISOString(),
4527
+ file: target.rel,
4528
+ action: 'rename',
4529
+ to: destRel,
4530
+ reason: opts.reason ? String(opts.reason) : '',
4531
+ });
4532
+ return { error: false, text: '已改名: ' + target.rel + ' → ' + destRel + '\n事务快照: ' + guard.snapshot.id };
4533
+ }
4534
+
4459
4535
  function forgetCore(fileRef, opts) {
4460
4536
  opts = opts || {};
4461
4537
  const selfAgent = resolveIdentity(opts).id;
@@ -6111,6 +6187,22 @@ function cmdForget(fileRef, opts) {
6111
6187
  console.log(r.text);
6112
6188
  if (r.error) process.exit(2);
6113
6189
  }
6190
+
6191
+ function cmdRename(fileRef, newName, opts) {
6192
+ const ident = resolveIdentity(opts);
6193
+ if (ident.error) {
6194
+ console.log(ident.error);
6195
+ process.exit(3);
6196
+ }
6197
+ const r = renameCore(fileRef, newName, {
6198
+ selfAgent: ident.id,
6199
+ unsafe: !!(opts && opts.unsafe),
6200
+ dryRun: !!(opts && opts.dryRun),
6201
+ reason: opts && opts.reason,
6202
+ });
6203
+ console.log(r.text);
6204
+ if (r.error) process.exit(2);
6205
+ }
6114
6206
  function cmdArchive(opts) {
6115
6207
  const r = archiveCore(opts);
6116
6208
  console.log(r.text);
@@ -9559,6 +9651,7 @@ async function main() {
9559
9651
  case 'distill': cmdDistill(opts); break;
9560
9652
  case 'consolidate': if (opts.undo === true && rest.length) opts.undo = rest[0]; cmdConsolidate(opts); break;
9561
9653
  case 'forget': cmdForget(rest[0], opts); break;
9654
+ case 'rename': cmdRename(rest[0], rest[1], opts); break;
9562
9655
  case 'archive': cmdArchive(opts); break;
9563
9656
  case 'backup': {
9564
9657
  const sub = rest[0];
@@ -9646,6 +9739,7 @@ module.exports = {
9646
9739
  contextCore: contextCore,
9647
9740
  importanceScore: importanceScore,
9648
9741
  forgetCore: forgetCore,
9742
+ renameCore: renameCore,
9649
9743
  archiveCore: archiveCore,
9650
9744
  maintainCore: maintainCore,
9651
9745
  consolidateCore: consolidateCore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yottameta/yotta-memory",
3
- "version": "0.17.3",
3
+ "version": "0.17.4",
4
4
  "description": "Yuanyi (元忆) — boundary-aware, file-based memory for AI agents. File-based, zero-dependency, diff/rollback-able; FACT/PREF/BOUND/COMMIT types (public shared / private isolated), user-level + project-level storage; v0.12 reliability baseline (init guard, trash-based deletion, independent-volume backup/list/doctor/restore, start-of-work doctor, transactional snapshots before destructive writes), v0.10 consolidation (periodic summaries with provenance, near-duplicate auto-merge, per-type decay, batch audit + rollback), v0.9 recall quality + context focus + optional local embedding plugin, plus v0.8 semantic search, feedback loop, self-organization and distillation.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -3,7 +3,7 @@
3
3
  "slug": "yotta-memory",
4
4
  "name": "元忆",
5
5
  "package": "@yottameta/yotta-memory",
6
- "version": "0.17.3",
6
+ "version": "0.17.4",
7
7
  "trust": "yottameta",
8
8
  "install": {
9
9
  "idempotent": true