@heihei0299/matt-skills 1.1.1 → 1.2.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.
|
@@ -24,12 +24,15 @@ Prompt: verify idempotent init without --force
|
|
|
24
24
|
Command: `init` twice without `--force`, second with local edit to `AGENTS.md`
|
|
25
25
|
Expected: second stdout `模板已存在.*跳过`, `上游技能:已装 0、跳过 22`; local edit preserved; exit 0.
|
|
26
26
|
|
|
27
|
-
### 3. Init --force with
|
|
28
|
-
Prompt: verify forced init
|
|
27
|
+
### 3. Init --force with direct overwrite
|
|
28
|
+
Prompt: verify forced init directly overwrites
|
|
29
29
|
Command: `init --force --dest <tmp>` after local edit
|
|
30
|
-
Expected: stdout
|
|
30
|
+
Expected: stdout `已覆盖` + `已装 22`; `AGENTS.md.bak` exists with local edit; `AGENTS.md` restored from template; no `.agents/skills/*.bak`; no `.opencode.bak`/`.pi.bak`; exit 0.
|
|
31
31
|
|
|
32
32
|
### 4. Sync on existing
|
|
33
|
+
Prompt: verify sync directly updates existing project
|
|
34
|
+
Command: `sync --dest <tmp>` after local edit
|
|
35
|
+
Expected: stdout `同步` + `已同步`/`已更新`; `AGENTS.md.bak` exists; `.agents/skills/tdd` updated; exit 0.
|
|
33
36
|
Prompt: verify sync backs up existing project
|
|
34
37
|
Command: `sync --dest <tmp>` after local edit
|
|
35
38
|
Expected: stdout `同步` + `已备份`; `AGENTS.md.bak` exists; exit 0.
|
package/README.md
CHANGED
|
@@ -31,16 +31,16 @@ npx @heihei0299/matt-skills init
|
|
|
31
31
|
1. 复制 `template/` 快照(`AGENTS.md`、`.opencode/`、`.pi/`)到当前目录;
|
|
32
32
|
2. 把上游技能(engineering 17 个 + productivity 5 个)复制到 `.agents/skills/`。
|
|
33
33
|
|
|
34
|
-
选项:`--dest <path>` 指定目标目录(默认当前目录);`--force`
|
|
34
|
+
选项:`--dest <path>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过)。
|
|
35
35
|
|
|
36
36
|
**增量同步(已有项目)**:已有项目更新到最新模板与技能:
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
npx @heihei0299/matt-skills sync #
|
|
40
|
-
npx @heihei0299/matt-skills sync --force #
|
|
39
|
+
npx @heihei0299/matt-skills sync # 增量同步,直接更新
|
|
40
|
+
npx @heihei0299/matt-skills sync --force # 同上(直接覆盖)
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
`sync` 专为已有项目设计:自动检测 `AGENTS.md`
|
|
43
|
+
`sync` 专为已有项目设计:自动检测 `AGENTS.md` 是否存在,存在则增量更新模板与上游技能(直接覆盖,`AGENTS.md` 会备份),不存在则等同全新 `init`。与 `init --force` 的区别:`sync` 语义更明确,建议已有项目优先用 `sync`。
|
|
44
44
|
|
|
45
45
|
上游没有 `tdd-implement`、`grill-to-spec`、`diagnose-fix`、`commit-check`,复制天然不冲突。目标仓库会话即自动加载全部技能(上游在 `.agents/skills/`、独有在 `.opencode/skills/`;pi 侧独有在 `.pi/skills/`)与项目级全局配置(行为路由表、分文件约定);`issue-audit` 以子代理 + 命令形式分发(`.opencode/agents/`、`.opencode/commands/`);9 个显式触发技能注册为 opencode 命令(`.opencode/commands/`,`/命令名` 触发)。
|
|
46
46
|
|
|
@@ -116,13 +116,13 @@ pi 下对应能力以内置工具或已装扩展为准(`AGENTS.md`「能力边
|
|
|
116
116
|
|
|
117
117
|
```sh
|
|
118
118
|
node bin/cli.js init [--dest <dir>] [--force] # 初始化项目:template + 上游技能
|
|
119
|
-
node bin/cli.js sync [--dest <dir>] [--force] #
|
|
119
|
+
node bin/cli.js sync [--dest <dir>] [--force] # 同步已有项目到最新(直接覆盖)
|
|
120
120
|
node bin/cli.js list [--json] # 列出 .agents/skills/ 下全部技能及描述
|
|
121
121
|
node bin/cli.js install [选项] # 把技能复制到目标工具目录(交互式选择)
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
-
`init` 选项:`--dest <dir>` 指定目标目录(默认当前目录);`--force`
|
|
125
|
-
`sync` 选项:`--dest <dir>` 指定目标目录;`--force`
|
|
124
|
+
`init` 选项:`--dest <dir>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过),见「初始化」。
|
|
125
|
+
`sync` 选项:`--dest <dir>` 指定目标目录;`--force` 直接覆盖(与默认一致),见「初始化」增量同步。
|
|
126
126
|
|
|
127
127
|
`install` 选项:
|
|
128
128
|
|
|
@@ -131,6 +131,25 @@ node bin/cli.js install [选项] # 把技能复制到目标工
|
|
|
131
131
|
- `--global`:安装到全局目录(`~/.codex/skills`、`~/.pi/agent/skills`、`~/.config/opencode/skills`、`~/.claude/skills`);`--project` 回到项目级
|
|
132
132
|
- `--all`:安装全部技能(默认交互勾选);`--force`:覆盖已存在的技能
|
|
133
133
|
|
|
134
|
+
|
|
135
|
+
### 上游同步(自动更新)
|
|
136
|
+
|
|
137
|
+
本仓库的 `.agents/skills/` 中 **非独有技能** 来自 `mattpocock/skills` 上游。已实现双通道自动同步:
|
|
138
|
+
|
|
139
|
+
- **本地 CLI**:`matt-skills check`(只读比对)与 `matt-skills update`(一键覆盖本地 `.agents/skills/`,自动处理 `writing-great-skills → writing-for-agents` 重命名与增删)
|
|
140
|
+
- **GitHub Actions**:`.github/workflows/sync-upstream.yml` 每周一 02:00 UTC 自动 `check`,有差异则 `apply` 并提 PR(`upstream-sync/<short-sha>`),支持 `workflow_dispatch` 手动触发(`ref`/`dry_run` 参数)
|
|
141
|
+
|
|
142
|
+
```sh
|
|
143
|
+
npx @heihei0299/matt-skills check # 只读检查,对比本地 vs 上游 HEAD(有差异 exit 1)
|
|
144
|
+
npx @heihei0299/matt-skills check --json # JSON 输出:{ head, counts, result: { added, updated, renamed, removed, same } }
|
|
145
|
+
npx @heihei0299/matt-skills update --dry-run # 演练,不写文件
|
|
146
|
+
npx @heihei0299/matt-skills update # 覆盖 .agents/skills 非独有技能
|
|
147
|
+
node scripts/sync-upstream.js --check # 等价底层脚本(CLI check/update 的实现)
|
|
148
|
+
node scripts/sync-upstream.js --apply --dry-run
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
实现细节:`scripts/sync-upstream.js` 为单一事实源(CLI 与 Actions 共用),以 `config/proprietary.json` 为独有白名单,上游通过 `git clone --depth 1 https://github.com/mattpocock/skills.git` 获取,比对 `SKILL.md` 的 sha256,自动处理新增/更新/重命名/删除;Actions 提 PR 后需人工合入,合入后按“发布”节打 `v*` 标签即发布(自动 patch 发版可在后续扩展为 PR 合入后自动 bump)。
|
|
152
|
+
上游重命名映射:`RENAMES = { "writing-great-skills": "writing-for-agents" }`,Actions/CLI 均会删除旧目录并复制新目录。
|
|
134
153
|
## 发布
|
|
135
154
|
|
|
136
155
|
推送 `v*` 标签自动发布到 npm(GitHub Actions,见 `.github/workflows/publish.yml`):
|
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readdir, readFile, cp, stat
|
|
2
|
+
import { readdir, readFile, cp, stat } from 'node:fs/promises';
|
|
3
3
|
import os from 'node:os';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
@@ -17,17 +17,30 @@ const HELP = `matt-skills — install and manage this skill collection
|
|
|
17
17
|
|
|
18
18
|
Usage:
|
|
19
19
|
matt-skills init [options] Initialize a project: template + upstream skills
|
|
20
|
-
matt-skills sync [options] Sync existing project to latest template + skills
|
|
20
|
+
matt-skills sync [options] Sync existing project to latest template + skills
|
|
21
21
|
matt-skills list [--json] List available skills and their descriptions
|
|
22
22
|
matt-skills install [options] Install skills (interactive by default)
|
|
23
|
+
matt-skills check [--json] [--upstream <url>] [--ref <ref>]
|
|
24
|
+
Check if upstream skills are up to date (read-only)
|
|
25
|
+
matt-skills update [--dry-run] [--force] [--upstream <url>] [--ref <ref>]
|
|
26
|
+
Update .agents/skills from upstream (auto handles renames)
|
|
23
27
|
matt-skills --help Show this help
|
|
24
28
|
|
|
25
29
|
Init options:
|
|
26
30
|
--dest <path> Target directory (default: current directory)
|
|
27
|
-
--force Overwrite existing files
|
|
31
|
+
--force Overwrite existing files
|
|
28
32
|
Sync options:
|
|
29
33
|
--dest <path> Target directory (default: current directory)
|
|
30
|
-
--force Overwrite
|
|
34
|
+
--force Overwrite existing files
|
|
35
|
+
Check options:
|
|
36
|
+
--json Output as JSON
|
|
37
|
+
--upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
|
|
38
|
+
--ref <ref> Upstream ref (default: HEAD)
|
|
39
|
+
Update options:
|
|
40
|
+
--dry-run Show what would change without writing files
|
|
41
|
+
--force Force overwrite (default is to overwrite; kept for compatibility)
|
|
42
|
+
--upstream <url> Upstream repo URL
|
|
43
|
+
--ref <ref> Upstream ref
|
|
31
44
|
|
|
32
45
|
Install options:
|
|
33
46
|
--tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection
|
|
@@ -88,21 +101,6 @@ async function backupIfExists(p) {
|
|
|
88
101
|
return bak;
|
|
89
102
|
}
|
|
90
103
|
|
|
91
|
-
async function ensureSkillsGitignore(skillsDir) {
|
|
92
|
-
try {
|
|
93
|
-
await mkdir(skillsDir, { recursive: true });
|
|
94
|
-
const gi = path.join(skillsDir, '.gitignore');
|
|
95
|
-
const content = '# matt-skills backup — pi skips .bak via explicit filter + this fallback\n*.bak\n*.bak/\n';
|
|
96
|
-
if (await pathExists(gi)) {
|
|
97
|
-
const cur = await readFile(gi, 'utf8');
|
|
98
|
-
if (cur.includes('*.bak')) return;
|
|
99
|
-
await writeFile(gi, cur.endsWith('\n') ? `${cur}${content}` : `${cur}\n${content}`);
|
|
100
|
-
} else {
|
|
101
|
-
await writeFile(gi, content);
|
|
102
|
-
}
|
|
103
|
-
} catch {}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
104
|
const TOOLS = ['codex', 'pi', 'opencode', 'claude'];
|
|
107
105
|
|
|
108
106
|
const PROJECT_DIRS = {
|
|
@@ -188,15 +186,13 @@ async function initCommand({ dest, force }) {
|
|
|
188
186
|
const target = dest ? path.resolve(process.cwd(), dest) : process.cwd();
|
|
189
187
|
const marker = path.join(target, 'AGENTS.md');
|
|
190
188
|
if (!force && (await pathExists(marker))) {
|
|
191
|
-
process.stdout.write('模板已存在(AGENTS.md),跳过;用 --force
|
|
189
|
+
process.stdout.write('模板已存在(AGENTS.md),跳过;用 --force 覆盖\n');
|
|
192
190
|
} else {
|
|
193
191
|
if (force && (await pathExists(marker))) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
if (await pathExists(cur)) await backupIfExists(cur);
|
|
197
|
-
}
|
|
192
|
+
const cur = path.join(target, 'AGENTS.md');
|
|
193
|
+
if (await pathExists(cur)) await backupIfExists(cur);
|
|
198
194
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
199
|
-
process.stdout.write('
|
|
195
|
+
process.stdout.write('模板:已覆盖(AGENTS.md、.opencode/、.pi/)\n');
|
|
200
196
|
} else {
|
|
201
197
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
202
198
|
process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
|
|
@@ -210,7 +206,6 @@ async function initCommand({ dest, force }) {
|
|
|
210
206
|
const skillsDir = path.join(target, '.agents', 'skills');
|
|
211
207
|
let installed = 0;
|
|
212
208
|
let skipped = 0;
|
|
213
|
-
let backedUp = 0;
|
|
214
209
|
for (const name of upstream) {
|
|
215
210
|
const src = path.join(SKILLS_DIR, name);
|
|
216
211
|
const dst = path.join(skillsDir, name);
|
|
@@ -222,19 +217,10 @@ async function initCommand({ dest, force }) {
|
|
|
222
217
|
skipped++;
|
|
223
218
|
continue;
|
|
224
219
|
}
|
|
225
|
-
if (force && (await pathExists(dst))) {
|
|
226
|
-
await backupIfExists(dst);
|
|
227
|
-
backedUp++;
|
|
228
|
-
}
|
|
229
220
|
await cp(src, dst, { recursive: true, force: true });
|
|
230
221
|
installed++;
|
|
231
222
|
}
|
|
232
|
-
|
|
233
|
-
process.stdout.write(`上游技能:已装 ${installed}、跳过 ${skipped}、备份 ${backedUp} 到 .bak\n`);
|
|
234
|
-
} else {
|
|
235
|
-
process.stdout.write(`上游技能:已装 ${installed}、跳过 ${skipped}\n`);
|
|
236
|
-
}
|
|
237
|
-
await ensureSkillsGitignore(skillsDir);
|
|
223
|
+
process.stdout.write(`上游技能:已装 ${installed}、跳过 ${skipped}\n`);
|
|
238
224
|
process.stdout.write(`目标路径:${target}\n`);
|
|
239
225
|
}
|
|
240
226
|
|
|
@@ -247,15 +233,13 @@ async function syncCommand({ dest, force }) {
|
|
|
247
233
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
248
234
|
process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
|
|
249
235
|
} else {
|
|
250
|
-
process.stdout.write('
|
|
236
|
+
process.stdout.write('同步:检测到现有项目,将增量更新\n');
|
|
251
237
|
if (backup) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (await pathExists(cur)) await backupIfExists(cur);
|
|
255
|
-
}
|
|
238
|
+
const cur = path.join(target, 'AGENTS.md');
|
|
239
|
+
if (await pathExists(cur)) await backupIfExists(cur);
|
|
256
240
|
}
|
|
257
241
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
258
|
-
process.stdout.write(backup ? '
|
|
242
|
+
process.stdout.write(backup ? '模板:已同步(AGENTS.md、.opencode/、.pi/)\n' : '模板:已覆盖(AGENTS.md、.opencode/、.pi/)\n');
|
|
259
243
|
}
|
|
260
244
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
261
245
|
const upstream = entries
|
|
@@ -265,7 +249,6 @@ async function syncCommand({ dest, force }) {
|
|
|
265
249
|
const skillsDir = path.join(target, '.agents', 'skills');
|
|
266
250
|
let installed = 0;
|
|
267
251
|
let updated = 0;
|
|
268
|
-
let backedUp = 0;
|
|
269
252
|
for (const name of upstream) {
|
|
270
253
|
const src = path.join(SKILLS_DIR, name);
|
|
271
254
|
const dst = path.join(skillsDir, name);
|
|
@@ -275,10 +258,6 @@ async function syncCommand({ dest, force }) {
|
|
|
275
258
|
}
|
|
276
259
|
const exists = await pathExists(dst);
|
|
277
260
|
if (exists) {
|
|
278
|
-
if (backup) {
|
|
279
|
-
await backupIfExists(dst);
|
|
280
|
-
backedUp++;
|
|
281
|
-
}
|
|
282
261
|
await cp(src, dst, { recursive: true, force: true });
|
|
283
262
|
updated++;
|
|
284
263
|
} else {
|
|
@@ -286,12 +265,7 @@ async function syncCommand({ dest, force }) {
|
|
|
286
265
|
installed++;
|
|
287
266
|
}
|
|
288
267
|
}
|
|
289
|
-
|
|
290
|
-
process.stdout.write(`上游技能:新增 ${installed}、更新 ${updated}(已备份 ${backedUp} 到 .bak)\n`);
|
|
291
|
-
} else {
|
|
292
|
-
process.stdout.write(`上游技能:新增 ${installed}、更新 ${updated}\n`);
|
|
293
|
-
}
|
|
294
|
-
await ensureSkillsGitignore(skillsDir);
|
|
268
|
+
process.stdout.write(`上游技能:新增 ${installed}、更新 ${updated}\n`);
|
|
295
269
|
process.stdout.write(`目标路径:${target}\n`);
|
|
296
270
|
}
|
|
297
271
|
|
|
@@ -330,6 +304,77 @@ function parseInstallArgs(args) {
|
|
|
330
304
|
return { dest, all, force, global, tools };
|
|
331
305
|
}
|
|
332
306
|
|
|
307
|
+
async function checkCommand(args) {
|
|
308
|
+
const { compare } = await import('../scripts/sync-upstream.js');
|
|
309
|
+
const json = args.includes('--json');
|
|
310
|
+
const upstreamIdx = args.indexOf('--upstream');
|
|
311
|
+
const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : undefined;
|
|
312
|
+
const refIdx = args.indexOf('--ref');
|
|
313
|
+
const ref = refIdx !== -1 ? args[refIdx + 1] : undefined;
|
|
314
|
+
const cmp = await compare({ upstreamUrl, ref });
|
|
315
|
+
if (json) {
|
|
316
|
+
process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result }, null, 2) + '\n');
|
|
317
|
+
} else {
|
|
318
|
+
const lines = [];
|
|
319
|
+
lines.push(`上游 HEAD: ${cmp.head}`);
|
|
320
|
+
lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream}`);
|
|
321
|
+
lines.push('');
|
|
322
|
+
const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
|
|
323
|
+
if (totalDiff === 0) {
|
|
324
|
+
lines.push('✅ 已是最新,无差异');
|
|
325
|
+
} else {
|
|
326
|
+
if (cmp.result.added.length) lines.push(`新增 (${cmp.result.added.length}): ${cmp.result.added.join(', ')}`);
|
|
327
|
+
if (cmp.result.updated.length) lines.push(`更新 (${cmp.result.updated.length}): ${cmp.result.updated.join(', ')}`);
|
|
328
|
+
if (cmp.result.renamed.length) lines.push(`重命名 (${cmp.result.renamed.length}): ${cmp.result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
|
|
329
|
+
if (cmp.result.removed.length) lines.push(`删除 (${cmp.result.removed.length}): ${cmp.result.removed.join(', ')}`);
|
|
330
|
+
if (cmp.result.same.length) lines.push(`一致 (${cmp.result.same.length}): ${cmp.result.same.join(', ')}`);
|
|
331
|
+
}
|
|
332
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
333
|
+
}
|
|
334
|
+
const { rm } = await import('node:fs/promises');
|
|
335
|
+
await rm(cmp.dest, { recursive: true, force: true });
|
|
336
|
+
const hasDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length > 0;
|
|
337
|
+
if (hasDiff) process.exitCode = 1;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async function updateCommand(args) {
|
|
341
|
+
const { applySync } = await import('../scripts/sync-upstream.js');
|
|
342
|
+
const dryRun = args.includes('--dry-run');
|
|
343
|
+
const json = args.includes('--json');
|
|
344
|
+
const upstreamIdx = args.indexOf('--upstream');
|
|
345
|
+
const upstreamUrl = upstreamIdx !== -1 ? args[upstreamIdx + 1] : undefined;
|
|
346
|
+
const refIdx = args.indexOf('--ref');
|
|
347
|
+
const ref = refIdx !== -1 ? args[refIdx + 1] : undefined;
|
|
348
|
+
const res = await applySync({ upstreamUrl, ref, dryRun });
|
|
349
|
+
if (json) {
|
|
350
|
+
process.stdout.write(JSON.stringify({ head: res.head, result: res.result, actions: res.actions, dryRun }, null, 2) + '\n');
|
|
351
|
+
} else {
|
|
352
|
+
const lines = [];
|
|
353
|
+
lines.push(`上游 HEAD: ${res.head}`);
|
|
354
|
+
lines.push(`本地非独有: ${res.counts.local} 上游: ${res.counts.upstream}`);
|
|
355
|
+
lines.push('');
|
|
356
|
+
const totalDiff = res.result.added.length + res.result.updated.length + res.result.removed.length + res.result.renamed.length;
|
|
357
|
+
if (totalDiff === 0) {
|
|
358
|
+
lines.push('✅ 已是最新,无差异');
|
|
359
|
+
} else {
|
|
360
|
+
if (res.result.added.length) lines.push(`新增 (${res.result.added.length}): ${res.result.added.join(', ')}`);
|
|
361
|
+
if (res.result.updated.length) lines.push(`更新 (${res.result.updated.length}): ${res.result.updated.join(', ')}`);
|
|
362
|
+
if (res.result.renamed.length) lines.push(`重命名 (${res.result.renamed.length}): ${res.result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
|
|
363
|
+
if (res.result.removed.length) lines.push(`删除 (${res.result.removed.length}): ${res.result.removed.join(', ')}`);
|
|
364
|
+
if (res.result.same.length) lines.push(`一致 (${res.result.same.length}): ${res.result.same.join(', ')}`);
|
|
365
|
+
}
|
|
366
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
367
|
+
if (res.actions.length) {
|
|
368
|
+
process.stdout.write(`\n已执行 ${res.actions.length} 项:\n`);
|
|
369
|
+
for (const a of res.actions) process.stdout.write(` - ${a}\n`);
|
|
370
|
+
}
|
|
371
|
+
if (dryRun) process.stdout.write('\n(dry-run,未写文件)\n');
|
|
372
|
+
}
|
|
373
|
+
if (!dryRun && res.actions.length) {
|
|
374
|
+
process.stdout.write('\n提示:请运行 npm test 验证,并按需执行 npm run build:template 更新模板镜像\n');
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
333
378
|
async function main() {
|
|
334
379
|
const args = process.argv.slice(2);
|
|
335
380
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
@@ -360,10 +405,17 @@ async function main() {
|
|
|
360
405
|
await installCommand(parseInstallArgs(rest));
|
|
361
406
|
return;
|
|
362
407
|
}
|
|
408
|
+
if (command === 'check') {
|
|
409
|
+
await checkCommand(rest);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (command === 'update') {
|
|
413
|
+
await updateCommand(rest);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
363
416
|
process.stderr.write(HELP);
|
|
364
417
|
process.exitCode = 1;
|
|
365
418
|
}
|
|
366
|
-
|
|
367
419
|
main().catch((error) => {
|
|
368
420
|
process.stderr.write(`error: ${error.message}\n`);
|
|
369
421
|
process.exitCode = 1;
|
package/package.json
CHANGED
|
@@ -24,12 +24,15 @@ Prompt: verify idempotent init without --force
|
|
|
24
24
|
Command: `init` twice without `--force`, second with local edit to `AGENTS.md`
|
|
25
25
|
Expected: second stdout `模板已存在.*跳过`, `上游技能:已装 0、跳过 22`; local edit preserved; exit 0.
|
|
26
26
|
|
|
27
|
-
### 3. Init --force with
|
|
28
|
-
Prompt: verify forced init
|
|
27
|
+
### 3. Init --force with direct overwrite
|
|
28
|
+
Prompt: verify forced init directly overwrites
|
|
29
29
|
Command: `init --force --dest <tmp>` after local edit
|
|
30
|
-
Expected: stdout
|
|
30
|
+
Expected: stdout `已覆盖` + `已装 22`; `AGENTS.md.bak` exists with local edit; `AGENTS.md` restored from template; no `.agents/skills/*.bak`; no `.opencode.bak`/`.pi.bak`; exit 0.
|
|
31
31
|
|
|
32
32
|
### 4. Sync on existing
|
|
33
|
+
Prompt: verify sync directly updates existing project
|
|
34
|
+
Command: `sync --dest <tmp>` after local edit
|
|
35
|
+
Expected: stdout `同步` + `已同步`/`已更新`; `AGENTS.md.bak` exists; `.agents/skills/tdd` updated; exit 0.
|
|
33
36
|
Prompt: verify sync backs up existing project
|
|
34
37
|
Command: `sync --dest <tmp>` after local edit
|
|
35
38
|
Expected: stdout `同步` + `已备份`; `AGENTS.md.bak` exists; exit 0.
|
|
@@ -24,12 +24,15 @@ Prompt: verify idempotent init without --force
|
|
|
24
24
|
Command: `init` twice without `--force`, second with local edit to `AGENTS.md`
|
|
25
25
|
Expected: second stdout `模板已存在.*跳过`, `上游技能:已装 0、跳过 22`; local edit preserved; exit 0.
|
|
26
26
|
|
|
27
|
-
### 3. Init --force with
|
|
28
|
-
Prompt: verify forced init
|
|
27
|
+
### 3. Init --force with direct overwrite
|
|
28
|
+
Prompt: verify forced init directly overwrites
|
|
29
29
|
Command: `init --force --dest <tmp>` after local edit
|
|
30
|
-
Expected: stdout
|
|
30
|
+
Expected: stdout `已覆盖` + `已装 22`; `AGENTS.md.bak` exists with local edit; `AGENTS.md` restored from template; no `.agents/skills/*.bak`; no `.opencode.bak`/`.pi.bak`; exit 0.
|
|
31
31
|
|
|
32
32
|
### 4. Sync on existing
|
|
33
|
+
Prompt: verify sync directly updates existing project
|
|
34
|
+
Command: `sync --dest <tmp>` after local edit
|
|
35
|
+
Expected: stdout `同步` + `已同步`/`已更新`; `AGENTS.md.bak` exists; `.agents/skills/tdd` updated; exit 0.
|
|
33
36
|
Prompt: verify sync backs up existing project
|
|
34
37
|
Command: `sync --dest <tmp>` after local edit
|
|
35
38
|
Expected: stdout `同步` + `已备份`; `AGENTS.md.bak` exists; exit 0.
|