@heihei0299/matt-skills 1.3.0 → 1.3.1
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/README.md +19 -15
- package/bin/cli.js +90 -58
- package/package.json +1 -1
- package/template/.opencode/CONTEXT.md +4 -0
package/README.md
CHANGED
|
@@ -36,11 +36,13 @@ npx @heihei0299/matt-skills init
|
|
|
36
36
|
**增量同步(已有项目)**:已有项目更新到最新模板与技能:
|
|
37
37
|
|
|
38
38
|
```sh
|
|
39
|
-
npx @heihei0299/matt-skills sync
|
|
40
|
-
npx @heihei0299/matt-skills sync --
|
|
39
|
+
npx @heihei0299/matt-skills sync # 默认仅对比不写盘(check),有差异 exit 1,--json 可解析
|
|
40
|
+
npx @heihei0299/matt-skills sync --apply # 安全增量:AGENTS.md 定制跳过,上游技能强制覆盖但不删
|
|
41
|
+
npx @heihei0299/matt-skills sync --force # 硬盖:AGENTS.md 备份后硬盖,技能与模板全量 add/update/remove
|
|
42
|
+
npx @heihei0299/matt-skills sync --apply --dest <path> --upstream <url> --ref <ref> --json # 选项可组合
|
|
41
43
|
```
|
|
42
44
|
|
|
43
|
-
`sync`
|
|
45
|
+
`sync` 专为已有项目设计,三档语义:默认 `check` 仅对比不写盘(打印“上游 HEAD / 本地非独有 vs 上游 / 新增/更新/删除/一致”表,`--json` 可解析,有差异 `exit 1`);`--apply` 安全增量写盘(`AGENTS.md` 若含独有路由如 `tdd-implement` 则跳过,`.agents/skills` 对上游技能 `rm+cp force` 覆盖但不 `remove`,`template/.opencode/.pi` 增量 `add/update`);`--force` 硬盖(`AGENTS.md` 备份 `.bak` 后强制覆盖,技能与模板均 `add/update/remove` 全做)。`--dest`、`--upstream`、`--ref`、`--json` 在三档均可透传。
|
|
44
46
|
|
|
45
47
|
上游没有 `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
48
|
|
|
@@ -115,14 +117,16 @@ pi 下对应能力以内置工具或已装扩展为准(`AGENTS.md`「能力边
|
|
|
115
117
|
仓库内提供安装管理 CLI(`bin/cli.js`,依赖 `prompts`,见 `package.json`),同时作为 npm 包 `@heihei0299/matt-skills` 分发(`npx @heihei0299/matt-skills <command>`):
|
|
116
118
|
|
|
117
119
|
```sh
|
|
118
|
-
node bin/cli.js init [--dest <dir>] [--force]
|
|
119
|
-
node bin/cli.js sync [--dest <
|
|
120
|
-
node bin/cli.js list [--json]
|
|
121
|
-
node bin/cli.js install [选项]
|
|
120
|
+
node bin/cli.js init [--dest <dir>] [--force] # 初始化项目:template + 上游技能
|
|
121
|
+
node bin/cli.js sync [--apply|--force] [--dest <path>] [--upstream <url>] [--ref <ref>] [--json] # 同步已有项目到最新
|
|
122
|
+
node bin/cli.js list [--json] # 列出 .agents/skills/ 下全部技能及描述
|
|
123
|
+
node bin/cli.js install [选项] # 把技能复制到目标工具目录(交互式选择)
|
|
124
|
+
node bin/cli.js check [--json] [--upstream <url>] [--ref <ref>] # 只读检查上游技能是否最新(等价 sync 默认)
|
|
122
125
|
```
|
|
123
126
|
|
|
124
|
-
`init` 选项:`--dest <
|
|
125
|
-
`sync` 选项:`--dest <
|
|
127
|
+
`init` 选项:`--dest <path>` 指定目标目录(默认当前目录);`--force` 覆盖已存在的文件(直接覆盖,`AGENTS.md` 会备份到 `.bak`)(默认跳过),见「初始化」。
|
|
128
|
+
`sync` 选项:`--apply` 安全增量(`AGENTS.md` 定制跳过、技能不删),`--force` 硬盖(`AGENTS.md` 备份后硬盖、技能与模板全量 `remove`);`--dest <path>` 目标目录;`--upstream <url>` 上游地址;`--ref <ref>` 上游分支;`--json` JSON 输出;默认无参等价 `check` 仅对比不写盘,`--json` 可解析,有差异 `exit 1`。
|
|
129
|
+
`check` 选项:`--json`、`--upstream <url>`、`--ref <ref>`(等价 `sync` 默认 `check`)。
|
|
126
130
|
|
|
127
131
|
`install` 选项:
|
|
128
132
|
|
|
@@ -136,15 +140,15 @@ node bin/cli.js install [选项] # 把技能复制到目标工
|
|
|
136
140
|
|
|
137
141
|
本仓库的 `.agents/skills/` 中 **非独有技能** 来自 `mattpocock/skills` 上游。已实现双通道自动同步:
|
|
138
142
|
|
|
139
|
-
- **本地 CLI**:`matt-skills check
|
|
143
|
+
- **本地 CLI**:`matt-skills sync` 三档——默认 `check` 只读比对(等价 `matt-skills check`,有差异 `exit 1`,`--json` 可解析)、`matt-skills sync --apply` 安全增量、`matt-skills sync --force` 硬盖;`matt-skills check [--json] [--upstream <url>] [--ref <ref>]` 仍保留为只读别名;`matt-skills update` 已合并到 `sync --apply`(执行提示 `update 已合并到 sync --apply` 且 `exit 1`)
|
|
140
144
|
- **GitHub Actions**:`.github/workflows/sync-upstream.yml` 每周一 02:00 UTC 自动 `check`,有差异则 `apply` 并提 PR(`upstream-sync/<short-sha>`),支持 `workflow_dispatch` 手动触发(`ref`/`dry_run` 参数)
|
|
141
145
|
|
|
142
146
|
```sh
|
|
143
|
-
npx @heihei0299/matt-skills
|
|
144
|
-
npx @heihei0299/matt-skills
|
|
145
|
-
npx @heihei0299/matt-skills
|
|
146
|
-
npx @heihei0299/matt-skills
|
|
147
|
-
node scripts/sync-upstream.js --check # 等价底层脚本(CLI check
|
|
147
|
+
npx @heihei0299/matt-skills sync --json # 默认 check 只读检查,JSON 输出:{ head, counts, result: { added, updated, renamed, removed, same } }
|
|
148
|
+
npx @heihei0299/matt-skills sync --apply # 安全增量写盘
|
|
149
|
+
npx @heihei0299/matt-skills sync --force # 硬盖写盘
|
|
150
|
+
npx @heihei0299/matt-skills check --json # 等价 sync 默认 check
|
|
151
|
+
node scripts/sync-upstream.js --check # 等价底层脚本(CLI sync/check 的实现)
|
|
148
152
|
node scripts/sync-upstream.js --apply --dry-run
|
|
149
153
|
```
|
|
150
154
|
|
package/bin/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readdir, readFile, cp, stat } from 'node:fs/promises';
|
|
2
|
+
import { readdir, readFile, cp, stat, rm, mkdir } 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,31 +17,27 @@ 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 [
|
|
20
|
+
matt-skills sync [--apply|--force] [--dest <path>] [--upstream <url>] [--ref <ref>] [--json] 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
23
|
matt-skills check [--json] [--upstream <url>] [--ref <ref>]
|
|
24
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)
|
|
27
25
|
matt-skills --help Show this help
|
|
28
26
|
|
|
29
27
|
Init options:
|
|
30
28
|
--dest <path> Target directory (default: current directory)
|
|
31
29
|
--force Overwrite existing files
|
|
32
30
|
Sync options:
|
|
31
|
+
--apply Apply changes (safe incremental, respects custom AGENTS.md)
|
|
32
|
+
--force Hard overwrite (backup .bak + full sync)
|
|
33
33
|
--dest <path> Target directory (default: current directory)
|
|
34
|
-
--
|
|
34
|
+
--upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
|
|
35
|
+
--ref <ref> Upstream ref (default: HEAD)
|
|
36
|
+
--json Output as JSON
|
|
35
37
|
Check options:
|
|
36
38
|
--json Output as JSON
|
|
37
39
|
--upstream <url> Upstream repo URL (default: https://github.com/mattpocock/skills.git)
|
|
38
40
|
--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
|
|
44
|
-
|
|
45
41
|
Install options:
|
|
46
42
|
--tools <a,b> Install for the given tools (codex, pi, opencode, claude); skips tool selection
|
|
47
43
|
--all Install all skills; skips skill selection
|
|
@@ -224,22 +220,69 @@ async function initCommand({ dest, force }) {
|
|
|
224
220
|
process.stdout.write(`目标路径:${target}\n`);
|
|
225
221
|
}
|
|
226
222
|
|
|
227
|
-
async function syncCommand({ dest, force }) {
|
|
223
|
+
async function syncCommand({ dest, force, apply, upstreamUrl, ref, json }) {
|
|
224
|
+
// 默认:仅对比不写盘 (check 模式),--apply / --force 显式写盘
|
|
225
|
+
const doApply = apply || force;
|
|
226
|
+
if (!doApply) {
|
|
227
|
+
const { compare } = await import('../scripts/sync-upstream.js');
|
|
228
|
+
const cmp = await compare({ upstreamUrl, ref });
|
|
229
|
+
if (json) {
|
|
230
|
+
process.stdout.write(JSON.stringify({ head: cmp.head, counts: cmp.counts, result: cmp.result }, null, 2) + '\n');
|
|
231
|
+
} else {
|
|
232
|
+
const lines = [];
|
|
233
|
+
lines.push(`上游 HEAD: ${cmp.head}`);
|
|
234
|
+
lines.push(`本地非独有: ${cmp.counts.local} 上游: ${cmp.counts.upstream}`);
|
|
235
|
+
lines.push('');
|
|
236
|
+
const totalDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length;
|
|
237
|
+
if (totalDiff === 0) {
|
|
238
|
+
lines.push('✅ 已是最新,无差异');
|
|
239
|
+
} else {
|
|
240
|
+
if (cmp.result.added.length) lines.push(`新增 (${cmp.result.added.length}): ${cmp.result.added.join(', ')}`);
|
|
241
|
+
if (cmp.result.updated.length) lines.push(`更新 (${cmp.result.updated.length}): ${cmp.result.updated.join(', ')}`);
|
|
242
|
+
if (cmp.result.renamed.length) lines.push(`重命名 (${cmp.result.renamed.length}): ${cmp.result.renamed.map((r) => `${r.from}→${r.to}`).join(', ')}`);
|
|
243
|
+
if (cmp.result.removed.length) lines.push(`删除 (${cmp.result.removed.length}): ${cmp.result.removed.join(', ')}`);
|
|
244
|
+
if (cmp.result.same.length) lines.push(`一致 (${cmp.result.same.length}): ${cmp.result.same.join(', ')}`);
|
|
245
|
+
}
|
|
246
|
+
process.stdout.write(lines.join('\n') + '\n');
|
|
247
|
+
}
|
|
248
|
+
const { rm } = await import('node:fs/promises');
|
|
249
|
+
await rm(cmp.dest, { recursive: true, force: true });
|
|
250
|
+
const hasDiff = cmp.result.added.length + cmp.result.updated.length + cmp.result.removed.length + cmp.result.renamed.length > 0;
|
|
251
|
+
if (hasDiff) process.exitCode = 1;
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
228
255
|
const target = dest ? path.resolve(process.cwd(), dest) : process.cwd();
|
|
229
256
|
const marker = path.join(target, 'AGENTS.md');
|
|
230
|
-
const backup = !force;
|
|
231
257
|
if (!(await pathExists(marker))) {
|
|
232
258
|
process.stdout.write('未检测到现有项目(AGENTS.md 不存在),将执行全新初始化\n');
|
|
233
259
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
234
260
|
process.stdout.write('模板:已复制(AGENTS.md、.opencode/、.pi/)\n');
|
|
235
|
-
} else {
|
|
261
|
+
} else if (force) {
|
|
236
262
|
process.stdout.write('同步:检测到现有项目,将增量更新\n');
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
263
|
+
await backupIfExists(path.join(target, 'AGENTS.md'));
|
|
264
|
+
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
265
|
+
process.stdout.write('模板:已覆盖(AGENTS.md、.opencode/、.pi/)\n');
|
|
266
|
+
} else if (apply) {
|
|
267
|
+
process.stdout.write('同步:检测到现有项目,将增量更新\n');
|
|
268
|
+
let skipAgents = false;
|
|
269
|
+
try {
|
|
270
|
+
const content = await readFile(path.join(target, 'AGENTS.md'), 'utf8');
|
|
271
|
+
if (content.includes('tdd-implement')) skipAgents = true;
|
|
272
|
+
} catch {}
|
|
273
|
+
if (skipAgents) {
|
|
274
|
+
await cp(path.join(TEMPLATE_DIR, '.opencode'), path.join(target, '.opencode'), { recursive: true, force: true });
|
|
275
|
+
await cp(path.join(TEMPLATE_DIR, '.pi'), path.join(target, '.pi'), { recursive: true, force: true });
|
|
276
|
+
process.stdout.write('模板:已同步(AGENTS.md 跳过,已含定制)\n');
|
|
277
|
+
} else {
|
|
278
|
+
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
279
|
+
process.stdout.write('模板:已同步(AGENTS.md、.opencode/、.pi/)\n');
|
|
240
280
|
}
|
|
281
|
+
} else {
|
|
282
|
+
// fallback for any other doApply case (should not reach here because !doApply already returned)
|
|
283
|
+
process.stdout.write('同步:检测到现有项目,将增量更新\n');
|
|
241
284
|
await cp(TEMPLATE_DIR, target, { recursive: true, force: true });
|
|
242
|
-
process.stdout.write(
|
|
285
|
+
process.stdout.write('模板:已同步(AGENTS.md、.opencode/、.pi/)\n');
|
|
243
286
|
}
|
|
244
287
|
const entries = await readdir(SKILLS_DIR, { withFileTypes: true });
|
|
245
288
|
const upstream = entries
|
|
@@ -247,6 +290,7 @@ async function syncCommand({ dest, force }) {
|
|
|
247
290
|
.map((e) => e.name)
|
|
248
291
|
.sort();
|
|
249
292
|
const skillsDir = path.join(target, '.agents', 'skills');
|
|
293
|
+
await mkdir(skillsDir, { recursive: true });
|
|
250
294
|
let installed = 0;
|
|
251
295
|
let updated = 0;
|
|
252
296
|
for (const name of upstream) {
|
|
@@ -258,6 +302,7 @@ async function syncCommand({ dest, force }) {
|
|
|
258
302
|
}
|
|
259
303
|
const exists = await pathExists(dst);
|
|
260
304
|
if (exists) {
|
|
305
|
+
await rm(dst, { recursive: true, force: true });
|
|
261
306
|
await cp(src, dst, { recursive: true, force: true });
|
|
262
307
|
updated++;
|
|
263
308
|
} else {
|
|
@@ -265,6 +310,20 @@ async function syncCommand({ dest, force }) {
|
|
|
265
310
|
installed++;
|
|
266
311
|
}
|
|
267
312
|
}
|
|
313
|
+
if (force) {
|
|
314
|
+
let localEntries = [];
|
|
315
|
+
try {
|
|
316
|
+
localEntries = await readdir(skillsDir, { withFileTypes: true });
|
|
317
|
+
} catch {}
|
|
318
|
+
for (const e of localEntries) {
|
|
319
|
+
if (!e.isDirectory()) continue;
|
|
320
|
+
if (e.name.endsWith('.bak')) continue;
|
|
321
|
+
if (e.name === '.git') continue;
|
|
322
|
+
if (PROPRIETARY_SKILLS.has(e.name)) continue;
|
|
323
|
+
if (upstream.includes(e.name)) continue;
|
|
324
|
+
await rm(path.join(skillsDir, e.name), { recursive: true, force: true });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
268
327
|
process.stdout.write(`上游技能:新增 ${installed}、更新 ${updated}\n`);
|
|
269
328
|
process.stdout.write(`目标路径:${target}\n`);
|
|
270
329
|
}
|
|
@@ -272,13 +331,23 @@ async function syncCommand({ dest, force }) {
|
|
|
272
331
|
function parseInitArgs(args) {
|
|
273
332
|
let dest;
|
|
274
333
|
let force = false;
|
|
334
|
+
let apply = false;
|
|
335
|
+
let json = false;
|
|
336
|
+
let upstreamUrl;
|
|
337
|
+
let ref;
|
|
275
338
|
for (let i = 0; i < args.length; i++) {
|
|
276
339
|
const arg = args[i];
|
|
277
340
|
if (arg === '--dest') dest = args[++i];
|
|
278
341
|
else if (arg.startsWith('--dest=')) dest = arg.slice('--dest='.length);
|
|
279
342
|
else if (arg === '--force') force = true;
|
|
343
|
+
else if (arg === '--apply') apply = true;
|
|
344
|
+
else if (arg === '--json') json = true;
|
|
345
|
+
else if (arg === '--upstream') upstreamUrl = args[++i];
|
|
346
|
+
else if (arg.startsWith('--upstream=')) upstreamUrl = arg.slice('--upstream='.length);
|
|
347
|
+
else if (arg === '--ref') ref = args[++i];
|
|
348
|
+
else if (arg.startsWith('--ref=')) ref = arg.slice('--ref='.length);
|
|
280
349
|
}
|
|
281
|
-
return { dest, force };
|
|
350
|
+
return { dest, force, apply, json, upstreamUrl, ref };
|
|
282
351
|
}
|
|
283
352
|
|
|
284
353
|
function parseInstallArgs(args) {
|
|
@@ -337,44 +406,6 @@ async function checkCommand(args) {
|
|
|
337
406
|
if (hasDiff) process.exitCode = 1;
|
|
338
407
|
}
|
|
339
408
|
|
|
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
|
-
|
|
378
409
|
async function main() {
|
|
379
410
|
const args = process.argv.slice(2);
|
|
380
411
|
if (args.length === 0 || args[0] === '--help' || args[0] === '-h') {
|
|
@@ -410,7 +441,8 @@ async function main() {
|
|
|
410
441
|
return;
|
|
411
442
|
}
|
|
412
443
|
if (command === 'update') {
|
|
413
|
-
|
|
444
|
+
process.stderr.write('update 已合并到 sync --apply\n');
|
|
445
|
+
process.exitCode = 1;
|
|
414
446
|
return;
|
|
415
447
|
}
|
|
416
448
|
process.stderr.write(HELP);
|
package/package.json
CHANGED
|
@@ -32,6 +32,10 @@ _Avoid_: inheriting repo, child repo
|
|
|
32
32
|
The one-time action of setting up a Target Repository: copying `template/` into its root (`cp -r template/. <target>/`), then fetching the 22 upstream skills from the Upstream Repository. Copying, not inheriting — no runtime relationship survives the copy.
|
|
33
33
|
_Avoid_: inherit, bootstrap
|
|
34
34
|
|
|
35
|
+
**Sync** (同步):
|
|
36
|
+
`matt-skills sync` 同步 Target Repository 的模板与上游技能:默认仅对比不写盘(`check`),`--apply` 为安全增量(`AGENTS.md` 有定制如 `tdd-implement` 则跳过,上游技能直接强制覆盖 `rm+cp` 但不 `remove`,独有技能与 `template/.opencode/.pi` 增量 `add/update`)、`--force` 为硬盖(`AGENTS.md` 备份 `.bak` 后强制覆盖,技能与模板全量 `add/update/remove`)。`update` 已合并到 `sync` 并删除。
|
|
37
|
+
_Avoid_: update, force sync
|
|
38
|
+
|
|
35
39
|
## Skill Design
|
|
36
40
|
|
|
37
41
|
**Turn Continuity** (回合连续性):
|