@topmindspace/topmind-skills 4.15.4 → 4.15.5

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
@@ -4,6 +4,14 @@ topmind-skills 的版本记录。GitHub 只保留最近 2 个 Release,本文
4
4
 
5
5
  版本真源:`topmind-pack.json`。`package.json`、每个 `SKILL.md` 的 `metadata.version`、`evals/evals.json` 与之一致。
6
6
 
7
+ ## 4.15.5 — 2026-10-16
8
+
9
+ ### 安装器
10
+
11
+ - 默认升级:包里版本更新(或已装版本缺失)就直接覆盖,不备份,打印 `Upgraded <skill> <旧> -> <新>`;同版本跳过,打印 `Already up to date`(`--force` 重装);已装版本比包里新时拒绝降级,需 `--force`。
12
+ - symlink 模式:之前装过的同名 skill 普通目录(SKILL.md name 一致)直接换成软链;无关目录才需要 `--force`,并给一行提示。
13
+ - 新增 `tests/installer-upgrade.test.mjs`;README 中英文、INSTALL.md、帮助文本同步。
14
+
7
15
  ## 4.15.4 — 2026-10-16
8
16
 
9
17
  ### 优化
package/INSTALL.md CHANGED
@@ -92,7 +92,7 @@ npx @topmindspace/topmind-skills add topmindspace/topmind-skills -p
92
92
  # 单目录(旧 --dest 仍可用)
93
93
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills --dest ~/.claude/skills
94
94
 
95
- # 升级(读 receipt)
95
+ # 升级(读 receipt):包里更新 → 直接覆盖,不备份,打印 Upgraded <skill> <旧> -> <新>;同版本 → Already up to date;已装更新 → 拒绝降级,需 --force
96
96
  npx @topmindspace/topmind-skills update -g
97
97
 
98
98
  # 查看
@@ -162,7 +162,7 @@ npx @topmindspace/topmind-skills doctor
162
162
  | `--skill <id>` | 只装列出的 skill id |
163
163
  | `--locale en-US` | locale overlay(当前未提供 overlay,回退中文正文) |
164
164
  | `-n` / `--dry-run` | 只打印计划 |
165
- | `--force` | symlink 模式下替换非软链目标 |
165
+ | `--force` | 允许降级(已装版本比包里新)、同版本重装、symlink 模式下替换无关目录。之前装过的同名 skill 普通目录(SKILL.md name 一致)在 symlink 模式下会直接换成软链,不需要 --force |
166
166
 
167
167
  ### Agents
168
168
 
package/README.en.md CHANGED
@@ -9,7 +9,7 @@ Portable AI skill pack: the same content contract on Claude Code / Codex / OpenC
9
9
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g
10
10
  # subset of agents
11
11
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g -a claude-code -a mimocode
12
- # upgrade
12
+ # upgrade (default: a newer packaged skill overwrites in place, no backup, "Upgraded <skill> <old> -> <new>"; same version is skipped "Already up to date"; a newer installed version is refused unless --force)
13
13
  npx @topmindspace/topmind-skills update -g
14
14
 
15
15
  # 2) Community CLI (fast try; omits shared/ — see INSTALL.md)
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g
10
10
  # 只装给部分智能体
11
11
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g -a claude-code -a mimocode
12
- # 升级
12
+ # 升级(默认:包里更新就直接覆盖,不备份,打印 Upgraded <skill> <旧> -> <新>;同版本跳过 Already up to date;已装更新拒绝降级,需 --force)
13
13
  npx @topmindspace/topmind-skills update -g
14
14
 
15
15
  # 2) 社区 CLI(快速试用;不含 shared/ — 见 INSTALL.md)
package/README.zh-CN.md CHANGED
@@ -9,7 +9,7 @@
9
9
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g
10
10
  # 只装给部分智能体
11
11
  npx @topmindspace/topmind-skills add topmindspace/topmind-skills -g -a claude-code -a mimocode
12
- # 升级
12
+ # 升级(默认:包里更新就直接覆盖,不备份,打印 Upgraded <skill> <旧> -> <新>;同版本跳过 Already up to date;已装更新拒绝降级,需 --force)
13
13
  npx @topmindspace/topmind-skills update -g
14
14
 
15
15
  # 2) 社区 CLI(快速试用;不含 shared/ — 见 INSTALL.md)
@@ -32,7 +32,7 @@
32
32
  * --skill <id> Only install these skill ids (repeatable / comma-separated)
33
33
  * --global / -g Alias: --host claude-code (user-level ~/.claude/skills)
34
34
  * --dry-run Print plan only
35
- * --force Replace non-symlink destinations when using symlink mode
35
+ * --force Downgrade / reinstall same version / replace unrelated folder in symlink mode
36
36
  *
37
37
  * Examples:
38
38
  * # Like `npx skills add topmindspace/topmind-skills -g`
@@ -250,7 +250,12 @@ Options:
250
250
  --skill <id> Only install these skill ids (repeatable / comma-separated)
251
251
  --locale <code> Install locale overlay (e.g. en-US); falls back to topmind_LOCALE env
252
252
  -n, --dry-run Print plan only
253
- --force Replace non-symlink destinations when using symlink mode
253
+ --force Allow downgrading a newer installed skill, reinstall an up-to-date one,
254
+ or replace an unrelated folder in symlink mode
255
+ Upgrade behaviour: a newer packaged skill overwrites the installed copy in place
256
+ (no backup, "Upgraded <skill> <old> -> <new>"); the same version is skipped
257
+ ("Already up to date"); a newer installed version is refused unless --force.
258
+ In symlink mode a previous plain-folder install of the same skill is replaced.
254
259
 
255
260
  Examples:
256
261
  node bin/install-skills.mjs add topmindspace/topmind-skills -g
@@ -701,12 +706,75 @@ async function applyLocaleOverlay(overlayDir, targetDir) {
701
706
  }
702
707
  }
703
708
 
704
- async function installCopy(skillsRoot, dest, entries, locale) {
709
+ /** Read name + version from <dir>/SKILL.md frontmatter (metadata.version or top-level version). */
710
+ async function readSkillMeta(dir) {
711
+ try {
712
+ const t = await fs.readFile(path.join(dir, "SKILL.md"), "utf8");
713
+ const fm = (t.match(/^---\r?\n([\s\S]*?)\r?\n---/) || [])[1] || "";
714
+ const name = ((fm.match(/^name:\s*(.+?)\s*$/m) || [])[1] || "").replace(/^["']|["']$/g, "");
715
+ const v =
716
+ fm.match(/^metadata:\s*\n(?:[ \t]+.*\n)*?[ \t]+version:\s*(.+?)\s*$/m) || fm.match(/^version:\s*(.+?)\s*$/m);
717
+ return { name, version: v ? v[1].replace(/^["']|["']$/g, "") : "" };
718
+ } catch {
719
+ return null;
720
+ }
721
+ }
722
+
723
+ function parseVer(v) {
724
+ const m = String(v || "").trim().replace(/^v/, "").match(/^(\d+)\.(\d+)\.(\d+)/);
725
+ return m ? m.slice(1, 4).map(Number) : null;
726
+ }
727
+
728
+ /** a vs b: 1 / 0 / -1. Missing or unparseable b counts as oldest (upgrade). */
729
+ function compareVersions(a, b) {
730
+ const pa = parseVer(a);
731
+ const pb = parseVer(b);
732
+ if (!pb) return 1;
733
+ if (!pa) return -1;
734
+ for (let i = 0; i < 3; i++) if (pa[i] !== pb[i]) return pa[i] > pb[i] ? 1 : -1;
735
+ return 0;
736
+ }
737
+
738
+ /** Existing non-symlink dst is a previous install of this skill (same SKILL.md name) or an installer-owned file. */
739
+ async function isPreviousInstall(src, dst) {
740
+ const st = await fs.lstat(dst).catch(() => null);
741
+ if (!st) return false;
742
+ const sst = await fs.lstat(src).catch(() => null);
743
+ if (sst && sst.isFile() && st.isFile()) return true;
744
+ if (!st.isDirectory()) return false;
745
+ const a = await readSkillMeta(src);
746
+ const b = await readSkillMeta(dst);
747
+ return !!(a && b && a.name && a.name === b.name);
748
+ }
749
+
750
+ async function installCopy(skillsRoot, dest, entries, locale, { force = false } = {}) {
705
751
  await fs.mkdir(dest, { recursive: true });
752
+ // Pre-pass: never silently downgrade a skill that is newer than the packaged one.
753
+ const plan = new Map();
754
+ const newer = [];
755
+ for (const name of entries) {
756
+ const src = path.join(skillsRoot, name);
757
+ const dst = path.join(dest, name);
758
+ const sm = await readSkillMeta(src);
759
+ if (!sm) continue;
760
+ const dm = (await fs.lstat(dst).catch(() => null))?.isSymbolicLink() ? null : await readSkillMeta(dst);
761
+ if (!dm) continue;
762
+ const cmp = compareVersions(sm.version, dm.version);
763
+ plan.set(name, { cmp, old: dm.version || "unknown", now: sm.version || "unknown" });
764
+ if (cmp < 0) newer.push(`${name} ${dm.version} > ${sm.version}`);
765
+ }
766
+ if (newer.length && !force) {
767
+ fail(`installed version is newer than packaged (${newer.join(", ")}); not downgrading — pass --force to downgrade`);
768
+ }
706
769
  for (const name of entries) {
707
770
  const src = path.join(skillsRoot, name);
708
771
  const dst = path.join(dest, name);
709
772
  if (!(await pathExists(src))) continue;
773
+ const pv = plan.get(name);
774
+ if (pv && pv.cmp === 0 && !force) {
775
+ log(` = Already up to date: ${name} ${pv.now}`);
776
+ continue;
777
+ }
710
778
  const st = await fs.lstat(src);
711
779
  if (await pathExists(dst)) await fs.rm(dst, { recursive: true, force: true });
712
780
  if (st.isDirectory()) {
@@ -727,7 +795,9 @@ async function installCopy(skillsRoot, dest, entries, locale) {
727
795
  } else {
728
796
  await fs.copyFile(src, dst);
729
797
  }
730
- log(` + ${name}`);
798
+ if (pv && pv.cmp > 0) log(` ↑ Upgraded ${name} ${pv.old} -> ${pv.now}`);
799
+ else if (pv) log(` + Reinstalled ${name} ${pv.now} (--force)`);
800
+ else log(` + ${name}`);
731
801
  }
732
802
  }
733
803
 
@@ -739,8 +809,8 @@ async function installSymlink(skillsRoot, dest, entries, { force }) {
739
809
  if (!(await pathExists(src))) continue;
740
810
  if (await pathExists(dst)) {
741
811
  const st = await fs.lstat(dst);
742
- if (st.isSymbolicLink() || force) await fs.rm(dst, { recursive: true, force: true });
743
- else fail(`${dst} exists and is not a symlink (pass --force)`);
812
+ if (st.isSymbolicLink() || force || (await isPreviousInstall(src, dst))) await fs.rm(dst, { recursive: true, force: true });
813
+ else fail(`${dst} is an unrelated folder (no matching SKILL.md name) — pass --force to replace it`);
744
814
  }
745
815
  await fs.symlink(src, dst, "junction");
746
816
  log(` ~ ${name} → ${src}`);
@@ -794,9 +864,9 @@ async function linkPackEntries(canonicalRoot, agentRoot, entryNames, { force })
794
864
  if ((await pathExists(dst)) || (await isBrokenSymlink(dst))) {
795
865
  const st = await fs.lstat(dst).catch(() => null);
796
866
  if (st && st.isSymbolicLink()) await fs.rm(dst, { recursive: true, force: true });
797
- else if (force) await fs.rm(dst, { recursive: true, force: true });
867
+ else if (force || (await isPreviousInstall(src, dst))) await fs.rm(dst, { recursive: true, force: true });
798
868
  else {
799
- log(` ! ${dst} exists (not symlink) — skip (pass --force to replace)`);
869
+ log(` ! ${dst} is an unrelated folder (no matching SKILL.md name) — skipped; pass --force to replace it`);
800
870
  continue;
801
871
  }
802
872
  }
@@ -845,7 +915,7 @@ async function installViaMatrix(skillsRoot, plan, opts) {
845
915
  }
846
916
 
847
917
  // 1) Full pack body → canonical
848
- await installCopy(skillsRoot, canonical, plan.entries, opts.locale);
918
+ await installCopy(skillsRoot, canonical, plan.entries, opts.locale, opts);
849
919
 
850
920
  // 2) Private agent dirs: symlink entries (default) or copy
851
921
  for (const t of targets) {
@@ -854,7 +924,7 @@ async function installViaMatrix(skillsRoot, plan, opts) {
854
924
  continue;
855
925
  }
856
926
  if (linkMode === "copy") {
857
- await installCopy(skillsRoot, t.root, plan.entries, opts.locale);
927
+ await installCopy(skillsRoot, t.root, plan.entries, opts.locale, opts);
858
928
  } else {
859
929
  await linkPackEntries(canonical, t.root, plan.entries, opts);
860
930
  }
@@ -1014,7 +1084,7 @@ async function cmdAdd(opts, sourceOverride) {
1014
1084
  }
1015
1085
  await installSymlink(skillsRoot, dest, plan.entries, opts);
1016
1086
  } else {
1017
- await installCopy(skillsRoot, dest, plan.entries, opts.locale);
1087
+ await installCopy(skillsRoot, dest, plan.entries, opts.locale, opts);
1018
1088
  }
1019
1089
 
1020
1090
  await writeInstallReceipt(dest, {
package/evals/evals.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "skill_name": "topmind",
3
3
  "daily_entry": "topmind",
4
- "version": "4.15.4",
4
+ "version": "4.15.5",
5
5
  "evals": [
6
6
  {
7
7
  "id": 1,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topmindspace/topmind-skills",
3
- "version": "4.15.4",
3
+ "version": "4.15.5",
4
4
  "description": "topmind Agent Skills pack — spec-valid Markdown skills for capture, organize, write, memory, maintain, loop.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/topmind/SKILL.md CHANGED
@@ -9,7 +9,7 @@ compatibility: >-
9
9
  topmind workspace with {NN-Name}/ categories. Host file tools primary; UTR optional.
10
10
  Install with shared/ sibling for progressive disclosure links.
11
11
  metadata:
12
- version: "4.15.4"
12
+ version: "4.15.5"
13
13
  action_category: "router"
14
14
  entrypoint: "true"
15
15
  triggers: "topmind, 知识库, 工作区, 待办, 有什么要做的, 要做的事, todo, todo list"
@@ -8,7 +8,7 @@ description: >-
8
8
  license: MIT
9
9
  compatibility: topmind workspace. Host file tools or optional UTR capture-note.
10
10
  metadata:
11
- version: "4.15.4"
11
+ version: "4.15.5"
12
12
  action_category: "capture"
13
13
  entrypoint: "false"
14
14
  triggers: "收集, 收进, 保存, 剪藏, 记一下, 临时存放, 随手记, 想法, 灵感, 暂存沉淀, 记录, 备忘, 存一下, 暂存, 笔记, 抄下来, 摘录, 记一下要做, 别忘了, 要做, capture, note, save, Note it"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: Host file tools or Kernel writeback. Desktop optional mini-app. Feishu/lark-cli is not required.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "memory"
11
11
  entrypoint: "false"
12
12
  triggers: "记账, 记一笔, 花了, 存入, 查看账单, 账户余额, bookkeeping, log expense"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: topmind workspace. Progress under .topmind/loop/*. Host LLM runs the walk; UTR optional.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "loop"
11
11
  entrypoint: "false"
12
12
  triggers: "跑一遍 loop, loop 一下, 整体体检, 工作区复盘, 把工作区跑一遍, 巡检, 继续 loop, 从断点继续, 全面检查, 复盘, review, audit, walk"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: topmind engine and/or workspace. Prefer read-only diagnostics first.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "maintain"
11
11
  entrypoint: "false"
12
12
  triggers: "快速体检, 体检, 诊断, doctor, 健康检查, 清理, 修复, 快速检查, 检查, 状态, workspace check, scan, fix, maintain"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: workspace core profile (memory/profile.md) + periodic reflections.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "memory"
11
11
  entrypoint: "false"
12
12
  triggers: "写入专题记忆, 更新我的情况, stable memory, 追加确认结论, 记住这个, 记住我, 加到专题记忆, 沉淀结论, 提炼到记忆, update profile, memory"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: topmind workspace with stream period notes, topics, or Inbox.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "organize"
11
11
  entrypoint: "false"
12
12
  triggers: "整理本周, 理顺本周, 周复盘, 整理, 组织, 研究, 分析, 对比, 提取, 梳理, 归纳, 证据, 实体, 质量审查, 总结, 概括, 汇总, 提纲, 分类整理, 找关联, 总结成笔记要点, organize, summarize, analyze, weekly review"
package/topmind-pack.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "label": "topmind",
5
5
  "description": "topmind skill pack: single daily entry topmind + progressive disclosure (shared/ + references/), Agent Skills spec-valid frontmatter (custom fields under metadata, checked by agentskills validate), capture-first template-driven categories, optional connectors topmind-weread and topmind-x, optional topmind-ledger (记账). None of the optional skills is an extra user concept. WeChat (公众号) and other writing skills live in the external topmind-writing-skills pack (topmind-wechat-post etc.) and are routed only when installed. File-system-first content truth; UTR optional.",
6
6
  "daily_entry": "topmind",
7
- "version": "4.15.4",
7
+ "version": "4.15.5",
8
8
  "updated": "2026-10-16",
9
9
  "license": "MIT",
10
10
  "locales": {
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: Requires WeRead Skill API key (wrk-*). Desktop WereadService preferred; host may call gateway manually.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "connector"
11
11
  entrypoint: "false"
12
12
  triggers: "微信读书, weread, 划线同步, 读书笔记, 阅读统计, 书架, weread sync, sync highlights"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: topmind workspace. Delivery role category (often 88-交付 / 88-Delivery).
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "write"
11
11
  entrypoint: "false"
12
12
  triggers: "写, 写作, 起草, 续写, 修订, 润色, 交付, 改写, de-AI, 输出, 出稿, 定稿, 发布, 导出, 排版, write, draft, deliver, publish, export"
@@ -6,7 +6,7 @@ description: >-
6
6
  license: MIT
7
7
  compatibility: Desktop needs Bearer (read) and/or xurl (post). Agent hosts use official X MCP + xurl bridge.
8
8
  metadata:
9
- version: "4.15.4"
9
+ version: "4.15.5"
10
10
  action_category: "connector"
11
11
  entrypoint: "false"
12
12
  triggers: "发推, 推特, twitter, x.com, 发帖, 推文, post tweet, x platform"