@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 +15 -3
- package/README.md +37 -23
- package/bin/adapters/codex.js +6 -6
- package/bin/adapters/gemini.js +1 -1
- package/bin/interactive.js +68 -1
- package/bin/{ag-kit.js → ling-cli.js} +553 -120
- package/bin/ling.js +1 -1
- package/bin/utils/managed-block.js +17 -4
- package/bin/utils.js +1 -1
- package/docs/TECH.md +16 -7
- package/package.json +6 -3
- package/scripts/ci-verify.js +9 -3
- package/scripts/postinstall-check.js +5 -6
- package/tests/clean-script.test.js +1 -1
- package/tests/cli-smoke.test.js +85 -0
- package/tests/managed-block.test.js +18 -1
- package/tests/phase-c.test.js +2 -2
- package/tests/standards-compliance.test.js +1 -1
- package/tests/transformer.test.js +1 -1
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/
|
|
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
|
|
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(技能) |
|
|
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
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
276
|
-
|
|
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 ©
|
|
372
|
+
MIT © [vudovn](https://github.com/vudovn), [Mison](https://github.com/MisonL), [2217173240](https://github.com/2217173240)
|
package/bin/adapters/codex.js
CHANGED
|
@@ -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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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(), "
|
|
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");
|
package/bin/adapters/gemini.js
CHANGED
|
@@ -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(), "
|
|
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 = () => {
|
package/bin/interactive.js
CHANGED
|
@@ -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
|
};
|