diff --git a/README.md b/README.md index 162bd18..9e944f0 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # agents-skills -> 可复用的 **agent 技能库**(执行方法,不含项目结论)。每个技能一个**顶层目录** + `SKILL.md`,因此本仓**可以直接作为技能根**(如 junction 到 `<项目>/.dsh/skills` 后由 DSH 发现)。 +> 可复用的 **agent 技能库**(执行方法,不含项目结论)。每个技能一个**顶层目录** + `SKILL.md`,按 DSH 的发现规则逐个接到技能根下即可(见「使用」)。 ## 技能清单 @@ -16,11 +16,30 @@ 4. **工具要能自检**:脚本必须能在自己的仓里跑通一条最小用例。 5. **令牌不落盘**:脚本从 `GITEA_TOKEN` 或 `--token-file` 读,**绝不把令牌写进仓库、URL 或提交**。 -## 使用(把本仓接到项目) +## 使用(把本仓接到 DSH) + +DSH 的技能发现**只有一层**:技能根下的 `/SKILL.md` 或平铺 `.md`,**不认**嵌套的 `**/SKILL.md`。所以要把**每个技能目录**接到技能根下,不能把本仓整体 junction 成 `<技能根>/agents-skills`(那层里没有 `SKILL.md`,会发现不到)。 + +技能根与优先级(同名时 rank 小者胜): + +| rank | 来源 | 路径 | +|---|---|---| +| 100 | 项目 | `<项目根>/.dsh/skills` | +| 200 | 项目 | `<项目根>/.agents/skills` | +| 400 | 用户 | `~/.dsh/skills`(即 `$DSH_HOME/skills`) | ```powershell -# 方式一:作为技能根之一(Windows 目录 junction) -New-Item -ItemType Junction -Path '<项目>/.dsh/skills/agents-skills' -Target '<本仓路径>' -# 方式二:只取一个技能 -Copy-Item -Recurse '<本仓>/ticket-management' '<项目>/.dsh/skills/' -``` \ No newline at end of file +# 方式一:用户级 —— 所有项目都可见(推荐;本机已按此接好 ticket-management) +New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.dsh\skills" +New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\skills\ticket-management" -Target '<本仓>\ticket-management' + +# 方式二:项目级 —— 只在该项目可见 +New-Item -ItemType Junction -Path '<项目>/.dsh/skills/ticket-management' -Target '<本仓>\ticket-management' + +# 方式三:拷一份 —— 与本仓脱钩,之后改动不再同步 +Copy-Item -Recurse '<本仓>\ticket-management' '<技能根>\' +``` + +junction 指向本仓 ⇒ 在这里改 `SKILL.md`,DSH 的下一次目录扫描即生效:**正文编辑不用重启,也不用重新链接**。技能名取自 frontmatter 的 `name`;技能根下的新增 / 改名 / 删除由 DSH 的目录监视自动发现。 + +> 进阶:也可让本仓**整体**充当一个技能根(`@deepseek-ai/dsh-skill-filesystem` 的 `customSkillDirs`,rank 300),但要在部署侧给该插件配目录;默认预设不带这行配置。 \ No newline at end of file diff --git a/ticket-management/SKILL.md b/ticket-management/SKILL.md index fdd75d3..192438d 100644 --- a/ticket-management/SKILL.md +++ b/ticket-management/SKILL.md @@ -1,6 +1,6 @@ --- name: ticket-management -description: 用 Gitea 工单库做「人读 + 机读」双面管理 —— 标签零新增;标题用【问题域】前缀(闸门仅「不能动」时作第二段);正文**首行一行机读字段**(域/闸门/优先级/类型/模式/端/环境/证据);正文**自包含**(禁本地路径与内部分析编号,源码用仓库相对路径 + 该 ref 的 permalink);来源只能进**末尾机读引用块**(须写明 ref-owner)。含 bin/ticket.mjs 的 labels/milestones/list/verify/create/rename。适用于:建单、批量规范化既有工单标题、校验工单是否合规、用 API 读写 Gitea issue。 +description: 用 Gitea 工单库做「人读 + 机读」双面管理 —— 标签零新增;标题用【问题域】前缀(闸门仅「不能动」时作第二段);正文**首行一行机读字段**(域/闸门/优先级/类型/模式/端/环境/证据);正文**自包含**(禁本地路径与内部分析编号,源码用仓库相对路径 + 该 ref 的 permalink);来源只能进**末尾机读引用块**(须写明 ref-owner)。含 bin/ticket.mjs 的 labels/milestones/list/verify/create/rename/selftest(自检不联网、不需要令牌)。适用于:建单、批量规范化既有工单标题、校验工单是否合规、用 API 读写 Gitea issue。 --- # ticket-management(Gitea 工单管理) @@ -68,7 +68,11 @@ refs: ## 5 工具 `bin/ticket.mjs` ```bash +# 自检:不联网、不需要令牌,改完脚本先跑这条 +node bin/ticket.mjs selftest + # 令牌:GITEA_TOKEN 环境变量,或 --token-file=<含 key=value 的文件>(不落盘、不回显) +# 取值型选项两种写法等价:--repo=xbc/xbc 与 --repo xbc/xbc 都能用 node bin/ticket.mjs labels --repo xbc/xbc node bin/ticket.mjs milestones --repo xbc/xbc node bin/ticket.mjs list --repo xbc/xbc --state=open @@ -83,12 +87,14 @@ node bin/ticket.mjs rename 142 --repo xbc/xbc --domain=平台 [--gate=等口径] | `verify ` | **校验**(只读):见 §7 六项断言 | `read:issue` | | `create` | 建单(**先按标题查重**) | `write:issue` | | `rename ` | 规范化标题(重建前缀,描述不动) | `write:issue` | +| `selftest` | **自检**:解析器两种写法 + §7 七项断言的正反样例;不联网、不需要令牌 | 无 | ## 6 常见坑(实测) - **HTTP 分块解码**:`let d = ''; res.on('data', c => d += c)` 会把**跨块的多字节字符**打成 `U+FFFD`(中文标签名就被打坏过)⇒ 必须 `res.setEncoding('utf8')`,或用 `fetch` 的 `text()`。 - **建单前必须查重**:按标题查,否则会重复建单。 - **标签传 id**:`POST /issues` 的 `labels` 是**标签 id 数组**,不是名字。 +- **参数写法的坑(已修,留档)**:早先 `parseArgs` 只认 `--repo=o/r`,而本文档示例写的是 `--repo o/r` ⇒ `xbc/xbc` 被当成位置参数、`repo` 变成 `true`,请求打到 `/repos/true/...`,报出来的是 403「缺 read 权限」与 405「方法不允许」——**全是假故障**,白排查了三轮。现在取值型选项两种写法都收;改动仍以 `selftest` 兜底。 - **批量改首行时必须保持 ` | ` 形态**(空格 + 竖线 + 空格):把 `域=…` 换成新值时若顺手吃掉它后面的空格,`闸门=` 会与 `域` 黏成一段 ⇒ **断言 4 全线 FAIL**(实测 14/14 中招,工具拦住了)。 - **改标题与正文**:都是 `PATCH /repos/{owner}/{repo}/issues/{n}`。 - **令牌 scope**:只读 `read:issue,read:repository` 建不了单(403,报错会写 required scope);建 / 改需 `write:issue`;`GET /user` 另需 `read:user`。 diff --git a/ticket-management/bin/ticket.mjs b/ticket-management/bin/ticket.mjs index 43fc3b6..a2d5a11 100644 --- a/ticket-management/bin/ticket.mjs +++ b/ticket-management/bin/ticket.mjs @@ -10,11 +10,18 @@ const NL = String.fromCharCode(10); const BACKSLASH = String.fromCharCode(92); const BAD = ["codes/", "分析/数据/", "分析/D-", "分析/E-", "分析/A-", "分析/B-", "分析/C-", "分析/F-", "分析/_tmp", "分析/_archive", "分析/README", "86 §", "80 · V", "/home/", "C:" + BACKSLASH]; +// 取值型选项:`--repo=o/r` 与 `--repo o/r` 两种写法都收(SKILL.md 的示例用的是后者) +const VALUE_OPTS = ["repo", "title", "body-file", "label", "milestone", "token-file", "host", "state", "limit", "domain", "gate"]; function parseArgs(argv) { const pos = [], opt = {}; - for (const a of argv) { - if (a.startsWith("--")) { const i = a.indexOf("="); if (i < 0) opt[a.slice(2)] = true; else opt[a.slice(2, i)] = a.slice(i + 1); } - else pos.push(a); + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (!a.startsWith("--")) { pos.push(a); continue; } + const j = a.indexOf("="); + if (j >= 0) { opt[a.slice(2, j)] = a.slice(j + 1); continue; } + const k = a.slice(2), next = argv[i + 1]; + if (VALUE_OPTS.indexOf(k) >= 0 && next !== undefined && !next.startsWith("--")) { opt[k] = next; i++; } + else opt[k] = true; } return { pos: pos, opt: opt }; } @@ -62,6 +69,29 @@ function checksOf(title, body) { ["6 有引用块 ⇒ 有 ref-owner", body.indexOf("