@mison/ling 1.0.2 → 1.1.0

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,18 @@
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [ling-1.1.0] - 2026-03-13
11
+
12
+ ### 新增
13
+
14
+ - 已有资产冲突处理:`init/update/update-all/global sync/spec enable` 在交互终端逐项确认(保留 / 备份后移除 / 直接移除),并支持按资产类别复用选择。
15
+ - 预备份与回退:项目级覆盖前快照落盘到 `.agent-backup/.agents-backup`;全局与 Spec 维持快照备份路径。
16
+
17
+ ### 维护
18
+
19
+ - ag-kit 更名清理:内部 CLI 入口更名为 `bin/ling-cli.js`,并移除 `AG_KIT_*` 兼容环境变量与 `~/.ag-kit` 控制目录迁移逻辑。
20
+ - 托管区块标记更名为 `LING MANAGED BLOCK`,并兼容识别旧 `AG-KIT` 标记后自动迁移。
21
+
10
22
  ## [ling-1.0.2] - 2026-03-13
11
23
 
12
24
  ### 变更
@@ -22,7 +34,7 @@
22
34
 
23
35
  ### 维护
24
36
 
25
- - CLI 入口权限:`bin/ling.js` 设置为可执行,保持与 `bin/ag-kit.js` 一致。
37
+ - CLI 入口权限:`bin/ling.js` 设置为可执行,保持与 `bin/ling-cli.js` 一致。
26
38
 
27
39
  ## [ling-1.0.0] - 2026-03-13
28
40
 
@@ -35,7 +47,6 @@
35
47
 
36
48
  - 品牌更名基础设施:
37
49
  - 主命令切换为 `ling`
38
- - `ag-kit` 保留兼容入口
39
50
  - 目标目录结构:`gemini -> .agent/`,`codex -> .agents/`
40
51
  - 控制目录、索引和备份默认迁移到 `~/.ling/`
41
52
  - `antigravity.rules` 收敛为 `ling.rules`
@@ -56,7 +67,8 @@
56
67
 
57
68
  本项目在 Ling 重启前的 2.x/3.x 版本记录已冻结,不再维护。
58
69
 
59
- [Unreleased]: https://github.com/MisonL/Ling/compare/ling-1.0.2...HEAD
70
+ [Unreleased]: https://github.com/MisonL/Ling/compare/ling-1.1.0...HEAD
71
+ [ling-1.1.0]: https://github.com/MisonL/Ling/releases/tag/ling-1.1.0
60
72
  [ling-1.0.2]: https://github.com/MisonL/Ling/releases/tag/ling-1.0.2
61
73
  [ling-1.0.1]: https://github.com/MisonL/Ling/releases/tag/ling-1.0.1
62
74
  [ling-1.0.0]: https://github.com/MisonL/Ling/releases/tag/ling-1.0.0
package/README.md CHANGED
@@ -6,25 +6,12 @@
6
6
 
7
7
  > 面向 Gemini CLI、Antigravity 与 Codex 的中文 AI Agent 模板工具包,提供 Skills、Agents、Workflows 与 CLI 的一键安装、更新和治理。
8
8
 
9
- ## 致谢
10
-
11
- 本项目吸收并整合了社区项目的经验与资产,感谢:
12
-
13
- - [vudovn/antigravity-kit](https://github.com/vudovn/antigravity-kit)
14
- - [2217173240/Coding-Agent-prompt-best-practice](https://github.com/2217173240/Coding-Agent-prompt-best-practice)
15
-
16
9
  ## 快速安装
17
10
 
18
11
  ```bash
19
12
  npm install -g @mison/ling
20
13
  ```
21
14
 
22
- 或使用 Bun:
23
-
24
- ```bash
25
- bun install -g @mison/ling
26
- ```
27
-
28
15
  然后在你的目标项目中初始化:
29
16
 
30
17
  ```bash
@@ -83,6 +70,27 @@ ling global status
83
70
  - npm 包版本遵循 SemVer(`package.json`)
84
71
  - git tag 与 CLI `--version` 显示使用 `ling-<SemVer>`(例如 `ling-1.0.0`)
85
72
 
73
+ ### 已有资产冲突处理(交互确认 + 备份回退)
74
+
75
+ 当检测到目标目录已存在且内容不同(或 Codex 目录存在漂移、缺失 `manifest.json`、包含未知文件)时,`ling` 会在交互终端中逐项询问处理方式:
76
+
77
+ - `k` 保留(跳过此资产)
78
+ - `b` 备份后移除(推荐)
79
+ - `r` 直接移除(不备份)
80
+
81
+ 同一类别的冲突可选择“一键复用”,后续遇到同类资产不再重复询问。
82
+
83
+ 备份落盘位置:
84
+ - 项目级预备份:
85
+ - Gemini:`<project>/.agent-backup/<timestamp>/preflight/.agent/`
86
+ - Codex:`<project>/.agents-backup/<timestamp>/preflight/.agents/` 或 `<project>/.agents-backup/<timestamp>/preflight/.codex/`
87
+ - 全局 Skills:`$HOME/.ling/backups/global/<timestamp>/...`
88
+ - Spec Profile:`$HOME/.ling/backups/spec/<timestamp>/before/...`
89
+
90
+ 非交互环境(无 TTY 或 `--non-interactive`)不会进入询问:
91
+ - `update`/`update-all`/`global sync`/`spec enable` 在需要覆盖时默认执行“备份后覆盖”
92
+ - `init` 在检测到已有资产且未指定 `--force` 时会报错提示改用交互终端或显式 `--force`
93
+
86
94
  ### Spec Profile(可选进阶能力)
87
95
 
88
96
  - 默认关闭,不随 `ling init / update / global sync` 自动安装
@@ -138,7 +146,7 @@ ling spec disable --target codex
138
146
  | 组件 | 数量 | 描述 |
139
147
  | --- | --- | --- |
140
148
  | Agents(智能体) | 20 | 专家级 AI 人设(前端、后端、安全、产品、QA 等) |
141
- | Skills(技能) | 37 | 特定领域的知识模块 |
149
+ | Skills(技能) | 38 | 特定领域的知识模块 |
142
150
  | Workflows(工作流) | 12 | 斜杠命令流程 |
143
151
 
144
152
  ## 使用方法
@@ -263,21 +271,20 @@ ling exclude remove --path /path/to/dir # 删除排除路径
263
271
  ### 开发维护命令
264
272
 
265
273
  ```bash
266
- bun run clean # 清理本地生成产物(如 web/.next、web/node_modules)
267
- bun run clean:dry-run # 预览将被清理的路径
268
- bun run test # 只执行 tests/ 目录下测试
269
- bun run health-check # 一键执行全链路健康复检
274
+ npm run clean # 清理本地生成产物(如 web/.next、web/node_modules)
275
+ npm run clean:dry-run # 预览将被清理的路径
276
+ npm test # 执行 tests/ 目录下测试
277
+ npm run health-check # 一键执行全链路健康复检
270
278
  ```
271
279
 
272
280
  如果你在 `web/` 子项目内开发,可按需执行:
273
281
 
274
282
  ```bash
275
- bun install --cwd web
276
- bun run lint --cwd web
283
+ cd web
284
+ npm install
285
+ npm run lint
277
286
  ```
278
287
 
279
- > 说明:若你通过 `bun install -g` 安装 CLI,Bun 默认会阻止本包 `postinstall`。上游同名包冲突提示会在首次执行 `ling init/update/update-all/global sync` 时给出。
280
-
281
288
  ## 卸载
282
289
 
283
290
  ### 卸载本机全局 CLI
@@ -353,6 +360,13 @@ ling exclude add --path /path/to/your-project
353
360
  </tr>
354
361
  </table>
355
362
 
363
+ ## 致谢
364
+
365
+ 本项目吸收并整合了社区项目的经验与资产,感谢:
366
+
367
+ - [vudovn/antigravity-kit](https://github.com/vudovn/antigravity-kit)
368
+ - [2217173240/Coding-Agent-prompt-best-practice](https://github.com/2217173240/Coding-Agent-prompt-best-practice)
369
+
356
370
  ## 许可证
357
371
 
358
- MIT © Vudovn, Mison
372
+ MIT © [vudovn](https://github.com/vudovn), [Mison](https://github.com/MisonL), [2217173240](https://github.com/2217173240)
@@ -140,11 +140,11 @@ class CodexAdapter extends BaseAdapter {
140
140
  if (!isCodexPrebuilt) {
141
141
  if (hasSkillsDir) {
142
142
  this.log("[build] 检测到模板目录格式,正在构建 Codex 结构...");
143
- const mockRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-root-"));
143
+ const mockRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ling-build-root-"));
144
144
  const mockAgents = path.join(mockRoot, ".agents");
145
145
  this._copyDir(installSource, mockAgents);
146
146
 
147
- buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-out-"));
147
+ buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ling-build-out-"));
148
148
  CodexBuilder.build(mockRoot, buildTemp);
149
149
  installSource = buildTemp;
150
150
  sourceLabel = `${sourceLabel}:compiled`;
@@ -157,7 +157,7 @@ class CodexAdapter extends BaseAdapter {
157
157
  };
158
158
  } else if (hasAgentsRoot) {
159
159
  this.log("[build] 检测到仓库根目录格式(.agents),正在构建 Codex 结构...");
160
- buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-out-"));
160
+ buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ling-build-out-"));
161
161
  CodexBuilder.build(installSource, buildTemp);
162
162
  installSource = buildTemp;
163
163
  sourceLabel = `${sourceLabel}:compiled`;
@@ -169,11 +169,11 @@ class CodexAdapter extends BaseAdapter {
169
169
  };
170
170
  } else if (hasLegacyAgentRoot) {
171
171
  this.log("[build] 检测到旧版仓库根目录格式(.agent),正在构建 Codex 结构...");
172
- const mockRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-root-"));
172
+ const mockRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ling-build-root-"));
173
173
  const mockAgents = path.join(mockRoot, ".agents");
174
174
  this._copyDir(path.join(installSource, ".agent"), mockAgents);
175
175
 
176
- buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-build-out-"));
176
+ buildTemp = fs.mkdtempSync(path.join(os.tmpdir(), "ling-build-out-"));
177
177
  CodexBuilder.build(mockRoot, buildTemp);
178
178
  installSource = buildTemp;
179
179
  sourceLabel = `${sourceLabel}:compiled`;
@@ -191,7 +191,7 @@ class CodexAdapter extends BaseAdapter {
191
191
  }
192
192
 
193
193
  _createStaging(installSource, sourceLabel) {
194
- const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-codex-stage-"));
194
+ const stagingDir = fs.mkdtempSync(path.join(os.tmpdir(), "ling-codex-stage-"));
195
195
  this._copyDir(installSource, stagingDir);
196
196
 
197
197
  const manifestPath = path.join(stagingDir, "manifest.json");
@@ -70,7 +70,7 @@ class GeminiAdapter extends BaseAdapter {
70
70
  }
71
71
 
72
72
  if (this._samePath(installSource, targetDir) && !this.options.dryRun) {
73
- const tempSource = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-gemini-src-"));
73
+ const tempSource = fs.mkdtempSync(path.join(os.tmpdir(), "ling-gemini-src-"));
74
74
  this._copyDir(installSource, tempSource);
75
75
  const oldCleanup = cleanup;
76
76
  cleanup = () => {
@@ -15,6 +15,10 @@ function askQuestion(rl, query) {
15
15
  });
16
16
  }
17
17
 
18
+ function isInteractiveTerminal() {
19
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
20
+ }
21
+
18
22
  /**
19
23
  * Prompt user to select targets from a list.
20
24
  * Supports multiple selection by comma separated numbers or names.
@@ -27,6 +31,10 @@ async function selectTargets(options) {
27
31
  throw new Error("非交互模式下必须通过 --target 或 --targets 指定目标");
28
32
  }
29
33
 
34
+ if (!isInteractiveTerminal()) {
35
+ throw new Error("当前环境不是交互终端,请通过 --target 或 --targets 指定目标");
36
+ }
37
+
30
38
  const rl = createInterface();
31
39
 
32
40
  try {
@@ -60,6 +68,65 @@ async function selectTargets(options) {
60
68
  }
61
69
  }
62
70
 
71
+ /**
72
+ * Create an interactive prompter for resolving asset conflicts.
73
+ * @param {object} options CLI options
74
+ * @returns {{resolveConflict: function, close: function}|null}
75
+ */
76
+ function createConflictPrompter(options) {
77
+ if (options.nonInteractive || !isInteractiveTerminal()) {
78
+ return null;
79
+ }
80
+
81
+ const rl = createInterface();
82
+ const categoryDefaults = new Map();
83
+
84
+ async function resolveConflict(conflict) {
85
+ const category = String(conflict.category || "default");
86
+ if (categoryDefaults.has(category)) {
87
+ return categoryDefaults.get(category);
88
+ }
89
+
90
+ const label = String(conflict.label || "未知资产");
91
+ const location = conflict.path ? ` (${conflict.path})` : "";
92
+
93
+ console.log(`\n[confirm] 检测到已存在资产: ${label}${location}`);
94
+ console.log("请选择处理方式:");
95
+ console.log(" k) 保留(跳过此资产)");
96
+ console.log(" b) 备份后移除(推荐)");
97
+ console.log(" r) 直接移除(不备份)");
98
+
99
+ while (true) {
100
+ const answer = String(await askQuestion(rl, "请输入 k / b / r: ")).trim().toLowerCase();
101
+ let action = "";
102
+ if (answer === "k") action = "keep";
103
+ if (answer === "b") action = "backup";
104
+ if (answer === "r") action = "remove";
105
+
106
+ if (!action) {
107
+ console.log("[warn] 输入无效,请重新输入。");
108
+ continue;
109
+ }
110
+
111
+ const applyAnswer = String(await askQuestion(rl, `是否对同类资产(类别: ${category})后续冲突复用该选择? (y/N): `))
112
+ .trim()
113
+ .toLowerCase();
114
+ if (applyAnswer === "y" || applyAnswer === "yes") {
115
+ categoryDefaults.set(category, action);
116
+ }
117
+
118
+ return action;
119
+ }
120
+ }
121
+
122
+ function close() {
123
+ rl.close();
124
+ }
125
+
126
+ return { resolveConflict, close };
127
+ }
128
+
63
129
  module.exports = {
64
- selectTargets
130
+ selectTargets,
131
+ createConflictPrompter,
65
132
  };