@mison/ling 1.2.3 → 1.2.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
@@ -7,6 +7,16 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [ling-1.2.4] - 2026-03-15
11
+
12
+ ### 修复
13
+
14
+ - 修复 `ling spec enable` 在全局 `codex` Skills 已存在时重新写回 Gemini CLI 重复 Skill 的问题,并避免将仅同名但内容不同的 Gemini 专用 Skill 误判为重复副本。
15
+
16
+ ### 维护
17
+
18
+ - 完善仓库根 `.gitignore`,补充测试临时目录、日志缓存和 Web 构建产物忽略规则。
19
+
10
20
  ## [ling-1.2.3] - 2026-03-15
11
21
 
12
22
  ### 修复
package/README.md CHANGED
@@ -47,7 +47,7 @@ ling global sync --target antigravity
47
47
  | 你要做什么 | 命令 | 结果 |
48
48
  | --- | --- | --- |
49
49
  | 给当前项目安装完整资产 | `ling init` | 项目内生成 `.agent/` / `.agents/`;共享 `.agent/` 时会维护 `.ling/install-state.json` |
50
- | 给电脑全局同步可复用 Skills | `ling global sync` | 写入 `~/.agents/skills/`、`~/.gemini/antigravity/skills/` 等,并自动清理 Gemini CLI 的重复副本 |
50
+ | 给电脑全局同步可复用 Skills | `ling global sync` | 写入 `~/.agents/skills/`、`~/.gemini/skills/`、`~/.gemini/antigravity/skills/`;若与 universal 根目录重复,会自动清理 Gemini CLI 副本 |
51
51
  | 给项目启用 Spec 工作流 | `ling spec init` | 项目内生成 `issues.csv` 等 Spec 资产 |
52
52
 
53
53
  一句话区分:
@@ -128,6 +128,8 @@ ling spec init --csv-only
128
128
  - `--csv-only`:`<project>/issues.csv`、`<project>/docs/reviews/`、`<project>/docs/handoff/`
129
129
  - 全局 Spec 资源:`~/.ling/spec/templates/`、`~/.ling/spec/references/`、`~/.ling/spec/profiles/`
130
130
 
131
+ 如果这台电脑已经有 `~/.agents/skills/`,`ling spec enable` 在启用 `gemini` 目标时也会自动清理 `~/.gemini/skills/` 里的同名重复副本;若同名 Skill 内容不同,则会保留 Gemini 专用版本,不会误删。
132
+
131
133
  如果你只想要一个本机演练空间,而不是某个真实项目:
132
134
 
133
135
  ```bash
package/bin/ling-cli.js CHANGED
@@ -1364,11 +1364,12 @@ function reconcileGeminiGlobalSkillsAgainstUniversalRoot(timestamp, options) {
1364
1364
  const geminiRoot = geminiDestination.skillsRoot;
1365
1365
 
1366
1366
  if (!fs.existsSync(universalRoot) || !fs.existsSync(geminiRoot)) {
1367
- return { removed: 0, backedUp: 0 };
1367
+ return { removed: 0, backedUp: 0, skippedConflicts: 0 };
1368
1368
  }
1369
1369
 
1370
1370
  let removed = 0;
1371
1371
  let backedUp = 0;
1372
+ let skippedConflicts = 0;
1372
1373
  for (const skillName of listSkillDirectories(geminiRoot)) {
1373
1374
  const geminiSkillDir = path.join(geminiRoot, skillName);
1374
1375
  const universalSkillDir = path.join(universalRoot, skillName);
@@ -1376,6 +1377,12 @@ function reconcileGeminiGlobalSkillsAgainstUniversalRoot(timestamp, options) {
1376
1377
  continue;
1377
1378
  }
1378
1379
 
1380
+ if (!areDirectoriesEqual(geminiSkillDir, universalSkillDir)) {
1381
+ log(options, `[warn] Gemini CLI Skill 与 universal 根目录同名但内容不同,已保留: ${geminiSkillDir}`);
1382
+ skippedConflicts += 1;
1383
+ continue;
1384
+ }
1385
+
1379
1386
  if (options.dryRun) {
1380
1387
  log(options, `[dry-run] 将移除 Gemini CLI 重复 Skill(由 universal 根目录提供): ${geminiSkillDir}`);
1381
1388
  removed += 1;
@@ -1400,7 +1407,7 @@ function reconcileGeminiGlobalSkillsAgainstUniversalRoot(timestamp, options) {
1400
1407
  log(options, `[clean] 已删除空的 Gemini CLI skills 根目录: ${geminiRoot}`);
1401
1408
  }
1402
1409
 
1403
- return { removed, backedUp };
1410
+ return { removed, backedUp, skippedConflicts };
1404
1411
  }
1405
1412
 
1406
1413
  function syncSkillDirectory(destination, srcDir, destDir, timestamp, options) {
@@ -2752,6 +2759,9 @@ async function commandSpecEnable(options) {
2752
2759
  if (targets.includes("codex")) {
2753
2760
  migrateLegacyCodexGlobalSkills(timestamp, options);
2754
2761
  }
2762
+ if (targets.includes("codex") || targets.includes("gemini")) {
2763
+ reconcileGeminiGlobalSkillsAgainstUniversalRoot(timestamp, options);
2764
+ }
2755
2765
 
2756
2766
  log(options, `[ok] Spec Profile 已启用 (Targets: ${targets.join(", ")})`);
2757
2767
  }
package/docs/PLAN.md CHANGED
@@ -41,6 +41,7 @@
41
41
  - References:`$HOME/.ling/spec/references/`
42
42
  - Profiles:`$HOME/.ling/spec/profiles/`
43
43
  - 回退原则:启用前先备份同名资源;停用时优先恢复原资源
44
+ - 与全局 Skills 的关系:若机器上已存在 `$HOME/.agents/skills/`,启用包含 `gemini` 的 Spec 目标时会自动清理 `$HOME/.gemini/skills/` 中与 universal 根目录完全相同的重复副本;同名但内容不同的 Gemini 专用 Skill 会保留
44
45
  - 项目层(工作区级)命令:`ling spec init` / `ling spec doctor`
45
46
  - 默认作用于当前目录;`--spec-workspace` 才会使用 `$HOME/.ling/spec-workspace`
46
47
  - 完整模式:写入 `.ling/spec/`、`issues.csv`、`docs/reviews/`、`docs/handoff/`
package/docs/TECH.md CHANGED
@@ -65,7 +65,7 @@ cd web && npm install && npm run lint
65
65
  - 覆盖前备份:覆盖同名 Skill 前备份到 `$HOME/.ling/backups/global/<timestamp>/<consumer>/<skill>/...`
66
66
  - `consumer` 可能是 `codex`、`gemini-cli`、`antigravity`
67
67
  - 遗留迁移:若检测到旧版 `~/.codex/skills/`,同步 codex 时会迁移到 `~/.agents/skills/` 并清理,避免 Skills 重复(冲突内容会备份到 `.../codex-legacy/<skill>/...`)
68
- - Gemini 去重:若 `~/.agents/skills/` 已存在,同步 gemini 后会移除 `~/.gemini/skills/` 内同名重复目录,并备份到 `.../gemini-cli-redundant/<skill>/...`
68
+ - Gemini 去重:若 `~/.agents/skills/` 已存在,同步 gemini 后会移除 `~/.gemini/skills/` 内内容完全相同的同名重复目录,并备份到 `.../gemini-cli-redundant/<skill>/...`;若只是同名但内容不同,则记录警告并保留 Gemini 专用版本
69
69
 
70
70
  ### 测试隔离
71
71
  - `LING_GLOBAL_ROOT`:替代 `$HOME`(用于测试与 CI,避免污染真实用户目录)
@@ -89,6 +89,7 @@ cd web && npm install && npm run lint
89
89
  - 会校验 `issues.csv` 表头与状态枚举
90
90
  - 当项目缺少 `.ling/spec/` 但已启用全局 Spec 时,会使用全局 Spec 资产作为后备
91
91
  - Spec 源目录:`.spec/`
92
+ - 若 `spec enable` 包含 `gemini` 或 `codex`,启用后还会执行一次 Gemini CLI 重复 Skill 清理,避免全局 universal Skill 与 Gemini CLI 同时暴露同名副本
92
93
 
93
94
  ### 心智模型
94
95
  - `ling spec enable`:给这台电脑安装 Spec 工具箱
@@ -172,8 +173,8 @@ Windows PowerShell 示例:
172
173
  ```powershell
173
174
  $ts = "2026-03-12T12-00-00-000Z"
174
175
  $skill = "clean-code"
175
- Remove-Item "$HOME\\.codex\\skills\\$skill" -Recurse -Force -ErrorAction SilentlyContinue
176
- Copy-Item "$HOME\\.ling\\backups\\global\\$ts\\codex\\$skill" "$HOME\\.codex\\skills\\$skill" -Recurse -Force
176
+ Remove-Item "$HOME\\.agents\\skills\\$skill" -Recurse -Force -ErrorAction SilentlyContinue
177
+ Copy-Item "$HOME\\.ling\\backups\\global\\$ts\\codex\\$skill" "$HOME\\.agents\\skills\\$skill" -Recurse -Force
177
178
  ```
178
179
 
179
180
  Gemini CLI 回滚示例:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mison/ling",
3
- "version": "1.2.3",
3
+ "version": "1.2.4",
4
4
  "description": "面向 Gemini CLI、Antigravity 与 Codex 的中文 AI Agent 模板工具包",
5
5
  "repository": {
6
6
  "type": "git",
@@ -142,6 +142,47 @@ describe("Spec Profile", () => {
142
142
  assert.ok(!fs.existsSync(antigravitySkill), "gemini spec enable should not install antigravity skill");
143
143
  });
144
144
 
145
+ test("spec enable should prune redundant gemini skills when codex universal skills already exist", () => {
146
+ const env = { LING_GLOBAL_ROOT: tempRoot };
147
+
148
+ const globalResult = runCli(["global", "sync", "--target", "codex", "--quiet"], { env });
149
+ assert.strictEqual(globalResult.status, 0, globalResult.stderr || globalResult.stdout);
150
+
151
+ const enableResult = runCli(["spec", "enable", "--quiet"], { env });
152
+ assert.strictEqual(enableResult.status, 0, enableResult.stderr || enableResult.stdout);
153
+
154
+ const geminiSkillRoot = path.join(tempRoot, ".gemini", "skills");
155
+ const antigravitySkill = path.join(tempRoot, ".gemini", "antigravity", "skills", "harness-engineering", "SKILL.md");
156
+ const codexSkill = path.join(tempRoot, ".agents", "skills", "harness-engineering", "SKILL.md");
157
+ assert.ok(!fs.existsSync(geminiSkillRoot), "redundant gemini spec skills should be pruned after enable");
158
+ assert.ok(fs.existsSync(antigravitySkill), "antigravity spec skill should still be installed");
159
+ assert.ok(fs.existsSync(codexSkill), "codex spec skill should still be installed");
160
+
161
+ const backupRoot = path.join(tempRoot, ".ling", "backups", "global");
162
+ const timestamps = fs
163
+ .readdirSync(backupRoot, { withFileTypes: true })
164
+ .filter((entry) => entry.isDirectory())
165
+ .map((entry) => entry.name);
166
+ assert.ok(timestamps.length > 0, "backup timestamp directory missing");
167
+
168
+ const redundantBackup = path.join(backupRoot, timestamps[timestamps.length - 1], "gemini-cli-redundant", "harness-engineering", "SKILL.md");
169
+ assert.ok(fs.existsSync(redundantBackup), "backup for redundant gemini spec skill missing");
170
+ });
171
+
172
+ test("spec enable should keep gemini-only skill when content differs from codex universal root", () => {
173
+ const env = { LING_GLOBAL_ROOT: tempRoot };
174
+ const codexSkillDir = path.join(tempRoot, ".agents", "skills", "harness-engineering");
175
+ fs.mkdirSync(codexSkillDir, { recursive: true });
176
+ fs.writeFileSync(path.join(codexSkillDir, "SKILL.md"), "codex-only variant", "utf8");
177
+
178
+ const enableResult = runCli(["spec", "enable", "--target", "gemini", "--quiet"], { env });
179
+ assert.strictEqual(enableResult.status, 0, enableResult.stderr || enableResult.stdout);
180
+
181
+ const geminiSkill = path.join(tempRoot, ".gemini", "skills", "harness-engineering", "SKILL.md");
182
+ assert.ok(fs.existsSync(geminiSkill), "gemini-only skill should be kept when content differs");
183
+ assert.strictEqual(fs.readFileSync(geminiSkill, "utf8"), fs.readFileSync(path.join(REPO_ROOT, ".spec", "skills", "harness-engineering", "SKILL.md"), "utf8"));
184
+ });
185
+
145
186
  test("spec enable should migrate legacy codex global skill path to ~/.agents/skills", () => {
146
187
  const env = { LING_GLOBAL_ROOT: tempRoot };
147
188
  const stateDir = path.join(tempRoot, ".ling", "spec");